diff options
51 files changed, 961 insertions, 247 deletions
diff --git a/.expeditor/config.yml b/.expeditor/config.yml index 1ce2a6c964..5a6d9a57ac 100644 --- a/.expeditor/config.yml +++ b/.expeditor/config.yml @@ -1,5 +1,5 @@ # Documentation available at https://expeditor.chef.io/docs/getting-started/ - +--- # the name we use for this project when interacting with expeditor chatbot project: alias: chef-15 @@ -11,13 +11,14 @@ product_key: # Slack channel in Chef Software slack to send notifications about build failures, etc slack: - notify_channel: chef-notify + notify_channel: chef-infra-notify # Which Ruby Gems, built when the Omnibus package is built, to publish to rubygems.org # This publish is triggered by the `built_in:publish_rubygems` artifact_action. rubygems: - chef - chef-config + - chef-bin docker_images: - chef @@ -33,12 +34,6 @@ pipelines: - ADHOC: true github: - # The file where the MAJOR.MINOR.PATCH version is kept. The version in this file - # is bumped automatically via the `built_in:bump_version` merge_action. - version_file: "VERSION" - # The file where our CHANGELOG is kept. This file is updated automatically with - # details from the Pull Request via the `built_in:update_changelog` merge_action. - changelog_file: "CHANGELOG.md" # This deletes the GitHub PR branch after successfully merged into the release branch delete_branch_on_merge: true # The tag format to use (e.g. v1.0.0) @@ -58,6 +53,9 @@ github: - chef-14: version_constraint: 14* +changelog: + rollup_header: Changes not yet released to rubygems.org + # These actions are taken, in order they are specified, anytime a Pull Request is merged. merge_actions: - built_in:bump_version: diff --git a/.expeditor/update_version.sh b/.expeditor/update_version.sh index e058d06f06..85f7340811 100755 --- a/.expeditor/update_version.sh +++ b/.expeditor/update_version.sh @@ -12,6 +12,7 @@ set -evx sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"$(cat VERSION)\"/" chef-config/lib/chef-config/version.rb +sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"$(cat VERSION)\"/" chef-bin/lib/chef-bin/version.rb sed -i -r "s/VersionString\.new\(\".+\"\)/VersionString.new(\"$(cat VERSION)\")/" lib/chef/version.rb # Update the version inside Gemfile.lock diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index ac0fc52309..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,16 +0,0 @@ -### Description - -[Please describe what this change achieves] - -### Issues Resolved - -[List any existing issues this PR resolves, or any Discourse or -StackOverflow discussions that are relevant] - -### Check List - -- [ ] New functionality includes tests -- [ ] All tests pass -- [ ] RELEASE\_NOTES.md has been updated if required (not required for bugfixes, required for API changes) -- [ ] All commits have been signed-off for the Developer Certificate of Origin. See <https://github.com/chef/chef/blob/master/CONTRIBUTING.md#developer-certification-of-origin-dco> -- [ ] PR title is a worthy inclusion in the CHANGELOG
\ No newline at end of file diff --git a/.gitignore b/.gitignore index 200db3afc5..d88442ba7c 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,9 @@ nodes/ # chef-config chef-config/.bundle chef-config/Gemfile.lock +chef-config/pkg + +# chef-bin +chef-bin/.bundle +chef-bin/Gemfile.lock +chef-bin/pkg diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b1270b55..a60bc5fb85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,19 @@ <!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ --> -<!-- latest_release 15.0.237 --> -## [v15.0.237](https://github.com/chef/chef/tree/v15.0.237) (2019-04-29) +<!-- latest_release 15.0.241 --> +## [v15.0.241](https://github.com/chef/chef/tree/v15.0.241) (2019-04-30) #### Merged Pull Requests -- "chef-client" => #{Chef::Dist::CLIENT} [#8418](https://github.com/chef/chef/pull/8418) ([bobchaos](https://github.com/bobchaos)) +- Update InSpec preview to 4.2.0 [#8426](https://github.com/chef/chef/pull/8426) ([tas50](https://github.com/tas50)) <!-- latest_release --> <!-- release_rollup --> ### Changes since latest stable release #### Merged Pull Requests +- Update InSpec preview to 4.2.0 [#8426](https://github.com/chef/chef/pull/8426) ([tas50](https://github.com/tas50)) <!-- 15.0.241 --> +- Remove chef-* bin files from chef gem [#8413](https://github.com/chef/chef/pull/8413) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.240 --> +- Enable license acceptance during bootstrap [#8411](https://github.com/chef/chef/pull/8411) ([marcparadise](https://github.com/marcparadise)) <!-- 15.0.239 --> +- Implement bootstrap directly with train [#8419](https://github.com/chef/chef/pull/8419) ([marcparadise](https://github.com/marcparadise)) <!-- 15.0.238 --> - "chef-client" => #{Chef::Dist::CLIENT} [#8418](https://github.com/chef/chef/pull/8418) ([bobchaos](https://github.com/bobchaos)) <!-- 15.0.237 --> - Add the introduced field to snap_package [#8412](https://github.com/chef/chef/pull/8412) ([tas50](https://github.com/tas50)) <!-- 15.0.236 --> - windows_task: Add start_when_available support [#8420](https://github.com/chef/chef/pull/8420) ([vsingh-msys](https://github.com/vsingh-msys)) <!-- 15.0.235 --> @@ -11,6 +11,7 @@ gem "chef", path: "." gem "ohai", git: "https://github.com/chef/ohai.git", branch: "master" gem "chef-config", path: File.expand_path("../chef-config", __FILE__) if File.exist?(File.expand_path("../chef-config", __FILE__)) +gem "chef-bin", path: File.expand_path("../chef-bin", __FILE__) if File.exist?(File.expand_path("../chef-bin", __FILE__)) gem "cheffish", "~> 14" group(:omnibus_package) do diff --git a/Gemfile.lock b/Gemfile.lock index 82c2fed634..070787e6c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,7 +8,7 @@ GIT GIT remote: https://github.com/chef/ohai.git - revision: 5c70e89388ebc8ddf7d2d7bfd489024b11c0aece + revision: a1ec73298d623d18cbe05c2b53ac57c8cc9beae0 branch: master specs: ohai (15.0.34) @@ -27,12 +27,11 @@ GIT PATH remote: . specs: - chef (15.0.237) + chef (15.0.241) addressable bcrypt_pbkdf (~> 1.0) bundler (>= 1.10) - chef-config (= 15.0.237) - chef-core (~> 0.0.3) + chef-config (= 15.0.241) chef-zero (>= 14.0.11) diff-lcs (~> 1.2, >= 1.2.4) ed25519 (~> 1.2) @@ -54,14 +53,14 @@ PATH plist (~> 3.2) proxifier (~> 1.0) syslog-logger (~> 1.6) + train-core (~> 2.0, >= 2.0.12) tty-screen (~> 0.6) uuidtools (~> 2.1.5) - chef (15.0.237-universal-mingw32) + chef (15.0.241-universal-mingw32) addressable bcrypt_pbkdf (~> 1.0) bundler (>= 1.10) - chef-config (= 15.0.237) - chef-core (~> 0.0.3) + chef-config (= 15.0.241) chef-zero (>= 14.0.11) diff-lcs (~> 1.2, >= 1.2.4) ed25519 (~> 1.2) @@ -84,6 +83,7 @@ PATH plist (~> 3.2) proxifier (~> 1.0) syslog-logger (~> 1.6) + train-core (~> 2.0, >= 2.0.12) tty-screen (~> 0.6) uuidtools (~> 2.1.5) win32-api (~> 1.5.3) @@ -99,9 +99,15 @@ PATH wmi-lite (~> 1.0) PATH + remote: chef-bin + specs: + chef-bin (15.0.241) + chef (= 15.0.241) + +PATH remote: chef-config specs: - chef-config (15.0.237) + chef-config (15.0.241) addressable fuzzyurl mixlib-config (>= 2.2.12, < 4.0) @@ -113,7 +119,7 @@ GEM specs: addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) - appbundler (0.12.4) + appbundler (0.12.5) mixlib-cli (>= 1.4, < 3.0) mixlib-shellout (>= 2.0, < 4.0) ast (2.4.0) @@ -124,20 +130,6 @@ GEM debug_inspector (>= 0.0.1) builder (3.2.3) byebug (11.0.1) - chef-core (0.0.3) - chef-telemetry - mixlib-log - pastel - r18n-desktop - train-core (~> 2.0, >= 2.0.12) - tty-color - tty-cursor - tty-spinner - chef-telemetry (0.1.8) - chef-config - concurrent-ruby (~> 1.0) - ffi-yajl (~> 2.2) - http (~> 2.2) chef-vault (3.4.3) chef-zero (14.0.12) ffi-yajl (~> 2.2) @@ -149,14 +141,11 @@ GEM chef-zero (~> 14.0) net-ssh coderay (1.1.2) - concurrent-ruby (1.1.5) crack (0.4.3) safe_yaml (~> 1.0.0) debug_inspector (0.0.3) diff-lcs (1.3) docile (1.3.1) - domain_name (0.5.20180417) - unf (>= 0.0.5, < 1.0.0) ed25519 (1.2.4) equatable (0.5.0) erubis (2.7.0) @@ -182,18 +171,9 @@ GEM hashie (3.6.0) highline (1.7.10) htmlentities (4.3.4) - http (2.2.2) - addressable (~> 2.3) - http-cookie (~> 1.0) - http-form_data (~> 1.0.1) - http_parser.rb (~> 0.6.0) - http-cookie (1.0.3) - domain_name (~> 0.5) - http-form_data (1.0.3) - http_parser.rb (0.6.0) httpclient (2.8.3) iniparse (1.4.4) - inspec-core (4.1.4.preview) + inspec-core (4.2.0.preview) addressable (~> 2.4) faraday (>= 0.9.0) faraday_middleware (~> 0.12.2) @@ -223,7 +203,7 @@ GEM jaro_winkler (1.5.2) json (2.2.0) libyajl2 (1.2.0) - license-acceptance (0.2.13) + license-acceptance (0.2.16) pastel (~> 0.7) tomlrb (~> 1.2) tty-box (~> 0.3) @@ -264,7 +244,7 @@ GEM octokit (4.14.0) sawyer (~> 0.8.0, >= 0.5.3) parallel (1.17.0) - parser (2.6.2.1) + parser (2.6.3.0) ast (~> 2.4.0) parslet (1.8.2) pastel (0.7.2) @@ -286,9 +266,6 @@ GEM binding_of_caller (>= 0.7) pry (>= 0.9.11) public_suffix (3.0.3) - r18n-core (3.2.0) - r18n-desktop (3.2.0) - r18n-core (= 3.2.0) rack (2.0.7) rainbow (3.0.0) rake (12.3.2) @@ -375,17 +352,12 @@ GEM tty-screen (~> 0.6.4) wisper (~> 2.0.0) tty-screen (0.6.5) - tty-spinner (0.9.0) - tty-cursor (~> 0.6.0) tty-table (0.10.0) equatable (~> 0.5.0) necromancer (~> 0.4.0) pastel (~> 0.7.2) strings (~> 0.1.0) tty-screen (~> 0.6.4) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.6) unicode-display_width (1.4.1) unicode_utils (1.4.0) uuidtools (2.1.5) @@ -417,7 +389,7 @@ GEM win32-taskscheduler (2.0.4) ffi structured_warnings - winrm (2.3.1) + winrm (2.3.2) builder (>= 2.1.2) erubis (~> 2.7) gssapi (~> 1.2) @@ -444,6 +416,7 @@ DEPENDENCIES appbundler bcrypt_pbkdf chef! + chef-bin! chef-config! chef-vault cheffish (~> 14) @@ -9,9 +9,9 @@ **Project State**: [Active](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md#active) -**Issues Response SLA**: 10 business days +**Issues [Response Time Maximum](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md)**: 14 days -**Pull Request Response SLA**: 10 business days +**Pull Request [Response Time Maximum](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md)**: 14 days ## Getting Started @@ -34,6 +34,13 @@ task install: :super_install gemspec = Gem.win_platform? ? "chef-universal-mingw32" : "chef" Bundler::GemHelper.install_tasks name: gemspec +# this gets appended to the normal bundler install helper +task :install do + chef_bin_path = ::File.join(::File.dirname(__FILE__), "chef-bin") + Dir.chdir(chef_bin_path) + sh("rake install:force") +end + task :pedant, :chef_zero_spec task :build_eventlog do @@ -1 +1 @@ -15.0.237
\ No newline at end of file +15.0.241
\ No newline at end of file diff --git a/chef-bin/.rspec b/chef-bin/.rspec new file mode 100644 index 0000000000..eb3ef03653 --- /dev/null +++ b/chef-bin/.rspec @@ -0,0 +1,2 @@ +--color +-fd diff --git a/chef-bin/Gemfile b/chef-bin/Gemfile new file mode 100644 index 0000000000..96ab544690 --- /dev/null +++ b/chef-bin/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +# Specify your gem's dependencies in chef-config.gemspec +gemspec diff --git a/chef-bin/LICENSE b/chef-bin/LICENSE new file mode 100644 index 0000000000..11069edd79 --- /dev/null +++ b/chef-bin/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/chef-bin/Rakefile b/chef-bin/Rakefile new file mode 100644 index 0000000000..5626d1bc5d --- /dev/null +++ b/chef-bin/Rakefile @@ -0,0 +1,15 @@ +# we need to force the install in order to overwrite the binstubs from +# old chef gems. + +Bundler::GemHelper.install_tasks + +# this is necessary to use to overwrite any chef-14 or earlier era gem which has the bin files in +# the chef gem itself +desc "force install the chef-bin gem" +task "install:force" do + sh "gem build -V chef-bin.gemspec" + built_gem_path = Dir["chef-bin-*.gem"].sort_by { |f| File.mtime(f) }.last + FileUtils.mkdir_p("pkg") unless Dir.exist?("pkg") + FileUtils.mv(built_gem_path, "pkg") + sh "gem install -f pkg/#{built_gem_path}" +end diff --git a/bin/chef-apply b/chef-bin/bin/chef-apply index ddbdc66907..ddbdc66907 100755 --- a/bin/chef-apply +++ b/chef-bin/bin/chef-apply diff --git a/bin/chef-client b/chef-bin/bin/chef-client index af27d7903d..af27d7903d 100755 --- a/bin/chef-client +++ b/chef-bin/bin/chef-client diff --git a/bin/chef-resource-inspector b/chef-bin/bin/chef-resource-inspector index 6a7eac0c32..6a7eac0c32 100755 --- a/bin/chef-resource-inspector +++ b/chef-bin/bin/chef-resource-inspector diff --git a/bin/chef-service-manager b/chef-bin/bin/chef-service-manager index 9021824fed..9021824fed 100755 --- a/bin/chef-service-manager +++ b/chef-bin/bin/chef-service-manager diff --git a/bin/chef-shell b/chef-bin/bin/chef-shell index 666ce1944c..666ce1944c 100755 --- a/bin/chef-shell +++ b/chef-bin/bin/chef-shell diff --git a/bin/chef-solo b/chef-bin/bin/chef-solo index 06c0452d26..06c0452d26 100755 --- a/bin/chef-solo +++ b/chef-bin/bin/chef-solo diff --git a/bin/chef-windows-service b/chef-bin/bin/chef-windows-service index 646e3b4a93..646e3b4a93 100755 --- a/bin/chef-windows-service +++ b/chef-bin/bin/chef-windows-service diff --git a/chef-bin/chef-bin.gemspec b/chef-bin/chef-bin.gemspec new file mode 100644 index 0000000000..66d14f3f52 --- /dev/null +++ b/chef-bin/chef-bin.gemspec @@ -0,0 +1,26 @@ +# coding: utf-8 +lib = File.expand_path("../lib", __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require "chef-bin/version" + +Gem::Specification.new do |spec| + spec.name = "chef-bin" + spec.version = ChefBin::VERSION + spec.authors = ["Adam Jacob"] + spec.email = ["adam@chef.io"] + + spec.summary = %q{Chef-branded binstubs for chef-client} + spec.homepage = "https://github.com/chef/chef" + spec.license = "Apache-2.0" + + spec.require_paths = ["lib"] + + spec.add_dependency "chef", "= #{ChefBin::VERSION}" + spec.add_development_dependency "rake" + + spec.files = %w{Gemfile Rakefile LICENSE} + Dir.glob("*.gemspec") + + Dir.glob("{lib}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } + + spec.bindir = "bin" + spec.executables = %w{ chef-apply chef-client chef-resource-inspector chef-service-manager chef-shell chef-solo chef-windows-service } +end diff --git a/chef-bin/lib/chef-bin.rb b/chef-bin/lib/chef-bin.rb new file mode 100644 index 0000000000..b40bf9fa11 --- /dev/null +++ b/chef-bin/lib/chef-bin.rb @@ -0,0 +1,20 @@ +# +# Copyright:: Copyright 2015-2019, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 ChefBin + +end diff --git a/chef-bin/lib/chef-bin/version.rb b/chef-bin/lib/chef-bin/version.rb new file mode 100644 index 0000000000..10092ec457 --- /dev/null +++ b/chef-bin/lib/chef-bin/version.rb @@ -0,0 +1,34 @@ +# Copyright:: Copyright 2010-2019, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 ChefBin + CHEFBIN_ROOT = File.expand_path("../..", __FILE__) + VERSION = "15.0.241".freeze +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. +# diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb index 1673164441..daa191398d 100644 --- a/chef-config/lib/chef-config/version.rb +++ b/chef-config/lib/chef-config/version.rb @@ -21,7 +21,7 @@ module ChefConfig CHEFCONFIG_ROOT = File.expand_path("../..", __FILE__) - VERSION = "15.0.237".freeze + VERSION = "15.0.241".freeze end # diff --git a/chef.gemspec b/chef.gemspec index 32e12bad41..ebd6d192bb 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 2.5.0" s.add_dependency "chef-config", "= #{Chef::VERSION}" - s.add_dependency "chef-core", "~> 0.0.3" + s.add_dependency "train-core", "~> 2.0", ">= 2.0.12" s.add_dependency "mixlib-cli", ">= 1.7", "< 3.0" s.add_dependency "mixlib-log", ">= 2.0.3", "< 4.0" @@ -51,7 +51,7 @@ Gem::Specification.new do |s| s.add_dependency "bundler", ">= 1.10" s.bindir = "bin" - s.executables = %w{ chef-client chef-solo knife chef-shell chef-apply chef-resource-inspector } + s.executables = %w{ knife } s.require_paths = %w{ lib } s.files = %w{Gemfile Rakefile LICENSE README.md} + Dir.glob("{lib,tasks,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } + Dir.glob("*.gemspec") diff --git a/kitchen-tests/kitchen.yml b/kitchen-tests/kitchen.yml index 6de7be8ebd..5fe3b8540f 100644 --- a/kitchen-tests/kitchen.yml +++ b/kitchen-tests/kitchen.yml @@ -15,7 +15,7 @@ provisioner: lifecycle: pre_converge: - - remote: /opt/chef/embedded/bin/gem install appbundle-updater + - remote: /opt/chef/embedded/bin/gem install appbundler appbundle-updater - remote: /opt/chef/embedded/bin/appbundle-updater chef ohai <%= File.readlines('../Gemfile.lock', File.expand_path(File.dirname(__FILE__))).find { |l| l =~ /^\s+ohai \((\d+\.\d+\.\d+)\)/ }; 'v' + $1 %> --tarball --github chef/ohai - remote: /opt/chef/embedded/bin/appbundle-updater chef chef <%= ENV['TRAVIS_COMMIT'] || %x(git rev-parse HEAD).chomp %> --tarball --github chef/chef diff --git a/lib/chef.rb b/lib/chef.rb index c58f46debd..8869a5a890 100644 --- a/lib/chef.rb +++ b/lib/chef.rb @@ -18,18 +18,6 @@ require "chef/version" -# Ensure that this loads ahead of anything that -# might cause rubygems to hit Gem.load_yaml, including -# evaluating gemspecs. When load_yaml is invoked, -# it stubs out the YAML::Syck namespace. This causes -# r18n to break, which expects either YAML::Syck to be there -# and fully defined (particularly, the Syck::PrivateType class), -# or for it to not be there at all. -# -# When it's not - because it's a stub - r18n explodes on loading. -# Ensuring chef_core/text and r18n are loaded first prevents this. -require "chef_core/text" - require "chef/nil_argument" require "chef/mash" require "chef/exceptions" diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index daca0957d4..d46bf455e9 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -18,11 +18,6 @@ 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" -require "chef/dist" class Chef class Knife @@ -387,14 +382,16 @@ class Chef attr_accessor :client_builder attr_accessor :chef_vault_handler - attr_reader :target_host + attr_reader :connection deps do + require "erubis" + require "chef/json_compat" - require "tempfile" - require "chef_core/text" # i18n and standardized error structures - require "chef_core/target_host" - require "chef_core/target_resolver" + require "chef/util/path_helper" + require "chef/knife/bootstrap/chef_vault_handler" + require "chef/knife/bootstrap/client_builder" + require "chef/knife/bootstrap/train_connector" end banner "knife bootstrap [PROTOCOL://][USER@]FQDN (options)" @@ -417,7 +414,7 @@ class Chef # # @return [String] Default bootstrap template def default_bootstrap_template - if target_host.base_os == :windows + if connection.windows? "windows-#{Chef::Dist::CLIENT}-msi" else "chef-full" @@ -482,11 +479,11 @@ class Chef end # Establish bootstrap context for template rendering. - # Requires target_host to be a live connection in order to determine + # Requires connection to be a live connection in order to determine # the correct platform. def bootstrap_context @bootstrap_context ||= - if target_host.base_os == :windows + if connection.windows? require "chef/knife/core/windows_bootstrap_context" Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config, secret) else @@ -556,17 +553,12 @@ class Chef bootstrap_path = upload_bootstrap(content) perform_bootstrap(bootstrap_path) ensure - target_host.del_file(bootstrap_path) if target_host && bootstrap_path + connection.del_file!(bootstrap_path) if connection && bootstrap_path end def register_client # chef-vault integration must use the new client-side hawtness, otherwise to use the # new client-side hawtness, just delete your validation key. - # 2019-04-01 TODO - # TODO - should this raise if config says to use vault because json/file/item exists - # but we still have a validation key? That means we can't use the new client hawtness, - # but we also don't tell the operator that their requested vault operations - # won't be performed if chef_vault_handler.doing_chef_vault? || (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key]))) @@ -589,8 +581,8 @@ class Chef def perform_bootstrap(remote_bootstrap_script_path) ui.info("Bootstrapping #{ui.color(server_name, :bold)}") cmd = bootstrap_command(remote_bootstrap_script_path) - r = target_host.run_command(cmd) do |data| - ui.msg("#{ui.color(" [#{target_host.hostname}]", :cyan)} #{data}") + r = connection.run_command(cmd) do |data| + ui.msg("#{ui.color(" [#{connection.hostname}]", :cyan)} #{data}") end if r.exit_status != 0 ui.error("The following error occurred on #{server_name}:") @@ -603,24 +595,13 @@ class Chef ui.info("Connecting to #{ui.color(server_name, :bold)}") opts = connection_opts.dup do_connect(opts) - rescue => e - # Ugh. TODO: Train raises a Train::Transports::SSHFailed for a number of different errors. chef_core makes that - # a more general ConnectionFailed, with an error code based on the specific error text/reason provided from trainm. - # This means we have to look three layers into the exception to find out what actually happened instead of just - # looking at the exception type - # - # It doesn't help to provide our own error if it does't let the caller know what they need to identify the problem. - # Let's update chef_core to be a bit smarter about resolving the errors to an appropriate exception type - # (eg ChefCore::ConnectionFailed::AuthError or similar) that will work across protocols, instead of just a single - # ConnectionFailure type - # - - if e.cause && e.cause.cause && e.cause.cause.class == Net::SSH::AuthenticationFailed - if opts[:password] + rescue Train::Error => e + if e.cause && e.cause.class == Net::SSH::AuthenticationFailed + if connection.password_auth? raise else ui.warn("Failed to authenticate #{opts[:user]} to #{server_name} - trying password auth") - password = ui.ask("Enter password for #{opts[:user]}@#{server_name} - trying password auth") do |q| + password = ui.ask("Enter password for #{opts[:user]}@#{server_name}.") do |q| q.echo = false end end @@ -631,6 +612,9 @@ class Chef end end + # TODO - maybe remove the footgun detection this was built on. + # url values override CLI flags, if you provide both + # we'll use the one that you gave in the URL. def connection_protocol return @connection_protocol if @connection_protocol from_url = host_descriptor =~ /^(.*):\/\// ? $1 : nil @@ -640,14 +624,8 @@ class Chef end def do_connect(conn_options) - # Resolve the given host name to a TargetHost instance. We will limit - # the number of hosts to 1 (effectivly eliminating wildcard support) since - # we only support running bootstrap against one host at a time. - resolver = ChefCore::TargetResolver.new(host_descriptor, connection_protocol, - conn_options, max_expanded_targets: 1) - @target_host = resolver.targets.first - target_host.connect! - target_host + @connection = TrainConnector.new(host_descriptor, connection_protocol, conn_options) + connection.connect! end # Fail if both first_boot_attributes and first_boot_attributes_from_file @@ -664,7 +642,7 @@ class Chef # TODO test for this method # TODO check that the protoocol is valid. def validate_winrm_transport_opts! - return true if connection_protocol != "winrm" + return true unless winrm? if Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])) if config_value(:winrm_auth_method) == "plaintext" && @@ -739,7 +717,7 @@ class Chef end def winrm_warn_no_ssl_verification - return if connection_protocol != "winrm" + return unless winrm? # REVIEWER NOTE # The original check from knife plugin did not include winrm_ssl_peer_fingerprint @@ -768,13 +746,8 @@ class Chef end end - # - - # Create a configuration hash for TargetHost to connect - # to the remote host via Train. - # # @return a configuration hash suitable for connecting to the remote - # host via TargetHost. + # host via train def connection_opts return @connection_opts unless @connection_opts.nil? @connection_opts = {} @@ -788,9 +761,16 @@ class Chef @connection_opts end + def winrm? + connection_protocol == "winrm" + end + + def ssh? + connection_protocol == "ssh" + end + # Common configuration for all protocols def base_opts - # port = config_value(:connection_port, knife_key_for_protocol(connection_protocol, :port)) user = config_value(:connection_user, @@ -807,10 +787,9 @@ class Chef end def host_verify_opts - case connection_protocol - when "winrm" + if winrm? { self_signed: config_value(:winrm_no_verify_cert) === true } - when "ssh" + elsif ssh? # Fall back to the old knife config key name for back compat. { verify_host_key: config_value(:ssh_verify_host_key, :host_key_verify, true) === true } @@ -959,16 +938,16 @@ class Chef end def upload_bootstrap(content) - script_name = target_host.base_os == :windows ? "bootstrap.bat" : "bootstrap.sh" - remote_path = target_host.normalize_path(File.join(target_host.temp_dir, script_name)) - target_host.save_as_remote_file(content, remote_path) + script_name = connection.windows? ? "bootstrap.bat" : "bootstrap.sh" + remote_path = connection.normalize_path(File.join(connection.temp_dir, script_name)) + connection.upload_file_content!(content, remote_path) remote_path end # build the command string for bootrapping # @return String def bootstrap_command(remote_path) - if target_host.base_os == :windows + if connection.windows? "cmd.exe /C #{remote_path}" else "sh #{remote_path}" @@ -977,8 +956,9 @@ class Chef # To avoid cluttering the CLI options, some flags (such as port and user) # are shared between protocols. However, there is still a need to allow the operator - # to specify defaults separately, since they may not be the same values for different protocols. - # + # to specify defaults separately, since they may not be the same values for different + # protocols. + # These keys are available in Chef::Config, and are prefixed with the protocol name. # For example, :user CLI option will map to :winrm_user and :ssh_user Chef::Config keys, # based on the connection protocol in use. diff --git a/lib/chef/knife/bootstrap/train_connector.rb b/lib/chef/knife/bootstrap/train_connector.rb new file mode 100644 index 0000000000..5230d6638c --- /dev/null +++ b/lib/chef/knife/bootstrap/train_connector.rb @@ -0,0 +1,286 @@ +# Copyright:: Copyright (c) 2019 Chef Software Inc. +# +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "train" +require "tempfile" +require "uri" + +class Chef + class Knife + class Bootstrap < Knife + class TrainConnector + SSH_CONFIG_OVERRIDE_KEYS = [:user, :port, :proxy].freeze + + MKTEMP_WIN_COMMAND = <<~EOM.freeze + $parent = [System.IO.Path]::GetTempPath(); + [string] $name = [System.Guid]::NewGuid(); + $tmp = New-Item -ItemType Directory -Path; + (Join-Path $parent $name); + $tmp.FullName + EOM + + MKTEMP_NIX_COMMAND = "bash -c 'd=$(mktemp -d ${TMPDIR:-/tmp}/chef_XXXXXX); echo $d'".freeze + + def initialize(host_url, default_transport, opts) + uri_opts = opts_from_uri(host_url) + uri_opts[:backend] ||= @default_transport + @transport_type = uri_opts[:backend] + + # opts in the URI will override user-provided options + @config = transport_config(host_url, opts.merge(uri_opts)) + end + + # Because creating a valid train connection for testing is a two-step process in which + # we need to connect before mocking config, + # we expose test_instance as a way for tests to create actual instances + # but ensure that they don't connect to any back end. + def self.test_instance(url, protocol: "ssh", + family: "unknown", name: "unknown", + release: "unknown", arch: "x86_64", + opts: {}) + # Specifying sudo: false ensures that attempted operations + # don't fail because the mock platform doesn't support sudo + tc = TrainConnector.new(url, protocol, { sudo: false }.merge(opts)) + tc.connect! + tc.connection.mock_os( + family: family, + name: name, + release: release, + arch: arch + ) + tc + end + + def connect! + # Force connection to establish + connection.wait_until_ready + true + end + + def hostname + @config[:host] + end + + def password_auth? + @config.key? :password + end + + # True if we're connected to a linux host + def linux? + connection.platform.linux? + end + + # True if we're connected to a unix host. + # NOTE: this is always true + # for a linux host because train classifies + # linux as a unix + def unix? + connection.platform.unix? + end + + # True if we're connected to a windows host + def windows? + connection.platform.windows? + end + + def winrm? + @transport_type == "winrm" + end + + def ssh? + @transport_type == "ssh" + end + + # Creates a temporary directory on the remote host if it + # hasn't already. Caches directory location. + # + # Returns the path on the remote host. + def temp_dir + cmd = windows? ? MKTEMP_WIN_COMMAND : MKTEMP_NIX_COMMAND + @tmpdir ||= begin + res = run_command!(cmd) + dir = res.stdout.chomp.strip + unless windows? + # Ensure that dir has the correct owner. We are possibly + # running with sudo right now - so this directory would be owned by root. + # File upload is performed over SCP as the current logged-in user, + # so we'll set ownership to ensure that works. + run_command!("chown #{@config[:user]} '#{dir}'") + end + dir + end + end + + def upload_file!(local_path, remote_path) + connection.upload(local_path, remote_path) + end + + def upload_file_content!(content, remote_path) + t = Tempfile.new("chef-content") + t << content + t.close + upload_file!(t.path, remote_path) + ensure + t.close + t.unlink + end + + def del_file!(path) + if windows? + run_command!("If (Test-Path \"#{path}\") { Remove-Item -Force -Path \"#{path}\" }") + else + run_command!("rm -f \"#{path}\"") + end + end + + # normalizes path across OS's + def normalize_path(path) + path.tr("\\", "/") + end + + def run_command(command, &data_handler) + connection.run_command(command, &data_handler) + end + + def run_command!(command, &data_handler) + result = run_command(command, &data_handler) + if result.exit_status != 0 + raise RemoteExecutionFailed.new(hostname, command, result) + end + result + end + + def connection + @connection ||= begin + Train.validate_backend(@config) + train = Train.create(@transport_type, @config) + train.connection + end + end + + private + + # For a given url and set of options, create a config + # hash suitable for passing into train. + def transport_config(host_url, opts_in) + opts = { target: host_url, + sudo: opts_in[:sudo] === false ? false : true, + www_form_encoded_password: true, + key_files: opts_in[:key_files], + non_interactive: true, # Prevent password prompts + transport_retries: 2, + transport_retry_sleep: 1, + logger: opts_in[:logger], + backend: @transport_type } + + # Base opts are those provided by the caller directly + opts.merge!(opts_from_caller(opts, opts_in)) + + # WinRM has some additional computed options + opts.merge!(opts_inferred_from_winrm(opts, opts_in)) + + # Now that everything is populated, fill in anything left + # from user ssh config that may be present + opts.merge!(missing_opts_from_ssh_config(opts, opts_in)) + + Train.target_config(opts) + end + + # Some winrm options are inferred based on other options. + # Return a hash of winrm options based on configuration already built. + def opts_inferred_from_winrm(config, opts_in) + return {} unless winrm? + opts_out = {} + + if opts_in[:ssl] + opts_out[:ssl] = true + opts_out[:self_signed] = opts_in[:self_signed] || false + end + + # See note here: https://github.com/mwrock/WinRM#example + if %w{ssl plaintext}.include?(opts_in[:winrm_auth_method]) + opts_out[:winrm_disable_sspi] = true + end + opts_out + end + + # Returns a hash containing valid options for the current + # transport protocol that are not already present in config + def opts_from_caller(config, opts_in) + # Train.options gives us the supported config options for the + # backend provider (ssh, winrm). We'll use that + # to filter out options that don't belong + # to the transport type we're using. + valid_opts = Train.options(config[:backend]) + opts_in.select do |key, _v| + valid_opts.key?(key) && !config.key?(key) + end + end + + # Extract any of username/password/host/port/transport + # that are in the URI and return them as a config has + def opts_from_uri(uri) + # Train.unpack_target_from_uri only works for complete URIs in + # form of proto://[user[:pass]@]host[:port]/ + # So we'll add the protocol prefix if it's not supplied. + uri_to_check = if URI.regexp.match(uri) + uri + else + "#{@transport_type}://#{uri}" + end + + Train.unpack_target_from_uri(uri_to_check) + end + + # This returns a hash that consists of settings + # populated from SSH configuration that are not already present + # in the configuration passed in. + # This is necessary because train will default these values + # itself - causing SSH config data to be ignored + def missing_opts_from_ssh_config(config, opts_in) + return {} unless ssh? + host_cfg = ssh_config_for_host(config[:host]) + opts_out = {} + opts_in.each do |key, _value| + if SSH_CONFIG_OVERRIDE_KEYS.include?(key) && !config.key?(key) + opts_out[key] = host_cfg[key] + end + end + opts_out + end + + # Having this as a method makes it easier to mock + # SSH Config for testing. + def ssh_config_for_host(host) + require "net/ssh" + Net::SSH::Config.for(host) + end + end + + class RemoteExecutionFailed < StandardError + attr_reader :exit_status, :command, :hostname, :stdout, :stderr + def initialize(hostname, command, result) + @hostname = hostname + @exit_status = result.exit_status + @stderr = result.stderr + @stdout = result.stdout + end + end + + end + end +end diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 5e987745e6..dcca7b8a69 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -171,7 +171,7 @@ class Chef 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::Dist::CLIENT}" - s = "#{client_path} -j /etc/chef/first-boot.json" + s = "CHEF_LICENSE=accept #{client_path} -j /etc/chef/first-boot.json" if @config[:verbosity] && @config[:verbosity] >= 3 s << " -l trace" elsif @config[:verbosity] && @config[:verbosity] >= 2 diff --git a/lib/chef/knife/core/windows_bootstrap_context.rb b/lib/chef/knife/core/windows_bootstrap_context.rb index df69399074..7aa4c012aa 100644 --- a/lib/chef/knife/core/windows_bootstrap_context.rb +++ b/lib/chef/knife/core/windows_bootstrap_context.rb @@ -154,6 +154,7 @@ class Chef def start_chef bootstrap_environment_option = bootstrap_environment.nil? ? "" : " -E #{bootstrap_environment}" start_chef = "SET \"PATH=%SystemRoot%\\system32;%SystemRoot%;%SystemRoot%\\System32\\Wbem;%SYSTEMROOT%\\System32\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;C:\\opscode\\chef\\bin;C:\\opscode\\chef\\embedded\\bin\"\n" + start_chef << "SET \"CHEF_LICENSE=accept\"\n" start_chef << "chef-client -c c:/chef/client.rb -j c:/chef/first-boot.json#{bootstrap_environment_option}\n" end diff --git a/lib/chef/version.rb b/lib/chef/version.rb index 14ae1e880a..4ecb821286 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -23,7 +23,7 @@ require "chef/version_string" class Chef CHEF_ROOT = File.expand_path("../..", __FILE__) - VERSION = Chef::VersionString.new("15.0.237") + VERSION = Chef::VersionString.new("15.0.241") end # diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock index db2232b90c..490531f84e 100644 --- a/omnibus/Gemfile.lock +++ b/omnibus/Gemfile.lock @@ -18,7 +18,7 @@ GIT GIT remote: https://github.com/chef/omnibus-software - revision: 3d4327c0032b8cc7d25eb33290a7aec2fce20a2a + revision: e4553dad2f70aae8fee6b19689e8d8596b60bb0e branch: master specs: omnibus-software (4.0.0) @@ -31,9 +31,9 @@ GEM addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) awesome_print (1.8.0) - aws-eventstream (1.0.2) - aws-partitions (1.151.0) - aws-sdk-core (3.48.4) + aws-eventstream (1.0.3) + aws-partitions (1.155.0) + aws-sdk-core (3.48.6) aws-eventstream (~> 1.0, >= 1.0.2) aws-partitions (~> 1.0) aws-sigv4 (~> 1.1) @@ -154,6 +154,7 @@ GEM concurrent-ruby (1.1.5) diff-lcs (1.3) ed25519 (1.2.4) + equatable (0.5.0) erubis (2.7.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) @@ -180,6 +181,11 @@ GEM kitchen-vagrant (1.5.1) test-kitchen (>= 1.4, < 3) libyajl2 (1.2.0) + license-acceptance (1.0.0) + pastel (~> 0.7) + tomlrb (~> 1.2) + tty-box (~> 0.3) + tty-prompt (~> 0.18) license_scout (1.0.24) ffi-yajl (~> 2.2) mixlib-shellout (~> 2.2) @@ -210,6 +216,7 @@ GEM molinillo (0.6.6) multi_json (1.13.1) multipart-post (2.0.0) + necromancer (0.4.0) net-scp (2.0.0) net-ssh (>= 2.6.5, < 6.0.0) net-sftp (2.1.2) @@ -236,6 +243,9 @@ GEM plist (~> 3.1) systemu (~> 2.6.4) wmi-lite (~> 1.0) + pastel (0.7.2) + equatable (~> 0.5.0) + tty-color (~> 0.4.0) pedump (0.5.2) awesome_print iostruct (>= 0.0.4) @@ -288,12 +298,18 @@ GEM net-ssh (>= 2.7) net-telnet (= 0.1.1) sfl + strings (0.1.5) + strings-ansi (~> 0.1) + unicode-display_width (~> 1.5) + unicode_utils (~> 1.4) + strings-ansi (0.1.0) structured_warnings (0.3.0) syslog-logger (1.6.8) systemu (2.6.5) - test-kitchen (2.1.0) + test-kitchen (2.2.0) bcrypt_pbkdf (~> 1.0) ed25519 (~> 1.2) + license-acceptance (>= 0.2.16, < 2.0) mixlib-install (~> 3.6) mixlib-shellout (>= 1.2, < 3.0) net-scp (>= 1.1, < 3.0) @@ -304,9 +320,29 @@ GEM winrm-elevated (~> 1.0) winrm-fs (~> 1.1) thor (0.20.3) + timers (4.3.0) toml-rb (1.1.2) citrus (~> 3.0, > 3.0) tomlrb (1.2.8) + tty-box (0.3.0) + pastel (~> 0.7.2) + strings (~> 0.1.4) + tty-cursor (~> 0.6.0) + tty-color (0.4.3) + tty-cursor (0.6.1) + tty-prompt (0.18.1) + necromancer (~> 0.4.0) + pastel (~> 0.7.0) + timers (~> 4.0) + tty-cursor (~> 0.6.0) + tty-reader (~> 0.5.0) + tty-reader (0.5.0) + tty-cursor (~> 0.6.0) + tty-screen (~> 0.6.4) + wisper (~> 2.0.0) + tty-screen (0.6.5) + unicode-display_width (1.5.0) + unicode_utils (1.4.0) uuidtools (2.1.5) win32-api (1.5.3-universal-mingw32) win32-certstore (0.3.0) @@ -332,7 +368,7 @@ GEM win32-taskscheduler (2.0.4) ffi structured_warnings - winrm (2.3.1) + winrm (2.3.2) builder (>= 2.1.2) erubis (~> 2.7) gssapi (~> 1.2) @@ -349,6 +385,7 @@ GEM logging (>= 1.6.1, < 3.0) rubyzip (~> 1.1) winrm (~> 2.0) + wisper (2.0.0) wmi-lite (1.0.2) zhexdump (0.0.2) diff --git a/spec/functional/shell_spec.rb b/spec/functional/shell_spec.rb index 3990f1afe0..dd0455fc9e 100644 --- a/spec/functional/shell_spec.rb +++ b/spec/functional/shell_spec.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2012-2017, Chef Software Inc. +# Copyright:: Copyright 2012-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -82,8 +82,7 @@ describe Shell do 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}") + reader, writer, pid = PTY.spawn("bundle exec chef-shell -c #{config} #{options}") read_until(reader, "chef (#{Chef::VERSION})>") yield reader, writer if block_given? writer.puts('"done"') diff --git a/spec/functional/version_spec.rb b/spec/functional/version_spec.rb index d968c36e8c..b12d235405 100644 --- a/spec/functional/version_spec.rb +++ b/spec/functional/version_spec.rb @@ -1,6 +1,6 @@ # # Author:: Serdar Sutay (<dan@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ describe "Chef Versions" do 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 match(/.*: #{Chef::VERSION}/) + expect(shell_out!("bundle exec #{binary} -v", cwd: chef_dir).stdout.chomp).to match(/.*: #{Chef::VERSION}/) end end diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index 006839be3f..cde25662c1 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -45,10 +45,8 @@ describe "chef-client" do # 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(:chef_solo) { "ruby '#{chef_dir}/chef-solo' --legacy-mode --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(" ") } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } + let(:chef_solo) { "bundle exec chef-solo --legacy-mode --minimal-ohai" } when_the_repository "has a cookbook with a no-op recipe" do before { file "cookbooks/x/recipes/default.rb", "" } @@ -62,22 +60,6 @@ describe "chef-client" do shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", cwd: chef_dir) end - it "should complete successfully with no other environment variables", skip: (Chef::Platform.windows?) do - file "config/client.rb", <<~EOM - local_mode true - cookbook_path "#{path_to('cookbooks')}" - EOM - - begin - result = shell_out("env -i #{critical_env_vars} #{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", cwd: 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", <<~EOM local_mode true diff --git a/spec/integration/client/exit_code_spec.rb b/spec/integration/client/exit_code_spec.rb index 2e29502070..6600a65c9f 100644 --- a/spec/integration/client/exit_code_spec.rb +++ b/spec/integration/client/exit_code_spec.rb @@ -21,7 +21,7 @@ describe "chef-client" do # 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(:chef_client) { "bundle exec chef-client --no-fork --minimal-ohai" } let(:critical_env_vars) { %w{PATH RUBYOPT BUNDLE_GEMFILE GEM_PATH}.map { |o| "#{o}=#{ENV[o]}" } .join(" ") } diff --git a/spec/integration/client/ipv6_spec.rb b/spec/integration/client/ipv6_spec.rb index 04154c296f..b97eb4e8b4 100644 --- a/spec/integration/client/ipv6_spec.rb +++ b/spec/integration/client/ipv6_spec.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -75,7 +75,7 @@ describe "chef-client" do 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} } + let(:chef_client_cmd) { %Q{bundle exec chef-client --minimal-ohai -c "#{path_to('config/client.rb')}" -lwarn} } after do FileUtils.rm_rf(cache_path) diff --git a/spec/integration/recipes/accumulator_spec.rb b/spec/integration/recipes/accumulator_spec.rb index 65a05fcdc5..d19d8637bb 100644 --- a/spec/integration/recipes/accumulator_spec.rb +++ b/spec/integration/recipes/accumulator_spec.rb @@ -16,7 +16,7 @@ describe "Accumulators" do # 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(:chef_client) { "bundle exec chef-client --minimal-ohai" } let(:aliases_temppath) do t = Tempfile.new("chef_accumulator_test") diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb index 2f4ef92f31..6bc857df48 100644 --- a/spec/integration/recipes/lwrp_inline_resources_spec.rb +++ b/spec/integration/recipes/lwrp_inline_resources_spec.rb @@ -16,7 +16,7 @@ describe "LWRPs with inline resources" do # 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(:chef_client) { "bundle exec chef-client --minimal-ohai" } context "with a use_inline_resources provider with 'def action_a' instead of action :a" do class LwrpInlineResourcesTest < Chef::Resource diff --git a/spec/integration/recipes/lwrp_spec.rb b/spec/integration/recipes/lwrp_spec.rb index b5af6978ac..ce2861d43b 100644 --- a/spec/integration/recipes/lwrp_spec.rb +++ b/spec/integration/recipes/lwrp_spec.rb @@ -16,7 +16,7 @@ describe "LWRPs" do # 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(:chef_client) { "bundle exec chef-client --minimal-ohai" } when_the_repository "has a cookbook named l-w-r-p" do before do diff --git a/spec/integration/recipes/notifies_spec.rb b/spec/integration/recipes/notifies_spec.rb index 0df7aa311f..860a109e4d 100644 --- a/spec/integration/recipes/notifies_spec.rb +++ b/spec/integration/recipes/notifies_spec.rb @@ -6,7 +6,7 @@ describe "notifications" do include Chef::Mixin::ShellOut let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } when_the_repository "notifies a nameless resource" do before do diff --git a/spec/integration/recipes/notifying_block_spec.rb b/spec/integration/recipes/notifying_block_spec.rb index 6c50854038..753e81dadb 100644 --- a/spec/integration/recipes/notifying_block_spec.rb +++ b/spec/integration/recipes/notifying_block_spec.rb @@ -1,6 +1,6 @@ # # Author:: John Keiser (<jkeiser@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ describe "notifying_block" do include Chef::Mixin::ShellOut let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } when_the_repository "notifying_block test one" do before do diff --git a/spec/integration/recipes/remote_directory.rb b/spec/integration/recipes/remote_directory.rb index a0e3e23ef3..6f67a38fc8 100644 --- a/spec/integration/recipes/remote_directory.rb +++ b/spec/integration/recipes/remote_directory.rb @@ -16,7 +16,7 @@ describe Chef::Resource::RemoteDirectory do # 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(:chef_client) { "bundle exec chef-client --minimal-ohai" } when_the_repository "has a cookbook with a source_dir with two subdirectories, each with one file and subdir in a different alphabetical order" do before do diff --git a/spec/integration/solo/solo_spec.rb b/spec/integration/solo/solo_spec.rb index efd889c5d0..a098256d36 100644 --- a/spec/integration/solo/solo_spec.rb +++ b/spec/integration/solo/solo_spec.rb @@ -16,7 +16,7 @@ describe "chef-solo" do let(:cookbook_ancient_100_metadata_rb) { cb_metadata("ancient", "1.0.0") } - let(:chef_solo) { "ruby bin/chef-solo --legacy-mode --minimal-ohai" } + let(:chef_solo) { "bundle exec chef-solo --legacy-mode --minimal-ohai" } when_the_repository "creates nodes" do let(:nodes_dir) { File.join(@repository_dir, "nodes") } @@ -26,7 +26,7 @@ describe "chef-solo" do file "config/solo.rb", <<~EOM chef_repo_path "#{@repository_dir}" EOM - result = shell_out("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -l debug", cwd: chef_dir) + result = shell_out("bundle exec chef-solo -c \"#{path_to('config/solo.rb')}\" -l debug", cwd: chef_dir) result.error! end diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb index b6851f2d0e..5fc9de4de7 100644 --- a/spec/support/shared/integration/integration_helper.rb +++ b/spec/support/shared/integration/integration_helper.rb @@ -19,7 +19,6 @@ require "tmpdir" require "fileutils" -require "chef_core/text" require "chef/config" require "chef/json_compat" require "chef/server_api" diff --git a/spec/unit/knife/bootstrap/train_connector_spec.rb b/spec/unit/knife/bootstrap/train_connector_spec.rb new file mode 100644 index 0000000000..08bf21dd42 --- /dev/null +++ b/spec/unit/knife/bootstrap/train_connector_spec.rb @@ -0,0 +1,155 @@ +# +# Copyright:: Copyright (c) 2019 Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS 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 "chef/knife/bootstrap/train_connector" + +describe Chef::Knife::Bootstrap::TrainConnector do + let(:protocol) { "mock" } + let(:family) { "unknown" } + let(:release) { "unknown" } # version + let(:name) { "unknown" } + let(:arch) { "x86_64" } + let(:host_url) { "mock://user1@example.com" } + let(:opts) { {} } + subject do + # Create a valid TargetHost with the backend stubbed out. + Chef::Knife::Bootstrap::TrainConnector.test_instance(host_url, + protocol: protocol, + family: family, + name: name, + release: release, + arch: arch, + opts: opts) + end + + context "connect!" do + end + + describe "platform helpers" do + context "on linux" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "reports that it is linux and unix, because that is how train classifies it" do + expect(subject.unix?).to eq true + expect(subject.linux?).to eq true + expect(subject.windows?).to eq false + end + end + context "on unix" do + let(:family) { "os" } + let(:name) { "mac_os_x" } + it "reports only a unix OS" do + expect(subject.unix?).to eq true + expect(subject.linux?).to eq false + expect(subject.windows?).to eq false + end + end + context "on windows" do + let(:family) { "windows" } + let(:name) { "windows" } + it "reports only a windows OS" do + expect(subject.unix?).to eq false + expect(subject.linux?).to eq false + expect(subject.windows?).to eq true + end + end + end + + describe "#connect!" do + it "establishes the connection to the remote host by waiting for it" do + expect(subject.connection).to receive(:wait_until_ready) + subject.connect! + end + end + + describe "#temp_dir" do + context "under windows" do + let(:family) { "windows" } + let(:name) { "windows" } + + it "uses the windows command to create the temp dir" do + expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_WIN_COMMAND + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "C:/a/path") + expect(subject.temp_dir).to eq "C:/a/path" + end + + end + context "under linux and unix-like" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "uses the *nix command to create the temp dir and sets ownership to logged-in user" do + expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_NIX_COMMAND + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "/a/path") + expect(subject).to receive(:run_command!).with("chown user1 '/a/path'") + expect(subject.temp_dir).to eq "/a/path" + end + + end + end + context "#upload_file_content!" do + it "creates a local file with expected content and uploads it" do + expect(subject).to receive(:upload_file!) do |local_path, remote_path| + expect(File.read(local_path)).to eq "test data" + expect(remote_path).to eq "/target/path" + end + subject.upload_file_content!("test data", "/target/path") + end + end + + context "del_file" do + context "on windows" do + let(:family) { "windows" } + let(:name) { "windows" } + it "deletes the file with a windows command" do + expect(subject).to receive(:run_command!) do |cmd, &_handler| + expect(cmd).to match(/Test-Path "deleteme\.txt".*/) + end + subject.del_file!("deleteme.txt") + end + end + context "on unix-like" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "deletes the file with a windows command" do + expect(subject).to receive(:run_command!) do |cmd, &_handler| + expect(cmd).to match(/rm -f "deleteme\.txt".*/) + end + subject.del_file!("deleteme.txt") + end + end + end + + context "#run_command!" do + it "raises a RemoteExecutionFailed when the remote execution failed" do + command_result = double("results", stdout: "", stderr: "failed", exit_status: 1) + expect(subject).to receive(:run_command).and_return command_result + + expect { subject.run_command!("test") }.to raise_error do |e| + expect(e.hostname).to eq subject.hostname + expect(e.class).to eq Chef::Knife::Bootstrap::RemoteExecutionFailed + expect(e.stderr).to eq "failed" + expect(e.stdout).to eq "" + expect(e.exit_status).to eq 1 + end + end + end + +end diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index f54c8ac1d6..ce590fc9ee 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -25,8 +25,17 @@ describe Chef::Knife::Bootstrap do let(:bootstrap_template) { nil } let(:stderr) { StringIO.new } let(:bootstrap_cli_options) { [ ] } - let(:base_os) { :linux } - let(:target_host) { double("TargetHost") } + let(:linux_test) { true } + let(:windows_test) { false } + let(:linux_test) { false } + let(:unix_test) { false } + let(:ssh_test) { false } + + let(:connection) do + double("TrainConnector", + windows?: windows_test, + linux?: linux_test, + unix?: unix_test) end let(:knife) do Chef::Log.logger = Logger.new(StringIO.new) @@ -35,15 +44,11 @@ describe Chef::Knife::Bootstrap do k = Chef::Knife::Bootstrap.new(bootstrap_cli_options) allow(k.ui).to receive(:stderr).and_return(stderr) allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) - allow(k).to receive(:target_host).and_return target_host + allow(k).to receive(:connection).and_return connection k.merge_configs k end - before do - allow(target_host).to receive(:base_os).and_return base_os - end - context "#bootstrap_template" do it "should default to chef-full" do expect(knife.bootstrap_template).to be_a_kind_of(String) @@ -320,7 +325,7 @@ describe Chef::Knife::Bootstrap do subject(:knife) do k = described_class.new Chef::Config[:knife][:bootstrap_template] = template_file - allow(k).to receive(:target_host).and_return target_host + allow(k).to receive(:connection).and_return connection k.parse_options(options) k.merge_configs k @@ -1578,7 +1583,7 @@ describe Chef::Knife::Bootstrap do expect(knife).to receive(:render_template).and_return "content" expect(knife).to receive(:upload_bootstrap).with("content").and_return "/remote/path.sh" expect(knife).to receive(:perform_bootstrap).with("/remote/path.sh") - expect(target_host).to receive(:del_file) # Make sure cleanup happens + expect(connection).to receive(:del_file!) # Make sure cleanup happens knife.run @@ -1687,14 +1692,14 @@ describe Chef::Knife::Bootstrap do let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message") } before do - allow(target_host).to receive(:hostname).and_return "testhost" + allow(connection).to receive(:hostname).and_return "testhost" end it "runs the remote script and logs the output" do expect(knife.ui).to receive(:info).with(/Bootstrapping.*/) expect(knife).to receive(:bootstrap_command) .with("/path.sh") .and_return("sh /path.sh") - expect(target_host) + expect(connection) .to receive(:run_command) .with("sh /path.sh") .and_yield("output here") @@ -1710,7 +1715,7 @@ describe Chef::Knife::Bootstrap do expect(knife).to receive(:bootstrap_command) .with("/path.sh") .and_return("sh /path.sh") - expect(target_host).to receive(:run_command).with("sh /path.sh").and_return result_mock + expect(connection).to receive(:run_command).with("sh /path.sh").and_return result_mock expect { knife.perform_bootstrap("/path.sh") }.to raise_error(SystemExit) end end @@ -1738,13 +1743,11 @@ describe Chef::Knife::Bootstrap do context "when an auth failure occurs" do let(:expected_error) do - # TODO This is awkward and ugly. Requires some refactor of chef_core/error - # to make it not so. See comment in rescue block of connect! for details. - e = RuntimeError.new - interim = RuntimeError.new + e = Train::Error.new actual = Net::SSH::AuthenticationFailed.new - allow(interim).to receive(:cause).and_return(actual) - allow(e).to receive(:cause).and_return(interim) + # Simulate train's nested error - they wrap + # ssh/network errors in a TrainError. + allow(e).to receive(:cause).and_return(actual) e end @@ -1754,7 +1757,7 @@ describe Chef::Knife::Bootstrap do context "and password auth was used" do before do - knife.config[:connection_password] = "tryme" + allow(connection).to receive(:password_auth?).and_return true end it "re-raises the error so as not to resubmit the same failing password" do @@ -1765,8 +1768,8 @@ describe Chef::Knife::Bootstrap do context "and password auth was not used" do before do - knife.config.delete :connection_password - allow(target_host).to receive(:user).and_return "testuser" + allow(connection).to receive(:password_auth?).and_return false + allow(connection).to receive(:user).and_return "testuser" end it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password" do @@ -1793,7 +1796,7 @@ describe Chef::Knife::Bootstrap do describe "#bootstrap_context" do context "under Windows" do - let(:base_os) { :windows } + let(:windows_test) { true } it "creates a WindowsBootstrapContext" do require "chef/knife/core/windows_bootstrap_context" expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::WindowsBootstrapContext @@ -1801,7 +1804,7 @@ describe Chef::Knife::Bootstrap do end context "under linux" do - let(:base_os) { :linux } + let(:linux_test) { true } it "creates a BootstrapContext" do require "chef/knife/core/bootstrap_context" expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::BootstrapContext @@ -1841,25 +1844,25 @@ describe Chef::Knife::Bootstrap do describe "#upload_bootstrap" do before do - allow(target_host).to receive(:temp_dir).and_return(temp_dir) - allow(target_host).to receive(:normalize_path) { |a| a } + allow(connection).to receive(:temp_dir).and_return(temp_dir) + allow(connection).to receive(:normalize_path) { |a| a } end let(:content) { "bootstrap script content" } context "under Windows" do - let(:base_os) { :windows } + let(:windows_test) { true } let(:temp_dir) { "C:/Temp/bootstrap" } - it "creates a bat file in the temp dir provided by target_host, using given content" do - expect(target_host).to receive(:save_as_remote_file).with(content, "C:/Temp/bootstrap/bootstrap.bat") + it "creates a bat file in the temp dir provided by connection, using given content" do + expect(connection).to receive(:upload_file_content!).with(content, "C:/Temp/bootstrap/bootstrap.bat") expect(knife.upload_bootstrap(content)).to eq "C:/Temp/bootstrap/bootstrap.bat" end end context "under Linux" do - let(:base_os) { :linux } + let(:linux_test) { true } let(:temp_dir) { "/tmp/bootstrap" } - it "creates a 'sh file in the temp dir provided by target_host, using given content" do - expect(target_host).to receive(:save_as_remote_file).with(content, "/tmp/bootstrap/bootstrap.sh") + it "creates a 'sh file in the temp dir provided by connection, using given content" do + expect(connection).to receive(:upload_file_content!).with(content, "/tmp/bootstrap/bootstrap.sh") expect(knife.upload_bootstrap(content)).to eq "/tmp/bootstrap/bootstrap.sh" end end @@ -1867,14 +1870,14 @@ describe Chef::Knife::Bootstrap do describe "#bootstrap_command" do context "under Windows" do - let(:base_os) { :windows } + let(:windows_test) { true } it "prefixes the command to run under cmd.exe" do expect(knife.bootstrap_command("autoexec.bat")).to eq "cmd.exe /C autoexec.bat" end end context "under Linux" do - let(:base_os) { :linux } + let(:linux_test) { true } it "prefixes the command to run under sh" do expect(knife.bootstrap_command("bootstrap")).to eq "sh bootstrap" end @@ -1883,14 +1886,14 @@ describe Chef::Knife::Bootstrap do describe "#default_bootstrap_template" do context "under Windows" do - let(:base_os) { :windows } + let(:windows_test) { true } it "is windows-chef-client-msi" do expect(knife.default_bootstrap_template).to eq "windows-chef-client-msi" end end context "under Linux" do - let(:base_os) { :linux } + let(:linux_test) { true } it "is chef-full" do expect(knife.default_bootstrap_template).to eq "chef-full" end @@ -1899,15 +1902,15 @@ describe Chef::Knife::Bootstrap do describe "#do_connect" do let(:host_descriptor) { "example.com" } - let(:target_host) { double("TargetHost") } - let(:resolver_mock) { double("TargetResolver", targets: [ target_host ]) } + let(:connection) { double("TrainConnector") } + let(:connector_mock) { double("TargetResolver", targets: [ connection ]) } before do allow(knife).to receive(:host_descriptor).and_return host_descriptor end - it "resolves the target and connects it" do - expect(ChefCore::TargetResolver).to receive(:new).and_return resolver_mock - expect(target_host).to receive(:connect!) + it "creates a TrainConnector and connects it" do + expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return connection + expect(connection).to receive(:connect!) knife.do_connect({}) end end diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 7b12177ab9..2ed8b6bc51 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -46,21 +46,21 @@ describe Chef::Knife::Core::BootstrapContext 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" + it "runs chef with the first-boot.json with no environment other than chef-license acceptance specified" do + expect(bootstrap_context.start_chef).to eq "CHEF_LICENSE=accept 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" + expect(bootstrap_context.start_chef).to eq "CHEF_LICENSE=accept 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" + expect(bootstrap_context.start_chef).to eq "CHEF_LICENSE=accept chef-client -j /etc/chef/first-boot.json --no-color" end end @@ -82,7 +82,7 @@ describe Chef::Knife::Core::BootstrapContext do 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" + expect(bootstrap_context.start_chef).to eq "CHEF_LICENSE=accept /usr/local/bin/chef-client -j /etc/chef/first-boot.json" end end @@ -105,7 +105,7 @@ describe Chef::Knife::Core::BootstrapContext do 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") + expect(bootstrap_context.start_chef).to eq("CHEF_LICENSE=accept chef-client -j /etc/chef/first-boot.json -E prodtastic") end end diff --git a/spec/unit/knife/core/windows_bootstrap_context_spec.rb b/spec/unit/knife/core/windows_bootstrap_context_spec.rb index 3ab173f316..a19ad11247 100644 --- a/spec/unit/knife/core/windows_bootstrap_context_spec.rb +++ b/spec/unit/knife/core/windows_bootstrap_context_spec.rb @@ -264,4 +264,12 @@ describe Chef::Knife::Core::WindowsBootstrapContext do end end end + + describe "#start_chef" do + it "the command includes the license acceptance environment variable" do + expect(bootstrap_context.start_chef).to match(/SET "CHEF_LICENSE=accept"\n/m) + end + + end + end |