summaryrefslogtreecommitdiff
path: root/lib/chef
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2016-11-30 14:54:43 -0800
committerLamont Granquist <lamont@scriptkiddie.org>2016-12-13 13:31:50 -0800
commit626213cb2d6f1e4de18c6f29573e3aa34ed76cef (patch)
tree54a4eb2513feb465bac14ffeb93884a6095db264 /lib/chef
parent673f733fe5d469ba025393b06544fbcabeca0cc9 (diff)
downloadchef-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.rb14
-rw-r--r--lib/chef/provider/package.rb18
-rw-r--r--lib/chef/provider/package/dnf.rb220
-rw-r--r--lib/chef/provider/package/dnf_helper.py81
-rwxr-xr-xlib/chef/provider/package/dnf_helper.rb22
-rw-r--r--lib/chef/providers.rb1
-rw-r--r--lib/chef/resource/dnf_package.rb43
-rw-r--r--lib/chef/resource/yum_package.rb1
-rw-r--r--lib/chef/resources.rb1
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"