diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2017-01-11 13:56:52 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-11 13:56:52 -0800 |
commit | 00e7bb54cd90e9080f24e19d3beca604fa070afc (patch) | |
tree | 1b0bf2046c655a730e01d0e5b35f2ad8a54ee71a | |
parent | c79ed56d52ecff6452740e14ebb9546f3baf399e (diff) | |
parent | aebaee14c2d6c106914d1b3e3c9c3cbdf5bc29cb (diff) | |
download | chef-00e7bb54cd90e9080f24e19d3beca604fa070afc.tar.gz |
Merge pull request #4894 from chef/lcg/dnf-provider
DNF Provider PR #2
27 files changed, 1423 insertions, 28 deletions
diff --git a/lib/chef/deprecated.rb b/lib/chef/deprecated.rb index 5b0bac552e..1cadacec98 100644 --- a/lib/chef/deprecated.rb +++ b/lib/chef/deprecated.rb @@ -196,6 +196,16 @@ class Chef end end + class DnfPackageAllowDowngrade < Base + def id + 10 + end + + def target + "dnf_package_allow_downgrade.html" + end + end + class Generic < Base def url "https://docs.chef.io/chef_deprecations_client.html" diff --git a/lib/chef/mixin/which.rb b/lib/chef/mixin/which.rb index 63c84883d5..4fa79eeccb 100644 --- a/lib/chef/mixin/which.rb +++ b/lib/chef/mixin/which.rb @@ -18,17 +18,13 @@ class Chef module Mixin module Which - def which(cmd, opts = {}) - extra_path = - if opts[:extra_path].nil? - [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ] - else - [ opts[:extra_path] ].flatten - end + def which(cmd, extra_path: nil) + # NOTE: unnecessarily duplicates function of path_sanity + extra_path ||= [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ] paths = ENV["PATH"].split(File::PATH_SEPARATOR) + extra_path paths.each do |path| - filename = File.join(path, cmd) - return filename if File.executable?(Chef.path_to(filename)) + filename = Chef.path_to(File.join(path, cmd)) + return filename if File.executable?(filename) end false end diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index ecf3dbecb5..f52614672a 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -37,6 +37,9 @@ class Chef subclass_directive :use_multipackage_api # subclasses declare this if they want sources (filenames) pulled from their package names subclass_directive :use_package_name_for_source + # keeps package_names_for_targets and versions_for_targets indexed the same as package_name at + # the cost of having the subclass needing to deal with nils + subclass_directive :allow_nils # # Hook that subclasses use to populate the candidate_version(s) @@ -390,9 +393,12 @@ class Chef def package_names_for_targets package_names_for_targets = [] target_version_array.each_with_index do |target_version, i| - next if target_version.nil? - package_name = package_name_array[i] - package_names_for_targets.push(package_name) + if !target_version.nil? + package_name = package_name_array[i] + package_names_for_targets.push(package_name) + else + package_names_for_targets.push(nil) if allow_nils? + end end multipackage? ? package_names_for_targets : package_names_for_targets[0] end @@ -407,8 +413,11 @@ class Chef def versions_for_targets versions_for_targets = [] target_version_array.each_with_index do |target_version, i| - next if target_version.nil? - versions_for_targets.push(target_version) + if !target_version.nil? + versions_for_targets.push(target_version) + else + versions_for_targets.push(nil) if allow_nils? + end end multipackage? ? versions_for_targets : versions_for_targets[0] end diff --git a/lib/chef/provider/package/dnf.rb b/lib/chef/provider/package/dnf.rb new file mode 100644 index 0000000000..bf6aa2438f --- /dev/null +++ b/lib/chef/provider/package/dnf.rb @@ -0,0 +1,183 @@ +# +# Copyright:: Copyright 2016, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/provider/package" +require "chef/resource/dnf_package" +require "chef/mixin/which" +require "chef/mixin/get_source_from_package" +require "chef/provider/package/dnf/python_helper" +require "chef/provider/package/dnf/version" + +class Chef + class Provider + class Package + class Dnf < Chef::Provider::Package + extend Chef::Mixin::Which + include Chef::Mixin::GetSourceFromPackage + + allow_nils + use_multipackage_api + use_package_name_for_source + + provides :package, platform_family: %w{rhel fedora} do + which("dnf") + end + + provides :dnf_package, os: "linux" + + # + # Most of the magic in this class happens in the python helper script. The ruby side of this + # provider knows only enough to translate Chef-style new_resource name+package+version into + # a request to the python side. The python side is then responsible for knowing everything + # about RPMs and what is installed and what is available. The ruby side of this class should + # remain a lightweight translation layer to translate Chef requests into RPC requests to + # python. This class knows nothing about how to compare RPM versions, and does not maintain + # any cached state of installed/available versions and should be kept that way. + # + def python_helper + @python_helper ||= PythonHelper.instance + end + + def load_current_resource + flushcache if new_resource.flush_cache[:before] + + @current_resource = Chef::Resource::DnfPackage.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).version_with_arch + end + end + + def get_current_versions + package_name_array.each_with_index.map do |pkg, i| + installed_version(i).version_with_arch + end + end + + def install_package(names, versions) + if new_resource.source + dnf(new_resource.options, "-y install", new_resource.source) + else + resolved_names = names.each_with_index.map { |name, i| available_version(i).to_s unless name.nil? } + dnf(new_resource.options, "-y install", resolved_names) + end + flushcache + end + + # dnf upgrade does not work on uninstalled packaged, while install will upgrade + alias_method :upgrade_package, :install_package + + def remove_package(names, versions) + resolved_names = names.each_with_index.map { |name, i| installed_version(i).to_s unless name.nil? } + dnf(new_resource.options, "-y remove", resolved_names) + flushcache + end + + alias_method :purge_package, :remove_package + + action :flush_cache do + flushcache + end + + private + + def resolve_source_to_version_obj + shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n' #{new_resource.source}").stdout.each_line do |line| + # this is another case of committing the sin of doing some lightweight mangling of RPM versions in ruby -- but the output of the rpm command + # does not match what the dnf library accepts. + case line + when /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/ + return Version.new($1, "#{$2 == "(none)" ? "0" : $2}:#{$3}-#{$4}", $5) + end + end + end + + # @returns Array<Version> + def available_version(index) + @available_version ||= [] + + if new_resource.source + @available_version[index] ||= resolve_source_to_version_obj + else + @available_version[index] ||= python_helper.query(:whatavailable, package_name_array[index], safe_version_array[index], safe_arch_array[index]) + end + + @available_version[index] + end + + # @returns Array<Version> + def installed_version(index) + @installed_version ||= [] + if new_resource.source + @installed_version[index] ||= python_helper.query(:whatinstalled, available_version(index).name, safe_version_array[index], safe_arch_array[index]) + else + @installed_version[index] ||= python_helper.query(:whatinstalled, package_name_array[index], safe_version_array[index], safe_arch_array[index]) + end + @installed_version[index] + end + + # cache flushing is accomplished by simply restarting the python helper. this produces a roughly + # 15% hit to the runtime of installing/removing/upgrading packages. correctly using multipackage + # array installs (and the multipackage cookbook) can produce 600% improvements in runtime. + def flushcache + python_helper.restart + end + + def dnf(*args) + shell_out_with_timeout!(a_to_s("dnf", *args)) + 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 + + def safe_arch_array + if new_resource.arch.is_a?(Array) + new_resource.arch + elsif new_resource.arch.nil? + package_name_array.map { nil } + else + [ new_resource.arch ] + end + end + + end + end + end +end diff --git a/lib/chef/provider/package/dnf/dnf_helper.py b/lib/chef/provider/package/dnf/dnf_helper.py new file mode 100644 index 0000000000..236b967710 --- /dev/null +++ b/lib/chef/provider/package/dnf/dnf_helper.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import sys +import dnf +import hawkey +import signal +import os +import json + +base = None + +def get_sack(): + global base + if base is None: + base = dnf.Base() + base.read_all_repos() + base.fill_sack() + return base.sack + +# FIXME: leaks memory and does not work +def flushcache(): + try: + os.remove('/var/cache/dnf/@System.solv') + except OSError: + pass + get_sack().load_system_repo(build_cache=True) + +def query(command): + sack = get_sack() + + subj = dnf.subject.Subject(command['provides']) + q = subj.get_best_query(sack, with_provides=True) + + if command['action'] == "whatinstalled": + q = q.installed() + + if command['action'] == "whatavailable": + q = q.available() + + if 'epoch' in command: + q = q.filterm(epoch=int(command['epoch'])) + if 'version' in command: + q = q.filterm(version__glob=command['version']) + if 'release' in command: + q = q.filterm(release__glob=command['release']) + + if 'arch' in command: + q = q.filterm(arch__glob=command['arch']) + + # only apply the default arch query filter if it returns something + archq = q.filter(arch=[ 'noarch', hawkey.detect_arch() ]) + if len(archq.run()) > 0: + q = archq + + pkgs = dnf.query.latest_limit_pkgs(q, 1) + + if not pkgs: + sys.stdout.write('{} nil nil\n'.format(command['provides'].split().pop(0))) + else: + # make sure we picked the package with the highest version + pkgs.sort + pkg = pkgs.pop() + sys.stdout.write('{} {}:{}-{} {}\n'.format(pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch)) + +# the design of this helper is that it should try to be 'brittle' and fail hard and exit in order +# to keep process tables clean. additional error handling should probably be added to the retry loop +# on the ruby side. +def exit_handler(signal, frame): + sys.exit(0) + +signal.signal(signal.SIGINT, exit_handler) +signal.signal(signal.SIGHUP, exit_handler) +signal.signal(signal.SIGPIPE, exit_handler) +signal.signal(signal.SIGCHLD, exit_handler) + +while 1: + # kill self if we get orphaned (tragic) + ppid = os.getppid() + if ppid == 1: + sys.exit(0) + line = sys.stdin.readline() + command = json.loads(line) + if command['action'] == "whatinstalled": + query(command) + elif command['action'] == "whatavailable": + query(command) + elif command['action'] == "flushcache": + flushcache() + else: + raise RuntimeError("bad command") diff --git a/lib/chef/provider/package/dnf/python_helper.rb b/lib/chef/provider/package/dnf/python_helper.rb new file mode 100644 index 0000000000..466114b339 --- /dev/null +++ b/lib/chef/provider/package/dnf/python_helper.rb @@ -0,0 +1,120 @@ +# +# Copyright:: Copyright 2016, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/provider/package/dnf/version" +require "timeout" + +class Chef + class Provider + class Package + class Dnf < Chef::Provider::Package + class PythonHelper + include Singleton + extend Chef::Mixin::Which + + attr_accessor :stdin + attr_accessor :stdout + attr_accessor :stderr + attr_accessor :wait_thr + + DNF_HELPER = ::File.expand_path(::File.join(::File.dirname(__FILE__), "dnf_helper.py")).freeze + DNF_COMMAND = "#{which("python3")} #{DNF_HELPER}" + + def start + ENV["PYTHONUNBUFFERED"] = "1" + @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(DNF_COMMAND) + end + + def reap + unless wait_thr.nil? + Process.kill("KILL", wait_thr.pid) rescue nil + stdin.close unless stdin.nil? + stdout.close unless stdout.nil? + stderr.close unless stderr.nil? + wait_thr.value # this calls waitpit() + end + end + + def check + start if stdin.nil? + end + + # i couldn't figure out how to decompose an evr on the python side, it seems reasonably + # painless to do it in ruby (generally massaging nevras in the ruby side is HIGHLY + # discouraged -- this is an "every rule has an exception" exception -- any additional + # functionality should probably trigger moving this regexp logic into python) + def add_version(hash, version) + epoch = nil + if version =~ /(\S+):(\S+)/ + epoch, version = $1, $2 + end + if version =~ /(\S+)-(\S+)/ + version, release = $1, $2 + end + hash["epoch"] = epoch unless epoch.nil? + hash["release"] = release unless release.nil? + hash["version"] = version + end + + def build_query(action, provides, version, arch) + hash = { "action" => action } + hash["provides"] = provides + add_version(hash, version) unless version.nil? + hash["arch" ] = arch unless arch.nil? + FFI_Yajl::Encoder.encode(hash) + end + + def parse_response(output) + array = output.split.map { |x| x == "nil" ? nil : x } + array.each_slice(3).map { |x| Version.new(*x) }.first + end + + # @returns Array<Version> + def query(action, provides, version = nil, arch = nil) + with_helper do + json = build_query(action, provides, version, arch) + Chef::Log.debug "sending '#{json}' to python helper" + stdin.syswrite json + "\n" + output = stdout.sysread(4096).chomp + Chef::Log.debug "got '#{output}' from python helper" + version = parse_response(output) + Chef::Log.debug "parsed #{version} from python helper" + version + end + end + + def restart + reap + start + end + + def with_helper + max_retries ||= 5 + Timeout.timeout(600) do + check + yield + end + rescue EOFError, Errno::EPIPE, Timeout::Error, Errno::ESRCH => e + raise e unless ( max_retries -= 1 ) > 0 + restart + retry + end + end + end + end + end +end diff --git a/lib/chef/provider/package/dnf/version.rb b/lib/chef/provider/package/dnf/version.rb new file mode 100644 index 0000000000..b326913c3a --- /dev/null +++ b/lib/chef/provider/package/dnf/version.rb @@ -0,0 +1,56 @@ +# +# Copyright:: Copyright 2016, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Provider + class Package + class Dnf < Chef::Provider::Package + + # helper class to assist in passing around name/version/arch triples + class Version + attr_accessor :name + attr_accessor :version + attr_accessor :arch + + def initialize(name, version, arch) + @name = name + @version = version + @arch = arch + end + + def to_s + "#{name}-#{version}.#{arch}" + end + + def version_with_arch + "#{version}.#{arch}" unless version.nil? + end + + def matches_name_and_arch?(other) + other.version == version && other.arch == arch + end + + def ==(other) + name == other.name && version == other.version && arch == other.arch + end + + alias_method :eql?, :== + end + end + end + end +end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index ebb4c45ae8..35722840e6 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -65,6 +65,7 @@ require "chef/provider/env/windows" require "chef/provider/package/apt" require "chef/provider/package/chocolatey" require "chef/provider/package/dpkg" +require "chef/provider/package/dnf" require "chef/provider/package/easy_install" require "chef/provider/package/freebsd/port" require "chef/provider/package/freebsd/pkg" diff --git a/lib/chef/resource/dnf_package.rb b/lib/chef/resource/dnf_package.rb new file mode 100644 index 0000000000..92f7532fc2 --- /dev/null +++ b/lib/chef/resource/dnf_package.rb @@ -0,0 +1,64 @@ +# +# Copyright:: Copyright 2016, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/resource/package" + +class Chef + class Resource + class DnfPackage < Chef::Resource::Package + extend Chef::Mixin::Which + + resource_name :dnf_package + + allowed_actions :install, :upgrade, :remove, :purge, :reconfig, :lock, :unlock, :flush_cache + + provides :package, os: "linux", platform_family: %w{rhel fedora} do + which("dnf") + end + + provides :dnf_package + + # Install a specific arch + property :arch, [String, Array], coerce: proc { |x| [x].flatten } + + # Flush the in-memory available/installed cache, this does not flush the dnf caches on disk + property :flush_cache, + Hash, + default: { before: false, after: false }, + coerce: proc { |v| + if v.is_a?(Hash) + v + elsif v.is_a?(Array) + v.each_with_object({}) { |arg, obj| obj[arg] = true } + elsif v.is_a?(TrueClass) || v.is_a?(FalseClass) + { before: v, after: v } + elsif v == :before + { before: true, after: false } + elsif v == :after + { after: true, before: false } + end + } + + def allow_downgrade(arg = nil) + if !arg.nil? + Chef.deprecated(:dnf_package_allow_downgrade, "the allow_downgrade property on the dnf_package provider is not used, DNF supports downgrades by default.") + end + false + end + end + end +end diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb index 9d69897f5f..1e0ad197ba 100644 --- a/lib/chef/resource/yum_package.rb +++ b/lib/chef/resource/yum_package.rb @@ -17,7 +17,6 @@ # require "chef/resource/package" -require "chef/provider/package/yum" class Chef class Resource @@ -27,22 +26,27 @@ class Chef # Install a specific arch property :arch, [ String, Array ] - # the {} on the proc here is because rspec chokes if it's do...end + property :flush_cache, - Hash, - default: { before: false, after: false }, - coerce: proc { |v| - if v.is_a?(Array) - v.each_with_object({}) { |arg, obj| obj[arg] = true } - elsif v.any? - v - else - { before: v, after: v } - end - } + Hash, + default: { before: false, after: false }, + coerce: proc { |v| + if v.is_a?(Hash) + v + elsif v.is_a?(Array) + v.each_with_object({}) { |arg, obj| obj[arg] = true } + elsif v.is_a?(TrueClass) || v.is_a?(FalseClass) + { before: v, after: v } + elsif v == :before + { before: true, after: false } + elsif v == :after + { after: true, before: false } + end + } + property :allow_downgrade, [ true, false ], default: false - property :yum_binary, String + property :yum_binary, String end end end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index de421839e0..ab89ce66e0 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -31,6 +31,7 @@ require "chef/resource/deploy" require "chef/resource/deploy_revision" require "chef/resource/directory" require "chef/resource/dpkg_package" +require "chef/resource/dnf_package" require "chef/resource/dsc_script" require "chef/resource/dsc_resource" require "chef/resource/easy_install_package" diff --git a/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.i686.rpm b/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.i686.rpm Binary files differnew file mode 100644 index 0000000000..29a4624971 --- /dev/null +++ b/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.i686.rpm diff --git a/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.src.rpm b/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.src.rpm Binary files differnew file mode 100644 index 0000000000..b6a6ec3176 --- /dev/null +++ b/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.src.rpm diff --git a/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.x86_64.rpm b/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.x86_64.rpm Binary files differnew file mode 100644 index 0000000000..239b6ef145 --- /dev/null +++ b/spec/functional/assets/yumrepo/chef_rpm-1.10-1.fc24.x86_64.rpm diff --git a/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.i686.rpm b/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.i686.rpm Binary files differnew file mode 100644 index 0000000000..3421c3628f --- /dev/null +++ b/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.i686.rpm diff --git a/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.src.rpm b/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.src.rpm Binary files differnew file mode 100644 index 0000000000..d420659fd5 --- /dev/null +++ b/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.src.rpm diff --git a/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm b/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm Binary files differnew file mode 100644 index 0000000000..93c1f5e3e3 --- /dev/null +++ b/spec/functional/assets/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm diff --git a/spec/functional/assets/yumrepo/repodata/313329137b55fd333b2dc66394a6661a2befa6cc535d8460d92a4a78a9c581f0-primary.sqlite.bz2 b/spec/functional/assets/yumrepo/repodata/313329137b55fd333b2dc66394a6661a2befa6cc535d8460d92a4a78a9c581f0-primary.sqlite.bz2 Binary files differnew file mode 100644 index 0000000000..d7726b9df6 --- /dev/null +++ b/spec/functional/assets/yumrepo/repodata/313329137b55fd333b2dc66394a6661a2befa6cc535d8460d92a4a78a9c581f0-primary.sqlite.bz2 diff --git a/spec/functional/assets/yumrepo/repodata/31ac4db5d5ac593728fcc26aef82b7b93c4cc4dbec843786b1845b939b658553-other.xml.gz b/spec/functional/assets/yumrepo/repodata/31ac4db5d5ac593728fcc26aef82b7b93c4cc4dbec843786b1845b939b658553-other.xml.gz Binary files differnew file mode 100644 index 0000000000..30d7778ac4 --- /dev/null +++ b/spec/functional/assets/yumrepo/repodata/31ac4db5d5ac593728fcc26aef82b7b93c4cc4dbec843786b1845b939b658553-other.xml.gz diff --git a/spec/functional/assets/yumrepo/repodata/4ac40fa3c6728c1401318e2e20a997436624e83dcf7a5f952b851ef422637773-filelists.sqlite.bz2 b/spec/functional/assets/yumrepo/repodata/4ac40fa3c6728c1401318e2e20a997436624e83dcf7a5f952b851ef422637773-filelists.sqlite.bz2 Binary files differnew file mode 100644 index 0000000000..2df608aa34 --- /dev/null +++ b/spec/functional/assets/yumrepo/repodata/4ac40fa3c6728c1401318e2e20a997436624e83dcf7a5f952b851ef422637773-filelists.sqlite.bz2 diff --git a/spec/functional/assets/yumrepo/repodata/66391e53f0510b98b3f0b79f40ba1048026d9a1ef20905d9c40ba6f5411f3243-primary.xml.gz b/spec/functional/assets/yumrepo/repodata/66391e53f0510b98b3f0b79f40ba1048026d9a1ef20905d9c40ba6f5411f3243-primary.xml.gz Binary files differnew file mode 100644 index 0000000000..d9b7cb879a --- /dev/null +++ b/spec/functional/assets/yumrepo/repodata/66391e53f0510b98b3f0b79f40ba1048026d9a1ef20905d9c40ba6f5411f3243-primary.xml.gz diff --git a/spec/functional/assets/yumrepo/repodata/8b34697595fcc87928e12d24644dda9462c3857bd932861e28bc77ae1f31be16-filelists.xml.gz b/spec/functional/assets/yumrepo/repodata/8b34697595fcc87928e12d24644dda9462c3857bd932861e28bc77ae1f31be16-filelists.xml.gz Binary files differnew file mode 100644 index 0000000000..35a973d170 --- /dev/null +++ b/spec/functional/assets/yumrepo/repodata/8b34697595fcc87928e12d24644dda9462c3857bd932861e28bc77ae1f31be16-filelists.xml.gz diff --git a/spec/functional/assets/yumrepo/repodata/b97cca3fe14bcf06c52be4449b6108f7731239ff221111dcce8aada5467f60dc-other.sqlite.bz2 b/spec/functional/assets/yumrepo/repodata/b97cca3fe14bcf06c52be4449b6108f7731239ff221111dcce8aada5467f60dc-other.sqlite.bz2 Binary files differnew file mode 100644 index 0000000000..e682fc0f0b --- /dev/null +++ b/spec/functional/assets/yumrepo/repodata/b97cca3fe14bcf06c52be4449b6108f7731239ff221111dcce8aada5467f60dc-other.sqlite.bz2 diff --git a/spec/functional/assets/yumrepo/repodata/repomd.xml b/spec/functional/assets/yumrepo/repodata/repomd.xml new file mode 100644 index 0000000000..92937e151a --- /dev/null +++ b/spec/functional/assets/yumrepo/repodata/repomd.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<repomd xmlns="http://linux.duke.edu/metadata/repo" xmlns:rpm="http://linux.duke.edu/metadata/rpm"> + <revision>1479418959</revision> +<data type="filelists"> + <checksum type="sha256">8b34697595fcc87928e12d24644dda9462c3857bd932861e28bc77ae1f31be16</checksum> + <open-checksum type="sha256">9f5be999b4a535c19afc53703851577e1a325227fab651189c5c39708b9a1e38</open-checksum> + <location href="repodata/8b34697595fcc87928e12d24644dda9462c3857bd932861e28bc77ae1f31be16-filelists.xml.gz"/> + <timestamp>1479418959</timestamp> + <size>419</size> + <open-size>1127</open-size> +</data> +<data type="primary"> + <checksum type="sha256">66391e53f0510b98b3f0b79f40ba1048026d9a1ef20905d9c40ba6f5411f3243</checksum> + <open-checksum type="sha256">dc25cfbf4520861130e0ba203d27cc40b183fbb7c576aac33d838fb20a68aa32</open-checksum> + <location href="repodata/66391e53f0510b98b3f0b79f40ba1048026d9a1ef20905d9c40ba6f5411f3243-primary.xml.gz"/> + <timestamp>1479418959</timestamp> + <size>859</size> + <open-size>4529</open-size> +</data> +<data type="primary_db"> + <checksum type="sha256">313329137b55fd333b2dc66394a6661a2befa6cc535d8460d92a4a78a9c581f0</checksum> + <open-checksum type="sha256">720b637c782cce8604b922e9989ecfff9091e26163d643bd1b676778beb1c933</open-checksum> + <location href="repodata/313329137b55fd333b2dc66394a6661a2befa6cc535d8460d92a4a78a9c581f0-primary.sqlite.bz2"/> + <timestamp>1479418959</timestamp> + <database_version>10</database_version> + <size>2460</size> + <open-size>32768</open-size> +</data> +<data type="other_db"> + <checksum type="sha256">b97cca3fe14bcf06c52be4449b6108f7731239ff221111dcce8aada5467f60dc</checksum> + <open-checksum type="sha256">938156bcfc95828cb6857e1b2790dceaef57196843a80464ba5749772fc15e83</open-checksum> + <location href="repodata/b97cca3fe14bcf06c52be4449b6108f7731239ff221111dcce8aada5467f60dc-other.sqlite.bz2"/> + <timestamp>1479418959</timestamp> + <database_version>10</database_version> + <size>967</size> + <open-size>6144</open-size> +</data> +<data type="other"> + <checksum type="sha256">31ac4db5d5ac593728fcc26aef82b7b93c4cc4dbec843786b1845b939b658553</checksum> + <open-checksum type="sha256">2ea64cdb2f5ba3859af29fe67a85d61d5b4de23f3da1ee71d5af175d8d887ab6</open-checksum> + <location href="repodata/31ac4db5d5ac593728fcc26aef82b7b93c4cc4dbec843786b1845b939b658553-other.xml.gz"/> + <timestamp>1479418959</timestamp> + <size>413</size> + <open-size>1035</open-size> +</data> +<data type="filelists_db"> + <checksum type="sha256">4ac40fa3c6728c1401318e2e20a997436624e83dcf7a5f952b851ef422637773</checksum> + <open-checksum type="sha256">8bc15efa19d02a5112e20c6ed1be17c5851287ddfba17aee2283ddb216dd08d7</open-checksum> + <location href="repodata/4ac40fa3c6728c1401318e2e20a997436624e83dcf7a5f952b851ef422637773-filelists.sqlite.bz2"/> + <timestamp>1479418959</timestamp> + <database_version>10</database_version> + <size>1131</size> + <open-size>7168</open-size> +</data> +</repomd> diff --git a/spec/functional/resource/dnf_package_spec.rb b/spec/functional/resource/dnf_package_spec.rb new file mode 100644 index 0000000000..4c9ee6ca97 --- /dev/null +++ b/spec/functional/resource/dnf_package_spec.rb @@ -0,0 +1,686 @@ +# +# Copyright:: Copyright 2016, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" +require "functional/resource/base" +require "chef/mixin/shell_out" + +# run this test only for following platforms. +exclude_test = !(%w{rhel fedora}.include?(ohai[:platform_family]) && File.exist?("/usr/bin/dnf")) +describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test do + include Chef::Mixin::ShellOut + + def flush_cache + # needed on at least fc23/fc24 sometimes to deal with the dnf cache getting out of sync with the rpm db + FileUtils.rm_f "/var/cache/dnf/@System.solv" + Chef::Resource::DnfPackage.new("shouldnt-matter", run_context).run_action(:flush_cache) + end + + def preinstall(*rpms) + rpms.each do |rpm| + shell_out!("rpm -ivh #{CHEF_SPEC_ASSETS}/yumrepo/#{rpm}") + end + flush_cache + end + + before(:each) do + File.open("/etc/yum.repos.d/chef-dnf-localtesting.repo", "w+") do |f| + f.write <<-EOF +[chef-dnf-localtesting] +name=Chef DNF spec testing repo +baseurl=file://#{CHEF_SPEC_ASSETS}/yumrepo +enable=1 +gpgcheck=0 + EOF + end + shell_out!("rpm -qa | grep chef_rpm | xargs -r rpm -e") + end + + after(:all) do + shell_out!("rpm -qa | grep chef_rpm | xargs -r rpm -e") + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + end + + let(:package_name) { "chef_rpm" } + let(:dnf_package) { Chef::Resource::DnfPackage.new(package_name, run_context) } + + describe ":install" do + context "vanilla use case" do + let(:package_name) { "chef_rpm" } + + it "installs if the package is not installed" do + flush_cache + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "does not install if the package is installed" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "does not install twice" do + flush_cache + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "does not install if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "does not install if the i686 package is installed" do + skip "FIXME: do nothing, or install the x86_64 version?" + preinstall("chef_rpm-1.10-1.fc24.i686.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.i686") + end + + it "does not install if the prior version i686 package is installed" do + skip "FIXME: do nothing, or install the x86_64 version?" + preinstall("chef_rpm-1.2-1.fc24.i686.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.i686") + end + end + + context "with versions or globs in the name" do + it "works with a version" do + flush_cache + dnf_package.package_name("chef_rpm-1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "works with an older version" do + flush_cache + dnf_package.package_name("chef_rpm-1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "works with an evr" do + flush_cache + dnf_package.package_name("chef_rpm-0:1.2-1.fc24") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "works with a version glob" do + flush_cache + dnf_package.package_name("chef_rpm-1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "works with a name glob + version glob" do + flush_cache + dnf_package.package_name("chef_rp*-1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + end + + # version only matches the actual dnf version, does not work with epoch or release or combined evr + context "with version property" do + it "matches the full version" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "matches with a glob" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "matches the vr" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("1.10-1.fc24") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "matches the evr" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("0:1.10-1.fc24") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "matches with a vr glob" do + pending "doesn't work on command line either" + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("1.10-1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "matches with an evr glob" do + pending "doesn't work on command line either" + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("0:1.10-1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + end + + context "downgrades" do + it "just work with DNF" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.version("1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "throws a deprecation warning with allow_downgrade" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(Chef).to receive(:deprecated).with(:dnf_package_allow_downgrade, /^the allow_downgrade property on the dnf_package provider is not used/) + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.version("1.2") + dnf_package.run_action(:install) + dnf_package.allow_downgrade true + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + end + + context "with arches" do + it "installs with 64-bit arch in the name" do + flush_cache + dnf_package.package_name("chef_rpm.x86_64") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "installs with 32-bit arch in the name" do + flush_cache + dnf_package.package_name("chef_rpm.i686") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.i686") + end + + it "installs with 64-bit arch in the property" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.arch("x86_64") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "installs with 32-bit arch in the property" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.arch("i686") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.i686") + end + end + + context "with constraints" do + it "with nothing installed, it installs the latest version" do + flush_cache + dnf_package.package_name("chef_rpm >= 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "when it is met, it does nothing" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.package_name("chef_rpm >= 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "when it is met, it does nothing" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.package_name("chef_rpm >= 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "with nothing intalled, it installs the latest version" do + flush_cache + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "when it is not met by an installed rpm, it upgrades" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "when it is met by an installed rpm, it does nothing" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "when there is no solution to the contraint" do + flush_cache + dnf_package.package_name("chef_rpm > 2.0") + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "when there is no solution to the contraint but an rpm is preinstalled" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.package_name("chef_rpm > 2.0") + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + end + + context "with source arguments" do + it "raises an exception when the package does not exist" do + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm") + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "does not raise a hard exception in why-run mode when the package does not exist" do + Chef::Config[:why_run] = true + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm") + dnf_package.run_action(:install) + expect { dnf_package.run_action(:install) }.not_to raise_error + end + + it "installs the package when using the source argument" do + flush_cache + dnf_package.name "something" + dnf_package.package_name "somethingelse" + dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "installs the package when the name is a path to a file" do + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "does not downgrade the package with :install" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "does not upgrade the package with :install" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "is idempotent when the package is already installed" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "works with a local source" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + end + + context "multipackage with arches" do + it "installs two rpms" do + flush_cache + dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.x86_64/) + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.i686/) + end + + it "does nothing if both are installed" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm", "chef_rpm-1.10-1.fc24.i686.rpm") + flush_cache + dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs the second rpm if the first is installed" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.x86_64/) + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.i686/) + end + + it "installs the first rpm if the second is installed" do + preinstall("chef_rpm-1.10-1.fc24.i686.rpm") + dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.x86_64/) + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.i686/) + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs two rpms with multi-arch" do + flush_cache + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch(%w{x86_64 i686}) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.x86_64/) + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.i686/) + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs the second rpm if the first is installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch(%w{x86_64 i686}) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.x86_64/) + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.i686/) + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs the first rpm if the second is installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch(%w{x86_64 i686}) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.x86_64/) + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.fc24.i686/) + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "does nothing if both are installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm", "chef_rpm-1.10-1.fc24.i686.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch(%w{x86_64 i686}) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + end + + describe ":upgrade" do + context "downgrades" do + it "just work with DNF" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.version("1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "throws a deprecation warning with allow_downgrade" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(Chef).to receive(:deprecated).with(:dnf_package_allow_downgrade, /^the allow_downgrade property on the dnf_package provider is not used/) + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.version("1.2") + dnf_package.run_action(:install) + dnf_package.allow_downgrade true + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + end + + context "with source arguments" do + it "installs the package when using the source argument" do + flush_cache + dnf_package.name "something" + dnf_package.package_name "somethingelse" + dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "installs the package when the name is a path to a file" do + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "downgrades the package" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "upgrades the package" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + + it "is idempotent when the package is already installed" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + + it "works with a local source" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.x86_64") + end + end + end + + describe ":remove" do + context "vanilla use case" do + let(:package_name) { "chef_rpm" } + it "does nothing if the package is not installed" do + flush_cache + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "does not remove the package twice" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the i686 package is installed" do + skip "FIXME: should this be fixed or is the current behavior correct?" + preinstall("chef_rpm-1.10-1.fc24.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the prior version i686 package is installed" do + skip "FIXME: should this be fixed or is the current behavior correct?" + preinstall("chef_rpm-1.2-1.fc24.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + end + + context "with 64-bit arch" do + let(:package_name) { "chef_rpm.x86_64" } + it "does nothing if the package is not installed" do + flush_cache + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "does nothing if the i686 package is installed" do + preinstall("chef_rpm-1.10-1.fc24.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.i686") + end + + it "does nothing if the prior version i686 package is installed" do + preinstall("chef_rpm-1.2-1.fc24.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.fc24.i686") + end + end + + context "with 32-bit arch" do + let(:package_name) { "chef_rpm.i686" } + it "removes only the 32-bit arch if both are installed" do + preinstall("chef_rpm-1.10-1.fc24.x86_64.rpm", "chef_rpm-1.10-1.fc24.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.fc24.x86_64") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + preinstall("chef_rpm-1.2-1.fc24.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + end + end +end diff --git a/spec/unit/resource/dnf_package_spec.rb b/spec/unit/resource/dnf_package_spec.rb new file mode 100644 index 0000000000..0cc673d897 --- /dev/null +++ b/spec/unit/resource/dnf_package_spec.rb @@ -0,0 +1,99 @@ +# +# Copyright:: Copyright 2016, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" +require "support/shared/unit/resource/static_provider_resolution" + +describe Chef::Resource::DnfPackage, "initialize" do + + static_provider_resolution( + resource: Chef::Resource::DnfPackage, + provider: Chef::Provider::Package::Dnf, + name: :dnf_package, + action: :install, + os: "linux", + platform_family: "rhel" + ) + +end + +describe Chef::Resource::DnfPackage, "arch" do + before(:each) do + @resource = Chef::Resource::DnfPackage.new("foo") + end + + it "should set the arch variable to whatever is passed in" do + @resource.arch("i386") + expect(@resource.arch).to eql(["i386"]) + end +end + +describe Chef::Resource::DnfPackage, "flush_cache" do + before(:each) do + @resource = Chef::Resource::DnfPackage.new("foo") + end + + it "should default the flush timing to false" do + flush_hash = { :before => false, :after => false } + expect(@resource.flush_cache).to eq(flush_hash) + end + + it "should allow you to set the flush timing with an array" do + flush_array = [ :before, :after ] + flush_hash = { :before => true, :after => true } + @resource.flush_cache(flush_array) + expect(@resource.flush_cache).to eq(flush_hash) + end + + it "should allow you to set the flush timing with a hash" do + flush_hash = { :before => true, :after => true } + @resource.flush_cache(flush_hash) + expect(@resource.flush_cache).to eq(flush_hash) + end + + it "should allow 'true' for flush_cache" do + @resource.flush_cache(true) + expect(@resource.flush_cache).to eq({ before: true, after: true }) + end + + it "should allow 'false' for flush_cache" do + @resource.flush_cache(false) + expect(@resource.flush_cache).to eq({ before: false, after: false }) + end + + it "should allow ':before' for flush_cache" do + @resource.flush_cache(:before) + expect(@resource.flush_cache).to eq({ before: true, after: false }) + end + + it "should allow ':after' for flush_cache" do + @resource.flush_cache(:after) + expect(@resource.flush_cache).to eq({ before: false, after: true }) + end +end + +describe Chef::Resource::DnfPackage, "allow_downgrade" do + before(:each) do + @resource = Chef::Resource::DnfPackage.new("foo") + end + + it "should allow you to specify whether allow_downgrade is true or false" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect { @resource.allow_downgrade true }.not_to raise_error + expect { @resource.allow_downgrade false }.not_to raise_error + end +end diff --git a/spec/unit/resource/yum_package_spec.rb b/spec/unit/resource/yum_package_spec.rb index dd0d3ae928..bc2d19d50e 100644 --- a/spec/unit/resource/yum_package_spec.rb +++ b/spec/unit/resource/yum_package_spec.rb @@ -65,6 +65,26 @@ describe Chef::Resource::YumPackage, "flush_cache" do @resource.flush_cache(flush_hash) expect(@resource.flush_cache).to eq(flush_hash) end + + it "should allow 'true' for flush_cache" do + @resource.flush_cache(true) + expect(@resource.flush_cache).to eq({ before: true, after: true }) + end + + it "should allow 'false' for flush_cache" do + @resource.flush_cache(false) + expect(@resource.flush_cache).to eq({ before: false, after: false }) + end + + it "should allow ':before' for flush_cache" do + @resource.flush_cache(:before) + expect(@resource.flush_cache).to eq({ before: true, after: false }) + end + + it "should allow ':after' for flush_cache" do + @resource.flush_cache(:after) + expect(@resource.flush_cache).to eq({ before: false, after: true }) + end end describe Chef::Resource::YumPackage, "allow_downgrade" do |