From 3494a29604b7719ab640a325becabbcdc57a7bce Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Thu, 19 Mar 2020 11:33:22 -0700 Subject: DNF refresh and aarch64-el8 support should port all fixes from the yum provider back into the dnf provider. adds aarch for el8 since the func tests now pass. Signed-off-by: Lamont Granquist --- .expeditor/release.omnibus.yml | 1 + lib/chef/provider/package/dnf.rb | 78 ++- lib/chef/provider/package/dnf/dnf_helper.py | 110 +++- lib/chef/provider/package/dnf/python_helper.rb | 117 ++-- lib/chef/provider/package/dnf/version.rb | 8 +- lib/chef/provider/package/yum.rb | 10 +- lib/chef/provider/package/yum/python_helper.rb | 2 +- spec/functional/resource/dnf_package_spec.rb | 599 +++++++++++++++------ .../provider/package/dnf/python_helper_spec.rb | 4 +- 9 files changed, 684 insertions(+), 245 deletions(-) diff --git a/.expeditor/release.omnibus.yml b/.expeditor/release.omnibus.yml index 4e365a5c7b..5e09b706ae 100644 --- a/.expeditor/release.omnibus.yml +++ b/.expeditor/release.omnibus.yml @@ -20,6 +20,7 @@ builder-to-testers-map: - el-6-x86_64 el-7-aarch64: - el-7-aarch64 + - el-8-aarch64 el-7-ppc64: - el-7-ppc64 el-7-ppc64le: diff --git a/lib/chef/provider/package/dnf.rb b/lib/chef/provider/package/dnf.rb index d7b61e0789..9044965884 100644 --- a/lib/chef/provider/package/dnf.rb +++ b/lib/chef/provider/package/dnf.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2016-2018, Chef Software Inc. +# Copyright:: Copyright 2016-2020, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -118,23 +118,79 @@ class Chef flushcache end + # NB: the dnf_package provider manages individual single packages, please do not submit issues or PRs to try to add wildcard + # support to lock / unlock. The best solution is to write an execute resource which does a not_if `dnf versionlock | grep '^pattern`` kind of approach + def lock_package(names, versions) + dnf("-d0", "-e0", "-y", options, "versionlock", "add", resolved_package_lock_names(names)) + end + + # NB: the dnf_package provider manages individual single packages, please do not submit issues or PRs to try to add wildcard + # support to lock / unlock. The best solution is to write an execute resource which does a only_if `dnf versionlock | grep '^pattern`` kind of approach + def unlock_package(names, versions) + # dnf versionlock delete on rhel6 needs the glob nonsense in the following command + dnf("-d0", "-e0", "-y", options, "versionlock", "delete", resolved_package_lock_names(names).map { |n| "*:#{n}-*" }) + end + private - def resolve_source_to_version_obj - shell_out!("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) + # this will resolve things like `/usr/bin/perl` or virtual packages like `mysql` -- it will not work (well? at all?) with globs that match multiple packages + def resolved_package_lock_names(names) + names.each_with_index.map do |name, i| + unless name.nil? + if installed_version(i).version.nil? + available_version(i).name + else + installed_version(i).name + end end end end + def locked_packages + @locked_packages ||= + begin + locked = dnf("versionlock", "list") + locked.stdout.each_line.map do |line| + line.sub(/-[^-]*-[^-]*$/, "").split(":").last.strip + end + end + end + + def packages_all_locked?(names, versions) + resolved_package_lock_names(names).all? { |n| locked_packages.include? n } + end + + def packages_all_unlocked?(names, versions) + !resolved_package_lock_names(names).any? { |n| locked_packages.include? n } + end + + def version_gt?(v1, v2) + return false if v1.nil? || v2.nil? + + python_helper.compare_versions(v1, v2) == 1 + end + + def version_equals?(v1, v2) + return false if v1.nil? || v2.nil? + + python_helper.compare_versions(v1, v2) == 0 + end + def version_compare(v1, v2) python_helper.compare_versions(v1, v2) end + def resolve_source_to_version_obj + shell_out!("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 yum 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 + # @return Array def available_version(index) @available_version ||= [] @@ -142,7 +198,7 @@ class Chef @available_version[index] ||= if new_resource.source resolve_source_to_version_obj else - python_helper.query(:whatavailable, package_name_array[index], safe_version_array[index], safe_arch_array[index]) + python_helper.package_query(:whatavailable, package_name_array[index], version: safe_version_array[index], arch: safe_arch_array[index], options: options) end @available_version[index] @@ -152,9 +208,9 @@ class Chef def installed_version(index) @installed_version ||= [] @installed_version[index] ||= if new_resource.source - python_helper.query(:whatinstalled, available_version(index).name, safe_version_array[index], safe_arch_array[index]) + python_helper.package_query(:whatinstalled, available_version(index).name, arch: safe_arch_array[index], options: options) else - python_helper.query(:whatinstalled, package_name_array[index], safe_version_array[index], safe_arch_array[index]) + python_helper.package_query(:whatinstalled, package_name_array[index], arch: safe_arch_array[index], options: options) end @installed_version[index] end diff --git a/lib/chef/provider/package/dnf/dnf_helper.py b/lib/chef/provider/package/dnf/dnf_helper.py index 4ec343efac..f4c031dec0 100644 --- a/lib/chef/provider/package/dnf/dnf_helper.py +++ b/lib/chef/provider/package/dnf/dnf_helper.py @@ -26,6 +26,17 @@ def get_sack(): except AttributeError: pass base.read_all_repos() + repos = base.repos + + if 'repos' in command: + for repo_pattern in command['repos']: + if 'enable' in repo_pattern: + for repo in repos.get_matching(repo_pattern['enable']): + repo.enable() + if 'disable' in repo_pattern: + for repo in repos.get_matching(repo_pattern['disable']): + repo.disable() + try: base.configure_plugins() except AttributeError: @@ -41,13 +52,38 @@ def flushcache(): pass get_sack().load_system_repo(build_cache=True) +def version_tuple(versionstr): + e = '0' + v = None + r = None + colon_index = versionstr.find(':') + if colon_index > 0: + e = str(versionstr[:colon_index]) + dash_index = versionstr.find('-') + if dash_index > 0: + tmp = versionstr[colon_index + 1:dash_index] + if tmp != '': + v = tmp + arch_index = versionstr.find('.', dash_index) + if arch_index > 0: + r = versionstr[dash_index + 1:arch_index] + else: + r = versionstr[dash_index + 1:] + else: + tmp = versionstr[colon_index + 1:] + if tmp != '': + v = tmp + return (e, v, r) + def versioncompare(versions): sack = get_sack() if (versions[0] is None) or (versions[1] is None): - sys.stdout.write('0\n') + outpipe.write('0\n') + outpipe.flush() else: - evr_comparison = sack.evr_cmp(versions[0], versions[1]) - sys.stdout.write('{}\n'.format(evr_comparison)) + evr_comparison = dnf.rpm.rpm.labelCompare(version_tuple(versions[0]), version_tuple(versions[1])) + outpipe.write('{}\n'.format(evr_comparison)) + outpipe.flush() def query(command): sack = get_sack() @@ -79,37 +115,59 @@ def query(command): pkgs = q.latest(1).run() if not pkgs: - sys.stdout.write('{} nil nil\n'.format(command['provides'].split().pop(0))) + outpipe.write('{} nil nil\n'.format(command['provides'].split().pop(0))) + outpipe.flush() 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)) + outpipe.write('{} {}:{}-{} {}\n'.format(pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch)) + outpipe.flush() # 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): + if base is not None: + base.close() sys.exit(0) -signal.signal(signal.SIGINT, exit_handler) -signal.signal(signal.SIGHUP, exit_handler) -signal.signal(signal.SIGPIPE, 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() - elif command['action'] == "versioncompare": - versioncompare(command['versions']) - else: - raise RuntimeError("bad command") +def setup_exit_handler(): + signal.signal(signal.SIGINT, exit_handler) + signal.signal(signal.SIGHUP, exit_handler) + signal.signal(signal.SIGPIPE, exit_handler) + signal.signal(signal.SIGQUIT, exit_handler) + +if len(sys.argv) < 3: + inpipe = sys.stdin + outpipe = sys.stdout +else: + inpipe = os.fdopen(int(sys.argv[1]), "r") + outpipe = os.fdopen(int(sys.argv[2]), "w") + +try: + while 1: + # kill self if we get orphaned (tragic) + ppid = os.getppid() + if ppid == 1: + raise RuntimeError("orphaned") + + setup_exit_handler() + line = inpipe.readline() + + try: + command = json.loads(line) + except ValueError: + raise RuntimeError("bad json parse") + + if command['action'] == "whatinstalled": + query(command) + elif command['action'] == "whatavailable": + query(command) + elif command['action'] == "versioncompare": + versioncompare(command['versions']) + else: + raise RuntimeError("bad command") +finally: + if base is not None: + base.closeRpmDB() diff --git a/lib/chef/provider/package/dnf/python_helper.rb b/lib/chef/provider/package/dnf/python_helper.rb index 176ed55d3d..c591571a69 100644 --- a/lib/chef/provider/package/dnf/python_helper.rb +++ b/lib/chef/provider/package/dnf/python_helper.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2016-2017, Chef Software Inc. +# Copyright:: Copyright 2016-2020, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ require_relative "../../../mixin/which" require_relative "../../../mixin/shell_out" require_relative "version" +require "singleton" unless defined?(Singleton) require "timeout" unless defined?(Timeout) class Chef @@ -32,6 +33,8 @@ class Chef attr_accessor :stdin attr_accessor :stdout attr_accessor :stderr + attr_accessor :inpipe + attr_accessor :outpipe attr_accessor :wait_thr DNF_HELPER = ::File.expand_path(::File.join(::File.dirname(__FILE__), "dnf_helper.py")).freeze @@ -50,16 +53,28 @@ class Chef def start ENV["PYTHONUNBUFFERED"] = "1" - @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(dnf_command) + @inpipe, inpipe_write = IO.pipe + outpipe_read, @outpipe = IO.pipe + @stdin, @stdout, @stderr, @wait_thr = Open3.popen3("#{dnf_command} #{outpipe_read.fileno} #{inpipe_write.fileno}", outpipe_read.fileno => outpipe_read, inpipe_write.fileno => inpipe_write, close_others: false) + outpipe_read.close + inpipe_write.close end def reap unless wait_thr.nil? - Process.kill("KILL", wait_thr.pid) rescue nil + Process.kill("INT", wait_thr.pid) rescue nil + begin + Timeout.timeout(3) do + wait_thr.value # this calls waitpid() + end + rescue Timeout::Error + Process.kill("KILL", wait_thr.pid) rescue nil + end stdin.close unless stdin.nil? stdout.close unless stdout.nil? stderr.close unless stderr.nil? - wait_thr.value # this calls waitpit() + inpipe.close unless inpipe.nil? + outpipe.close unless outpipe.nil? end end @@ -68,26 +83,39 @@ class Chef end def compare_versions(version1, version2) - with_helper do - json = build_version_query("versioncompare", [version1, version2]) - Chef::Log.trace "sending '#{json}' to python helper" - stdin.syswrite json + "\n" - stdout.sysread(4096).chomp.to_i + query("versioncompare", { "versions" => [version1, version2] }).to_i + end + + def options_params(options) + options.each_with_object({}) do |opt, h| + if opt =~ /--enablerepo=(.+)/ + $1.split(",").each do |repo| + h["repos"] ||= [] + h["repos"].push( { "enable" => repo } ) + end + end + if opt =~ /--disablerepo=(.+)/ + $1.split(",").each do |repo| + h["repos"] ||= [] + h["repos"].push( { "disable" => repo } ) + end + end end end # @return Array - def query(action, provides, version = nil, arch = nil) - with_helper do - json = build_query(action, provides, version, arch) - Chef::Log.trace "sending '#{json}' to python helper" - stdin.syswrite json + "\n" - output = stdout.sysread(4096).chomp - Chef::Log.trace "got '#{output}' from python helper" - version = parse_response(output) - Chef::Log.trace "parsed #{version} from python helper" - version - end + # NB: "options" here is the dnf_package options hash and is deliberately not **opts + def package_query(action, provides, version: nil, arch: nil, options: {}) + parameters = { "provides" => provides, "version" => version, "arch" => arch } + repo_opts = options_params(options || {}) + parameters.merge!(repo_opts) + # XXX: for now we restart before and after every query with an enablerepo/disablerepo to clean the helpers internal state + restart unless repo_opts.empty? + query_output = query(action, parameters) + version = parse_response(query_output.lines.last) + Chef::Log.trace "parsed #{version} from python helper" + restart unless repo_opts.empty? + version end def restart @@ -116,17 +144,28 @@ class Chef 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) + def query(action, parameters) + with_helper do + json = build_query(action, parameters) + Chef::Log.trace "sending '#{json}' to python helper" + outpipe.syswrite json + "\n" + output = inpipe.sysread(4096).chomp + Chef::Log.trace "got '#{output}' from python helper" + return output + end end - def build_version_query(action, versions) + def build_query(action, parameters) hash = { "action" => action } - hash["versions"] = versions + parameters.each do |param_name, param_value| + hash[param_name] = param_value unless param_value.nil? + end + + # Special handling for certain action / param combos + if %i{whatinstalled whatavailable}.include?(action) + add_version(hash, parameters["version"]) unless parameters["version"].nil? + end + FFI_Yajl::Encoder.encode(hash) end @@ -135,12 +174,16 @@ class Chef array.each_slice(3).map { |x| Version.new(*x) }.first end - def drain_stderr + def drain_fds output = "" - output += stderr.sysread(4096).chomp until IO.select([stderr], nil, nil, 0).nil? + fds, = IO.select([stderr, stdout, inpipe], nil, nil, 0) + unless fds.nil? + fds.each do |fd| + output += fd.sysread(4096) rescue "" + end + end output - rescue - # we must rescue EOFError, and we don't much care about errors on stderr anyway + rescue => e output end @@ -151,23 +194,23 @@ class Chef check ret = yield end - output = drain_stderr + output = drain_fds unless output.empty? - Chef::Log.trace "discarding output on stderr from python helper: #{output}" + Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}" end ret rescue EOFError, Errno::EPIPE, Timeout::Error, Errno::ESRCH => e - output = drain_stderr + output = drain_fds if ( max_retries -= 1 ) > 0 unless output.empty? - Chef::Log.trace "discarding output on stderr from python helper: #{output}" + Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}" end restart retry else raise e if output.empty? - raise "dnf-helper.py had stderr output:\n\n#{output}" + raise "dnf-helper.py had stderr/stdout output:\n\n#{output}" end end end diff --git a/lib/chef/provider/package/dnf/version.rb b/lib/chef/provider/package/dnf/version.rb index 3cff5b0437..5fe3a76e3e 100644 --- a/lib/chef/provider/package/dnf/version.rb +++ b/lib/chef/provider/package/dnf/version.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2016, Chef Software, Inc. +# Copyright:: Copyright 2016-2020, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,13 +33,17 @@ class Chef end def to_s - "#{name}-#{version}.#{arch}" + "#{name}-#{version}.#{arch}" unless version.nil? end def version_with_arch "#{version}.#{arch}" unless version.nil? end + def name_with_arch + "#{name}.#{arch}" unless name.nil? + end + def matches_name_and_arch?(other) other.version == version && other.arch == arch end diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb index ca24ea2f5b..3251f4e236 100644 --- a/lib/chef/provider/package/yum.rb +++ b/lib/chef/provider/package/yum.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2016-2018, Chef Software Inc. +# Copyright:: Copyright 2016-2020, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -202,14 +202,6 @@ class Chef python_helper.compare_versions(v1, v2) end - # Generate the yum syntax for the package - def yum_syntax(name, version, arch) - s = name - s += "-#{version}" if version - s += ".#{arch}" if arch - s - end - def resolve_source_to_version_obj shell_out!("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 diff --git a/lib/chef/provider/package/yum/python_helper.rb b/lib/chef/provider/package/yum/python_helper.rb index 19c0b0a474..2080fed6ff 100644 --- a/lib/chef/provider/package/yum/python_helper.rb +++ b/lib/chef/provider/package/yum/python_helper.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2016-2018, Chef Software Inc. +# Copyright:: Copyright 2016-2020, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/spec/functional/resource/dnf_package_spec.rb b/spec/functional/resource/dnf_package_spec.rb index 0e7009f003..ec167238f7 100644 --- a/spec/functional/resource/dnf_package_spec.rb +++ b/spec/functional/resource/dnf_package_spec.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2016-2019, Chef Software Inc. +# Copyright:: Copyright 2016-2020, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,13 @@ require "chef/mixin/shell_out" # run this test only for following platforms. exclude_test = !(%w{rhel fedora amazon}.include?(ohai[:platform_family]) && File.exist?("/usr/bin/dnf")) -describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do +describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do include Chef::Mixin::ShellOut + # NOTE: every single test here either needs to explicitly call flush_cache or needs to explicitly + # call preinstall (which explicitly calls flush_cache). It is your responsibility to do one or the + # other in order to minimize calling flush_cache a half dozen times per test. + 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" @@ -37,6 +41,10 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do flush_cache end + before(:all) do + shell_out!("dnf -y install dnf-plugins-core") + end + before(:each) do File.open("/etc/yum.repos.d/chef-dnf-localtesting.repo", "w+") do |f| f.write <<~EOF @@ -47,17 +55,24 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do gpgcheck=0 EOF end - shell_out!("rpm -qa | grep chef_rpm | xargs -r rpm -e") + shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | grep chef_rpm | xargs -r rpm -e") + # next line is useful cleanup if you happen to have been testing both yum + dnf func tests on the same box and + # have some yum garbage left around + FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo" end after(:all) do - shell_out!("rpm -qa | grep chef_rpm | xargs -r rpm -e") + shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | 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) } + def pkg_arch + ohai[:kernel][:machine] + end + describe ":install" do context "vanilla use case" do let(:package_name) { "chef_rpm" } @@ -66,47 +81,47 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "does not install if the package is installed" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "does not install if the prior version package is installed" do - preinstall("chef_rpm-1.2-1.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end - it "does not install if the i686 package is installed" do - skip "FIXME: do nothing, or install the x86_64 version?" + it "does not install if the i686 package is installed", :intel_64bit do + skip "FIXME: do nothing, or install the #{pkg_arch} version?" preinstall("chef_rpm-1.10-1.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.i686") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.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?" + it "does not install if the prior version i686 package is installed", :intel_64bit do + skip "FIXME: do nothing, or install the #{pkg_arch} version?" preinstall("chef_rpm-1.2-1.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.i686") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$") end end @@ -116,7 +131,7 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "works with an older version" do @@ -124,15 +139,23 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with an evra" do + flush_cache + dnf_package.package_name("chef_rpm-0:1.2-1.#{pkg_arch}") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end - it "works with an evr" do + it "works with version and release" do flush_cache - dnf_package.package_name("chef_rpm-0:1.2-1") + dnf_package.package_name("chef_rpm-1.2-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.2-1.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end it "works with a version glob" do @@ -140,7 +163,7 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "works with a name glob + version glob" do @@ -148,7 +171,23 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "upgrades when the installed version does not match the version string" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + 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 --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}") + end + + it "downgrades when the installed version is higher than the package_name version" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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 --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end end @@ -160,7 +199,7 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "matches with a glob" do @@ -169,7 +208,7 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "matches the vr" do @@ -178,7 +217,7 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "matches the evr" do @@ -187,58 +226,47 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end - # only works on rhel >= 8, was broken on old dnf it "matches with a vr glob", :rhel_gte_8 do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end - # only works on rhel >= 8, was broken on old dnf it "matches with an evr glob", :rhel_gte_8 do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end end context "downgrades" do - it "just work with DNF" do - preinstall("chef_rpm-1.10-1.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.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.x86_64.rpm") - dnf_package.version("1.2") + it "downgrades the package when allow_downgrade" do + flush_cache + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version("1.2-1") 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end end - context "with arches" do + context "with arches", :intel_64bit do it "installs with 64-bit arch in the name" do flush_cache - dnf_package.package_name("chef_rpm.x86_64") + dnf_package.package_name("chef_rpm.#{pkg_arch}") 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "installs with 32-bit arch in the name" do @@ -246,16 +274,16 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.i686") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.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.arch((pkg_arch).to_s) 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "installs with 32-bit arch in the property" do @@ -264,7 +292,7 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.i686") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") end end @@ -274,23 +302,23 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "when it is met, it does nothing" do - preinstall("chef_rpm-1.2-1.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end it "when it is met, it does nothing" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "with nothing intalled, it installs the latest version" do @@ -298,23 +326,39 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do 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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "when it is not met by an installed rpm, it upgrades" do - preinstall("chef_rpm-1.2-1.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with an equality constraint, when it is not met by an installed rpm, it upgrades" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + 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 --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with an equality constraint, when it is met by an installed rpm, it does nothing" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.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 --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end it "when it is met by an installed rpm, it does nothing" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "when there is no solution to the contraint" do @@ -324,10 +368,34 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do end it "when there is no solution to the contraint but an rpm is preinstalled" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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 + + it "with a less than constraint, when nothing is installed, it installs" 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 --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a less than constraint, when the install version matches, it does nothing" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a less than constraint, when the install version fails, it should downgrade" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + 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 --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end end context "with source arguments" do @@ -349,163 +417,237 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do flush_cache dnf_package.name "something" dnf_package.package_name "somethingelse" - dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") 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.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "downgrade on a local file with allow_downgrade true works" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.version "1.2-1" + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end it "does not downgrade the package with :install" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") - dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "does not upgrade the package with :install" do - preinstall("chef_rpm-1.2-1.x86_64.rpm") - dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end it "is idempotent when the package is already installed" do - preinstall("chef_rpm-1.2-1.x86_64.rpm") - dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "is idempotent when the package is already installed and there is a version string" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.version "1.2-1" + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "is idempotent when the package is already installed and there is a version string with arch" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.version "1.2-1.#{pkg_arch}" + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") 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.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") 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.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end end - context "multipackage with arches" do + context "multipackage with arches", :intel_64bit do it "installs two rpms" do flush_cache - dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "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.x86_64/) - expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.i686/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) end it "does nothing if both are installed" do - preinstall("chef_rpm-1.10-1.x86_64.rpm", "chef_rpm-1.10-1.i686.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm") flush_cache - dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "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.x86_64.rpm") - dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "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.x86_64/) - expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.i686/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) end it "installs the first rpm if the second is installed" do preinstall("chef_rpm-1.10-1.i686.rpm") - dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "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.x86_64/) - expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.i686/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.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.arch([pkg_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 match(/chef_rpm-1.10-1.x86_64/) - expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.i686/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.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.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") dnf_package.package_name(%w{chef_rpm chef_rpm} ) - dnf_package.arch(%w{x86_64 i686}) + dnf_package.arch([pkg_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 match(/chef_rpm-1.10-1.x86_64/) - expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.i686/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.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.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") dnf_package.package_name(%w{chef_rpm chef_rpm} ) - dnf_package.arch(%w{x86_64 i686}) + dnf_package.arch([pkg_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 match(/chef_rpm-1.10-1.x86_64/) - expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match(/chef_rpm-1.10-1.i686/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.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.x86_64.rpm", "chef_rpm-1.10-1.i686.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm") dnf_package.package_name(%w{chef_rpm chef_rpm} ) - dnf_package.arch(%w{x86_64 i686}) + dnf_package.arch([pkg_arch, "i686"]) dnf_package.run_action(:install) expect(dnf_package.updated_by_last_action?).to be false end end + + context "repo controls" do + it "should fail with the repo disabled" do + flush_cache + dnf_package.options("--disablerepo=chef-dnf-localtesting") + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "should work with disablerepo first" do + flush_cache + dnf_package.options(["--disablerepo=*", "--enablerepo=chef-dnf-localtesting"]) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "should work to enable a disabled repo" do + shell_out!("dnf config-manager --set-disabled chef-dnf-localtesting") + flush_cache + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + flush_cache + dnf_package.options("--enablerepo=chef-dnf-localtesting") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "when an idempotent install action is run, does not leave repos disabled" do + flush_cache + # this is a bit tricky -- we need this action to be idempotent, so that it doesn't recycle any + # caches, but need it to hit whatavailable with the repo disabled. using :upgrade like this + # accomplishes both those goals (it would be easier if we had other rpms in this repo, but with + # one rpm we neeed to do this). + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.options("--disablerepo=chef-dnf-localtesting") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + # now we're still using the same cache in the dnf_helper.py cache and we test to see if the + # repo that we temporarily disabled is enabled on this pass. + dnf_package.package_name("chef_rpm-1.10-1.#{pkg_arch}") + dnf_package.options(nil) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + end end describe ":upgrade" do context "downgrades" do it "just work with DNF" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}") 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.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}") end end @@ -514,62 +656,144 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do flush_cache dnf_package.name "something" dnf_package.package_name "somethingelse" - dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") 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.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end - it "downgrades the package" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") - dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + it "downgrades the package when allow_downgrade is true" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end it "upgrades the package" do - preinstall("chef_rpm-1.2-1.x86_64.rpm") - dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") end it "is idempotent when the package is already installed" do - preinstall("chef_rpm-1.2-1.x86_64.rpm") - dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") 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.x86_64.rpm") - dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") 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.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "version pinning" do + it "with an equality pin in the name it upgrades a prior package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a prco equality pin in the name it upgrades a prior package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm == 1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with an equality pin in the name it downgrades a later package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a prco equality pin in the name it downgrades a later package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm == 1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a > pin in the name and no rpm installed it installs" do + flush_cache + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a < pin in the name and no rpm installed it installs" do + flush_cache + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a > pin in the name and matching rpm installed it does nothing" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a < pin in the name and no rpm installed it installs" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a > pin in the name and non-matching rpm installed it upgrades" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a < pin in the name and non-matching rpm installed it downgrades" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") end end end @@ -581,106 +805,167 @@ describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test 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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") end it "removes the package if the package is installed" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") end it "does not remove the package twice" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^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.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") end - it "removes the package if the i686 package is installed" do + it "removes the package if the i686 package is installed", :intel_64bit do skip "FIXME: should this be fixed or is the current behavior correct?" preinstall("chef_rpm-1.10-1.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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") end - it "removes the package if the prior version i686 package is installed" do + it "removes the package if the prior version i686 package is installed", :intel_64bit do skip "FIXME: should this be fixed or is the current behavior correct?" preinstall("chef_rpm-1.2-1.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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") end end - context "with 64-bit arch" do - let(:package_name) { "chef_rpm.x86_64" } + context "with 64-bit arch", :intel_64bit do + let(:package_name) { "chef_rpm.#{pkg_arch}" } 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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") end it "removes the package if the package is installed" do - preinstall("chef_rpm-1.10-1.x86_64.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^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.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") end it "does nothing if the i686 package is installed" do preinstall("chef_rpm-1.10-1.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.i686") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") end it "does nothing if the prior version i686 package is installed" do preinstall("chef_rpm-1.2-1.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.i686") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$") end end - context "with 32-bit arch" do + context "with 32-bit arch", :intel_64bit 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.x86_64.rpm", "chef_rpm-1.10-1.i686.rpm") + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.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.x86_64") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") 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.x86_64.rpm") + preinstall("chef_rpm-1.2-1.#{pkg_arch}.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") + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") end end end + + describe ":lock and :unlock" do + before(:all) do + shell_out("dnf -y install python3-dnf-plugin-versionlock") + end + + before(:each) do + shell_out("dnf versionlock delete 'chef_rpm-*'") # will exit with error when nothing is locked, we don't care + end + + it "locks an rpm" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.run_action(:lock) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:") + end + + it "does not lock if its already locked" do + flush_cache + shell_out!("dnf versionlock add chef_rpm") + dnf_package.package_name("chef_rpm") + dnf_package.run_action(:lock) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:") + end + + it "unlocks an rpm" do + flush_cache + shell_out!("dnf versionlock add chef_rpm") + dnf_package.package_name("chef_rpm") + dnf_package.run_action(:unlock) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:") + end + + it "does not unlock an already locked rpm" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.run_action(:unlock) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:") + end + + it "check that we can lock based on provides" do + flush_cache + dnf_package.package_name("chef_rpm_provides") + dnf_package.run_action(:lock) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:") + end + + it "check that we can unlock based on provides" do + flush_cache + shell_out!("dnf versionlock add chef_rpm") + dnf_package.package_name("chef_rpm_provides") + dnf_package.run_action(:unlock) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:") + end + end end diff --git a/spec/unit/provider/package/dnf/python_helper_spec.rb b/spec/unit/provider/package/dnf/python_helper_spec.rb index 505217bf90..a7873bf1f8 100644 --- a/spec/unit/provider/package/dnf/python_helper_spec.rb +++ b/spec/unit/provider/package/dnf/python_helper_spec.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2017-2017, Chef Software Inc. +# Copyright:: Copyright 2017-2020, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,6 +24,6 @@ describe Chef::Provider::Package::Dnf::PythonHelper do it "propagates stacktraces on stderr from the forked subprocess" do allow(helper).to receive(:dnf_command).and_return("ruby -e 'raise \"your hands in the air\"'") - expect { helper.query(:whatprovides, "tcpdump") }.to raise_error(/your hands in the air/) + expect { helper.package_query(:whatprovides, "tcpdump") }.to raise_error(/your hands in the air/) end end -- cgit v1.2.1