diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2016-11-30 14:54:43 -0800 |
---|---|---|
committer | Lamont Granquist <lamont@scriptkiddie.org> | 2016-12-13 13:31:50 -0800 |
commit | 626213cb2d6f1e4de18c6f29573e3aa34ed76cef (patch) | |
tree | 54a4eb2513feb465bac14ffeb93884a6095db264 /lib/chef | |
parent | 673f733fe5d469ba025393b06544fbcabeca0cc9 (diff) | |
download | chef-626213cb2d6f1e4de18c6f29573e3aa34ed76cef.tar.gz |
squashing dnf work
Signed-off-by: Lamont Granquist <lamont@scriptkiddie.org>
Diffstat (limited to 'lib/chef')
-rw-r--r-- | lib/chef/mixin/which.rb | 14 | ||||
-rw-r--r-- | lib/chef/provider/package.rb | 18 | ||||
-rw-r--r-- | lib/chef/provider/package/dnf.rb | 220 | ||||
-rw-r--r-- | lib/chef/provider/package/dnf_helper.py | 81 | ||||
-rwxr-xr-x | lib/chef/provider/package/dnf_helper.rb | 22 | ||||
-rw-r--r-- | lib/chef/providers.rb | 1 | ||||
-rw-r--r-- | lib/chef/resource/dnf_package.rb | 43 | ||||
-rw-r--r-- | lib/chef/resource/yum_package.rb | 1 | ||||
-rw-r--r-- | lib/chef/resources.rb | 1 |
9 files changed, 391 insertions, 10 deletions
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..1721ca28ea 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -626,6 +626,24 @@ class Chef end args end + + # Helper to construct Hash of names-to-versions, requested on the new_resource. + # If new_resource.version is nil, then all values will be nil. + # + # @return [Hash] Mapping of requested names to versions + def desired_name_versions + desired_versions = as_array(new_resource.version) || as_array(new_resource.package_name).map { nil } + Hash[*as_array(new_resource.package_name).zip(desired_versions).flatten] + end + + # Helper to construct Hash of names-to-arches, requested on the new_resource. + # If new_resource.arch is nil, then all values will be nil. + # + # @return [Hash] Mapping of requested names to versions + def desired_name_archs + desired_archs = as_array(new_resource.arch) || as_array(new_resource.package_name).map { nil } + Hash[*as_array(new_resource.package_name).zip(desired_archs).flatten] + end end end end diff --git a/lib/chef/provider/package/dnf.rb b/lib/chef/provider/package/dnf.rb new file mode 100644 index 0000000000..b081c2056f --- /dev/null +++ b/lib/chef/provider/package/dnf.rb @@ -0,0 +1,220 @@ +# 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 "timeout" + +class Chef + class Provider + class Package + class Dnf < Chef::Provider::Package + extend Chef::Mixin::Which + + class Version + attr_accessor :name + attr_accessor :version + attr_accessor :arch + + def initialize(name, version, arch) + @name = name + @version = ( version == "nil" ) ? nil : version + @arch = ( arch == "nil" ) ? nil : 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 + end + + attr_accessor :python_helper + + 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) + stdin.close unless stdin.nil? + stdout.close unless stdout.nil? + stderr.close unless stderr.nil? + wait_thr.value + end + end + + def check + start if stdin.nil? + end + + # @returns Array<Version> + def whatinstalled(provides, version = nil, arch = nil) + with_helper do + hash = { "action" => "whatinstalled" } + hash["provides"] = provides + hash["version"] = version unless version.nil? + hash["arch" ] = arch unless arch.nil? + json = FFI_Yajl::Encoder.encode(hash) + puts json + stdin.syswrite json + "\n" + output = stdout.sysread(4096) + puts output + output.split.each_slice(3).map { |x| Version.new(*x) }.first + end + end + + # @returns Array<Version> + def whatavailable(provides, version = nil, arch = nil) + with_helper do + hash = { "action" => "whatavailable" } + hash["provides"] = provides + hash["version"] = version unless version.nil? + hash["arch" ] = arch unless arch.nil? + json = FFI_Yajl::Encoder.encode(hash) + puts json + stdin.syswrite json + "\n" + output = stdout.sysread(4096) + puts output + output.split.each_slice(3).map { |x| Version.new(*x) }.first + end + end + + def flushcache + restart # FIXME: make flushcache work + not leak memory + end + + def restart + reap + start + end + + def with_helper + max_retries ||= 5 + Timeout.timeout(60) do + check + yield + end + rescue EOFError, Errno::EPIPE, Timeout::Error, Errno::ESRCH => e + raise e unless ( max_retries -= 1 ) > 0 + restart + retry + end + end + + use_multipackage_api + + provides :package, platform_family: %w{rhel fedora} do + which("dnf") + end + + provides :dnf_package, os: "linux" + + def python_helper + @python_helper ||= PythonHelper.instance + end + + def load_current_resource + @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 candidate_version + package_name_array.map do |pkg| + available_version(pkg).version_with_arch + end + end + + def get_current_versions + package_name_array.map do |pkg| + installed_version(pkg).version_with_arch + end + end + + def install_package(names, versions) + resolved_names = names.map { |name| available_version(name).to_s } + dnf(new_resource.options, "-y install", resolved_names) + 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.map { |name| installed_version(name).to_s } + dnf(new_resource.options, "-y remove", resolved_names) + flushcache + end + + alias_method :purge_package, :remove_package + + action :flush_cache do + python_helper.flushcache + end + + private + + # @returns Array<Version> + def available_version(package_name) + @available_version ||= {} + @available_version[package_name] ||= python_helper.whatavailable(package_name, desired_name_versions[package_name], desired_name_archs[package_name]) + @available_version[package_name] + end + + # @returns Array<Version> + def installed_version(package_name) + @installed_version ||= {} + @installed_version[package_name] ||= python_helper.whatinstalled(package_name, desired_name_versions[package_name], desired_name_archs[package_name]) + @installed_version[package_name] + end + + def flushcache + python_helper.flushcache + end + + def dnf(*args) + shell_out_with_timeout!(a_to_s("dnf", *args)) + end + + end + end + end +end diff --git a/lib/chef/provider/package/dnf_helper.py b/lib/chef/provider/package/dnf_helper.py new file mode 100644 index 0000000000..6295df5892 --- /dev/null +++ b/lib/chef/provider/package/dnf_helper.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import sys +import dnf +import hawkey +import signal +import os +import json + +from pprint import pprint + +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 'version' in command: + q = q.filterm(version__glob=command['version']) + + if 'arch' in command: + q = q.filterm(arch__glob=command['arch']) + + # FIXME: if the filter already selected the other arch this will be busted? + q = q.filter(arch=[ 'noarch', hawkey.detect_arch() ]) + + pkgs = dnf.query.latest_limit_pkgs(q, 1) + + if not pkgs: + sys.stdout.write('{} nil nil\n'.format(command['provides'].split().pop(0))) + else: + pkg = pkgs.pop(0) + sys.stdout.write('{} {}:{}-{} {}\n'.format(pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch)) + +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: + 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_helper.rb b/lib/chef/provider/package/dnf_helper.rb new file mode 100755 index 0000000000..e225eb3bd1 --- /dev/null +++ b/lib/chef/provider/package/dnf_helper.rb @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby + +def whatprovides(package_name) + provides = `dnf repoquery -q --latest-limit 1 --whatprovides #{package_name}` + provides.each_line do |line| + if line =~ /^(\S+)\-(\S+)\-(\S+)\.(\S+)/ + STDOUT.syswrite "#{$1} #{$2}-#{$3}.#{$4}\n" + return nil + end + end + STDOUT.syswrite "#{package_name}\n" +end + +while line = STDIN.sysread(4096).chomp + args = line.split(/\s+/) + case args.shift + when "whatprovides" + whatprovides(*args) + else + raise "bad command" + 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..eb901d7f6a --- /dev/null +++ b/lib/chef/resource/dnf_package.rb @@ -0,0 +1,43 @@ +# +# Author:: AJ Christensen (<aj@chef.io>) +# Copyright:: Copyright 2008-2016, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/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 ] + + # FIXME: dnf install should downgrade, so this should warn that users do not need to use it any more? + property :allow_downgrade, [ true, false ], default: false + end + end +end diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb index 9d69897f5f..f8b670bd93 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 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" |