diff options
44 files changed, 1302 insertions, 144 deletions
diff --git a/.travis.yml b/.travis.yml index b918dfe7d3..3a311ed931 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,36 +35,17 @@ env: matrix: include: - env: - INTEGRATION_SPECS_24: 1 - rvm: 2.4.5 - sudo: true - script: sudo -E $(which bundle) exec rake spec:integration; - bundler_args: --without ci docgen guard integration maintenance omnibus_package --frozen - - env: INTEGRATION_SPECS_25: 1 rvm: 2.5.3 sudo: true script: sudo -E $(which bundle) exec rake spec:integration; bundler_args: --without ci docgen guard integration maintenance omnibus_package --frozen - env: - FUNCTIONAL_SPECS_24: 1 - rvm: 2.4.5 - sudo: true - script: sudo -E $(which bundle) exec rake spec:functional; - bundler_args: --without ci docgen guard integration maintenance omnibus_package --frozen - - env: FUNCTIONAL_SPECS_25: 1 rvm: 2.5.3 sudo: true - script: sudo -E $(which bundle) exec rake spec:functional; - bundler_args: --without ci docgen guard integration maintenance omnibus_package --frozen - - env: - UNIT_SPECS_24: 1 - rvm: 2.4.5 - sudo: true - script: - - sudo -E $(which bundle) exec rake spec:unit; - - sudo -E $(which bundle) exec rake component_specs + # the travis apt proxy screws with our functional testing something fierce + script: sudo rm -f /etc/apt/apt.conf.d/99-travis-apt-proxy; sudo -E $(which bundle) exec rake spec:functional; bundler_args: --without ci docgen guard integration maintenance omnibus_package --frozen - env: UNIT_SPECS_25: 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index afa46ea9cc..fc1d0a4c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,33 @@ <!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ --> -<!-- latest_release 15.0.87 --> -## [v15.0.87](https://github.com/chef/chef/tree/v15.0.87) (2018-11-22) +<!-- latest_release 15.0.105 --> +## [v15.0.105](https://github.com/chef/chef/tree/v15.0.105) (2018-12-13) #### Merged Pull Requests -- Support apt-get --allow-downgrades [#7963](https://github.com/chef/chef/pull/7963) ([lamont-granquist](https://github.com/lamont-granquist)) +- Remove travis apt proxy before running functional tests [#8040](https://github.com/chef/chef/pull/8040) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- latest_release --> <!-- release_rollup --> ### Changes since latest stable release #### Merged Pull Requests +- Remove travis apt proxy before running functional tests [#8040](https://github.com/chef/chef/pull/8040) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.105 --> +- Chef 15: Windows Server 2019 version detection [#8031](https://github.com/chef/chef/pull/8031) ([stuartpreston](https://github.com/stuartpreston)) <!-- 15.0.104 --> +- Allow the use of tagged?(tags) method in both only_if and not_if blocks [#7977](https://github.com/chef/chef/pull/7977) ([kapilchouhan99](https://github.com/kapilchouhan99)) <!-- 15.0.103 --> +- Bugfixes to powershell_package_source [#8025](https://github.com/chef/chef/pull/8025) ([Happycoil](https://github.com/Happycoil)) <!-- 15.0.102 --> +- Require Ruby 2.5 or later [#8023](https://github.com/chef/chef/pull/8023) ([tas50](https://github.com/tas50)) <!-- 15.0.101 --> +- timezone: updated description to include windows [#8018](https://github.com/chef/chef/pull/8018) ([Stromweld](https://github.com/Stromweld)) <!-- 15.0.100 --> +- Update InSpec to 3.0.61 and Ohai to 15.0.20 [#8010](https://github.com/chef/chef/pull/8010) ([tas50](https://github.com/tas50)) <!-- 15.0.99 --> +- Fix locking ohai to to the value in the Gemfile.lock [#8014](https://github.com/chef/chef/pull/8014) ([tas50](https://github.com/tas50)) <!-- 15.0.98 --> +- Pin the ohai definition to use the ohai version from Gemfile.lock [#8012](https://github.com/chef/chef/pull/8012) ([tas50](https://github.com/tas50)) <!-- 15.0.97 --> +- RHEL8 yum_package fix. [#8005](https://github.com/chef/chef/pull/8005) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.96 --> +- package resource: Add RHEL 8 support to DNF package installer [#8003](https://github.com/chef/chef/pull/8003) ([pixdrift](https://github.com/pixdrift)) <!-- 15.0.95 --> +- Initial suppport for snap packages [#7999](https://github.com/chef/chef/pull/7999) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.94 --> +- better kithen ohai pinning [#7998](https://github.com/chef/chef/pull/7998) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.93 --> +- Make sure which mixin requires chef_class [#7989](https://github.com/chef/chef/pull/7989) ([tas50](https://github.com/tas50)) <!-- 15.0.92 --> +- pull the ohai version from the bundle not from master [#7987](https://github.com/chef/chef/pull/7987) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.91 --> +- gem_package provider supports --no-document and rubygems 3.x [#7986](https://github.com/chef/chef/pull/7986) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.90 --> +- Added windows support for the timezone resource [#7806](https://github.com/chef/chef/pull/7806) ([username-is-already-taken2](https://github.com/username-is-already-taken2)) <!-- 15.0.89 --> +- cab_package: Chef should fail when specified package is not applicable to the image [#7951](https://github.com/chef/chef/pull/7951) ([kapilchouhan99](https://github.com/kapilchouhan99)) <!-- 15.0.88 --> - Support apt-get --allow-downgrades [#7963](https://github.com/chef/chef/pull/7963) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.87 --> - Update openssl to 1.0.2q [#7979](https://github.com/chef/chef/pull/7979) ([tas50](https://github.com/tas50)) <!-- 15.0.86 --> - Update inspec to 3.0.52 [#7978](https://github.com/chef/chef/pull/7978) ([tas50](https://github.com/tas50)) <!-- 15.0.85 --> diff --git a/Gemfile.lock b/Gemfile.lock index f4b34f419e..236efb7cbf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,10 +8,10 @@ GIT GIT remote: https://github.com/chef/ohai.git - revision: a946ff969ae335b163a1766901ccd960ad6dc4eb + revision: dbbd71fd79de6f03492e69936467412c43463a4e branch: master specs: - ohai (15.0.7) + ohai (15.0.25) chef-config (>= 12.8, < 16) ffi (~> 1.9) ffi-yajl (~> 2.2) @@ -27,10 +27,10 @@ GIT PATH remote: . specs: - chef (15.0.87) + chef (15.0.105) addressable bundler (>= 1.10) - chef-config (= 15.0.87) + chef-config (= 15.0.105) chef-zero (>= 14.0.11) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -57,10 +57,10 @@ PATH specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) - chef (15.0.87-universal-mingw32) + chef (15.0.105-universal-mingw32) addressable bundler (>= 1.10) - chef-config (= 15.0.87) + chef-config (= 15.0.105) chef-zero (>= 14.0.11) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -104,7 +104,7 @@ PATH PATH remote: chef-config specs: - chef-config (15.0.87) + chef-config (15.0.105) addressable fuzzyurl mixlib-config (>= 2.2.12, < 3.0) @@ -143,7 +143,7 @@ GEM erubis (2.7.0) ethon (0.11.0) ffi (>= 1.3.0) - faraday (0.15.3) + faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday_middleware (0.12.2) faraday (>= 0.7.4, < 1.0) @@ -167,7 +167,7 @@ GEM highline (1.7.10) htmlentities (4.3.4) iniparse (1.4.4) - inspec-core (3.0.52) + inspec-core (3.0.61) addressable (~> 2.4) faraday (>= 0.9.0) hashie (~> 3.4) @@ -194,17 +194,17 @@ GEM addressable (~> 2.3) libyajl2 (1.2.0) method_source (0.9.2) - mixlib-archive (0.4.18) + mixlib-archive (0.4.19) mixlib-log - mixlib-archive (0.4.18-universal-mingw32) + mixlib-archive (0.4.19-universal-mingw32) mixlib-log mixlib-authentication (2.1.1) mixlib-cli (1.7.0) mixlib-config (2.2.13) tomlrb mixlib-log (2.0.4) - mixlib-shellout (2.4.0) - mixlib-shellout (2.4.0-universal-mingw32) + mixlib-shellout (2.4.4) + mixlib-shellout (2.4.4-universal-mingw32) win32-process (~> 0.8.2) wmi-lite (~> 1.0) multi_json (1.13.1) @@ -298,7 +298,7 @@ GEM simplecov-html (~> 0.10.0) simplecov-html (0.10.2) slop (3.6.0) - specinfra (2.76.3) + specinfra (2.76.5) net-scp net-ssh (>= 2.7) net-telnet (= 0.1.1) @@ -356,7 +356,7 @@ GEM structured_warnings windows-api (0.4.4) win32-api (>= 1.4.5) - wmi-lite (1.0.0) + wmi-lite (1.0.1) yard (0.9.16) PLATFORMS diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a4aca19c26..cf162cb7e5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,9 +6,21 @@ Chef 15 release notes will be added here as development progresses. ## Breaking Changes +### Chef packages now remove /opt/chef before installation + +The intent of this change is that on upgrading packages the /opt/chef directory is removed of any `chef_gem` installed gem versions and other +modifications to /opt/chef that might be preserved and cause issues on upgrades. Due to technical details with rpm script execution order +the way this was implemented was that a pre-installation script wipes /opt/chef before every install (done consistently this way on +every package manager). + +Users who are properly managing customizations to /opt/chef through Chef recipes won't be affected, because their customizations will still be installed by +the new chef-client package. + +You'll see a warning that the /opt/chef directory will be removed during the package installation process. + ### Package provider allow_downgrade is now true by default -We reversed the default behavior to `allow_downgrade true` for our package providers. To override this setting to refuse downgrades, use the `allow_downgrade —false` flag. This behavior change will mostly affect users of the rpm and zypper package providers. +We reversed the default behavior to `allow_downgrade true` for our package providers. To override this setting to prevent downgrades, use the `allow_downgrade false` flag. This behavior change will mostly affect users of the rpm and zypper package providers. ``` package "foo" do @@ -111,6 +123,14 @@ node.default["foo"][0][:bar] # does not work due to the sub-Hash not The new behavior uses a Mash so that the attributes will work as expected. +### Ohai's system_profile plugin for macOS removed + +We removed the system_profile plugin because it incorrectly returned data on modern Mac systems. If you relied on this plugin, you'll want to update recipes to use `node['hardware']` instead, which correctly returns the same data, but in a more easily consumed format. Removing this plugin speeds up Ohai (and Chef) by ~3 seconds and dramatically reduces the size of the node object on the Chef server. + +### Ohai's Ohai::Util::Win32::GroupHelper class has been removed + +We removed the Ohai::Util::Win32::GroupHelper helper class from Ohai. This class was intended for use internally in several Windows plugins, but it was never marked private in the codebase. If any of your Ohai plugins rely on this helper class, you will need to update your plugins for Ohai 15. + # Chef Client Release Notes 14.7: ## New Resources @@ -1 +1 @@ -15.0.87
\ No newline at end of file +15.0.105
\ No newline at end of file diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb index 8eebaa9627..0734a481dc 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.87".freeze + VERSION = "15.0.105".freeze end # diff --git a/chef.gemspec b/chef.gemspec index c787e00580..15fc862deb 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.email = "adam@chef.io" s.homepage = "https://www.chef.io" - s.required_ruby_version = ">= 2.4.0" + s.required_ruby_version = ">= 2.5.0" s.add_dependency "chef-config", "= #{Chef::VERSION}" diff --git a/kitchen-tests/kitchen.travis.yml b/kitchen-tests/kitchen.travis.yml index 06b789a3c2..160f92eb51 100644 --- a/kitchen-tests/kitchen.travis.yml +++ b/kitchen-tests/kitchen.travis.yml @@ -17,7 +17,7 @@ provisioner: github_owner: "chef" github_repo: "chef" refname: <%= ENV['TRAVIS_COMMIT'] %> - ohai_refname: "master" + ohai_refname: "<%= File.readlines('../Gemfile.lock', File.expand_path(File.dirname(__FILE__))).find { |l| l =~ /^\s+ohai \((\d+\.\d+\.\d+)\)/ }; 'v' + $1 %>" github_access_token: <%= ENV['KITCHEN_GITHUB_TOKEN'] %> data_path: test/fixtures # disable file provider diffs so we don't overflow travis' line limit diff --git a/lib/chef/dsl/universal.rb b/lib/chef/dsl/universal.rb index f3b79b1d60..0856012cbd 100644 --- a/lib/chef/dsl/universal.rb +++ b/lib/chef/dsl/universal.rb @@ -47,6 +47,13 @@ class Chef include Chef::Mixin::PowershellExec include Chef::Mixin::PowershellOut include Chef::Mixin::ShellOut + + def tagged?(*tags) + tags.each do |tag| + return false unless run_context.node.tags.include?(tag) + end + true + end end end end diff --git a/lib/chef/mixin/which.rb b/lib/chef/mixin/which.rb index 8a825f55c9..ffa324c911 100644 --- a/lib/chef/mixin/which.rb +++ b/lib/chef/mixin/which.rb @@ -1,6 +1,6 @@ #-- # Author:: Lamont Granquist <lamont@chef.io> -# Copyright:: Copyright 2010-2017, Chef Software Inc. +# Copyright:: Copyright 2010-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,8 @@ class Chef module Mixin module Which + require "chef/chef_class" + def which(*cmds, extra_path: nil, &block) where(*cmds, extra_path: extra_path, &block).first || false end diff --git a/lib/chef/provider/package/cab.rb b/lib/chef/provider/package/cab.rb index 79292293d2..cfc629b9ef 100644 --- a/lib/chef/provider/package/cab.rb +++ b/lib/chef/provider/package/cab.rb @@ -74,9 +74,13 @@ class Chef end def dism_command(command) - shellout = Mixlib::ShellOut.new("dism.exe /Online /English #{command} /NoRestart", timeout: new_resource.timeout) with_os_architecture(nil) do - shellout.run_command + result = shell_out("dism.exe /Online /English #{command} /NoRestart", { timeout: new_resource.timeout }) + if result.exitstatus == -2146498530 + raise Chef::Exceptions::Package, "The specified package is not applicable to this image." if result.stdout.include?("0x800f081e") + result.error! + end + result end end diff --git a/lib/chef/provider/package/dnf/python_helper.rb b/lib/chef/provider/package/dnf/python_helper.rb index b3593f6efd..56706764d0 100644 --- a/lib/chef/provider/package/dnf/python_helper.rb +++ b/lib/chef/provider/package/dnf/python_helper.rb @@ -37,7 +37,8 @@ class Chef DNF_HELPER = ::File.expand_path(::File.join(::File.dirname(__FILE__), "dnf_helper.py")).freeze def dnf_command - @dnf_command ||= which("python", "python3", "python2", "python2.7") do |f| + # platform-python is used for system tools on RHEL 8 and is installed under /usr/libexec + @dnf_command ||= which("platform-python", "python", "python3", "python2", "python2.7", extra_path: "/usr/libexec") do |f| shell_out("#{f} -c 'import dnf'").exitstatus == 0 end + " #{DNF_HELPER}" end diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb index d99dce8972..cd595e64f4 100644 --- a/lib/chef/provider/package/rubygems.rb +++ b/lib/chef/provider/package/rubygems.rb @@ -1,7 +1,7 @@ # # Author:: Adam Jacob (<adam@chef.io>) # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2008-2016, 2010-2017, Chef Software Inc. +# Copyright:: Copyright 2008-2016, 2010-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,42 +49,44 @@ class Chef DEFAULT_UNINSTALLER_OPTS = { ignore: true, executables: true }.freeze - ## # 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 + # + # @return [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 + # + # @return [Gem::Specification] + # def gem_specification raise NotImplementedError end - ## + def rubygems_version + 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 + # + # @param gem_dep [Gem::Dependency] the version specification that constrains + # which gems are used. + # @return [Array<Gem::Specification>] an array of Gem::Specification objects + # def installed_versions(gem_dep) rubygems_version = Gem::Version.new(Gem::VERSION) if rubygems_version >= Gem::Version.new("2.7") @@ -266,6 +268,10 @@ class Chef Gem::Specification end + def rubygems_version + Gem::VERSION + end + def candidate_version_from_remote(gem_dependency, *sources) with_gem_sources(*sources) do find_newest_remote_version(gem_dependency, *sources) @@ -293,6 +299,10 @@ class Chef @gem_binary_location = gem_binary_location end + def rubygems_version + @rubygems_version ||= shell_out!("#{@gem_binary_location} --version").stdout.chomp + end + def gem_paths if self.class.gempath_cache.key?(@gem_binary_location) self.class.gempath_cache[@gem_binary_location] @@ -547,9 +557,9 @@ class Chef end src_str = src.empty? ? "" : " #{src.join(" ")}" if !version.nil? && !version.empty? - shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src_str}#{opts}", env: nil) + shell_out!("#{gem_binary_path} install #{name} -q #{rdoc_string} -v \"#{version}\"#{src_str}#{opts}", env: nil) else - shell_out!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src_str}#{opts}", env: nil) + shell_out!("#{gem_binary_path} install \"#{name}\" -q #{rdoc_string} #{src_str}#{opts}", env: nil) end end @@ -585,6 +595,18 @@ class Chef private + def rdoc_string + if needs_nodocument? + "--no-document" + else + "--no-rdoc --no-ri" + end + end + + def needs_nodocument? + Gem::Requirement.new(">= 3.0.0.beta1").satisfied_by?(Gem::Version.new(gem_env.rubygems_version)) + end + def opts expand_options(new_resource.options) end diff --git a/lib/chef/provider/package/snap.rb b/lib/chef/provider/package/snap.rb new file mode 100644 index 0000000000..691cfcd62e --- /dev/null +++ b/lib/chef/provider/package/snap.rb @@ -0,0 +1,358 @@ +# +# Author:: S.Cavallo (<smcavallo@hotmail.com>) +# Copyright:: Copyright 2016-2018, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR 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/snap_package" +require "chef/mixin/shell_out" +require "socket" +require "json" + +class Chef + class Provider + class Package + class Snap < Chef::Provider::Package + allow_nils + use_multipackage_api + use_package_name_for_source + + provides :snap_package + + def load_current_resource + @current_resource = Chef::Resource::SnapPackage.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 + requirements.assert(:install, :upgrade, :remove, :purge) do |a| + a.assertion { !new_resource.source || ::File.exist?(new_resource.source) } + a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}" + a.whyrun "assuming #{new_resource.source} would have previously been created" + end + + super + end + + def candidate_version + package_name_array.each_with_index.map do |pkg, i| + available_version(i) + end + end + + def get_current_versions + package_name_array.each_with_index.map do |pkg, i| + installed_version(i) + end + end + + def install_package(names, versions) + if new_resource.source + install_snap_from_source(names, new_resource.source) + else + resolved_names = names.each_with_index.map { |name, i| available_version(i).to_s unless name.nil? } + install_snaps(resolved_names) + end + end + + def upgrade_package(names, versions) + if new_resource.source + install_snap_from_source(names, new_resource.source) + else + resolved_names = names.each_with_index.map { |name, i| available_version(i).to_s unless name.nil? } + update_snaps(resolved_names) + end + end + + def remove_package(names, versions) + resolved_names = names.each_with_index.map { |name, i| installed_version(i).to_s unless name.nil? } + uninstall_snaps(resolved_names) + end + + alias purge_package remove_package + + private + + # @return Array<Version> + def available_version(index) + @available_version ||= [] + + @available_version[index] ||= if new_resource.source + get_snap_version_from_source(new_resource.source) + else + get_latest_package_version(package_name_array[index], new_resource.channel) + end + + @available_version[index] + end + + # @return [Array<Version>] + def installed_version(index) + @installed_version ||= [] + @installed_version[index] ||= get_installed_package_version_by_name(package_name_array[index]) + @installed_version[index] + end + + def safe_version_array + if new_resource.version.is_a?(Array) + new_resource.version + elsif new_resource.version.nil? + package_name_array.map { nil } + else + [new_resource.version] + end + end + + # ToDo: Support authentication + # ToDo: Support private snap repos + # https://github.com/snapcore/snapd/wiki/REST-API + + # ToDo: Would prefer to use net/http over socket + def call_snap_api(method, uri, post_data = nil?) + request = "#{method} #{uri} HTTP/1.0\r\n" + + "Accept: application/json\r\n" + + "Content-Type: application/json\r\n" + if method == "POST" + request.concat("Content-Length: #{post_data.bytesize}\r\n\r\n#{post_data}") + end + request.concat("\r\n") + # While it is expected to allow clients to connect using HTTPS over a TCP socket, + # at this point only a UNIX socket is supported. The socket is /run/snapd.socket + # Note - UNIXSocket is not defined on windows systems + if defined?(::UNIXSocket) + UNIXSocket.open("/run/snapd.socket") do |socket| + # Send request, read the response, split the response and parse the body + socket.print(request) + response = socket.read + headers, body = response.split("\r\n\r\n", 2) + JSON.parse(body) + end + end + end + + def get_change_id(id) + call_snap_api("GET", "/v2/changes/#{id}") + end + + def get_id_from_async_response(response) + if response["type"] == "error" + raise "status: #{response["status"]}, kind: #{response["result"]["kind"]}, message: #{response["result"]["message"]}" + end + response["change"] + end + + def wait_for_completion(id) + n = 0 + waiting = true + while waiting + result = get_change_id(id) + puts "STATUS: #{result["result"]["status"]}" + case result["result"]["status"] + when "Do", "Doing", "Undoing", "Undo" + # Continue + when "Abort" + raise result + when "Hold", "Error" + raise result + when "Done" + waiting = false + else + # How to handle unknown status + end + n += 1 + raise "Snap operating timed out after #{n} seconds." if n == 300 + sleep(1) + end + end + + def snapctl(*args) + shell_out!("snap", *args) + end + + def get_snap_version_from_source(path) + body = { + "context-id" => "get_snap_version_from_source_#{path}", + "args" => ["info", path,], + }.to_json + + # json = call_snap_api('POST', '/v2/snapctl', body) + response = snapctl(["info", path]) + Chef::Log.trace(response) + response.error! + get_version_from_stdout(response.stdout) + end + + def get_version_from_stdout(stdout) + stdout.match(/version: (\S+)/)[1] + end + + def install_snap_from_source(name, path) + # json = call_snap_api('POST', '/v2/snapctl', body) + response = snapctl(["install", path]) + Chef::Log.trace(response) + response.error! + end + + def install_snaps(snap_names) + response = post_snaps(snap_names, "install", new_resource.channel, new_resource.options) + id = get_id_from_async_response(response) + wait_for_completion(id) + end + + def update_snaps(snap_names) + response = post_snaps(snap_names, "refresh", new_resource.channel, new_resource.options) + id = get_id_from_async_response(response) + wait_for_completion(id) + end + + def uninstall_snaps(snap_names) + response = post_snaps(snap_names, "remove", new_resource.channel, new_resource.options) + id = get_id_from_async_response(response) + wait_for_completion(id) + end + + # Constructs the multipart/form-data required to sideload packages + # https://github.com/snapcore/snapd/wiki/REST-API#sideload-request + # + # @param snap_name [String] An array of snap package names to install + # @param action [String] The action. Valid: install or try + # @param options [Hash] Misc configuration Options + # @param path [String] Path to the package on disk + # @param content_length [Integer] byte size of the snap file + def generate_multipart_form_data(snap_name, action, options, path, content_length) + snap_options = options.map do |k, v| + <<~SNAP_OPTION + Content-Disposition: form-data; name="#{k}" + + #{v} + --#{snap_name} + SNAP_OPTION + end + + multipart_form_data = <<~SNAP_S + Host: + Content-Type: multipart/form-data; boundary=#{snap_name} + Content-Length: #{content_length} + + --#{snap_name} + Content-Disposition: form-data; name="action" + + #{action} + --#{snap_name} + #{snap_options.join("\n").chomp} + Content-Disposition: form-data; name="snap"; filename="#{path}" + + <#{content_length} bytes of snap file data> + --#{snap_name} + SNAP_S + multipart_form_data + end + + # Constructs json to post for snap changes + # + # @param snap_names [Array] An array of snap package names to install + # @param action [String] The action. install, refresh, remove, revert, enable, disable or switch + # @param channel [String] The release channel. Ex. stable + # @param options [Hash] Misc configuration Options + # @param revision [String] A revision/version + def generate_snap_json(snap_names, action, channel, options, revision = nil) + request = { + "action" => action, + "snaps" => snap_names, + } + if %w{install refresh switch}.include?(action) + request["channel"] = channel + end + + # No defensive handling of params + # Snap will throw the proper exception if called improperly + # And we can provide that exception to the end user + request["classic"] = true if options["classic"] + request["devmode"] = true if options["devmode"] + request["jailmode"] = true if options["jailmode"] + request["revision"] = revision unless revision.nil? + request["ignore_validation"] = true if options["ignore-validation"] + request + end + + # Post to the snap api to update snaps + # + # @param snap_names [Array] An array of snap package names to install + # @param action [String] The action. install, refresh, remove, revert, enable, disable or switch + # @param channel [String] The release channel. Ex. stable + # @param options [Hash] Misc configuration Options + # @param revision [String] A revision/version + def post_snaps(snap_names, action, channel, options, revision = nil) + json = generate_snap_json(snap_names, action, channel, options, revision = nil) + call_snap_api("POST", "/v2/snaps", json) + end + + def get_latest_package_version(name, channel) + json = call_snap_api("GET", "/v2/find?name=#{name}") + if json["status-code"] != 200 + raise Chef::Exceptions::Package, json["result"], caller + end + + # Return the version matching the channel + json["result"][0]["channels"]["latest/#{channel}"]["version"] + end + + def get_installed_packages + json = call_snap_api("GET", "/v2/snaps") + # We only allow 200 or 404s + unless [200, 404].include? json["status-code"] + raise Chef::Exceptions::Package, json["result"], caller + end + json["result"] + end + + def get_installed_package_version_by_name(name) + result = get_installed_package_by_name(name) + # Return nil if not installed + if result["status-code"] == 404 + nil + else + result["version"] + end + end + + def get_installed_package_by_name(name) + json = call_snap_api("GET", "/v2/snaps/#{name}") + # We only allow 200 or 404s + unless [200, 404].include? json["status-code"] + raise Chef::Exceptions::Package, json["result"], caller + end + json["result"] + end + + def get_installed_package_conf(name) + json = call_snap_api("GET", "/v2/snaps/#{name}/conf") + json["result"] + end + + def set_installed_package_conf(name, value) + response = call_snap_api("PUT", "/v2/snaps/#{name}/conf", value) + id = get_id_from_async_response(response) + wait_for_completion(id) + end + + end + end + end +end diff --git a/lib/chef/provider/package/yum/python_helper.rb b/lib/chef/provider/package/yum/python_helper.rb index 3da4bb92e8..6ae9cc911a 100644 --- a/lib/chef/provider/package/yum/python_helper.rb +++ b/lib/chef/provider/package/yum/python_helper.rb @@ -40,7 +40,7 @@ class Chef YUM_HELPER = ::File.expand_path(::File.join(::File.dirname(__FILE__), "yum_helper.py")).freeze def yum_command - @yum_command ||= which("python", "python2", "python2.7") do |f| + @yum_command ||= which("platform-python", "python", "python2", "python2.7", extra_path: "/usr/libexec") do |f| shell_out("#{f} -c 'import yum'").exitstatus == 0 end + " #{YUM_HELPER}" end diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb index f00b211630..209b7faa74 100644 --- a/lib/chef/recipe.rb +++ b/lib/chef/recipe.rb @@ -71,21 +71,6 @@ class Chef run_context.node.tag(*tags) end - # Returns true if the node is tagged with *all* of the supplied +tags+. - # - # === Parameters - # tags<Array>:: A list of tags - # - # === Returns - # true<TrueClass>:: If all the parameters are present - # false<FalseClass>:: 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 diff --git a/lib/chef/resource/powershell_package_source.rb b/lib/chef/resource/powershell_package_source.rb index 321a788822..061b9e632d 100644 --- a/lib/chef/resource/powershell_package_source.rb +++ b/lib/chef/resource/powershell_package_source.rb @@ -54,10 +54,14 @@ class Chef description: "The location where scripts will be published to for this source. Only valid if the provider is 'PowerShellGet'." load_current_value do - cmd = load_resource_state_script(name) + cmd = load_resource_state_script(source_name) repo = powershell_out!(cmd) - status = Chef::JSONCompat.from_json(repo.stdout) - url status["url"].nil? ? "not_set" : status["url"] + if repo.stdout.empty? + current_value_does_not_exist! + else + status = Chef::JSONCompat.from_json(repo.stdout) + end + url status["url"] trusted status["trusted"] provider_name status["provider_name"] publish_location status["publish_location"] @@ -112,7 +116,7 @@ class Chef action_class do def package_source_exists? - cmd = powershell_out!("(Get-PackageSource -Name '#{new_resource.source_name}').Name") + cmd = powershell_out!("(Get-PackageSource -Name '#{new_resource.source_name}' -WarningAction SilentlyContinue).Name") cmd.stdout.downcase.strip == new_resource.source_name.downcase end @@ -144,6 +148,9 @@ class Chef def load_resource_state_script(name) <<-EOH + $PSDefaultParameterValues = @{ + "*:WarningAction" = "SilentlyContinue" + } if(Get-PackageSource -Name '#{name}' -ErrorAction SilentlyContinue) { if ((Get-PackageSource -Name '#{name}').ProviderName -eq 'PowerShellGet') { (Get-PSRepository -Name '#{name}') | Select @{n='source_name';e={$_.Name}}, @{n='url';e={$_.SourceLocation}}, @@ -155,9 +162,6 @@ class Chef @{n='provider_name';e={$_.ProviderName}}, @{n='trusted';e={$_.IsTrusted}} | ConvertTo-Json } } - else { - "" | Select source_name, url, provider_name, trusted | ConvertTo-Json - } EOH end end diff --git a/lib/chef/resource/snap_package.rb b/lib/chef/resource/snap_package.rb new file mode 100644 index 0000000000..81904f8405 --- /dev/null +++ b/lib/chef/resource/snap_package.rb @@ -0,0 +1,35 @@ +# +# Author:: S.Cavallo (<smcavallo@hotmail.com>) +# Copyright:: Copyright 2008-2018, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR 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 SnapPackage < Chef::Resource::Package + resource_name :snap_package + + description "Use the snap_package resource to manage snap packages on Debian and Ubuntu platforms." + + property :channel, String, + description: "The default channel. For example: stable.", + default: "stable", + equal_to: %w{edge beta candidate stable}, + desired_state: false + end + end +end diff --git a/lib/chef/resource/timezone.rb b/lib/chef/resource/timezone.rb index da3cb70bb9..5d4f1aca5e 100644 --- a/lib/chef/resource/timezone.rb +++ b/lib/chef/resource/timezone.rb @@ -24,7 +24,7 @@ class Chef class Timezone < Chef::Resource resource_name :timezone - description "Use the timezone resource to change the system timezone on Linux and macOS hosts. Timezones are specified in tz database format, with a complete list of available TZ values here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones." + description "Use the timezone resource to change the system timezone on Windows, Linux, and macOS hosts. Timezones are specified in tz database format, with a complete list of available TZ values for Linux and macOS here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones and for Windows here: https://ss64.com/nt/timezones.html." introduced "14.6" property :timezone, String, @@ -95,6 +95,12 @@ class Chef shell_out!("sudo systemsetup -settimezone #{new_resource.timezone}") end end + when "windows" + unless current_windows_tz.casecmp?(new_resource.timezone) + converge_by("setting timezone to \"#{new_resource.timezone}\"") do + shell_out!("tzutil /s \"#{new_resource.timezone}\"") + end + end end end end @@ -112,6 +118,16 @@ class Chef /Time Zone: (.*)/.match(tz_shellout.stdout)[1] end end + + # detect the current timezone on windows hosts + # + # @since 14.7 + # @return [String] timezone id + def current_windows_tz + tz_shellout = shell_out("tzutil /g") + raise "There was an error running the tzutil command" if tz_shellout.exitstatus == 1 + tz_shellout.stdout.strip + end end end end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 32739087d5..9a418229c8 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -93,6 +93,7 @@ require "chef/resource/rhsm_register" require "chef/resource/rhsm_repo" require "chef/resource/rhsm_subscription" require "chef/resource/rpm_package" +require "chef/resource/snap_package" require "chef/resource/solaris_package" require "chef/resource/route" require "chef/resource/ruby" diff --git a/lib/chef/version.rb b/lib/chef/version.rb index 53316f3294..fb716b3c0a 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.87") + VERSION = Chef::VersionString.new("15.0.105") end # diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb index 9a506aa740..07f2b8d7a8 100644 --- a/lib/chef/win32/version.rb +++ b/lib/chef/win32/version.rb @@ -44,25 +44,25 @@ class Chef 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 Server 2019" => { major: 10, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION && build_number >= 17763 } }, + "Windows 10" => { major: 10, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } }, + "Windows Server 2016" => { major: 10, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION && build_number <= 14393 } }, + "Windows 8.1" => { major: 6, minor: 3, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } }, + "Windows Server 2012 R2" => { major: 6, minor: 3, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION } }, + "Windows 8" => { major: 6, minor: 2, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } }, + "Windows Server 2012" => { major: 6, minor: 2, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION } }, + "Windows 7" => { major: 6, minor: 1, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } }, + "Windows Server 2008 R2" => { major: 6, minor: 1, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION } }, + "Windows Server 2008" => { major: 6, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION } }, + "Windows Vista" => { major: 6, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } }, + "Windows Server 2003 R2" => { major: 5, minor: 2, callable: lambda { |product_type, suite_mask, build_number| get_system_metrics(SM_SERVERR2) != 0 } }, + "Windows Home Server" => { major: 5, minor: 2, callable: lambda { |product_type, suite_mask, build_number| (suite_mask & VER_SUITE_WH_SERVER) == VER_SUITE_WH_SERVER } }, + "Windows Server 2003" => { major: 5, minor: 2, callable: lambda { |product_type, suite_mask, build_number| get_system_metrics(SM_SERVERR2) == 0 } }, "Windows XP" => { major: 5, minor: 1 }, "Windows 2000" => { major: 5, minor: 0 }, }.freeze @@ -88,7 +88,7 @@ class Chef define_method(method_name) do (@major_version == v[:major]) && (@minor_version == v[:minor]) && - (v[:callable] ? v[:callable].call(@product_type, @suite_mask) : true) + (v[:callable] ? v[:callable].call(@product_type, @suite_mask, @build_number) : true) end marketing_names << [k, method_name] end diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock index b0a3f8007b..7945857987 100644 --- a/omnibus/Gemfile.lock +++ b/omnibus/Gemfile.lock @@ -1,9 +1,9 @@ GIT remote: https://github.com/chef/omnibus - revision: dfb28a83c3cbce42e6e5bbc4985930067e206362 + revision: 708e9c7413c83a6e19a40e212ad884c78a905a5d branch: master specs: - omnibus (6.0.6) + omnibus (6.0.8) aws-sdk-s3 (~> 1) chef-sugar (>= 3.3) cleanroom (~> 1.0) @@ -32,21 +32,21 @@ GEM public_suffix (>= 2.0.2, < 4.0) awesome_print (1.8.0) aws-eventstream (1.0.1) - aws-partitions (1.115.0) - aws-sdk-core (3.39.0) + aws-partitions (1.121.0) + aws-sdk-core (3.42.0) aws-eventstream (~> 1.0) aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-kms (1.12.0) + aws-sdk-kms (1.13.0) aws-sdk-core (~> 3, >= 3.39.0) aws-sigv4 (~> 1.0) - aws-sdk-s3 (1.25.0) + aws-sdk-s3 (1.29.0) aws-sdk-core (~> 3, >= 3.39.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.0) aws-sigv4 (1.0.3) - berkshelf (7.0.6) + berkshelf (7.0.7) chef (>= 13.6.52) chef-config cleanroom (~> 1.0) @@ -156,7 +156,7 @@ GEM debug_inspector (0.0.3) diff-lcs (1.3) erubis (2.7.0) - faraday (0.15.3) + faraday (0.15.4) multipart-post (>= 1.2, < 3) ffi (1.9.25) ffi (1.9.25-x64-mingw32) @@ -181,7 +181,7 @@ GEM kitchen-vagrant (1.3.6) test-kitchen (~> 1.4) libyajl2 (1.2.0) - license_scout (1.0.16) + license_scout (1.0.18) ffi-yajl (~> 2.2) mixlib-shellout (~> 2.2) toml-rb (~> 1.0) @@ -302,7 +302,7 @@ GEM structured_warnings (0.3.0) syslog-logger (1.6.8) systemu (2.6.5) - test-kitchen (1.23.2) + test-kitchen (1.23.3) mixlib-install (~> 3.6) mixlib-shellout (>= 1.2, < 3.0) net-scp (~> 1.1) diff --git a/omnibus_overrides.rb b/omnibus_overrides.rb index 8664617310..d3ee80b78a 100644 --- a/omnibus_overrides.rb +++ b/omnibus_overrides.rb @@ -23,3 +23,11 @@ override "util-macros", version: "1.19.0" override "xproto", version: "7.0.28" override "zlib", version: "1.2.11" override "openssl", version: "1.0.2q" + +# we build both a chef and ohai omnibus-software defintion which create the +# chef-client and ohai binstubs. Out of the box the ohai definition uses whatever +# is in master, which won't match what's in the Gemfile.lock and used by the chef +# definition. This pin will ensure that ohai and chef-client commands use the +# same (released) version of ohai. +gemfile_lock = File.join(File.expand_path(File.dirname(__FILE__)), "Gemfile.lock") +override "ohai", version: "#{::File.readlines(gemfile_lock).find { |l| l =~ /^\s+ohai \((\d+\.\d+\.\d+)\)/ }; 'v' + $1}" # rubocop: disable Layout/SpaceInsideStringInterpolation diff --git a/spec/data/snap_package/async_result_success.json b/spec/data/snap_package/async_result_success.json new file mode 100644 index 0000000000..09781ad5bd --- /dev/null +++ b/spec/data/snap_package/async_result_success.json @@ -0,0 +1,6 @@ +{ + "type": "async", + "status-code": 202, + "status": "Accepted", + "change": "401" +} diff --git a/spec/data/snap_package/change_id_result.json b/spec/data/snap_package/change_id_result.json new file mode 100644 index 0000000000..cd823d7cbc --- /dev/null +++ b/spec/data/snap_package/change_id_result.json @@ -0,0 +1,175 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "id": "15", + "kind": "install-snap", + "summary": "Install snap \"hello\"", + "status": "Done", + "tasks": [{ + "id": "165", + "kind": "prerequisites", + "summary": "Ensure prerequisites for \"hello\" are available", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.22104314Z", + "ready-time": "2018-09-22T20:25:25.231090966Z" + }, { + "id": "166", + "kind": "download-snap", + "summary": "Download snap \"hello\" (20) from channel \"stable\"", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221070859Z", + "ready-time": "2018-09-22T20:25:25.24321909Z" + }, { + "id": "167", + "kind": "validate-snap", + "summary": "Fetch and check assertions for snap \"hello\" (20)", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221080163Z", + "ready-time": "2018-09-22T20:25:25.55308904Z" + }, { + "id": "168", + "kind": "mount-snap", + "summary": "Mount snap \"hello\" (20)", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221082984Z", + "ready-time": "2018-09-22T20:25:25.782452658Z" + }, { + "id": "169", + "kind": "copy-snap-data", + "summary": "Copy snap \"hello\" data", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221085677Z", + "ready-time": "2018-09-22T20:25:25.790911883Z" + }, { + "id": "170", + "kind": "setup-profiles", + "summary": "Setup snap \"hello\" (20) security profiles", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221088261Z", + "ready-time": "2018-09-22T20:25:25.972796111Z" + }, { + "id": "171", + "kind": "link-snap", + "summary": "Make snap \"hello\" (20) available to the system", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221090669Z", + "ready-time": "2018-09-22T20:25:25.986931331Z" + }, { + "id": "172", + "kind": "auto-connect", + "summary": "Automatically connect eligible plugs and slots of snap \"hello\"", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221093357Z", + "ready-time": "2018-09-22T20:25:25.996914144Z" + }, { + "id": "173", + "kind": "set-auto-aliases", + "summary": "Set automatic aliases for snap \"hello\"", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221097651Z", + "ready-time": "2018-09-22T20:25:26.009155888Z" + }, { + "id": "174", + "kind": "setup-aliases", + "summary": "Setup snap \"hello\" aliases", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221100379Z", + "ready-time": "2018-09-22T20:25:26.021062388Z" + }, { + "id": "175", + "kind": "run-hook", + "summary": "Run install hook of \"hello\" snap if present", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221103116Z", + "ready-time": "2018-09-22T20:25:26.031383884Z" + }, { + "id": "176", + "kind": "start-snap-services", + "summary": "Start snap \"hello\" (20) services", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221110251Z", + "ready-time": "2018-09-22T20:25:26.039564637Z" + }, { + "id": "177", + "kind": "run-hook", + "summary": "Run configure hook of \"hello\" snap if present", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2018-09-22T20:25:25.221115952Z", + "ready-time": "2018-09-22T20:25:26.05069451Z" + } + ], + "ready": true, + "spawn-time": "2018-09-22T20:25:25.221130149Z", + "ready-time": "2018-09-22T20:25:26.050696298Z", + "data": { + "snap-names": ["hello"] + } + } +} diff --git a/spec/data/snap_package/find_result_failure.json b/spec/data/snap_package/find_result_failure.json new file mode 100644 index 0000000000..ec0d82a3b8 --- /dev/null +++ b/spec/data/snap_package/find_result_failure.json @@ -0,0 +1,10 @@ +{ + "type": "error", + "status-code": 404, + "status": "Not Found", + "result": { + "message": "snap not found", + "kind": "snap-not-found", + "value": "hello2" + } +} diff --git a/spec/data/snap_package/find_result_success.json b/spec/data/snap_package/find_result_success.json new file mode 100644 index 0000000000..f19f24dcef --- /dev/null +++ b/spec/data/snap_package/find_result_success.json @@ -0,0 +1,70 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": [{ + "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", + "title": "hello", + "summary": "GNU Hello, the \"hello world\" snap", + "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", + "download-size": 65536, + "name": "hello", + "publisher": { + "id": "canonical", + "username": "canonical", + "display-name": "Canonical", + "validation": "verified" + }, + "developer": "canonical", + "status": "available", + "type": "app", + "version": "2.10", + "channel": "stable", + "ignore-validation": false, + "revision": "20", + "confinement": "strict", + "private": false, + "devmode": false, + "jailmode": false, + "contact": "mailto:snaps@canonical.com", + "license": "GPL-3.0", + "channels": { + "latest/beta": { + "revision": "29", + "confinement": "strict", + "version": "2.10.1", + "channel": "beta", + "epoch": "0", + "size": 65536 + }, + "latest/candidate": { + "revision": "20", + "confinement": "strict", + "version": "2.10", + "channel": "candidate", + "epoch": "0", + "size": 65536 + }, + "latest/edge": { + "revision": "34", + "confinement": "strict", + "version": "2.10.42", + "channel": "edge", + "epoch": "0", + "size": 65536 + }, + "latest/stable": { + "revision": "20", + "confinement": "strict", + "version": "2.10", + "channel": "stable", + "epoch": "0", + "size": 65536 + } + }, + "tracks": ["latest"] + } + ], + "sources": ["store"], + "suggested-currency": "USD" +} diff --git a/spec/data/snap_package/get_by_name_result_failure.json b/spec/data/snap_package/get_by_name_result_failure.json new file mode 100644 index 0000000000..c8c1bb7342 --- /dev/null +++ b/spec/data/snap_package/get_by_name_result_failure.json @@ -0,0 +1,10 @@ +{ + "type": "error", + "status-code": 404, + "status": "Not Found", + "result": { + "message": "snap not installed", + "kind": "snap-not-found", + "value": "aws-cliasdfasdf" + } +} diff --git a/spec/data/snap_package/get_by_name_result_success.json b/spec/data/snap_package/get_by_name_result_success.json new file mode 100644 index 0000000000..05517362ab --- /dev/null +++ b/spec/data/snap_package/get_by_name_result_success.json @@ -0,0 +1,38 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "id": "CRrJViJiSuDcCkU31G0xpNRVNaj4P960", + "summary": "Universal Command Line Interface for Amazon Web Services", + "description": "This package provides a unified command line interface to Amazon Web\nServices.\n", + "installed-size": 15851520, + "name": "aws-cli", + "publisher": { + "id": "S7iQ7mKDXBDliQqRcgefvc2TKXIH9pYk", + "username": "aws", + "display-name": "Amazon Web Services", + "validation": "verified" + }, + "developer": "aws", + "status": "active", + "type": "app", + "version": "1.15.71", + "channel": "", + "tracking-channel": "stable", + "ignore-validation": false, + "revision": "135", + "confinement": "classic", + "private": false, + "devmode": false, + "jailmode": false, + "apps": [{ + "snap": "aws-cli", + "name": "aws" + } + ], + "contact": "", + "mounted-from": "/var/lib/snapd/snaps/aws-cli_135.snap", + "install-date": "2018-09-17T20:39:38.516Z" + } +} diff --git a/spec/data/snap_package/get_conf_success.json b/spec/data/snap_package/get_conf_success.json new file mode 100644 index 0000000000..e83ffbfbe3 --- /dev/null +++ b/spec/data/snap_package/get_conf_success.json @@ -0,0 +1,10 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "address": "0.0.0.0", + "allow-privileged": true, + "anonymous-auth": false + } +} diff --git a/spec/data/snap_package/result_failure.json b/spec/data/snap_package/result_failure.json new file mode 100644 index 0000000000..e65120ad33 --- /dev/null +++ b/spec/data/snap_package/result_failure.json @@ -0,0 +1,9 @@ +{ + "type": "error", + "status-code": 401, + "status": "Unauthorized", + "result": { + "message": "access denied", + "kind": "login-required" + } +} diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb index 2d65d69843..d4c8132313 100644 --- a/spec/functional/resource/group_spec.rb +++ b/spec/functional/resource/group_spec.rb @@ -146,7 +146,7 @@ describe Chef::Resource::Group, :requires_root_or_running_windows do 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 no users exist", :not_supported_on_macos 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) { [] } diff --git a/spec/functional/resource/msu_package_spec.rb b/spec/functional/resource/msu_package_spec.rb index 23342be6ae..d6811d99e7 100644 --- a/spec/functional/resource/msu_package_spec.rb +++ b/spec/functional/resource/msu_package_spec.rb @@ -76,6 +76,20 @@ describe Chef::Resource::MsuPackage, :win2012r2_only do end end + context "when an msu package is not applicable to the image." do + def package_name + "Package_for_KB4019990" + end + + def package_source + "http://download.windowsupdate.com/c/msdownload/update/software/updt/2017/05/windows8-rt-kb4019990-x64_a77f4e3e1f2d47205824763e7121bb11979c2716.msu" + end + + it "raises error while installing" do + expect { subject.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /The specified package is not applicable to this image./) + end + end + def remove_package pkg_to_remove = Chef::Resource::MsuPackage.new(package_name, run_context) pkg_to_remove.source = package_source diff --git a/spec/functional/resource/timezone_spec.rb b/spec/functional/resource/timezone_spec.rb new file mode 100644 index 0000000000..27e705e5da --- /dev/null +++ b/spec/functional/resource/timezone_spec.rb @@ -0,0 +1,39 @@ +# +# Author:: Gary Bright (<digitalgaz@hotmail.com>) +# 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::Timezone, :windows_only do + let(:timezone) { "GMT Standard Time" } + + def timezone_resource + Chef::Resource::Timezone.new(timezone, run_context) + end + + describe "when a timezone is provided on windows" do + it "should set a timezone" do + timezone_resource.run_action(:set) + end + end + + describe "when a timezone is not provided on windows" do + let(:timezone) { nil } + it "raises an exception" do + expect { timezone_resource.run_action(:set) }.to raise_error(Chef::Exceptions::ValidationFailed) + end + end +end diff --git a/spec/functional/resource/user/dscl_spec.rb b/spec/functional/resource/user/dscl_spec.rb index e73a4ad3ee..dab640d4da 100644 --- a/spec/functional/resource/user/dscl_spec.rb +++ b/spec/functional/resource/user/dscl_spec.rb @@ -19,7 +19,7 @@ require "spec_helper" require "chef/mixin/shell_out" metadata = { - mac_osx_only: true, + macos_only: true, requires_root: true, } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e87308e9ed..c493987554 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -143,14 +143,13 @@ RSpec.configure do |config| 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: true if mac_osx? - config.filter_run_excluding mac_osx_only: true if !mac_osx? + config.filter_run_excluding not_supported_on_macos: true if mac_osx? + config.filter_run_excluding macos_only: true if !mac_osx? config.filter_run_excluding not_supported_on_aix: true if aix? 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 win2012r2_only: true unless windows_2012r2? - 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? @@ -186,7 +185,6 @@ RSpec.configure do |config| config.filter_run_excluding not_wpar: true unless wpar? config.filter_run_excluding not_supported_under_fips: true if fips? config.filter_run_excluding rhel: true unless rhel? - config.filter_run_excluding rhel5: true unless rhel5? config.filter_run_excluding rhel6: true unless rhel6? config.filter_run_excluding rhel7: true unless rhel7? config.filter_run_excluding intel_64bit: true unless intel_64bit? diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb index 5ae9c01722..c4ab1db265 100644 --- a/spec/support/platform_helpers.rb +++ b/spec/support/platform_helpers.rb @@ -51,15 +51,6 @@ def windows_domain_joined? computer_system["partofdomain"] end -def windows_2008r2_or_later? - return false unless windows? - return false unless host_version - components = host_version.split(".").map do |component| - component.to_i - end - components.length >= 2 && components[0] >= 6 && components[1] >= 1 -end - def windows_2012r2? return false unless windows? (host_version && host_version.start_with?("6.3")) @@ -167,10 +158,6 @@ def rhel? !!(ohai[:platform_family] == "rhel") end -def rhel5? - rhel? && !!(ohai[:platform_version].to_i == 5) -end - def rhel6? rhel? && !!(ohai[:platform_version].to_i == 6) end diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb index 2e9888fb78..1bafefe5e8 100644 --- a/spec/unit/provider/package/rubygems_spec.rb +++ b/spec/unit/provider/package/rubygems_spec.rb @@ -396,6 +396,10 @@ describe Chef::Provider::Package::Rubygems do allow(RbConfig::CONFIG).to receive(:[]).with("arch").and_call_original allow(File).to receive(:executable?).and_return false allow(File).to receive(:executable?).with("#{bindir}/gem").and_return true + # XXX: we can't stub the provider object directly here because referencing it will create it and that + # will break later tests that want to test the initialize method, so we stub any instance + # (yet more evidence that initialize methods should be thin and do very little work) + allow_any_instance_of(Chef::Provider::Package::Rubygems).to receive(:needs_nodocument?).and_return true end describe "when new_resource version is nil" do @@ -696,7 +700,25 @@ describe Chef::Provider::Package::Rubygems do let(:options) { "-i /alt/install/location" } it "installs the gem by shelling out when options are provided as a String" do + expected = "gem install rspec-core -q --no-document -v \"#{target_version}\" --source=https://www.rubygems.org #{options}" + expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) + provider.run_action(:install) + expect(new_resource).to be_updated_by_last_action + end + + it "unmockening needs_nodocument?" do + expected = "gem install rspec-core -q --no-document -v \"#{target_version}\" --source=https://www.rubygems.org #{options}" + expect(provider).to receive(:needs_nodocument?).and_call_original + stub_const("Gem::VERSION", "3.0.0") + expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) + provider.run_action(:install) + expect(new_resource).to be_updated_by_last_action + end + + it "when the rubygems_version is old it uses the old flags" do expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://www.rubygems.org #{options}" + expect(provider).to receive(:needs_nodocument?).and_call_original + stub_const("Gem::VERSION", "2.8.0") expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -709,7 +731,7 @@ describe Chef::Provider::Package::Rubygems do it "installs the gem with rubygems.org as an added source" do Chef::Config[:rubygems_url] = "https://mirror1" expect(provider.gem_env).to receive(:candidate_version_from_remote).with(gem_dep, Chef::Config[:rubygems_url]).and_return(version) - expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://mirror1" + expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=https://mirror1" expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -721,7 +743,7 @@ describe Chef::Provider::Package::Rubygems do let(:gem_binary) { "/foo/bar" } it "installs the gem with rubygems.org as an added source" do - expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=#{source} --source=https://www.rubygems.org" + expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=#{source} --source=https://www.rubygems.org" expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -732,7 +754,7 @@ describe Chef::Provider::Package::Rubygems do it "ignores the Chef::Config setting" do Chef::Config[:rubygems_url] = "https://ignored" - expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=#{source}" + expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=#{source}" expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -745,7 +767,7 @@ describe Chef::Provider::Package::Rubygems do let(:gem_binary) { "/foo/bar" } it "installs the gem with an array as an added source" do - expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://mirror1 --source=https://mirror2 --source=https://www.rubygems.org" + expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=https://mirror1 --source=https://mirror2 --source=https://www.rubygems.org" expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -756,7 +778,7 @@ describe Chef::Provider::Package::Rubygems do it "ignores the Chef::Config setting" do Chef::Config[:rubygems_url] = "https://ignored" - expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://mirror1 --source=https://mirror2" + expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=https://mirror1 --source=https://mirror2" expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -770,7 +792,7 @@ describe Chef::Provider::Package::Rubygems do it "installs the gem" do new_resource.clear_sources(true) - expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --clear-sources --source=#{source} --source=https://www.rubygems.org" + expected = "#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --clear-sources --source=#{source} --source=https://www.rubygems.org" expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -782,7 +804,7 @@ describe Chef::Provider::Package::Rubygems do let(:options) { "-i /alt/install/location" } it "installs the gem by shelling out when options are provided but no version is given" do - expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{candidate_version}\" --source=https://www.rubygems.org #{options}" + expected = "gem install rspec-core -q --no-document -v \"#{candidate_version}\" --source=https://www.rubygems.org #{options}" expect(provider).to receive(:shell_out_compacted!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -846,6 +868,22 @@ describe Chef::Provider::Package::Rubygems do let(:gem_binary) { "/usr/weird/bin/gem" } it "installs the gem by shelling out to gem install" do + expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=https://www.rubygems.org", env: nil, timeout: 900) + provider.run_action(:install) + expect(new_resource).to be_updated_by_last_action + end + + it "unmockening needs_nodocument?" do + expect(provider).to receive(:needs_nodocument?).and_call_original + expect(provider.gem_env).to receive(:shell_out!).with("#{gem_binary} --version").and_return(instance_double(Mixlib::ShellOut, stdout: "3.0.0\n")) + expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install rspec-core -q --no-document -v \"#{target_version}\" --source=https://www.rubygems.org", env: nil, timeout: 900) + provider.run_action(:install) + expect(new_resource).to be_updated_by_last_action + end + + it "when the rubygems_version is old it uses the old flags" do + expect(provider).to receive(:needs_nodocument?).and_call_original + expect(provider.gem_env).to receive(:shell_out!).with("#{gem_binary} --version").and_return(instance_double(Mixlib::ShellOut, stdout: "2.8.0\n")) expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=https://www.rubygems.org", env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action @@ -856,7 +894,7 @@ describe Chef::Provider::Package::Rubygems do let(:target_version) { ">= 0" } it "installs the gem by shelling out to gem install" do - expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install #{source} -q --no-rdoc --no-ri -v \"#{target_version}\"", env: nil, timeout: 900) + expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install #{source} -q --no-document -v \"#{target_version}\"", env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end @@ -868,7 +906,7 @@ describe Chef::Provider::Package::Rubygems do 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_compacted!).with("#{gem_binary} install #{gem_name} -q --no-rdoc --no-ri -v \"#{target_version}\"", env: nil, timeout: 900) + expect(provider).to receive(:shell_out_compacted!).with("#{gem_binary} install #{gem_name} -q --no-document -v \"#{target_version}\"", env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end diff --git a/spec/unit/provider/package/snap_spec.rb b/spec/unit/provider/package/snap_spec.rb new file mode 100644 index 0000000000..674870824b --- /dev/null +++ b/spec/unit/provider/package/snap_spec.rb @@ -0,0 +1,208 @@ +# Author:: S.Cavallo (smcavallo@hotmail.com) +# Copyright 2014-2018, Chef Software Inc. <legal@chef.io> +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS 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" +require "chef/provider/package/snap" +require "json" + +describe Chef::Provider::Package::Snap do + let(:node) { Chef::Node.new } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:package) { "hello" } + let(:source) { "/tmp/hello_20.snap" } + let(:new_resource) do + new_resource = Chef::Resource::SnapPackage.new(package) + new_resource.source source + new_resource + end + let(:provider) { Chef::Provider::Package::Snap.new(new_resource, run_context) } + let(:snap_status) do + stdout = <<~SNAP_S + path: "/tmp/hello_20.snap" + name: hello + summary: GNU Hello, the "hello world" snap + version: 2.10 - + SNAP_S + status = double(stdout: stdout, stderr: "", exitstatus: 0) + allow(status).to receive(:error!).with(no_args).and_return(false) + status + end + + before(:each) do + allow(provider).to receive(:shell_out_compacted!).with("snap", "info", source, timeout: 900).and_return(snap_status) + end + + # Example output from https://github.com/snapcore/snapd/wiki/REST-API + find_result_success = JSON.parse(File.read(File.join(CHEF_SPEC_DATA, "snap_package", "find_result_success.json"))) + find_result_fail = JSON.parse(File.read(File.join(CHEF_SPEC_DATA, "snap_package", "find_result_failure.json"))) + get_by_name_result_success = JSON.parse(File.read(File.join(CHEF_SPEC_DATA, "snap_package", "get_by_name_result_success.json"))) + get_by_name_result_fail = JSON.parse(File.read(File.join(CHEF_SPEC_DATA, "snap_package", "get_by_name_result_failure.json"))) + async_result_success = JSON.parse(File.read(File.join(CHEF_SPEC_DATA, "snap_package", "async_result_success.json"))) + result_fail = JSON.parse(File.read(File.join(CHEF_SPEC_DATA, "snap_package", "result_failure.json"))) + change_id_result = JSON.parse(File.read(File.join(CHEF_SPEC_DATA, "snap_package", "change_id_result.json"))) + get_conf_success = JSON.parse(File.read(File.join(CHEF_SPEC_DATA, "snap_package", "get_conf_success.json"))) + + describe "#define_resource_requirements" do + + before do + allow_any_instance_of(Chef::Provider::Package::Snap).to receive(:call_snap_api).with("GET", "/v2/snaps/#{package}").and_return(get_by_name_result_success) + end + + 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 + end + + describe "when using a local file source" do + let(:source) { "/tmp/hello_20.snap" } + + before do + allow_any_instance_of(Chef::Provider::Package::Snap).to receive(:call_snap_api).with("GET", "/v2/snaps/#{package}").and_return(get_by_name_result_success) + end + + 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("hello") + end + + describe "gets the candidate version from the source package" do + + def check_version(version) + provider.load_current_resource + expect(provider.current_resource.package_name).to eq("hello") + expect(provider.get_current_versions).to eq(["1.15.71"]) + expect(provider.candidate_version).to eq([version]) + end + + it "checks the installed and local candidate versions" do + check_version("2.10") + end + + it "generates multipart form data" do + expected = <<~SNAP_S + Host: + Content-Type: multipart/form-data; boundary=foo + Content-Length: 20480 + + --foo + Content-Disposition: form-data; name="action" + + install + --foo + Content-Disposition: form-data; name="devmode" + + true + --foo + Content-Disposition: form-data; name="snap"; filename="hello-world_27.snap" + + <20480 bytes of snap file data> + --foo + SNAP_S + + options = {} + options["devmode"] = true + path = "hello-world_27.snap" + content_length = "20480" + + result = provider.send(:generate_multipart_form_data, "foo", "install", options, path, content_length) + + expect(result).to eq(expected) + + end + + end + end + + describe "when using the snap store" do + let(:source) { nil } + describe "gets the candidate version from the snap store" do + before do + allow_any_instance_of(Chef::Provider::Package::Snap).to receive(:call_snap_api).with("GET", "/v2/find?name=#{package}").and_return(find_result_success) + allow_any_instance_of(Chef::Provider::Package::Snap).to receive(:call_snap_api).with("GET", "/v2/snaps/#{package}").and_return(get_by_name_result_success) + end + + def check_version(version) + provider.load_current_resource + expect(provider.current_resource.package_name).to eq("hello") + expect(provider.get_current_versions).to eq(["1.15.71"]) + expect(provider.candidate_version).to eq([version]) + end + + it "checks the installed and store candidate versions" do + check_version("2.10") + end + + end + + describe "fails to get the candidate version from the snap store" do + before do + allow_any_instance_of(Chef::Provider::Package::Snap).to receive(:call_snap_api).with("GET", "/v2/find?name=#{package}").and_return(find_result_fail) + allow_any_instance_of(Chef::Provider::Package::Snap).to receive(:call_snap_api).with("GET", "/v2/snaps/#{package}").and_return(get_by_name_result_fail) + end + + it "throws an error if candidate version not found" do + provider.load_current_resource + expect { provider.candidate_version }.to raise_error(Chef::Exceptions::Package) + end + + it "does not throw an error if installed version not found" do + provider.load_current_resource + expect(provider.get_current_versions).to eq([nil]) + end + end + end + + describe "when calling async operations" do + + it "should should throw if the async response is an error" do + expect { provider.send(:get_id_from_async_response, result_fail) }.to raise_error(RuntimeError) + end + + it "should get the id from an async response" do + result = provider.send(:get_id_from_async_response, async_result_success) + expect(result).to eq("401") + end + + it "should wait for change completion" do + result = provider.send(:get_id_from_async_response, async_result_success) + expect(result).to eq("401") + end + end + + describe Chef::Provider::Package::Snap do + + it "should post the correct json" do + snap_names = ["hello"] + action = "install" + channel = "stable" + options = {} + revision = nil + actual = provider.send(:generate_snap_json, snap_names, action, channel, options, revision) + + expect(actual).to eq("action" => "install", "snaps" => ["hello"], "channel" => "stable") + end + + end +end diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index a3f2801adf..5066135b90 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -593,6 +593,7 @@ describe Chef::ProviderResolver do ruby: [ Chef::Resource::Ruby, Chef::Provider::Script ], script: [ Chef::Resource::Script, Chef::Provider::Script ], smartos_package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ], + snap_package: [ Chef::Resource::SnapPackage, Chef::Provider::Package::Snap ], solaris_package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ], solaris_user: [ Chef::Resource::User::SolarisUser, Chef::Provider::User::Solaris ], subversion: [ Chef::Resource::Subversion, Chef::Provider::Subversion ], diff --git a/spec/unit/resource/powershell_package_source_spec.rb b/spec/unit/resource/powershell_package_source_spec.rb index 4033f4515f..2640d9f3c5 100644 --- a/spec/unit/resource/powershell_package_source_spec.rb +++ b/spec/unit/resource/powershell_package_source_spec.rb @@ -205,13 +205,13 @@ describe Chef::Resource::PowershellPackageSource do describe "#package_source_exists?" do it "returns true if it exists" do - allow(provider).to receive(:powershell_out!).with("(Get-PackageSource -Name 'MyGallery').Name").and_return(double("powershell_out!", stdout: "MyGallery\r\n")) + allow(provider).to receive(:powershell_out!).with("(Get-PackageSource -Name 'MyGallery' -WarningAction SilentlyContinue).Name").and_return(double("powershell_out!", stdout: "MyGallery\r\n")) resource.source_name("MyGallery") expect(provider.package_source_exists?).to eql(true) end it "returns false if it doesn't exist" do - allow(provider).to receive(:powershell_out!).with("(Get-PackageSource -Name 'MyGallery').Name").and_return(double("powershell_out!", stdout: "")) + allow(provider).to receive(:powershell_out!).with("(Get-PackageSource -Name 'MyGallery' -WarningAction SilentlyContinue).Name").and_return(double("powershell_out!", stdout: "")) resource.source_name("MyGallery") expect(provider.package_source_exists?).to eql(false) end diff --git a/spec/unit/resource/snap_package_spec.rb b/spec/unit/resource/snap_package_spec.rb new file mode 100644 index 0000000000..e625d6b2c3 --- /dev/null +++ b/spec/unit/resource/snap_package_spec.rb @@ -0,0 +1,60 @@ +# +# Author:: S.Cavallo (<smcavallo@hotmail.com>) +# Copyright:: Copyright 2008-2018, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS 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/snap_package" +require "chef/provider/package/snap" +require "support/shared/unit/resource/static_provider_resolution" + +describe Chef::Resource::SnapPackage, "initialize" do + + static_provider_resolution( + resource: Chef::Resource::SnapPackage, + provider: Chef::Provider::Package::Snap, + name: :snap_package, + action: :install, + os: "linux" + ) + + let(:resource) { Chef::Resource::SnapPackage.new("foo") } + + it "sets the default action as :install" do + expect(resource.action).to eql([:install]) + end + + it "supports :install, :lock, :purge, :reconfig, :remove, :unlock, :upgrade actions" do + expect { resource.action :install }.not_to raise_error + expect { resource.action :lock }.not_to raise_error + expect { resource.action :purge }.not_to raise_error + expect { resource.action :reconfig }.not_to raise_error + expect { resource.action :remove }.not_to raise_error + expect { resource.action :unlock }.not_to raise_error + expect { resource.action :upgrade }.not_to raise_error + end + + it "channel defaults to stable" do + expect(resource.channel).to eql("stable") + end + + it "supports all channel values" do + expect { resource.channel "stable" }.not_to raise_error + expect { resource.channel "edge" }.not_to raise_error + expect { resource.channel "beta" }.not_to raise_error + expect { resource.channel "candidate" }.not_to raise_error + end +end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 921ca6d33e..7793b82034 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -1204,4 +1204,27 @@ describe Chef::Resource do klass.provides(:test_resource, allow_cookbook_override: false) end end + + describe "tagged" do + let(:recipe) do + Chef::Recipe.new("hjk", "test", run_context) + end + + describe "with the default node object" do + let(:node) { Chef::Node.new } + + it "should return false for any tags" do + expect(resource.tagged?("foo")).to be(false) + end + end + + it "should return true from tagged? if node is tagged" do + recipe.tag "foo" + expect(resource.tagged?("foo")).to be(true) + end + + it "should return false from tagged? if node is not tagged" do + expect(resource.tagged?("foo")).to be(false) + end + end end |