summaryrefslogtreecommitdiff
path: root/lib/chef/provider
diff options
context:
space:
mode:
authorNick McSpadden <nmcspadden@gmail.com>2018-05-14 23:33:45 -0700
committerNick McSpadden <nmcspadden@gmail.com>2018-05-14 23:33:45 -0700
commitc935ce85f591e53f2f200e55cb252185e1801387 (patch)
treedfeee6151c50d805138a3d10761a531c2ffbf984 /lib/chef/provider
parentc48fd22a51deb74a3d4bc3abca308043689b494a (diff)
parentc0609e449135fae43d436136a4f0fd3889a9b8f1 (diff)
downloadchef-c935ce85f591e53f2f200e55cb252185e1801387.tar.gz
Merge branch 'master' into mac_uid
Diffstat (limited to 'lib/chef/provider')
-rw-r--r--lib/chef/provider/apt_preference.rb94
-rw-r--r--lib/chef/provider/apt_repository.rb221
-rw-r--r--lib/chef/provider/apt_update.rb17
-rw-r--r--lib/chef/provider/batch.rb6
-rw-r--r--lib/chef/provider/breakpoint.rb38
-rw-r--r--lib/chef/provider/cookbook_file.rb12
-rw-r--r--lib/chef/provider/cookbook_file/content.rb2
-rw-r--r--lib/chef/provider/cron.rb109
-rw-r--r--lib/chef/provider/cron/unix.rb6
-rw-r--r--lib/chef/provider/deploy.rb476
-rw-r--r--lib/chef/provider/deploy/revision.rb109
-rw-r--r--lib/chef/provider/deploy/timestamped.rb34
-rw-r--r--lib/chef/provider/directory.rb72
-rw-r--r--lib/chef/provider/dsc_resource.rb42
-rw-r--r--lib/chef/provider/dsc_script.rb28
-rw-r--r--lib/chef/provider/env.rb169
-rw-r--r--lib/chef/provider/env/windows.rb72
-rw-r--r--lib/chef/provider/erl_call.rb108
-rw-r--r--lib/chef/provider/execute.rb32
-rw-r--r--lib/chef/provider/file.rb129
-rw-r--r--lib/chef/provider/git.rb193
-rw-r--r--lib/chef/provider/group.rb95
-rw-r--r--lib/chef/provider/group/aix.rb31
-rw-r--r--lib/chef/provider/group/dscl.rb93
-rw-r--r--lib/chef/provider/group/gpasswd.rb14
-rw-r--r--lib/chef/provider/group/groupadd.rb64
-rw-r--r--lib/chef/provider/group/groupmod.rb57
-rw-r--r--lib/chef/provider/group/pw.rb59
-rw-r--r--lib/chef/provider/group/suse.rb35
-rw-r--r--lib/chef/provider/group/usermod.rb21
-rw-r--r--lib/chef/provider/group/windows.rb38
-rw-r--r--lib/chef/provider/http_request.rb92
-rw-r--r--lib/chef/provider/ifconfig.rb217
-rw-r--r--lib/chef/provider/ifconfig/aix.rb53
-rw-r--r--lib/chef/provider/ifconfig/debian.rb47
-rw-r--r--lib/chef/provider/ifconfig/redhat.rb30
-rw-r--r--lib/chef/provider/launchd.rb31
-rw-r--r--lib/chef/provider/link.rb109
-rw-r--r--lib/chef/provider/log.rb26
-rw-r--r--lib/chef/provider/lwrp_base.rb14
-rw-r--r--lib/chef/provider/mdadm.rb58
-rw-r--r--lib/chef/provider/mount.rb42
-rw-r--r--lib/chef/provider/mount/aix.rb82
-rw-r--r--lib/chef/provider/mount/mount.rb58
-rw-r--r--lib/chef/provider/mount/solaris.rb47
-rw-r--r--lib/chef/provider/mount/windows.rb14
-rw-r--r--lib/chef/provider/noop.rb4
-rw-r--r--lib/chef/provider/ohai.rb12
-rw-r--r--lib/chef/provider/osx_profile.rb92
-rw-r--r--lib/chef/provider/package.rb328
-rw-r--r--lib/chef/provider/package/aix.rb136
-rw-r--r--lib/chef/provider/package/apt.rb256
-rw-r--r--lib/chef/provider/package/bff.rb142
-rw-r--r--lib/chef/provider/package/cab.rb183
-rw-r--r--lib/chef/provider/package/chocolatey.rb76
-rw-r--r--lib/chef/provider/package/dnf.rb196
-rw-r--r--lib/chef/provider/package/dnf/dnf_helper.py100
-rw-r--r--lib/chef/provider/package/dnf/python_helper.rb172
-rw-r--r--lib/chef/provider/package/dnf/version.rb56
-rw-r--r--lib/chef/provider/package/dpkg.rb55
-rw-r--r--lib/chef/provider/package/easy_install.rb135
-rw-r--r--lib/chef/provider/package/freebsd/base.rb20
-rw-r--r--lib/chef/provider/package/freebsd/pkg.rb30
-rw-r--r--lib/chef/provider/package/freebsd/pkgng.rb30
-rw-r--r--lib/chef/provider/package/freebsd/port.rb14
-rw-r--r--lib/chef/provider/package/homebrew.rb25
-rw-r--r--lib/chef/provider/package/ips.rb44
-rw-r--r--lib/chef/provider/package/macports.rb48
-rw-r--r--lib/chef/provider/package/msu.rb161
-rw-r--r--lib/chef/provider/package/openbsd.rb53
-rw-r--r--lib/chef/provider/package/pacman.rb35
-rw-r--r--lib/chef/provider/package/paludis.rb55
-rw-r--r--lib/chef/provider/package/portage.rb93
-rw-r--r--lib/chef/provider/package/powershell.rb129
-rw-r--r--lib/chef/provider/package/rpm.rb61
-rw-r--r--lib/chef/provider/package/rubygems.rb210
-rw-r--r--lib/chef/provider/package/smartos.rb36
-rw-r--r--lib/chef/provider/package/solaris.rb91
-rw-r--r--lib/chef/provider/package/windows.rb39
-rw-r--r--lib/chef/provider/package/windows/exe.rb30
-rw-r--r--lib/chef/provider/package/windows/msi.rb33
-rw-r--r--lib/chef/provider/package/windows/registry_uninstall_entry.rb34
-rw-r--r--lib/chef/provider/package/yum-dump.py307
-rw-r--r--lib/chef/provider/package/yum.rb1484
-rw-r--r--lib/chef/provider/package/yum/python_helper.rb221
-rw-r--r--lib/chef/provider/package/yum/rpm_utils.rb651
-rw-r--r--lib/chef/provider/package/yum/simplejson/LICENSE.txt79
-rw-r--r--lib/chef/provider/package/yum/simplejson/__init__.py318
-rw-r--r--lib/chef/provider/package/yum/simplejson/__init__.pycbin0 -> 12059 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/decoder.py354
-rw-r--r--lib/chef/provider/package/yum/simplejson/decoder.pycbin0 -> 11088 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/encoder.py440
-rw-r--r--lib/chef/provider/package/yum/simplejson/encoder.pycbin0 -> 13588 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/scanner.py65
-rw-r--r--lib/chef/provider/package/yum/simplejson/scanner.pycbin0 -> 2405 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/tool.py37
-rw-r--r--lib/chef/provider/package/yum/version.rb56
-rw-r--r--lib/chef/provider/package/yum/yum_cache.rb93
-rw-r--r--lib/chef/provider/package/yum/yum_helper.py210
-rw-r--r--lib/chef/provider/package/zypper.rb80
-rw-r--r--lib/chef/provider/powershell_script.rb26
-rw-r--r--lib/chef/provider/reboot.rb36
-rw-r--r--lib/chef/provider/registry_key.rb129
-rw-r--r--lib/chef/provider/remote_directory.rb26
-rw-r--r--lib/chef/provider/remote_file.rb31
-rw-r--r--lib/chef/provider/remote_file/cache_control_data.rb2
-rw-r--r--lib/chef/provider/remote_file/content.rb10
-rw-r--r--lib/chef/provider/remote_file/fetcher.rb3
-rw-r--r--lib/chef/provider/remote_file/ftp.rb4
-rw-r--r--lib/chef/provider/remote_file/http.rb18
-rw-r--r--lib/chef/provider/remote_file/local_file.rb2
-rw-r--r--lib/chef/provider/remote_file/network_file.rb23
-rw-r--r--lib/chef/provider/remote_file/sftp.rb4
-rw-r--r--lib/chef/provider/route.rb391
-rw-r--r--lib/chef/provider/ruby_block.rb12
-rw-r--r--lib/chef/provider/script.rb48
-rw-r--r--lib/chef/provider/service.rb109
-rw-r--r--lib/chef/provider/service/aix.rb12
-rw-r--r--lib/chef/provider/service/aixinit.rb4
-rw-r--r--lib/chef/provider/service/arch.rb4
-rw-r--r--lib/chef/provider/service/debian.rb65
-rw-r--r--lib/chef/provider/service/freebsd.rb9
-rw-r--r--lib/chef/provider/service/gentoo.rb9
-rw-r--r--lib/chef/provider/service/init.rb1
-rw-r--r--lib/chef/provider/service/insserv.rb8
-rw-r--r--lib/chef/provider/service/macosx.rb27
-rw-r--r--lib/chef/provider/service/openbsd.rb5
-rw-r--r--lib/chef/provider/service/redhat.rb8
-rw-r--r--lib/chef/provider/service/simple.rb26
-rw-r--r--lib/chef/provider/service/solaris.rb19
-rw-r--r--lib/chef/provider/service/systemd.rb37
-rw-r--r--lib/chef/provider/service/upstart.rb86
-rw-r--r--lib/chef/provider/service/windows.rb321
-rw-r--r--lib/chef/provider/subversion.rb86
-rw-r--r--lib/chef/provider/support/yum_repo.erb138
-rw-r--r--lib/chef/provider/support/zypper_repo.erb17
-rw-r--r--lib/chef/provider/systemd_unit.rb119
-rw-r--r--lib/chef/provider/template.rb12
-rw-r--r--lib/chef/provider/template/content.rb25
-rw-r--r--lib/chef/provider/template_finder.rb2
-rw-r--r--lib/chef/provider/user.rb157
-rw-r--r--lib/chef/provider/user/aix.rb54
-rw-r--r--lib/chef/provider/user/dscl.rb130
-rw-r--r--lib/chef/provider/user/linux.rb126
-rw-r--r--lib/chef/provider/user/pw.rb73
-rw-r--r--lib/chef/provider/user/solaris.rb58
-rw-r--r--lib/chef/provider/user/useradd.rb63
-rw-r--r--lib/chef/provider/user/windows.rb47
-rw-r--r--lib/chef/provider/whyrun_safe_ruby_block.rb8
-rw-r--r--lib/chef/provider/windows_env.rb207
-rw-r--r--lib/chef/provider/windows_path.rb61
-rw-r--r--lib/chef/provider/windows_script.rb7
-rw-r--r--lib/chef/provider/windows_task.rb587
-rw-r--r--lib/chef/provider/yum_repository.rb130
-rw-r--r--lib/chef/provider/zypper_repository.rb169
155 files changed, 9035 insertions, 5761 deletions
diff --git a/lib/chef/provider/apt_preference.rb b/lib/chef/provider/apt_preference.rb
new file mode 100644
index 0000000000..416a1c0d1d
--- /dev/null
+++ b/lib/chef/provider/apt_preference.rb
@@ -0,0 +1,94 @@
+#
+# Author:: Tim Smith (<tsmith@chef.io>)
+# Copyright:: 2016-2017, 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"
+require "chef/dsl/declare_resource"
+require "chef/provider/noop"
+require "chef/log"
+
+class Chef
+ class Provider
+ class AptPreference < Chef::Provider
+ provides :apt_preference, platform_family: "debian"
+
+ APT_PREFERENCE_DIR = "/etc/apt/preferences.d".freeze
+
+ def load_current_resource
+ end
+
+ action :add do
+ preference = build_pref(
+ new_resource.glob || new_resource.package_name,
+ new_resource.pin,
+ new_resource.pin_priority
+ )
+
+ declare_resource(:directory, APT_PREFERENCE_DIR) do
+ mode "0755"
+ action :create
+ end
+
+ sanitized_prefname = safe_name(new_resource.package_name)
+
+ # cleanup any existing pref files w/o the sanitized name (created by old apt cookbook)
+ if (sanitized_prefname != new_resource.package_name) && ::File.exist?("#{APT_PREFERENCE_DIR}/#{new_resource.package_name}.pref")
+ logger.warn "Replacing legacy #{new_resource.package_name}.pref with #{sanitized_prefname}.pref in #{APT_PREFERENCE_DIR}"
+ declare_resource(:file, "#{APT_PREFERENCE_DIR}/#{new_resource.package_name}.pref") do
+ action :delete
+ end
+ end
+
+ # cleanup any existing pref files without the .pref extension (created by old apt cookbook)
+ if ::File.exist?("#{APT_PREFERENCE_DIR}/#{new_resource.package_name}")
+ logger.warn "Replacing legacy #{new_resource.package_name} with #{sanitized_prefname}.pref in #{APT_PREFERENCE_DIR}"
+ declare_resource(:file, "#{APT_PREFERENCE_DIR}/#{new_resource.package_name}") do
+ action :delete
+ end
+ end
+
+ declare_resource(:file, "#{APT_PREFERENCE_DIR}/#{sanitized_prefname}.pref") do
+ mode "0644"
+ content preference
+ action :create
+ end
+ end
+
+ action :remove do
+ sanitized_prefname = safe_name(new_resource.package_name)
+
+ if ::File.exist?("#{APT_PREFERENCE_DIR}/#{sanitized_prefname}.pref")
+ logger.info "Un-pinning #{sanitized_prefname} from #{APT_PREFERENCE_DIR}"
+ declare_resource(:file, "#{APT_PREFERENCE_DIR}/#{sanitized_prefname}.pref") do
+ action :delete
+ end
+ end
+ end
+
+ # Build preferences.d file contents
+ def build_pref(package_name, pin, pin_priority)
+ "Package: #{package_name}\nPin: #{pin}\nPin-Priority: #{pin_priority}\n"
+ end
+
+ def safe_name(name)
+ name.tr(".", "_").gsub("*", "wildcard")
+ end
+ end
+ end
+end
+
+Chef::Provider::Noop.provides :apt_preference
diff --git a/lib/chef/provider/apt_repository.rb b/lib/chef/provider/apt_repository.rb
index 1e7db80620..973c10e94a 100644
--- a/lib/chef/provider/apt_repository.rb
+++ b/lib/chef/provider/apt_repository.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) 2016-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,27 +25,25 @@ require "chef/provider/noop"
class Chef
class Provider
class AptRepository < Chef::Provider
- use_inline_resources
-
include Chef::Mixin::ShellOut
- provides :apt_repository do
- uses_apt?
- end
+ provides :apt_repository, platform_family: "debian"
- def whyrun_supported?
- true
- end
+ LIST_APT_KEY_FINGERPRINTS = "apt-key adv --list-public-keys --with-fingerprint --with-colons".freeze
def load_current_resource
end
action :add do
- unless new_resource.key.nil?
- if is_key_id?(new_resource.key) && !has_cookbook_file?(new_resource.key)
- install_key_from_keyserver
- else
- install_key_from_uri
+ if new_resource.key.nil?
+ logger.debug "No 'key' property specified skipping key import"
+ else
+ new_resource.key.each do |k|
+ if is_key_id?(k) && !has_cookbook_file?(k)
+ install_key_from_keyserver(k)
+ else
+ install_key_from_uri(k)
+ end
end
end
@@ -59,22 +57,18 @@ class Chef
action :nothing
end
- components = if is_ppa_url?(new_resource.uri) && new_resource.components.empty?
- "main"
- else
- new_resource.components
- end
+ cleanup_legacy_file!
repo = build_repo(
new_resource.uri,
new_resource.distribution,
- components,
+ repo_components,
new_resource.trusted,
new_resource.arch,
new_resource.deb_src
)
- declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.name}.list") do
+ declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.repo_name}.list") do
owner "root"
group "root"
mode "0644"
@@ -87,9 +81,10 @@ class Chef
end
action :remove do
- if ::File.exist?("/etc/apt/sources.list.d/#{new_resource.name}.list")
- converge_by "Removing #{new_resource.name} repository from /etc/apt/sources.list.d/" do
- declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.name}.list") do
+ cleanup_legacy_file!
+ if ::File.exist?("/etc/apt/sources.list.d/#{new_resource.repo_name}.list")
+ converge_by "Removing #{new_resource.repo_name} repository from /etc/apt/sources.list.d/" do
+ declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.repo_name}.list") do
sensitive new_resource.sensitive
action :delete
notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild
@@ -99,94 +94,133 @@ class Chef
ignore_failure true
action :nothing
end
-
end
+ else
+ logger.trace("/etc/apt/sources.list.d/#{new_resource.repo_name}.list does not exist. Nothing to do")
end
end
- def self.uses_apt?
- ENV["PATH"] ||= ""
- paths = %w{ /bin /usr/bin /sbin /usr/sbin } + ENV["PATH"].split(::File::PATH_SEPARATOR)
- paths.any? { |path| ::File.executable?(::File.join(path, "apt-get")) }
- end
-
+ # is the provided ID a key ID from a keyserver. Looks at length and HEX only values
+ # @param [String] id the key value passed by the user that *may* be an ID
def is_key_id?(id)
id = id[2..-1] if id.start_with?("0x")
id =~ /^\h+$/ && [8, 16, 40].include?(id.length)
end
+ # run the specified command and extract the fingerprints from the output
+ # accepts a command so it can be used to extract both the current key's fingerprints
+ # and the fingerprint of the new key
+ # @param [String] cmd the command to run
+ #
+ # @return [Array] an array of fingerprints
def extract_fingerprints_from_cmd(cmd)
so = shell_out(cmd)
- so.run_command
so.stdout.split(/\n/).map do |t|
- if z = t.match(/^ +Key fingerprint = ([0-9A-F ]+)/)
+ if z = t.match(/^fpr:+([0-9A-F]+):/)
z[1].split.join
end
end.compact
end
- def key_is_valid?(cmd, key)
+ # validate the key against the apt keystore to see if that version is expired
+ # @param [String] key
+ #
+ # @return [Boolean] is the key valid or not
+ def key_is_valid?(key)
valid = true
- so = shell_out(cmd)
- so.run_command
+ so = shell_out("apt-key list")
so.stdout.split(/\n/).map do |t|
if t =~ %r{^\/#{key}.*\[expired: .*\]$}
- Chef::Log.debug "Found expired key: #{t}"
+ logger.debug "Found expired key: #{t}"
valid = false
break
end
end
- Chef::Log.debug "key #{key} #{valid ? "is valid" : "is not valid"}"
+ logger.debug "key #{key} #{valid ? "is valid" : "is not valid"}"
valid
end
+ # return the specified cookbook name or the cookbook containing the
+ # resource.
+ #
+ # @return [String] name of the cookbook
def cookbook_name
new_resource.cookbook || new_resource.cookbook_name
end
+ # determine if a cookbook file is available in the run
+ # @param [String] fn the path to the cookbook file
+ #
+ # @return [Boolean] cookbook file exists or doesn't
def has_cookbook_file?(fn)
run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn)
end
+ # determine if there are any new keys by comparing the fingerprints of installed
+ # keys to those of the passed file
+ # @param [String] file the keyfile of the new repository
+ #
+ # @return [Boolean] true: no new keys in the file. false: there are new keys
def no_new_keys?(file)
- installed_keys = extract_fingerprints_from_cmd("apt-key finger")
- proposed_keys = extract_fingerprints_from_cmd("gpg --with-fingerprint #{file}")
+ # Now we are using the option --with-colons that works across old os versions
+ # as well as the latest (16.10). This for both `apt-key` and `gpg` commands
+ installed_keys = extract_fingerprints_from_cmd(LIST_APT_KEY_FINGERPRINTS)
+ proposed_keys = extract_fingerprints_from_cmd("gpg --with-fingerprint --with-colons #{file}")
(installed_keys & proposed_keys).sort == proposed_keys.sort
end
- def install_key_from_uri
- key_name = new_resource.key.split(%r{\/}).last
+ # Given the provided key URI determine what kind of chef resource we need
+ # to fetch the key
+ # @param [String] uri the uri of the gpg key (local path or http URL)
+ #
+ # @raise [Chef::Exceptions::FileNotFound] Key isn't remote or found in the current run
+ #
+ # @return [Symbol] :remote_file or :cookbook_file
+ def key_type(uri)
+ if uri.start_with?("http")
+ :remote_file
+ elsif has_cookbook_file?(uri)
+ :cookbook_file
+ else
+ raise Chef::Exceptions::FileNotFound, "Cannot locate key file: #{uri}"
+ end
+ end
+
+ # Fetch the key using either cookbook_file or remote_file, validate it,
+ # and install it with apt-key add
+ # @param [String] key the key to install
+ #
+ # @raise [RuntimeError] Invalid key which can't verify the apt repository
+ #
+ # @return [void]
+ def install_key_from_uri(key)
+ key_name = key.gsub(/[^0-9A-Za-z\-]/, "_")
cached_keyfile = ::File.join(Chef::Config[:file_cache_path], key_name)
- type = if new_resource.key.start_with?("http")
- :remote_file
- elsif has_cookbook_file?(new_resource.key)
- :cookbook_file
- else
- raise Chef::Exceptions::FileNotFound, "Cannot locate key file"
- end
- declare_resource(type, cached_keyfile) do
- source new_resource.key
+ declare_resource(key_type(key), cached_keyfile) do
+ source key
mode "0644"
sensitive new_resource.sensitive
action :create
+ verify "gpg %{path}"
end
- raise "The key #{cached_keyfile} is invalid and cannot be used to verify an apt repository." unless key_is_valid?("gpg #{cached_keyfile}", "")
-
declare_resource(:execute, "apt-key add #{cached_keyfile}") do
sensitive new_resource.sensitive
action :run
- not_if do
- no_new_keys?(cached_keyfile)
- end
+ not_if { no_new_keys?(cached_keyfile) }
notifies :run, "execute[apt-cache gencaches]", :immediately
end
end
- def install_key_from_keyserver(key = new_resource.key, keyserver = new_resource.keyserver)
+ # build the apt-key command to install the keyserver
+ # @param [String] key the key to install
+ # @param [String] keyserver the key server to use
+ #
+ # @return [String] the full apt-key command to run
+ def keyserver_install_cmd(key, keyserver)
cmd = "apt-key adv --recv"
cmd << " --keyserver-options http-proxy=#{new_resource.key_proxy}" if new_resource.key_proxy
cmd << " --keyserver "
@@ -197,22 +231,37 @@ class Chef
end
cmd << " #{key}"
+ cmd
+ end
+ # @param [String] key
+ # @param [String] keyserver
+ #
+ # @raise [RuntimeError] Invalid key which can't verify the apt repository
+ #
+ # @return [void]
+ def install_key_from_keyserver(key, keyserver = new_resource.keyserver)
declare_resource(:execute, "install-key #{key}") do
- command cmd
+ command keyserver_install_cmd(key, keyserver)
sensitive new_resource.sensitive
not_if do
- present = extract_fingerprints_from_cmd("apt-key finger").any? do |fp|
+ present = extract_fingerprints_from_cmd(LIST_APT_KEY_FINGERPRINTS).any? do |fp|
fp.end_with? key.upcase
end
- present && key_is_valid?("apt-key list", key.upcase)
+ present && key_is_valid?(key.upcase)
end
notifies :run, "execute[apt-cache gencaches]", :immediately
end
- raise "The key #{key} is invalid and cannot be used to verify an apt repository." unless key_is_valid?("apt-key list", key.upcase)
+ raise "The key #{key} is invalid and cannot be used to verify an apt repository." unless key_is_valid?(key.upcase)
end
+ # @param [String] owner
+ # @param [String] repo
+ #
+ # @raise [RuntimeError] Could not access the Launchpad PPA API
+ #
+ # @return [void]
def install_ppa_key(owner, repo)
url = "https://launchpad.net/api/1.0/~#{owner}/+archive/#{repo}"
key_id = Chef::HTTP::Simple.new(url).get("signing_key_fingerprint").delete('"')
@@ -221,12 +270,33 @@ class Chef
raise "Could not access Launchpad ppa API: #{e.message}"
end
+ # determine if the repository URL is a PPA
+ # @param [String] url the url of the repository
+ #
+ # @return [Boolean] is the repo URL a PPA
def is_ppa_url?(url)
url.start_with?("ppa:")
end
+ # determine the repository's components:
+ # - "components" property if defined
+ # - "main" if "components" not defined and the repo is a PPA URL
+ # - otherwise nothing
+ #
+ # @return [String] the repository component
+ def repo_components
+ if is_ppa_url?(new_resource.uri) && new_resource.components.empty?
+ "main"
+ else
+ new_resource.components
+ end
+ end
+
+ # given a PPA return a PPA URL in http://ppa.launchpad.net format
+ # @param [String] ppa the ppa URL
+ #
+ # @return [String] full PPA URL
def make_ppa_url(ppa)
- return unless is_ppa_url?(ppa)
owner, repo = ppa[4..-1].split("/")
repo ||= "ppa"
@@ -234,6 +304,14 @@ class Chef
"http://ppa.launchpad.net/#{owner}/#{repo}/ubuntu"
end
+ # build complete repo text that will be written to the config
+ # @param [String] uri
+ # @param [Array] components
+ # @param [Boolean] trusted
+ # @param [String] arch
+ # @param [Boolean] add_src
+ #
+ # @return [String] complete repo config text
def build_repo(uri, distribution, components, trusted, arch, add_src = false)
uri = make_ppa_url(uri) if is_ppa_url?(uri)
@@ -250,8 +328,25 @@ class Chef
repo << "deb-src #{info}\n" if add_src
repo
end
+
+ # clean up a potentially legacy file from before we fixed the usage of
+ # new_resource.name vs. new_resource.repo_name. We might have the
+ # name.list file hanging around and need to clean it up.
+ #
+ # @return [void]
+ def cleanup_legacy_file!
+ legacy_path = "/etc/apt/sources.list.d/#{new_resource.name}.list"
+ if new_resource.name != new_resource.repo_name && ::File.exist?(legacy_path)
+ converge_by "Cleaning up legacy #{legacy_path} repo file" do
+ declare_resource(:file, legacy_path) do
+ action :delete
+ # Not triggering an update since it isn't super likely to be needed.
+ end
+ end
+ end
+ end
end
end
end
-Chef::Provider::Noop.provides :apt_resource
+Chef::Provider::Noop.provides :apt_repository
diff --git a/lib/chef/provider/apt_update.rb b/lib/chef/provider/apt_update.rb
index d2dd5cfb14..9d794abcf0 100644
--- a/lib/chef/provider/apt_update.rb
+++ b/lib/chef/provider/apt_update.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) 2016-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,18 @@
# limitations under the License.
#
-require "chef/resource"
+require "chef/provider"
+require "chef/provider/noop"
require "chef/dsl/declare_resource"
class Chef
class Provider
class AptUpdate < Chef::Provider
- use_inline_resources
-
- provides :apt_update, os: "linux"
+ provides :apt_update, platform_family: "debian"
APT_CONF_DIR = "/etc/apt/apt.conf.d"
STAMP_DIR = "/var/lib/apt/periodic"
- def whyrun_supported?
- true
- end
-
def load_current_resource
end
@@ -68,7 +63,7 @@ class Chef
end
declare_resource(:file, "#{APT_CONF_DIR}/15update-stamp") do
- content "APT::Update::Post-Invoke-Success {\"touch #{STAMP_DIR}/update-success-stamp 2>/dev/null || true\";};"
+ content "APT::Update::Post-Invoke-Success {\"touch #{STAMP_DIR}/update-success-stamp 2>/dev/null || true\";};\n"
action :create_if_missing
end
@@ -78,3 +73,5 @@ class Chef
end
end
end
+
+Chef::Provider::Noop.provides :apt_update
diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb
index bb294afd3f..dae1513a51 100644
--- a/lib/chef/provider/batch.rb
+++ b/lib/chef/provider/batch.rb
@@ -22,14 +22,14 @@ class Chef
class Provider
class Batch < Chef::Provider::WindowsScript
- provides :batch, os: "windows"
+ provides :batch
def initialize(new_resource, run_context)
super(new_resource, run_context, ".bat")
end
def command
- basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+ basepath = is_forced_32bit ? wow64_directory : run_context.node["kernel"]["os_info"]["system_directory"]
interpreter_path = Chef::Util::PathHelper.join(basepath, interpreter)
@@ -37,7 +37,7 @@ class Chef
end
def flags
- @new_resource.flags.nil? ? "/c" : new_resource.flags + " /c"
+ new_resource.flags.nil? ? "/c" : new_resource.flags + " /c"
end
end
diff --git a/lib/chef/provider/breakpoint.rb b/lib/chef/provider/breakpoint.rb
deleted file mode 100644
index a71c9e317d..0000000000
--- a/lib/chef/provider/breakpoint.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# 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.
-#
-
-class Chef
- class Provider
- class Breakpoint < Chef::Provider
-
- provides :breakpoint
-
- def load_current_resource
- end
-
- def action_break
- if defined?(Shell) && Shell.running?
- run_context.resource_collection.iterator.pause
- @new_resource.updated_by_last_action(true)
- run_context.resource_collection.iterator
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/provider/cookbook_file.rb b/lib/chef/provider/cookbook_file.rb
index 3ca0bbd47a..92383fd5fa 100644
--- a/lib/chef/provider/cookbook_file.rb
+++ b/lib/chef/provider/cookbook_file.rb
@@ -17,8 +17,6 @@
#
require "chef/provider/file"
-require "chef/deprecation/provider/cookbook_file"
-require "chef/deprecation/warnings"
class Chef
class Provider
@@ -26,25 +24,21 @@ class Chef
provides :cookbook_file
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::CookbookFile
- add_deprecation_warnings_for(Chef::Deprecation::Provider::CookbookFile.instance_methods)
-
def initialize(new_resource, run_context)
@content_class = Chef::Provider::CookbookFile::Content
super
end
def load_current_resource
- @current_resource = Chef::Resource::CookbookFile.new(@new_resource.name)
+ @current_resource = Chef::Resource::CookbookFile.new(new_resource.name)
super
end
private
def managing_content?
- return true if @new_resource.checksum
- return true if !@new_resource.source.nil? && @action != :create_if_missing
+ return true if new_resource.checksum
+ return true if !new_resource.source.nil? && @action != :create_if_missing
false
end
diff --git a/lib/chef/provider/cookbook_file/content.rb b/lib/chef/provider/cookbook_file/content.rb
index 1d24dee3e7..82e2cf4390 100644
--- a/lib/chef/provider/cookbook_file/content.rb
+++ b/lib/chef/provider/cookbook_file/content.rb
@@ -34,7 +34,7 @@ class Chef
else
tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
tempfile.close
- Chef::Log.debug("#{@new_resource} staging #{file_cache_location} to #{tempfile.path}")
+ logger.trace("#{@new_resource} staging #{file_cache_location} to #{tempfile.path}")
FileUtils.cp(file_cache_location, tempfile.path)
tempfile
end
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index 36b67ab6a5..70edd89636 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -17,13 +17,11 @@
#
require "chef/log"
-require "chef/mixin/command"
require "chef/provider"
class Chef
class Provider
class Cron < Chef::Provider
- include Chef::Mixin::Command
provides :cron, os: ["!aix", "!solaris2"]
@@ -42,21 +40,17 @@ class Chef
end
attr_accessor :cron_exists, :cron_empty
- def whyrun_supported?
- true
- end
-
def load_current_resource
crontab_lines = []
- @current_resource = Chef::Resource::Cron.new(@new_resource.name)
- @current_resource.user(@new_resource.user)
+ @current_resource = Chef::Resource::Cron.new(new_resource.name)
+ current_resource.user(new_resource.user)
@cron_exists = false
if crontab = read_crontab
cron_found = false
crontab.each_line do |line|
case line.chomp
- when "# Chef Name: #{@new_resource.name}"
- Chef::Log.debug("Found cron '#{@new_resource.name}'")
+ when "# Chef Name: #{new_resource.name}"
+ logger.trace("Found cron '#{new_resource.name}'")
cron_found = true
@cron_exists = true
next
@@ -65,18 +59,18 @@ class Chef
next
when SPECIAL_PATTERN
if cron_found
- @current_resource.time($2.to_sym)
- @current_resource.command($3)
+ current_resource.time($2.to_sym)
+ current_resource.command($3)
cron_found = false
end
when CRON_PATTERN
if cron_found
- @current_resource.minute($1)
- @current_resource.hour($2)
- @current_resource.day($3)
- @current_resource.month($4)
- @current_resource.weekday($5)
- @current_resource.command($6)
+ current_resource.minute($1)
+ current_resource.hour($2)
+ current_resource.day($3)
+ current_resource.month($4)
+ current_resource.weekday($5)
+ current_resource.command($6)
cron_found = false
end
next
@@ -85,36 +79,36 @@ class Chef
next
end
end
- Chef::Log.debug("Cron '#{@new_resource.name}' not found") unless @cron_exists
+ logger.trace("Cron '#{new_resource.name}' not found") unless @cron_exists
else
- Chef::Log.debug("Cron empty for '#{@new_resource.user}'")
+ logger.trace("Cron empty for '#{new_resource.user}'")
@cron_empty = true
end
- @current_resource
+ current_resource
end
def cron_different?
CRON_ATTRIBUTES.any? do |cron_var|
- @new_resource.send(cron_var) != @current_resource.send(cron_var)
+ new_resource.send(cron_var) != current_resource.send(cron_var)
end
end
def action_create
- crontab = String.new
- newcron = String.new
+ crontab = ""
+ newcron = ""
cron_found = false
newcron = get_crontab_entry
if @cron_exists
unless cron_different?
- Chef::Log.debug("Skipping existing cron entry '#{@new_resource.name}'")
+ logger.trace("Skipping existing cron entry '#{new_resource.name}'")
return
end
read_crontab.each_line do |line|
case line.chomp
- when "# Chef Name: #{@new_resource.name}"
+ when "# Chef Name: #{new_resource.name}"
cron_found = true
next
when ENV_PATTERN
@@ -144,29 +138,29 @@ class Chef
# Handle edge case where the Chef comment is the last line in the current crontab
crontab << newcron if cron_found
- converge_by("update crontab entry for #{@new_resource}") do
+ converge_by("update crontab entry for #{new_resource}") do
write_crontab crontab
- Chef::Log.info("#{@new_resource} updated crontab entry")
+ logger.info("#{new_resource} updated crontab entry")
end
else
crontab = read_crontab unless @cron_empty
crontab << newcron
- converge_by("add crontab entry for #{@new_resource}") do
+ converge_by("add crontab entry for #{new_resource}") do
write_crontab crontab
- Chef::Log.info("#{@new_resource} added crontab entry")
+ logger.info("#{new_resource} added crontab entry")
end
end
end
def action_delete
if @cron_exists
- crontab = String.new
+ crontab = ""
cron_found = false
read_crontab.each_line do |line|
case line.chomp
- when "# Chef Name: #{@new_resource.name}"
+ when "# Chef Name: #{new_resource.name}"
cron_found = true
next
when ENV_PATTERN
@@ -187,11 +181,10 @@ class Chef
end
crontab << line
end
- description = cron_found ? "remove #{@new_resource.name} from crontab" :
- "save unmodified crontab"
+ description = cron_found ? "remove #{new_resource.name} from crontab" : "save unmodified crontab"
converge_by(description) do
write_crontab crontab
- Chef::Log.info("#{@new_resource} deleted crontab entry")
+ logger.info("#{new_resource} deleted crontab entry")
end
end
end
@@ -200,60 +193,48 @@ class Chef
def set_environment_var(attr_name, attr_value)
if %w{MAILTO PATH SHELL HOME}.include?(attr_name)
- @current_resource.send(attr_name.downcase.to_sym, attr_value)
+ current_resource.send(attr_name.downcase.to_sym, attr_value.gsub(/^"|"$/, ""))
else
- @current_resource.environment(@current_resource.environment.merge(attr_name => attr_value))
+ current_resource.environment(current_resource.environment.merge(attr_name => attr_value))
end
end
def read_crontab
- crontab = nil
- status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr|
- crontab = stdout.read
- end
- if status.exitstatus > 1
- raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}"
- end
- crontab
+ so = shell_out!("crontab -l -u #{new_resource.user}", returns: [0, 1])
+ return nil if so.exitstatus == 1
+ so.stdout
+ rescue => e
+ raise Chef::Exceptions::Cron, "Error determining state of #{new_resource.name}, error: #{e}"
end
def write_crontab(crontab)
write_exception = false
- status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr|
- begin
- stdin.write crontab
- rescue Errno::EPIPE => e
- # popen4 could yield while child has already died.
- write_exception = true
- Chef::Log.debug("#{e.message}")
- end
- end
- if status.exitstatus > 0 || write_exception
- raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}"
- end
+ so = shell_out!("crontab -u #{new_resource.user} -", input: crontab)
+ rescue => e
+ raise Chef::Exceptions::Cron, "Error updating state of #{new_resource.name}, error: #{e}"
end
def get_crontab_entry
newcron = ""
newcron << "# Chef Name: #{new_resource.name}\n"
[ :mailto, :path, :shell, :home ].each do |v|
- newcron << "#{v.to_s.upcase}=#{@new_resource.send(v)}\n" if @new_resource.send(v)
+ newcron << "#{v.to_s.upcase}=\"#{new_resource.send(v)}\"\n" if new_resource.send(v)
end
- @new_resource.environment.each do |name, value|
+ new_resource.environment.each do |name, value|
newcron << "#{name}=#{value}\n"
end
- if @new_resource.time
- newcron << "@#{@new_resource.time} #{@new_resource.command}\n"
+ if new_resource.time
+ newcron << "@#{new_resource.time} #{new_resource.command}\n"
else
- newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n"
+ newcron << "#{new_resource.minute} #{new_resource.hour} #{new_resource.day} #{new_resource.month} #{new_resource.weekday} #{new_resource.command}\n"
end
newcron
end
def weekday_in_crontab
- weekday_in_crontab = WEEKDAY_SYMBOLS.index(@new_resource.weekday)
+ weekday_in_crontab = WEEKDAY_SYMBOLS.index(new_resource.weekday)
if weekday_in_crontab.nil?
- @new_resource.weekday
+ new_resource.weekday
else
weekday_in_crontab.to_s
end
diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb
index 108e73c9d3..15195dbb88 100644
--- a/lib/chef/provider/cron/unix.rb
+++ b/lib/chef/provider/cron/unix.rb
@@ -36,7 +36,7 @@ class Chef
crontab = shell_out("/usr/bin/crontab -l", :user => @new_resource.user)
status = crontab.status.exitstatus
- Chef::Log.debug crontab.format_for_exception if status > 0
+ logger.trace crontab.format_for_exception if status > 0
if status > 1
raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status}"
@@ -62,12 +62,12 @@ class Chef
exit_status = 1
end
rescue Chef::Exceptions::Exec => e
- Chef::Log.debug(e.message)
+ logger.trace(e.message)
exit_status = 1
error_message = e.message
rescue ArgumentError => e
# usually raised on invalid user.
- Chef::Log.debug(e.message)
+ logger.trace(e.message)
exit_status = 1
error_message = e.message
end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
deleted file mode 100644
index 4c758033ac..0000000000
--- a/lib/chef/provider/deploy.rb
+++ /dev/null
@@ -1,476 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# 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/mixin/command"
-require "chef/mixin/from_file"
-require "chef/provider/git"
-require "chef/provider/subversion"
-require "chef/dsl/recipe"
-require "chef/util/path_helper"
-
-class Chef
- class Provider
- class Deploy < Chef::Provider
-
- include Chef::DSL::Recipe
- include Chef::Mixin::FromFile
- include Chef::Mixin::Command
-
- attr_reader :scm_provider, :release_path, :shared_path, :previous_release_path
-
- def initialize(new_resource, run_context)
- super(new_resource, run_context)
-
- # will resolve to either git or svn based on resource attributes,
- # and will create a resource corresponding to that provider
- @scm_provider = new_resource.scm_provider.new(new_resource, run_context)
-
- # @configuration is not used by Deploy, it is only for backwards compat with
- # chef-deploy or capistrano hooks that might use it to get environment information
- @configuration = @new_resource.to_hash
- @configuration[:environment] = @configuration[:environment] && @configuration[:environment]["RAILS_ENV"]
- end
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- @scm_provider.load_current_resource
- @release_path = @new_resource.deploy_to + "/releases/#{release_slug}"
- @shared_path = @new_resource.shared_path
- end
-
- def sudo(command, &block)
- execute(command, &block)
- end
-
- def run(command, &block)
- exec = execute(command, &block)
- exec.user(@new_resource.user) if @new_resource.user
- exec.group(@new_resource.group) if @new_resource.group
- exec.cwd(release_path) unless exec.cwd
- exec.environment(@new_resource.environment) unless exec.environment
- converge_by("execute #{command}") do
- exec
- end
- end
-
- def define_resource_requirements
- requirements.assert(:rollback) do |a|
- a.assertion { all_releases[-2] }
- a.failure_message(RuntimeError, "There is no release to rollback to!")
- #There is no reason to assume 2 deployments in a single chef run, hence fails in whyrun.
- end
-
- [ @new_resource.before_migrate, @new_resource.before_symlink,
- @new_resource.before_restart, @new_resource.after_restart ].each do |script|
- requirements.assert(:deploy, :force_deploy) do |a|
- callback_file = "#{release_path}/#{script}"
- a.assertion do
- if script && script.class == String
- ::File.exist?(callback_file)
- else
- true
- end
- end
- a.failure_message(RuntimeError, "Can't find your callback file #{callback_file}")
- a.whyrun("Would assume callback file #{callback_file} included in release")
- end
- end
- end
-
- def action_deploy
- save_release_state
- if deployed?(release_path )
- if current_release?(release_path )
- Chef::Log.debug("#{@new_resource} is the latest version")
- else
- rollback_to release_path
- end
- else
-
- with_rollback_on_error do
- deploy
- end
- end
- end
-
- def action_force_deploy
- if deployed?(release_path)
- converge_by("delete deployed app at #{release_path} prior to force-deploy") do
- Chef::Log.info("Already deployed app at #{release_path}, forcing.")
- FileUtils.rm_rf(release_path)
- Chef::Log.info("#{@new_resource} forcing deploy of already deployed app at #{release_path}")
- end
- end
-
- # Alternatives:
- # * Move release_path directory before deploy and move it back when error occurs
- # * Rollback to previous commit
- # * Do nothing - because deploy is force, it will be retried in short time
- # Because last is simplest, keep it
- deploy
- end
-
- def action_rollback
- rollback_to all_releases[-2]
- end
-
- def rollback_to(target_release_path)
- @release_path = target_release_path
-
- rp_index = all_releases.index(release_path)
- releases_to_nuke = all_releases[(rp_index + 1)..-1]
-
- rollback
-
- releases_to_nuke.each do |i|
- converge_by("roll back by removing release #{i}") do
- Chef::Log.info "#{@new_resource} removing release: #{i}"
- FileUtils.rm_rf i
- end
- release_deleted(i)
- end
- end
-
- def deploy
- verify_directories_exist
- update_cached_repo # no converge-by - scm provider will dothis
- enforce_ownership
- copy_cached_repo
- install_gems
- enforce_ownership
- callback(:before_migrate, @new_resource.before_migrate)
- migrate
- callback(:before_symlink, @new_resource.before_symlink)
- symlink
- callback(:before_restart, @new_resource.before_restart)
- restart
- callback(:after_restart, @new_resource.after_restart)
- cleanup!
- Chef::Log.info "#{@new_resource} deployed to #{@new_resource.deploy_to}"
- end
-
- def rollback
- Chef::Log.info "#{@new_resource} rolling back to previous release #{release_path}"
- symlink
- Chef::Log.info "#{@new_resource} restarting with previous release"
- restart
- end
-
- def callback(what, callback_code = nil)
- @collection = Chef::ResourceCollection.new
- case callback_code
- when Proc
- Chef::Log.info "#{@new_resource} running callback #{what}"
- recipe_eval(&callback_code)
- when String
- run_callback_from_file("#{release_path}/#{callback_code}")
- when nil
- run_callback_from_file("#{release_path}/deploy/#{what}.rb")
- end
- end
-
- def migrate
- run_symlinks_before_migrate
-
- if @new_resource.migrate
- enforce_ownership
-
- environment = @new_resource.environment
- env_info = environment && environment.map do |key_and_val|
- "#{key_and_val.first}='#{key_and_val.last}'"
- end.join(" ")
-
- converge_by("execute migration command #{@new_resource.migration_command}") do
- Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
- shell_out!(@new_resource.migration_command, run_options(:cwd => release_path, :log_level => :info))
- end
- end
- end
-
- def symlink
- purge_tempfiles_from_current_release
- link_tempfiles_to_current_release
- link_current_release_to_production
- Chef::Log.info "#{@new_resource} updated symlinks"
- end
-
- def restart
- if restart_cmd = @new_resource.restart_command
- if restart_cmd.kind_of?(Proc)
- Chef::Log.info("#{@new_resource} restarting app with embedded recipe")
- recipe_eval(&restart_cmd)
- else
- converge_by("restart app using command #{@new_resource.restart_command}") do
- Chef::Log.info("#{@new_resource} restarting app")
- shell_out!(@new_resource.restart_command, run_options(:cwd => @new_resource.current_path))
- end
- end
- end
- end
-
- def cleanup!
- converge_by("update release history data") do
- release_created(release_path)
- end
-
- chop = -1 - @new_resource.keep_releases
- all_releases[0..chop].each do |old_release|
- converge_by("remove old release #{old_release}") do
- Chef::Log.info "#{@new_resource} removing old release #{old_release}"
- FileUtils.rm_rf(old_release)
- end
- release_deleted(old_release)
- end
- end
-
- def all_releases
- Dir.glob(Chef::Util::PathHelper.escape_glob_dir(@new_resource.deploy_to) + "/releases/*").sort
- end
-
- def update_cached_repo
- if @new_resource.svn_force_export
- # TODO assertion, non-recoverable - @scm_provider must be svn if force_export?
- svn_force_export
- else
- run_scm_sync
- end
- end
-
- def run_scm_sync
- @scm_provider.run_action(:sync)
- end
-
- def svn_force_export
- Chef::Log.info "#{@new_resource} exporting source repository"
- @scm_provider.run_action(:force_export)
- end
-
- def copy_cached_repo
- target_dir_path = @new_resource.deploy_to + "/releases"
- converge_by("deploy from repo to #{target_dir_path} ") do
- FileUtils.rm_rf(release_path) if ::File.exist?(release_path)
- FileUtils.mkdir_p(target_dir_path)
- FileUtils.cp_r(::File.join(@new_resource.destination, "."), release_path, :preserve => true)
- Chef::Log.info "#{@new_resource} copied the cached checkout to #{release_path}"
- end
- end
-
- def enforce_ownership
- converge_by("force ownership of #{@new_resource.deploy_to} to #{@new_resource.group}:#{@new_resource.user}") do
- FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to, :force => true)
- Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user
- Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group
- end
- end
-
- def verify_directories_exist
- create_dir_unless_exists(@new_resource.deploy_to)
- create_dir_unless_exists(@new_resource.shared_path)
- end
-
- def link_current_release_to_production
- converge_by(["remove existing link at #{@new_resource.current_path}",
- "link release #{release_path} into production at #{@new_resource.current_path}"]) do
- FileUtils.rm_f(@new_resource.current_path)
- begin
- FileUtils.ln_sf(release_path, @new_resource.current_path)
- rescue => e
- raise Chef::Exceptions::FileNotFound.new("Cannot symlink current release to production: #{e.message}")
- end
- Chef::Log.info "#{@new_resource} linked release #{release_path} into production at #{@new_resource.current_path}"
- end
- enforce_ownership
- end
-
- def run_symlinks_before_migrate
- links_info = @new_resource.symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ")
- converge_by("make pre-migration symlinks: #{links_info}") do
- @new_resource.symlink_before_migrate.each do |src, dest|
- begin
- FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}")
- rescue => e
- raise Chef::Exceptions::FileNotFound.new("Cannot symlink #{@new_resource.shared_path}/#{src} to #{release_path}/#{dest} before migrate: #{e.message}")
- end
- end
- Chef::Log.info "#{@new_resource} made pre-migration symlinks"
- end
- end
-
- def link_tempfiles_to_current_release
- dirs_info = @new_resource.create_dirs_before_symlink.join(",")
- @new_resource.create_dirs_before_symlink.each do |dir|
- create_dir_unless_exists(release_path + "/#{dir}")
- end
- Chef::Log.info("#{@new_resource} created directories before symlinking: #{dirs_info}")
-
- links_info = @new_resource.symlinks.map { |src, dst| "#{src} => #{dst}" }.join(", ")
- converge_by("link shared paths into current release: #{links_info}") do
- @new_resource.symlinks.each do |src, dest|
- begin
- FileUtils.ln_sf(::File.join(@new_resource.shared_path, src), ::File.join(release_path, dest))
- rescue => e
- raise Chef::Exceptions::FileNotFound.new("Cannot symlink shared data #{::File.join(@new_resource.shared_path, src)} to #{::File.join(release_path, dest)}: #{e.message}")
- end
- end
- Chef::Log.info("#{@new_resource} linked shared paths into current release: #{links_info}")
- end
- run_symlinks_before_migrate
- enforce_ownership
- end
-
- def create_dirs_before_symlink
- end
-
- def purge_tempfiles_from_current_release
- log_info = @new_resource.purge_before_symlink.join(", ")
- converge_by("purge directories in checkout #{log_info}") do
- @new_resource.purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") }
- Chef::Log.info("#{@new_resource} purged directories in checkout #{log_info}")
- end
- end
-
- protected
-
- # Internal callback, called after copy_cached_repo.
- # Override if you need to keep state externally.
- # Note that YOU are responsible for implementing whyrun-friendly behavior
- # in any actions you take in this callback.
- def release_created(release_path)
- end
-
- # Note that YOU are responsible for using appropriate whyrun nomenclature
- # Override if you need to keep state externally.
- # Note that YOU are responsible for implementing whyrun-friendly behavior
- # in any actions you take in this callback.
- def release_deleted(release_path)
- end
-
- def release_slug
- raise Chef::Exceptions::Override, "You must override release_slug in #{self}"
- end
-
- def install_gems
- gem_resource_collection_runner.converge
- end
-
- def gem_resource_collection_runner
- child_context = run_context.create_child
- gem_packages.each { |rbgem| child_context.resource_collection.insert(rbgem) }
- Chef::Runner.new(child_context)
- end
-
- def gem_packages
- return [] unless ::File.exist?("#{release_path}/gems.yml")
- gems = YAML.load(IO.read("#{release_path}/gems.yml"))
-
- gems.map do |g|
- r = Chef::Resource::GemPackage.new(g[:name], run_context)
- r.version g[:version]
- r.action :install
- r.source "http://gems.github.com"
- r
- end
- end
-
- def run_options(run_opts = {})
- run_opts[:user] = @new_resource.user if @new_resource.user
- run_opts[:group] = @new_resource.group if @new_resource.group
- run_opts[:environment] = @new_resource.environment if @new_resource.environment
- run_opts[:log_tag] = @new_resource.to_s
- run_opts[:log_level] ||= :debug
- if run_opts[:log_level] == :info
- if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info?
- run_opts[:live_stream] = STDOUT
- end
- end
- run_opts
- end
-
- def run_callback_from_file(callback_file)
- Chef::Log.info "#{@new_resource} queueing checkdeploy hook #{callback_file}"
- recipe_eval do
- Dir.chdir(release_path) do
- from_file(callback_file) if ::File.exist?(callback_file)
- end
- end
- end
-
- def create_dir_unless_exists(dir)
- if ::File.directory?(dir)
- Chef::Log.debug "#{@new_resource} not creating #{dir} because it already exists"
- return false
- end
- converge_by("create new directory #{dir}") do
- begin
- FileUtils.mkdir_p(dir)
- Chef::Log.debug "#{@new_resource} created directory #{dir}"
- if @new_resource.user
- FileUtils.chown(@new_resource.user, nil, dir)
- Chef::Log.debug("#{@new_resource} set user to #{@new_resource.user} for #{dir}")
- end
- if @new_resource.group
- FileUtils.chown(nil, @new_resource.group, dir)
- Chef::Log.debug("#{@new_resource} set group to #{@new_resource.group} for #{dir}")
- end
- rescue => e
- raise Chef::Exceptions::FileNotFound.new("Cannot create directory #{dir}: #{e.message}")
- end
- end
- end
-
- def with_rollback_on_error
- yield
- rescue ::Exception => e
- if @new_resource.rollback_on_error
- Chef::Log.warn "Error on deploying #{release_path}: #{e.message}"
- failed_release = release_path
-
- if previous_release_path
- @release_path = previous_release_path
- rollback
- end
- converge_by("remove failed deploy #{failed_release}") do
- Chef::Log.info "Removing failed deploy #{failed_release}"
- FileUtils.rm_rf failed_release
- end
- release_deleted(failed_release)
- end
-
- raise
- end
-
- def save_release_state
- if ::File.exists?(@new_resource.current_path)
- release = ::File.readlink(@new_resource.current_path)
- @previous_release_path = release if ::File.exists?(release)
- end
- end
-
- def deployed?(release)
- all_releases.include?(release)
- end
-
- def current_release?(release)
- @previous_release_path == release
- end
- end
- end
-end
diff --git a/lib/chef/provider/deploy/revision.rb b/lib/chef/provider/deploy/revision.rb
deleted file mode 100644
index f61e439486..0000000000
--- a/lib/chef/provider/deploy/revision.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Author:: Tim Hinderliter (<tim@chef.io>)
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2009-2016, Daniel DeLeo
-# Copyright:: Copyright 2010-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"
-require "chef/provider/deploy"
-require "chef/json_compat"
-
-class Chef
- class Provider
- class Deploy
- class Revision < Chef::Provider::Deploy
- provides :deploy_revision
- provides :deploy_branch
-
- def all_releases
- sorted_releases
- end
-
- def action_deploy
- validate_release_history!
- super
- end
-
- def cleanup!
- super
-
- known_releases = sorted_releases
-
- Dir["#{Chef::Util::PathHelper.escape_glob_dir(new_resource.deploy_to)}/releases/*"].each do |release_dir|
- unless known_releases.include?(release_dir)
- converge_by("Remove unknown release in #{release_dir}") do
- FileUtils.rm_rf(release_dir)
- end
- end
- end
- end
-
- protected
-
- def release_created(release)
- sorted_releases { |r| r.delete(release); r << release }
- end
-
- def release_deleted(release)
- sorted_releases { |r| r.delete(release) }
- end
-
- def release_slug
- scm_provider.revision_slug
- end
-
- private
-
- def sorted_releases
- cache = load_cache
- if block_given?
- yield cache
- save_cache(cache)
- end
- cache
- end
-
- def validate_release_history!
- sorted_releases do |release_list|
- release_list.each do |path|
- release_list.delete(path) unless ::File.exist?(path)
- end
- end
- end
-
- def sorted_releases_from_filesystem
- Dir.glob(Chef::Util::PathHelper.escape_glob_dir(new_resource.deploy_to) + "/releases/*").sort_by { |d| ::File.ctime(d) }
- end
-
- def load_cache
- begin
- Chef::JSONCompat.parse(Chef::FileCache.load("revision-deploys/#{new_resource.name}"))
- rescue Chef::Exceptions::FileNotFound
- sorted_releases_from_filesystem
- end
- end
-
- def save_cache(cache)
- Chef::FileCache.store("revision-deploys/#{new_resource.name}", Chef::JSONCompat.to_json(cache))
- cache
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/deploy/timestamped.rb b/lib/chef/provider/deploy/timestamped.rb
deleted file mode 100644
index 5486b092d3..0000000000
--- a/lib/chef/provider/deploy/timestamped.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2009-2016, Daniel DeLeo
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-class Chef
- class Provider
- class Deploy
- class Timestamped < Chef::Provider::Deploy
- provides :timestamped_deploy
- provides :deploy
-
- protected
-
- def release_slug
- Time.now.utc.strftime("%Y%m%d%H%M%S")
- end
- end
- end
- end
-end
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 7cc05259b6..3e816d5a06 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,17 +29,13 @@ class Chef
provides :directory
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Directory.new(@new_resource.name)
- @current_resource.path(@new_resource.path)
- if ::File.exists?(@current_resource.path) && @action != :create_if_missing
- load_resource_attributes_from_file(@current_resource)
+ @current_resource = Chef::Resource::Directory.new(new_resource.name)
+ current_resource.path(new_resource.path)
+ if ::File.exists?(current_resource.path) && @action != :create_if_missing
+ load_resource_attributes_from_file(current_resource)
end
- @current_resource
+ current_resource
end
def define_resource_requirements
@@ -49,9 +45,9 @@ class Chef
requirements.assert(:create) do |a|
# Make sure the parent dir exists, or else fail.
# for why run, print a message explaining the potential error.
- parent_directory = ::File.dirname(@new_resource.path)
+ parent_directory = ::File.dirname(new_resource.path)
a.assertion do
- if @new_resource.recursive
+ if new_resource.recursive
does_parent_exist = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
if ::File.exist?(base_dir)
@@ -60,20 +56,20 @@ class Chef
does_parent_exist.call(base_dir)
end
end
- does_parent_exist.call(@new_resource.path)
+ does_parent_exist.call(new_resource.path)
else
::File.directory?(parent_directory)
end
end
- a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist, cannot create #{@new_resource.path}")
+ a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist, cannot create #{new_resource.path}")
a.whyrun("Assuming directory #{parent_directory} would have been created")
end
requirements.assert(:create) do |a|
- parent_directory = ::File.dirname(@new_resource.path)
+ parent_directory = ::File.dirname(new_resource.path)
a.assertion do
- if @new_resource.recursive
- # find the lowest-level directory in @new_resource.path that already exists
+ if new_resource.recursive
+ # find the lowest-level directory in new_resource.path that already exists
# make sure we have write permissions to that directory
is_parent_writable = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
@@ -89,7 +85,7 @@ class Chef
is_parent_writable.call(base_dir)
end
end
- is_parent_writable.call(@new_resource.path)
+ is_parent_writable.call(new_resource.path)
else
# in why run mode & parent directory does not exist no permissions check is required
# If not in why run, permissions must be valid and we rely on prior assertion that dir exists
@@ -97,7 +93,7 @@ class Chef
if Chef::FileAccessControl.writable?(parent_directory)
true
elsif Chef::Util::PathHelper.is_sip_path?(parent_directory, node)
- Chef::Util::PathHelper.writable_sip_path?(@new_resource.path)
+ Chef::Util::PathHelper.writable_sip_path?(new_resource.path)
else
false
end
@@ -107,18 +103,18 @@ class Chef
end
end
a.failure_message(Chef::Exceptions::InsufficientPermissions,
- "Cannot create #{@new_resource} at #{@new_resource.path} due to insufficient permissions")
+ "Cannot create #{new_resource} at #{new_resource.path} due to insufficient permissions")
end
requirements.assert(:delete) do |a|
a.assertion do
- if ::File.exists?(@new_resource.path)
- ::File.directory?(@new_resource.path) && Chef::FileAccessControl.writable?(@new_resource.path)
+ if ::File.exists?(new_resource.path)
+ ::File.directory?(new_resource.path) && Chef::FileAccessControl.writable?(new_resource.path)
else
true
end
end
- a.failure_message(RuntimeError, "Cannot delete #{@new_resource} at #{@new_resource.path}!")
+ a.failure_message(RuntimeError, "Cannot delete #{new_resource} at #{new_resource.path}!")
# No why-run handling here:
# * if we don't have permissions, this is unlikely to be changed earlier in the run
# * if the target is a file (not a dir), there's no reasonable path by which this would have been changed
@@ -126,30 +122,32 @@ class Chef
end
def action_create
- unless ::File.exists?(@new_resource.path)
- converge_by("create new directory #{@new_resource.path}") do
- if @new_resource.recursive == true
- ::FileUtils.mkdir_p(@new_resource.path)
+ unless ::File.exists?(new_resource.path)
+ converge_by("create new directory #{new_resource.path}") do
+ if new_resource.recursive == true
+ ::FileUtils.mkdir_p(new_resource.path)
else
- ::Dir.mkdir(@new_resource.path)
+ ::Dir.mkdir(new_resource.path)
end
- Chef::Log.info("#{@new_resource} created directory #{@new_resource.path}")
+ logger.info("#{new_resource} created directory #{new_resource.path}")
end
end
do_acl_changes
do_selinux(true)
- load_resource_attributes_from_file(@new_resource)
+ load_resource_attributes_from_file(new_resource) unless Chef::Config[:why_run]
end
def action_delete
- if ::File.exists?(@new_resource.path)
- converge_by("delete existing directory #{@new_resource.path}") do
- if @new_resource.recursive == true
- FileUtils.rm_rf(@new_resource.path)
- Chef::Log.info("#{@new_resource} deleted #{@new_resource.path} recursively")
+ if ::File.exists?(new_resource.path)
+ converge_by("delete existing directory #{new_resource.path}") do
+ if new_resource.recursive == true
+ # we don't use rm_rf here because it masks all errors, including
+ # IO errors or permission errors that would prvent the deletion
+ FileUtils.rm_r(new_resource.path)
+ logger.info("#{new_resource} deleted #{new_resource.path} recursively")
else
- ::Dir.delete(@new_resource.path)
- Chef::Log.info("#{@new_resource} deleted #{@new_resource.path}")
+ ::Dir.delete(new_resource.path)
+ logger.info("#{new_resource} deleted #{new_resource.path}")
end
end
end
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 0f25065925..9c147cb634 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright 2014-2017, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -24,11 +24,12 @@ class Chef
class Provider
class DscResource < Chef::Provider
include Chef::Mixin::PowershellTypeCoercions
- provides :dsc_resource, os: "windows"
+ provides :dsc_resource
def initialize(new_resource, run_context)
super
@new_resource = new_resource
@module_name = new_resource.module_name
+ @module_version = new_resource.module_version
@reboot_resource = nil
end
@@ -44,10 +45,6 @@ class Chef
def load_current_resource
end
- def whyrun_supported?
- true
- end
-
def define_resource_requirements
requirements.assert(:run) do |a|
a.assertion { supports_dsc_invoke_resource? }
@@ -65,6 +62,13 @@ class Chef
a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
a.block_action!
end
+ requirements.assert(:run) do |a|
+ a.assertion { module_usage_valid? }
+ err = ["module_name must be supplied along with module_version."]
+ a.failure_message Chef::Exceptions::DSCModuleNameMissing,
+ err
+ a.block_action!
+ end
end
protected
@@ -92,6 +96,10 @@ class Chef
Chef::Platform.supports_refresh_mode_enabled?(node)
end
+ def module_usage_valid?
+ !(!@module_name && @module_version)
+ end
+
def generate_description
@converge_description
end
@@ -148,10 +156,14 @@ class Chef
end
end
+ def module_info_object
+ @module_version.nil? ? module_name : "@{ModuleName='#{module_name}';ModuleVersion='#{@module_version}'}"
+ end
+
def invoke_resource(method, output_format = :object)
- properties = translate_type(@new_resource.properties)
- switches = "-Method #{method} -Name #{@new_resource.resource}"\
- " -Property #{properties} -Module #{module_name} -Verbose"
+ properties = translate_type(new_resource.properties)
+ switches = "-Method #{method} -Name #{new_resource.resource}"\
+ " -Property #{properties} -Module #{module_info_object} -Verbose"
cmdlet = Chef::Util::Powershell::Cmdlet.new(
node,
"Invoke-DscResource #{switches}",
@@ -172,22 +184,22 @@ class Chef
def create_reboot_resource
@reboot_resource = Chef::Resource::Reboot.new(
- "Reboot for #{@new_resource.name}",
+ "Reboot for #{new_resource.name}",
run_context
).tap do |r|
- r.reason("Reboot for #{@new_resource.resource}.")
+ r.reason("Reboot for #{new_resource.resource}.")
end
end
def reboot_if_required
- reboot_action = @new_resource.reboot_action
+ reboot_action = new_resource.reboot_action
unless @reboot_resource.nil?
case reboot_action
when :nothing
- Chef::Log.debug("A reboot was requested by the DSC resource, but reboot_action is :nothing.")
- Chef::Log.debug("This dsc_resource will not reboot the node.")
+ logger.trace("A reboot was requested by the DSC resource, but reboot_action is :nothing.")
+ logger.trace("This dsc_resource will not reboot the node.")
else
- Chef::Log.debug("Requesting node reboot with #{reboot_action}.")
+ logger.trace("Requesting node reboot with #{reboot_action}.")
@reboot_resource.run_action(reboot_action)
end
end
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb
index 79769d9773..7a101fa68b 100644
--- a/lib/chef/provider/dsc_script.rb
+++ b/lib/chef/provider/dsc_script.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright 2014-2017, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -25,26 +25,26 @@ class Chef
class Provider
class DscScript < Chef::Provider
- provides :dsc_script, os: "windows"
+ provides :dsc_script
def initialize(dsc_resource, run_context)
super(dsc_resource, run_context)
@dsc_resource = dsc_resource
@resource_converged = false
@operations = {
- :set => Proc.new { |config_manager, document, shellout_flags|
+ :set => Proc.new do |config_manager, document, shellout_flags|
config_manager.set_configuration(document, shellout_flags)
- },
- :test => Proc.new { |config_manager, document, shellout_flags|
+ end,
+ :test => Proc.new do |config_manager, document, shellout_flags|
config_manager.test_configuration(document, shellout_flags)
- } }
+ end }
end
def action_run
if ! @resource_converged
converge_by(generate_description) do
run_configuration(:set)
- Chef::Log.info("DSC resource configuration completed successfully")
+ logger.info("DSC resource configuration completed successfully")
end
end
end
@@ -58,10 +58,6 @@ class Chef
end
end
- def whyrun_supported?
- true
- end
-
def define_resource_requirements
requirements.assert(:run) do |a|
err = [
@@ -99,7 +95,7 @@ class Chef
configuration_document = generate_configuration_document(config_directory, configuration_flags)
@operations[operation].call(config_manager, configuration_document, shellout_flags)
rescue Exception => e
- Chef::Log.error("DSC operation failed: #{e.message}")
+ logger.error("DSC operation failed: #{e.message}")
raise e
ensure
::FileUtils.rm_rf(config_directory)
@@ -128,7 +124,7 @@ class Chef
generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags, shellout_flags)
else
# If code is also not provided, we mimic what the other script resources do (execute nothing)
- Chef::Log.warn("Neither code or command were provided for dsc_resource[#{@dsc_resource.name}].") unless @dsc_resource.code
+ logger.warn("Neither code or command were provided for dsc_resource[#{@dsc_resource.name}].") unless @dsc_resource.code
generator.configuration_document_from_script_code(@dsc_resource.code || "", configuration_flags, @dsc_resource.imports, shellout_flags)
end
end
@@ -165,7 +161,11 @@ class Chef
if resource.changes_state?
# We ignore the last log message because it only contains the time it took, which looks weird
cleaned_messages = resource.change_log[0..-2].map { |c| c.sub(/^#{Regexp.escape(resource.name)}/, "").strip }
- "converge DSC resource #{resource.name} by #{cleaned_messages.find_all { |c| c != '' }.join("\n")}"
+ unless cleaned_messages.empty?
+ "converge DSC resource #{resource.name} by #{cleaned_messages.find_all { |c| c != '' }.join("\n")}"
+ else
+ "converge DSC resource #{resource.name}"
+ end
else
# This is needed because a dsc script can have resources that are both converged and not
"converge DSC resource #{resource.name} by doing nothing because it is already converged"
diff --git a/lib/chef/provider/env.rb b/lib/chef/provider/env.rb
deleted file mode 100644
index 5b252dd344..0000000000
--- a/lib/chef/provider/env.rb
+++ /dev/null
@@ -1,169 +0,0 @@
-#
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright 2010-2016, VMware, 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"
-require "chef/mixin/command"
-require "chef/resource/env"
-
-class Chef
- class Provider
- class Env < Chef::Provider
- include Chef::Mixin::Command
- attr_accessor :key_exists
-
- provides :env, os: "!windows"
-
- def initialize(new_resource, run_context)
- super
- @key_exists = true
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::Env.new(@new_resource.name)
- @current_resource.key_name(@new_resource.key_name)
-
- if env_key_exists(@new_resource.key_name)
- @current_resource.value(env_value(@new_resource.key_name))
- else
- @key_exists = false
- Chef::Log.debug("#{@new_resource} key does not exist")
- end
-
- @current_resource
- end
-
- def env_value(key_name)
- raise Chef::Exceptions::Env, "#{self} provider does not implement env_value!"
- end
-
- def env_key_exists(key_name)
- env_value(key_name) ? true : false
- end
-
- # Check to see if value needs any changes
- #
- # ==== Returns
- # <true>:: If a change is required
- # <false>:: If a change is not required
- def requires_modify_or_create?
- if @new_resource.delim
- #e.g. check for existing value within PATH
- new_values.inject(0) do |index, val|
- next_index = current_values.find_index val
- return true if next_index.nil? || next_index < index
- next_index
- end
- false
- else
- @new_resource.value != @current_resource.value
- end
- end
-
- alias_method :compare_value, :requires_modify_or_create?
-
- def action_create
- if @key_exists
- if requires_modify_or_create?
- modify_env
- Chef::Log.info("#{@new_resource} altered")
- @new_resource.updated_by_last_action(true)
- end
- else
- create_env
- Chef::Log.info("#{@new_resource} created")
- @new_resource.updated_by_last_action(true)
- end
- end
-
- #e.g. delete a PATH element
- #
- # ==== Returns
- # <true>:: If we handled the element case and caller should not delete the key
- # <false>:: Caller should delete the key, either no :delim was specific or value was empty
- # after we removed the element.
- def delete_element
- return false unless @new_resource.delim #no delim: delete the key
- needs_delete = new_values.any? { |v| current_values.include?(v) }
- if !needs_delete
- Chef::Log.debug("#{@new_resource} element '#{@new_resource.value}' does not exist")
- return true #do not delete the key
- else
- new_value =
- current_values.select do |item|
- not new_values.include?(item)
- end.join(@new_resource.delim)
-
- if new_value.empty?
- return false #nothing left here, delete the key
- else
- old_value = @new_resource.value(new_value)
- create_env
- Chef::Log.debug("#{@new_resource} deleted #{old_value} element")
- @new_resource.updated_by_last_action(true)
- return true #we removed the element and updated; do not delete the key
- end
- end
- end
-
- def action_delete
- if @key_exists && !delete_element
- delete_env
- Chef::Log.info("#{@new_resource} deleted")
- @new_resource.updated_by_last_action(true)
- end
- end
-
- def action_modify
- if @key_exists
- if requires_modify_or_create?
- modify_env
- Chef::Log.info("#{@new_resource} modified")
- @new_resource.updated_by_last_action(true)
- end
- else
- raise Chef::Exceptions::Env, "Cannot modify #{@new_resource} - key does not exist!"
- end
- end
-
- def create_env
- raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :#{@new_resource.action}"
- end
-
- def delete_env
- raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :delete"
- end
-
- def modify_env
- if @new_resource.delim
- @new_resource.value((new_values + current_values).uniq.join(@new_resource.delim))
- end
- create_env
- end
-
- # Returns the current values to split by delimiter
- def current_values
- @current_values ||= @current_resource.value.split(@new_resource.delim)
- end
-
- # Returns the new values to split by delimiter
- def new_values
- @new_values ||= @new_resource.value.split(@new_resource.delim)
- end
- end
- end
-end
diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb
deleted file mode 100644
index a68c8276e0..0000000000
--- a/lib/chef/provider/env/windows.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright 2010-2016, VMware, 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/mixin/windows_env_helper"
-
-class Chef
- class Provider
- class Env
- class Windows < Chef::Provider::Env
- include Chef::Mixin::WindowsEnvHelper
-
- provides :env, os: "windows"
-
- def create_env
- obj = env_obj(@new_resource.key_name)
- unless obj
- obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_
- obj.name = @new_resource.key_name
- obj.username = "<System>"
- end
- obj.variablevalue = @new_resource.value
- obj.put_
- value = @new_resource.value
- value = expand_path(value) if @new_resource.key_name.casecmp("PATH").zero?
- ENV[@new_resource.key_name] = value
- broadcast_env_change
- end
-
- def delete_env
- obj = env_obj(@new_resource.key_name)
- if obj
- obj.delete_
- broadcast_env_change
- end
- if ENV[@new_resource.key_name]
- ENV.delete(@new_resource.key_name)
- end
- end
-
- def env_value(key_name)
- obj = env_obj(key_name)
- return obj ? obj.variablevalue : ENV[key_name]
- end
-
- def env_obj(key_name)
- wmi = WmiLite::Wmi.new
- # Note that by design this query is case insensitive with regard to key_name
- environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'")
- if environment_variables && environment_variables.length > 0
- environment_variables[0].wmi_ole_object
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/erl_call.rb b/lib/chef/provider/erl_call.rb
deleted file mode 100644
index 7167f3b8a5..0000000000
--- a/lib/chef/provider/erl_call.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-#
-# Author:: Joe Williams (<joe@joetify.com>)
-# Copyright:: Copyright 2009-2016, Joe Williams
-# 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/log"
-require "chef/mixin/command"
-require "chef/provider"
-
-class Chef
- class Provider
- class ErlCall < Chef::Provider
- include Chef::Mixin::Command
-
- provides :erl_call
-
- def initialize(node, new_resource)
- super(node, new_resource)
- end
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- true
- end
-
- def action_run
- case @new_resource.name_type
- when "sname"
- node = "-sname #{@new_resource.node_name}"
- when "name"
- node = "-name #{@new_resource.node_name}"
- end
-
- if @new_resource.cookie
- cookie = "-c #{@new_resource.cookie}"
- else
- cookie = ""
- end
-
- if @new_resource.distributed
- distributed = "-s"
- else
- distributed = ""
- end
-
- command = "erl_call -e #{distributed} #{node} #{cookie}"
-
- converge_by("run erlang block") do
- begin
- pid, stdin, stdout, stderr = popen4(command, :waitlast => true)
-
- Chef::Log.debug("#{@new_resource} running")
- Chef::Log.debug("#{@new_resource} command: #{command}")
- Chef::Log.debug("#{@new_resource} code: #{@new_resource.code}")
-
- @new_resource.code.each_line { |line| stdin.puts(line.chomp) }
-
- stdin.close
-
- Chef::Log.debug("#{@new_resource} output: ")
-
- stdout_output = ""
- stdout.each_line { |line| stdout_output << line }
- stdout.close
-
- stderr_output = ""
- stderr.each_line { |line| stderr_output << line }
- stderr.close
-
- # fail if stderr contains anything
- if stderr_output.length > 0
- raise Chef::Exceptions::ErlCall, stderr_output
- end
-
- # fail if the first 4 characters aren't "{ok,"
- unless stdout_output[0..3].include?("{ok,")
- raise Chef::Exceptions::ErlCall, stdout_output
- end
-
- @new_resource.updated_by_last_action(true)
-
- Chef::Log.debug("#{@new_resource} #{stdout_output}")
- Chef::Log.info("#{@new_resouce} ran successfully")
- ensure
- Process.wait(pid) if pid
- end
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
index 45f0ad5488..6872e2d67d 100644
--- a/lib/chef/provider/execute.rb
+++ b/lib/chef/provider/execute.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,21 +27,17 @@ class Chef
provides :execute
- def_delegators :@new_resource, :command, :returns, :environment, :user, :group, :cwd, :umask, :creates
+ def_delegators :new_resource, :command, :returns, :environment, :user, :domain, :password, :group, :cwd, :umask, :creates, :elevated
def load_current_resource
current_resource = Chef::Resource::Execute.new(new_resource.name)
current_resource
end
- def whyrun_supported?
- true
- end
-
def define_resource_requirements
- # @todo: this should change to raise in some appropriate major version bump.
if creates && creates_relative? && !cwd
- Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail in the future (CHEF-3819)"
+ # FIXME? move this onto the resource?
+ raise Chef::Exceptions::Execute, "Please either specify a full path for the creates attribute, or specify a cwd property to the #{new_resource} resource"
end
end
@@ -53,22 +49,26 @@ class Chef
def action_run
if creates && sentinel_file.exist?
- Chef::Log.debug("#{new_resource} sentinel file #{sentinel_file} exists - nothing to do")
+ logger.debug("#{new_resource} sentinel file #{sentinel_file} exists - nothing to do")
return false
end
converge_by("execute #{description}") do
begin
- shell_out!(command, opts)
+ shell_out_with_systems_locale!(command, opts)
rescue Mixlib::ShellOut::ShellCommandFailed
if sensitive?
- raise Mixlib::ShellOut::ShellCommandFailed,
- "Command execution failed. STDOUT/STDERR suppressed for sensitive resource"
+ ex = Mixlib::ShellOut::ShellCommandFailed.new("Command execution failed. STDOUT/STDERR suppressed for sensitive resource")
+ # Forcibly hide the exception cause chain here so we don't log the unredacted version
+ def ex.cause
+ nil
+ end
+ raise ex
else
raise
end
end
- Chef::Log.info("#{new_resource} ran successfully")
+ logger.info("#{new_resource} ran successfully")
end
end
@@ -92,18 +92,21 @@ class Chef
opts[:returns] = returns if returns
opts[:environment] = environment if environment
opts[:user] = user if user
+ opts[:domain] = domain if domain
+ opts[:password] = password if password
opts[:group] = group if group
opts[:cwd] = cwd if cwd
opts[:umask] = umask if umask
opts[:log_level] = :info
opts[:log_tag] = new_resource.to_s
- if (Chef::Log.info? || live_stream?) && !sensitive?
+ if (logger.info? || live_stream?) && !sensitive?
if run_context.events.formatter?
opts[:live_stream] = Chef::EventDispatch::EventsOutputStream.new(run_context.events, :name => :execute)
elsif stream_to_stdout?
opts[:live_stream] = STDOUT
end
end
+ opts[:elevated] = elevated if elevated
opts
end
@@ -120,6 +123,7 @@ class Chef
( cwd && creates_relative? ) ? ::File.join(cwd, creates) : creates
))
end
+
end
end
end
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
index 7f85085eeb..05522f1eb8 100644
--- a/lib/chef/provider/file.rb
+++ b/lib/chef/provider/file.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,8 +30,6 @@ require "chef/mixin/enforce_ownership_and_permissions"
require "chef/util/backup"
require "chef/util/diff"
require "chef/util/selinux"
-require "chef/deprecation/provider/file"
-require "chef/deprecation/warnings"
require "chef/file_content_management/deploy"
# The Tao of File Providers:
@@ -52,10 +50,6 @@ class Chef
include Chef::Util::Selinux
include Chef::Mixin::FileClass
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::File
- add_deprecation_warnings_for(Chef::Deprecation::Provider::File.instance_methods)
-
provides :file
attr_reader :deployment_strategy
@@ -72,10 +66,6 @@ class Chef
super
end
- def whyrun_supported?
- true
- end
-
def load_current_resource
# true if there is a symlink and we need to manage what it points at
@managing_symlink = file_class.symlink?(new_resource.path) && ( new_resource.manage_symlink_source || new_resource.manage_symlink_source.nil? )
@@ -95,14 +85,14 @@ class Chef
# true if we are going to be creating a new file
@needs_creating = !::File.exist?(new_resource.path) || needs_unlinking?
- # Let children resources override constructing the @current_resource
+ # Let children resources override constructing the current_resource
@current_resource ||= Chef::Resource::File.new(new_resource.name)
current_resource.path(new_resource.path)
if !needs_creating?
# we are updating an existing file
if managing_content?
- Chef::Log.debug("#{new_resource} checksumming file at #{new_resource.path}.")
+ logger.trace("#{new_resource} checksumming file at #{new_resource.path}.")
current_resource.checksum(checksum(current_resource.path))
else
# if the file does not exist or is not a file, then the checksum is invalid/pointless
@@ -120,17 +110,17 @@ class Chef
# Make sure the parent directory exists, otherwise fail. For why-run assume it would have been created.
requirements.assert(:create, :create_if_missing, :touch) do |a|
- parent_directory = ::File.dirname(@new_resource.path)
+ parent_directory = ::File.dirname(new_resource.path)
a.assertion { ::File.directory?(parent_directory) }
a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist.")
a.whyrun("Assuming directory #{parent_directory} would have been created")
end
# Make sure the file is deletable if it exists, otherwise fail.
- if ::File.exist?(@new_resource.path)
+ if ::File.exist?(new_resource.path)
requirements.assert(:delete) do |a|
- a.assertion { ::File.writable?(@new_resource.path) }
- a.failure_message(Chef::Exceptions::InsufficientPermissions, "File #{@new_resource.path} exists but is not writable so it cannot be deleted")
+ a.assertion { ::File.writable?(new_resource.path) }
+ a.failure_message(Chef::Exceptions::InsufficientPermissions, "File #{new_resource.path} exists but is not writable so it cannot be deleted")
end
end
@@ -154,33 +144,33 @@ class Chef
do_contents_changes
do_acl_changes
do_selinux
- load_resource_attributes_from_file(@new_resource)
+ load_resource_attributes_from_file(new_resource) unless Chef::Config[:why_run]
end
def action_create_if_missing
- unless ::File.exist?(@new_resource.path)
+ unless ::File.exist?(new_resource.path)
action_create
else
- Chef::Log.debug("#{@new_resource} exists at #{@new_resource.path} taking no action.")
+ logger.trace("#{new_resource} exists at #{new_resource.path} taking no action.")
end
end
def action_delete
- if ::File.exists?(@new_resource.path)
- converge_by("delete file #{@new_resource.path}") do
- do_backup unless file_class.symlink?(@new_resource.path)
- ::File.delete(@new_resource.path)
- Chef::Log.info("#{@new_resource} deleted file at #{@new_resource.path}")
+ if ::File.exists?(new_resource.path)
+ converge_by("delete file #{new_resource.path}") do
+ do_backup unless file_class.symlink?(new_resource.path)
+ ::File.delete(new_resource.path)
+ logger.info("#{new_resource} deleted file at #{new_resource.path}")
end
end
end
def action_touch
action_create
- converge_by("update utime on file #{@new_resource.path}") do
+ converge_by("update utime on file #{new_resource.path}") do
time = Time.now
- ::File.utime(time, time, @new_resource.path)
- Chef::Log.info("#{@new_resource} updated atime and mtime to #{time}")
+ ::File.utime(time, time, new_resource.path)
+ logger.info("#{new_resource} updated atime and mtime to #{time}")
end
end
@@ -197,8 +187,8 @@ class Chef
# content (for things like doing checksums in load_current_resource). Expected to
# be overridden in subclasses.
def managing_content?
- return true if @new_resource.checksum
- return true if !@new_resource.content.nil? && @action != :create_if_missing
+ return true if new_resource.checksum
+ return true if !new_resource.content.nil? && @action != :create_if_missing
false
end
@@ -228,25 +218,25 @@ class Chef
# assertions, which then decide whether or not to raise or issue a
# warning for whyrun mode.
def inspect_existing_fs_entry
- path = @new_resource.path
+ path = new_resource.path
if !l_exist?(path)
[nil, nil, nil]
elsif real_file?(path)
[nil, nil, nil]
- elsif file_class.symlink?(path) && @new_resource.manage_symlink_source
+ elsif file_class.symlink?(path) && new_resource.manage_symlink_source
verify_symlink_sanity(path)
- elsif file_class.symlink?(@new_resource.path) && @new_resource.manage_symlink_source.nil?
- Chef::Log.warn("File #{path} managed by #{@new_resource} is really a symlink. Managing the source file instead.")
- Chef::Log.warn("Disable this warning by setting `manage_symlink_source true` on the resource")
- Chef::Log.warn("In a future Chef release, 'manage_symlink_source' will not be enabled by default")
+ elsif file_class.symlink?(new_resource.path) && new_resource.manage_symlink_source.nil?
+ logger.warn("File #{path} managed by #{new_resource} is really a symlink. Managing the source file instead.")
+ logger.warn("Disable this warning by setting `manage_symlink_source true` on the resource")
+ logger.warn("In a future Chef release, 'manage_symlink_source' will not be enabled by default")
verify_symlink_sanity(path)
- elsif @new_resource.force_unlink
+ elsif new_resource.force_unlink
[nil, nil, nil]
else
[ Chef::Exceptions::FileTypeMismatch,
- "File #{path} exists, but is a #{file_type_string(@new_resource.path)}, set force_unlink to true to remove",
- "Assuming #{file_type_string(@new_resource.path)} at #{@new_resource.path} would have been removed by a previous resource",
+ "File #{path} exists, but is a #{file_type_string(new_resource.path)}, set force_unlink to true to remove",
+ "Assuming #{file_type_string(new_resource.path)} at #{new_resource.path} would have been removed by a previous resource",
]
end
end
@@ -282,8 +272,8 @@ class Chef
def content
@content ||= begin
- load_current_resource if @current_resource.nil?
- @content_class.new(@new_resource, @current_resource, @run_context)
+ load_current_resource if current_resource.nil?
+ @content_class.new(new_resource, current_resource, @run_context, logger)
end
end
@@ -312,11 +302,9 @@ class Chef
# like real_file? that follows (sane) symlinks
def symlink_to_real_file?(path)
- begin
- real_file?(::File.realpath(path))
- rescue Errno::ELOOP, Errno::ENOENT
- false
- end
+ real_file?(::File.realpath(path))
+ rescue Errno::ELOOP, Errno::ENOENT
+ false
end
# Similar to File.exist?, but also returns true in the case that the
@@ -344,26 +332,26 @@ class Chef
end
def do_validate_content
- if new_resource.checksum && tempfile && ( new_resource.checksum != tempfile_checksum )
+ if new_resource.checksum && tempfile && ( new_resource.checksum.downcase != tempfile_checksum )
raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(tempfile_checksum))
end
if tempfile
new_resource.verify.each do |v|
if ! v.verify(tempfile.path)
- raise Chef::Exceptions::ValidationFailed.new "Proposed content for #{new_resource.path} failed verification #{v}"
+ raise Chef::Exceptions::ValidationFailed.new "Proposed content for #{new_resource.path} failed verification #{new_resource.sensitive ? '[sensitive]' : v}"
end
end
end
end
def do_unlink
- if @new_resource.force_unlink
+ if new_resource.force_unlink
if needs_unlinking?
# unlink things that aren't normal files
- description = "unlink #{file_type_string(@new_resource.path)} at #{@new_resource.path}"
+ description = "unlink #{file_type_string(new_resource.path)} at #{new_resource.path}"
converge_by(description) do
- unlink(@new_resource.path)
+ unlink(new_resource.path)
end
end
end
@@ -371,15 +359,15 @@ class Chef
def do_create_file
if needs_creating?
- converge_by("create new file #{@new_resource.path}") do
- deployment_strategy.create(@new_resource.path)
- Chef::Log.info("#{@new_resource} created file #{@new_resource.path}")
+ converge_by("create new file #{new_resource.path}") do
+ deployment_strategy.create(new_resource.path)
+ logger.info("#{new_resource} created file #{new_resource.path}")
end
end
end
def do_backup(file = nil)
- Chef::Util::Backup.new(@new_resource, file).backup!
+ Chef::Util::Backup.new(new_resource, file).backup!
end
def diff
@@ -389,7 +377,7 @@ class Chef
def update_file_contents
do_backup unless needs_creating?
deployment_strategy.deploy(tempfile.path, ::File.realpath(new_resource.path))
- Chef::Log.info("#{new_resource} updated file contents #{new_resource.path}")
+ logger.info("#{new_resource} updated file contents #{new_resource.path}")
if managing_content?
# save final checksum for reporting.
new_resource.final_checksum = checksum(new_resource.path)
@@ -405,17 +393,17 @@ class Chef
end
# the file? on the next line suppresses the case in why-run when we have a not-file here that would have otherwise been removed
- if ::File.file?(@new_resource.path) && contents_changed?
- description = [ "update content in file #{@new_resource.path} from \
-#{short_cksum(@current_resource.checksum)} to #{short_cksum(tempfile_checksum)}" ]
+ if ::File.file?(new_resource.path) && contents_changed?
+ description = [ "update content in file #{new_resource.path} from \
+#{short_cksum(current_resource.checksum)} to #{short_cksum(tempfile_checksum)}" ]
# Hide the diff output if the resource is marked as a sensitive resource
- if @new_resource.sensitive
- @new_resource.diff("suppressed sensitive resource")
+ if new_resource.sensitive
+ new_resource.diff("suppressed sensitive resource")
description << "suppressed sensitive resource"
else
- diff.diff(@current_resource.path, tempfile.path)
- @new_resource.diff( diff.for_reporting ) unless needs_creating?
+ diff.diff(current_resource.path, tempfile.path)
+ new_resource.diff( diff.for_reporting ) unless needs_creating?
description << diff.for_output
end
@@ -437,10 +425,10 @@ class Chef
if resource_updated? && Chef::Config[:enable_selinux_file_permission_fixup]
if selinux_enabled?
converge_by("restore selinux security context") do
- restore_security_context(::File.realpath(@new_resource.path), recursive)
+ restore_security_context(::File.realpath(new_resource.path), recursive)
end
else
- Chef::Log.debug "selinux utilities can not be found. Skipping selinux permission fixup."
+ logger.trace "selinux utilities can not be found. Skipping selinux permission fixup."
end
end
end
@@ -454,19 +442,14 @@ class Chef
end
def contents_changed?
- Chef::Log.debug "calculating checksum of #{tempfile.path} to compare with #{@current_resource.checksum}"
- tempfile_checksum != @current_resource.checksum
+ logger.trace "calculating checksum of #{tempfile.path} to compare with #{current_resource.checksum}"
+ tempfile_checksum != current_resource.checksum
end
def tempfile
@tempfile ||= content.tempfile
end
- def short_cksum(checksum)
- return "none" if checksum.nil?
- checksum.slice(0, 6)
- end
-
def load_resource_attributes_from_file(resource)
if Chef::Platform.windows?
# This is a work around for CHEF-3554.
@@ -475,7 +458,7 @@ class Chef
# reporting won't work for Windows.
return
end
- acl_scanner = ScanAccessControl.new(@new_resource, resource)
+ acl_scanner = ScanAccessControl.new(new_resource, resource)
acl_scanner.set_all!
end
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
index 82f5ca2ba5..239dfa09e6 100644
--- a/lib/chef/provider/git.rb
+++ b/lib/chef/provider/git.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,78 +25,79 @@ class Chef
class Provider
class Git < Chef::Provider
+ extend Forwardable
provides :git
- def whyrun_supported?
- true
- end
+ GIT_VERSION_PATTERN = Regexp.compile('git version (\d+\.\d+.\d+)')
+
+ def_delegator :new_resource, :destination, :cwd
def load_current_resource
@resolved_reference = nil
- @current_resource = Chef::Resource::Git.new(@new_resource.name)
+ @current_resource = Chef::Resource::Git.new(new_resource.name)
if current_revision = find_current_revision
- @current_resource.revision current_revision
+ current_resource.revision current_revision
end
end
def define_resource_requirements
# Parent directory of the target must exist.
requirements.assert(:checkout, :sync) do |a|
- dirname = ::File.dirname(@new_resource.destination)
+ dirname = ::File.dirname(cwd)
a.assertion { ::File.directory?(dirname) }
a.whyrun("Directory #{dirname} does not exist, this run will fail unless it has been previously created. Assuming it would have been created.")
a.failure_message(Chef::Exceptions::MissingParentDirectory,
- "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{dirname} does not exist")
+ "Cannot clone #{new_resource} to #{cwd}, the enclosing directory #{dirname} does not exist")
end
requirements.assert(:all_actions) do |a|
- a.assertion { !(@new_resource.revision =~ /^origin\//) }
+ a.assertion { !(new_resource.revision =~ /^origin\//) }
a.failure_message Chef::Exceptions::InvalidRemoteGitReference,
"Deploying remote branches is not supported. " +
"Specify the remote branch as a local branch for " +
"the git repository you're deploying from " +
- "(ie: '#{@new_resource.revision.gsub('origin/', '')}' rather than '#{@new_resource.revision}')."
+ "(ie: '#{new_resource.revision.gsub('origin/', '')}' rather than '#{new_resource.revision}')."
end
requirements.assert(:all_actions) do |a|
# this can't be recovered from in why-run mode, because nothing that
# we do in the course of a run is likely to create a valid target_revision
# if we can't resolve it up front.
- a.assertion { target_revision != nil }
+ a.assertion { !target_revision.nil? }
a.failure_message Chef::Exceptions::UnresolvableGitReference,
- "Unable to parse SHA reference for '#{@new_resource.revision}' in repository '#{@new_resource.repository}'. " +
+ "Unable to parse SHA reference for '#{new_resource.revision}' in repository '#{new_resource.repository}'. " +
"Verify your (case-sensitive) repository URL and revision.\n" +
- "`git ls-remote '#{@new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
+ "`git ls-remote '#{new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
end
end
def action_checkout
if target_dir_non_existent_or_empty?
clone
- if @new_resource.enable_checkout
+ if new_resource.enable_checkout
checkout
end
enable_submodules
add_remotes
else
- Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory"
+ logger.trace "#{new_resource} checkout destination #{cwd} already exists or is a non-empty directory"
end
end
def action_export
action_checkout
- converge_by("complete the export by removing #{@new_resource.destination}.git after checkout") do
- FileUtils.rm_rf(::File.join(@new_resource.destination, ".git"))
+ converge_by("complete the export by removing #{cwd}.git after checkout") do
+ FileUtils.rm_rf(::File.join(cwd, ".git"))
end
end
def action_sync
if existing_git_clone?
- Chef::Log.debug "#{@new_resource} current revision: #{@current_resource.revision} target revision: #{target_revision}"
+ logger.trace "#{new_resource} current revision: #{current_resource.revision} target revision: #{target_revision}"
unless current_revision_matches_target_revision?
fetch_updates
enable_submodules
- Chef::Log.info "#{@new_resource} updated to revision #{target_revision}"
+ logger.info "#{new_resource} updated to revision #{target_revision}"
end
add_remotes
else
@@ -104,32 +105,45 @@ class Chef
end
end
- def git_minor_version
- @git_minor_version ||= Gem::Version.new(shell_out!("git --version", run_options).stdout.split.last)
+ def git_has_single_branch_option?
+ @git_has_single_branch_option ||= !git_gem_version.nil? && git_gem_version >= Gem::Version.new("1.7.10")
+ end
+
+ def git_gem_version
+ return @git_gem_version if defined?(@git_gem_version)
+ output = git("--version").stdout
+ match = GIT_VERSION_PATTERN.match(output)
+ if match
+ @git_gem_version = Gem::Version.new(match[1])
+ else
+ logger.warn "Unable to parse git version from '#{output}'"
+ @git_gem_version = nil
+ end
+ @git_gem_version
end
def existing_git_clone?
- ::File.exist?(::File.join(@new_resource.destination, ".git"))
+ ::File.exist?(::File.join(cwd, ".git"))
end
def target_dir_non_existent_or_empty?
- !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == [".", ".."]
+ !::File.exist?(cwd) || Dir.entries(cwd).sort == [".", ".."]
end
def find_current_revision
- Chef::Log.debug("#{@new_resource} finding current git revision")
+ logger.trace("#{new_resource} finding current git revision")
if ::File.exist?(::File.join(cwd, ".git"))
# 128 is returned when we're not in a git repo. this is fine
- result = shell_out!("git rev-parse HEAD", :cwd => cwd, :returns => [0, 128]).stdout.strip
+ result = git("rev-parse", "HEAD", cwd: cwd, returns: [0, 128]).stdout.strip
end
sha_hash?(result) ? result : nil
end
def add_remotes
- if @new_resource.additional_remotes.length > 0
- @new_resource.additional_remotes.each_pair do |remote_name, remote_url|
+ if new_resource.additional_remotes.length > 0
+ new_resource.additional_remotes.each_pair do |remote_name, remote_url|
converge_by("add remote #{remote_name} from #{remote_url}") do
- Chef::Log.info "#{@new_resource} adding git remote #{remote_name} = #{remote_url}"
+ logger.info "#{new_resource} adding git remote #{remote_name} = #{remote_url}"
setup_remote_tracking_branches(remote_name, remote_url)
end
end
@@ -137,61 +151,60 @@ class Chef
end
def clone
- converge_by("clone from #{@new_resource.repository} into #{@new_resource.destination}") do
- remote = @new_resource.remote
-
- args = []
- args << "-o #{remote}" unless remote == "origin"
- args << "--depth #{@new_resource.depth}" if @new_resource.depth
- args << "--no-single-branch" if @new_resource.depth && git_minor_version >= Gem::Version.new("1.7.10")
-
- Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{@new_resource.destination}"
-
- clone_cmd = "git clone #{args.join(' ')} \"#{@new_resource.repository}\" \"#{@new_resource.destination}\""
- shell_out!(clone_cmd, run_options)
+ converge_by("clone from #{new_resource.repository} into #{cwd}") do
+ remote = new_resource.remote
+
+ clone_cmd = ["clone"]
+ clone_cmd << "-o #{remote}" unless remote == "origin"
+ clone_cmd << "--depth #{new_resource.depth}" if new_resource.depth
+ clone_cmd << "--no-single-branch" if new_resource.depth && git_has_single_branch_option?
+ clone_cmd << "\"#{new_resource.repository}\""
+ clone_cmd << "\"#{cwd}\""
+
+ logger.info "#{new_resource} cloning repo #{new_resource.repository} to #{cwd}"
+ git clone_cmd
end
end
def checkout
sha_ref = target_revision
- converge_by("checkout ref #{sha_ref} branch #{@new_resource.revision}") do
+ converge_by("checkout ref #{sha_ref} branch #{new_resource.revision}") do
# checkout into a local branch rather than a detached HEAD
- shell_out!("git branch -f #{@new_resource.checkout_branch} #{sha_ref}", run_options(:cwd => @new_resource.destination))
- shell_out!("git checkout #{@new_resource.checkout_branch}", run_options(:cwd => @new_resource.destination))
- Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} onto: #{@new_resource.checkout_branch} reference: #{sha_ref}"
+ git("branch", "-f", new_resource.checkout_branch, sha_ref, cwd: cwd)
+ git("checkout", new_resource.checkout_branch, cwd: cwd)
+ logger.info "#{new_resource} checked out branch: #{new_resource.revision} onto: #{new_resource.checkout_branch} reference: #{sha_ref}"
end
end
def enable_submodules
- if @new_resource.enable_submodules
- converge_by("enable git submodules for #{@new_resource}") do
- Chef::Log.info "#{@new_resource} synchronizing git submodules"
- command = "git submodule sync"
- shell_out!(command, run_options(:cwd => @new_resource.destination))
- Chef::Log.info "#{@new_resource} enabling git submodules"
+ if new_resource.enable_submodules
+ converge_by("enable git submodules for #{new_resource}") do
+ logger.info "#{new_resource} synchronizing git submodules"
+ git("submodule", "sync", cwd: cwd)
+ logger.info "#{new_resource} enabling git submodules"
# the --recursive flag means we require git 1.6.5+ now, see CHEF-1827
- command = "git submodule update --init --recursive"
- shell_out!(command, run_options(:cwd => @new_resource.destination))
+ git("submodule", "update", "--init", "--recursive", cwd: cwd)
end
end
end
def fetch_updates
- setup_remote_tracking_branches(@new_resource.remote, @new_resource.repository)
- converge_by("fetch updates for #{@new_resource.remote}") do
+ setup_remote_tracking_branches(new_resource.remote, new_resource.repository)
+ converge_by("fetch updates for #{new_resource.remote}") do
# since we're in a local branch already, just reset to specified revision rather than merge
- fetch_command = "git fetch #{@new_resource.remote} && git fetch #{@new_resource.remote} --tags && git reset --hard #{target_revision}"
- Chef::Log.debug "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
- shell_out!(fetch_command, run_options(:cwd => @new_resource.destination))
+ logger.trace "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
+ git("fetch", "--prune", new_resource.remote, cwd: cwd)
+ git("fetch", new_resource.remote, "--tags", cwd: cwd)
+ git("reset", "--hard", target_revision, cwd: cwd)
end
end
def setup_remote_tracking_branches(remote_name, remote_url)
converge_by("set up remote tracking branches for #{remote_url} at #{remote_name}") do
- Chef::Log.debug "#{@new_resource} configuring remote tracking branches for repository #{remote_url} " + "at remote #{remote_name}"
- check_remote_command = "git config --get remote.#{remote_name}.url"
- remote_status = shell_out!(check_remote_command, run_options(:cwd => @new_resource.destination, :returns => [0, 1, 2]))
+ logger.trace "#{new_resource} configuring remote tracking branches for repository #{remote_url} " + "at remote #{remote_name}"
+ check_remote_command = ["config", "--get", "remote.#{remote_name}.url"]
+ remote_status = git(check_remote_command, cwd: cwd, returns: [0, 1, 2])
case remote_status.exitstatus
when 0, 2
# * Status 0 means that we already have a remote with this name, so we should update the url
@@ -200,12 +213,10 @@ class Chef
# which we can fix by replacing them all with our target url (hence the --replace-all option)
if multiple_remotes?(remote_status) || !remote_matches?(remote_url, remote_status)
- update_remote_url_command = "git config --replace-all remote.#{remote_name}.url #{remote_url}"
- shell_out!(update_remote_url_command, run_options(:cwd => @new_resource.destination))
+ git("config", "--replace-all", "remote.#{remote_name}.url", remote_url, cwd: cwd)
end
when 1
- add_remote_command = "git remote add #{remote_name} #{remote_url}"
- shell_out!(add_remote_command, run_options(:cwd => @new_resource.destination))
+ git("remote", "add", remote_name, remote_url, cwd: cwd)
end
end
end
@@ -219,13 +230,13 @@ class Chef
end
def current_revision_matches_target_revision?
- (!@current_resource.revision.nil?) && (target_revision.strip.to_i(16) == @current_resource.revision.strip.to_i(16))
+ (!current_resource.revision.nil?) && (target_revision.strip.to_i(16) == current_resource.revision.strip.to_i(16))
end
def target_revision
@target_revision ||= begin
- if sha_hash?(@new_resource.revision)
- @target_revision = @new_resource.revision
+ if sha_hash?(new_resource.revision)
+ @target_revision = new_resource.revision
else
@target_revision = remote_resolve_reference
end
@@ -235,7 +246,7 @@ class Chef
alias :revision_slug :target_revision
def remote_resolve_reference
- Chef::Log.debug("#{@new_resource} resolving remote reference")
+ logger.trace("#{new_resource} resolving remote reference")
# The sha pointed to by an annotated tag is identified by the
# '^{}' suffix appended to the tag. In order to resolve
# annotated tags, we have to search for "revision*" and
@@ -250,11 +261,11 @@ class Chef
# confusing. We avoid the issue by disallowing the use of
# annotated tags named 'HEAD'.
if rev_search_pattern != "HEAD"
- found = find_revision(refs, @new_resource.revision, "^{}")
+ found = find_revision(refs, new_resource.revision, "^{}")
else
found = refs_search(refs, "HEAD")
end
- found = find_revision(refs, @new_resource.revision) if found.empty?
+ found = find_revision(refs, new_resource.revision) if found.empty?
found.size == 1 ? found.first[0] : nil
end
@@ -274,53 +285,57 @@ class Chef
end
def rev_search_pattern
- if ["", "HEAD"].include? @new_resource.revision
+ if ["", "HEAD"].include? new_resource.revision
"HEAD"
else
- @new_resource.revision + "*"
+ new_resource.revision + "*"
end
end
def git_ls_remote(rev_pattern)
- command = git(%Q{ls-remote "#{@new_resource.repository}" "#{rev_pattern}"})
- shell_out!(command, run_options).stdout
+ git("ls-remote", "\"#{new_resource.repository}\"", "\"#{rev_pattern}\"").stdout
end
def refs_search(refs, pattern)
refs.find_all { |m| m[1] == pattern }
end
+ alias git_minor_version git_gem_version
+
private
def run_options(run_opts = {})
env = {}
- if @new_resource.user
- run_opts[:user] = @new_resource.user
+ if new_resource.user
+ run_opts[:user] = new_resource.user
# Certain versions of `git` misbehave if git configuration is
# inaccessible in $HOME. We need to ensure $HOME matches the
# user who is executing `git` not the user running Chef.
env["HOME"] = begin
require "etc"
- Etc.getpwnam(@new_resource.user).dir
+ case new_resource.user
+ when Integer
+ Etc.getpwuid(new_resource.user).dir
+ else
+ Etc.getpwnam(new_resource.user.to_s).dir
+ end
rescue ArgumentError # user not found
- raise Chef::Exceptions::User, "Could not determine HOME for specified user '#{@new_resource.user}' for resource '#{@new_resource.name}'"
+ raise Chef::Exceptions::User, "Could not determine HOME for specified user '#{new_resource.user}' for resource '#{new_resource.name}'"
end
end
- run_opts[:group] = @new_resource.group if @new_resource.group
- env["GIT_SSH"] = @new_resource.ssh_wrapper if @new_resource.ssh_wrapper
- run_opts[:log_tag] = @new_resource.to_s
- run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout
- env.merge!(@new_resource.environment) if @new_resource.environment
+ run_opts[:group] = new_resource.group if new_resource.group
+ env["GIT_SSH"] = new_resource.ssh_wrapper if new_resource.ssh_wrapper
+ run_opts[:log_tag] = new_resource.to_s
+ run_opts[:timeout] = new_resource.timeout if new_resource.timeout
+ env.merge!(new_resource.environment) if new_resource.environment
run_opts[:environment] = env unless env.empty?
run_opts
end
- def cwd
- @new_resource.destination
- end
-
- def git(*args)
- ["git", *args].compact.join(" ")
+ def git(*args, **run_opts)
+ git_command = ["git", args].compact.join(" ")
+ logger.trace "running #{git_command}"
+ shell_out!(git_command, run_options(run_opts))
end
def sha_hash?(string)
diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb
index 8936bd2031..51e4f280fe 100644
--- a/lib/chef/provider/group.rb
+++ b/lib/chef/provider/group.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,59 +18,53 @@
require "chef/provider"
require "chef/mixin/shell_out"
-require "chef/mixin/command"
require "etc"
class Chef
class Provider
class Group < Chef::Provider
include Chef::Mixin::ShellOut
- include Chef::Mixin::Command
attr_accessor :group_exists
attr_accessor :change_desc
- def whyrun_supported?
- true
- end
-
def initialize(new_resource, run_context)
super
@group_exists = true
end
def load_current_resource
- @current_resource = Chef::Resource::Group.new(@new_resource.name)
- @current_resource.group_name(@new_resource.group_name)
+ @current_resource = Chef::Resource::Group.new(new_resource.name)
+ current_resource.group_name(new_resource.group_name)
group_info = nil
begin
- group_info = Etc.getgrnam(@new_resource.group_name)
- rescue ArgumentError => e
+ group_info = Etc.getgrnam(new_resource.group_name)
+ rescue ArgumentError
@group_exists = false
- Chef::Log.debug("#{@new_resource} group does not exist")
+ logger.trace("#{new_resource} group does not exist")
end
if group_info
- @new_resource.gid(group_info.gid) unless @new_resource.gid
- @current_resource.gid(group_info.gid)
- @current_resource.members(group_info.mem)
+ new_resource.gid(group_info.gid) unless new_resource.gid
+ current_resource.gid(group_info.gid)
+ current_resource.members(group_info.mem)
end
- @current_resource
+ current_resource
end
def define_resource_requirements
requirements.assert(:modify) do |a|
a.assertion { @group_exists }
- a.failure_message(Chef::Exceptions::Group, "Cannot modify #{@new_resource} - group does not exist!")
- a.whyrun("Group #{@new_resource} does not exist. Unless it would have been created earlier in this run, this attempt to modify it would fail.")
+ a.failure_message(Chef::Exceptions::Group, "Cannot modify #{new_resource} - group does not exist!")
+ a.whyrun("Group #{new_resource} does not exist. Unless it would have been created earlier in this run, this attempt to modify it would fail.")
end
requirements.assert(:all_actions) do |a|
# Make sure that the resource doesn't contain any common
# user names in the members and exclude_members properties.
- if !@new_resource.members.nil? && !@new_resource.excluded_members.nil?
- common_members = @new_resource.members & @new_resource.excluded_members
+ if !new_resource.members.nil? && !new_resource.excluded_members.nil?
+ common_members = new_resource.members & new_resource.excluded_members
a.assertion { common_members.empty? }
a.failure_message(Chef::Exceptions::ConflictingMembersInGroup, "Attempting to both add and remove users from a group: '#{common_members.join(', ')}'")
# No why-run alternative
@@ -86,41 +80,39 @@ class Chef
# <false>:: If a change is not required
def compare_group
@change_desc = [ ]
- if @new_resource.gid.to_s != @current_resource.gid.to_s
- @change_desc << "change gid #{@current_resource.gid} to #{@new_resource.gid}"
+ if new_resource.gid.to_s != current_resource.gid.to_s
+ @change_desc << "change gid #{current_resource.gid} to #{new_resource.gid}"
end
- if @new_resource.append
+ if new_resource.append
missing_members = []
- @new_resource.members.each do |member|
+ new_resource.members.each do |member|
next if has_current_group_member?(member)
validate_member!(member)
missing_members << member
end
- if missing_members.length > 0
- @change_desc << "add missing member(s): #{missing_members.join(", ")}"
+ unless missing_members.empty?
+ @change_desc << "add missing member(s): #{missing_members.join(', ')}"
end
members_to_be_removed = []
- @new_resource.excluded_members.each do |member|
+ new_resource.excluded_members.each do |member|
if has_current_group_member?(member)
members_to_be_removed << member
end
end
- if members_to_be_removed.length > 0
- @change_desc << "remove existing member(s): #{members_to_be_removed.join(", ")}"
- end
- else
- if @new_resource.members != @current_resource.members
- @change_desc << "replace group members with new list of members"
+ unless members_to_be_removed.empty?
+ @change_desc << "remove existing member(s): #{members_to_be_removed.join(', ')}"
end
+ elsif new_resource.members != current_resource.members
+ @change_desc << "replace group members with new list of members"
end
!@change_desc.empty?
end
def has_current_group_member?(member)
- @current_resource.members.include?(member)
+ current_resource.members.include?(member)
end
def validate_member!(member)
@@ -132,44 +124,41 @@ class Chef
def action_create
case @group_exists
when false
- converge_by("create group #{@new_resource.group_name}") do
+ converge_by("create group #{new_resource.group_name}") do
create_group
- Chef::Log.info("#{@new_resource} created")
+ logger.info("#{new_resource} created")
end
else
if compare_group
- converge_by(["alter group #{@new_resource.group_name}"] + change_desc) do
+ converge_by(["alter group #{new_resource.group_name}"] + change_desc) do
manage_group
- Chef::Log.info("#{@new_resource} altered")
+ logger.info("#{new_resource} altered")
end
end
end
end
def action_remove
- if @group_exists
- converge_by("remove group #{@new_resource.group_name}") do
- remove_group
- Chef::Log.info("#{@new_resource} removed")
- end
+ return unless @group_exists
+ converge_by("remove group #{new_resource.group_name}") do
+ remove_group
+ logger.info("#{new_resource} removed")
end
end
def action_manage
- if @group_exists && compare_group
- converge_by(["manage group #{@new_resource.group_name}"] + change_desc) do
- manage_group
- Chef::Log.info("#{@new_resource} managed")
- end
+ return unless @group_exists && compare_group
+ converge_by(["manage group #{new_resource.group_name}"] + change_desc) do
+ manage_group
+ logger.info("#{new_resource} managed")
end
end
def action_modify
- if compare_group
- converge_by(["modify group #{@new_resource.group_name}"] + change_desc) do
- manage_group
- Chef::Log.info("#{@new_resource} modified")
- end
+ return unless compare_group
+ converge_by(["modify group #{new_resource.group_name}"] + change_desc) do
+ manage_group
+ logger.info("#{new_resource} modified")
end
end
diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb
index 4a02d5ef8c..5c500e2753 100644
--- a/lib/chef/provider/group/aix.rb
+++ b/lib/chef/provider/group/aix.rb
@@ -17,7 +17,6 @@
#
require "chef/provider/group/groupadd"
-require "chef/mixin/shell_out"
class Chef
class Provider
@@ -33,48 +32,42 @@ class Chef
end
def create_group
- command = "mkgroup"
- command << set_options << " #{@new_resource.group_name}"
- run_command(:command => command)
+ shell_out_compact!("mkgroup", set_options, new_resource.group_name)
modify_group_members
end
def manage_group
- command = "chgroup"
options = set_options
- #Usage: chgroup [-R load_module] "attr=value" ... group
if options.size > 0
- command << options << " #{@new_resource.group_name}"
- run_command(:command => command)
+ shell_out_compact!("chgroup", options, new_resource.group_name)
end
modify_group_members
end
def remove_group
- run_command(:command => "rmgroup #{@new_resource.group_name}")
+ shell_out_compact!("rmgroup", new_resource.group_name)
end
def add_member(member)
- shell_out!("chgrpmem -m + #{member} #{@new_resource.group_name}")
+ shell_out_compact!("chgrpmem", "-m", "+", member, new_resource.group_name)
end
def set_members(members)
return if members.empty?
- shell_out!("chgrpmem -m = #{members.join(',')} #{@new_resource.group_name}")
+ shell_out_compact!("chgrpmem", "-m", "=", members.join(","), new_resource.group_name)
end
def remove_member(member)
- shell_out!("chgrpmem -m - #{member} #{@new_resource.group_name}")
+ shell_out_compact!("chgrpmem", "-m", "-", member, new_resource.group_name)
end
def set_options
- opts = ""
- { :gid => "id" }.sort { |a, b| a[0] <=> b[0] }.each do |field, option|
- if @current_resource.send(field) != @new_resource.send(field)
- if @new_resource.send(field)
- Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field)}")
- opts << " '#{option}=#{@new_resource.send(field)}'"
- end
+ opts = []
+ { gid: "id" }.sort_by { |a| a[0] }.each do |field, option|
+ next unless current_resource.send(field) != new_resource.send(field)
+ if new_resource.send(field)
+ logger.trace("#{new_resource} setting #{field} to #{new_resource.send(field)}")
+ opts << "#{option}=#{new_resource.send(field)}"
end
end
opts
diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb
index 00b4ce2b93..81c7d66aa8 100644
--- a/lib/chef/provider/group/dscl.rb
+++ b/lib/chef/provider/group/dscl.rb
@@ -24,12 +24,15 @@ class Chef
provides :group, os: "darwin"
def dscl(*args)
- host = "."
- stdout_result = ""; stderr_result = ""; cmd = "dscl #{host} -#{args.join(' ')}"
- status = shell_out(cmd)
+ argdup = args.dup
+ cmd = argdup.shift
+ shellcmd = [ "dscl", ".", "-#{cmd}", argdup ]
+ status = shell_out_compact(shellcmd)
+ stdout_result = ""
+ stderr_result = ""
status.stdout.each_line { |line| stdout_result << line }
status.stderr.each_line { |line| stderr_result << line }
- return [cmd, status, stdout_result, stderr_result]
+ [shellcmd.flatten.compact.join(" "), status, stdout_result, stderr_result]
end
def safe_dscl(*args)
@@ -37,18 +40,18 @@ class Chef
return "" if ( args.first =~ /^delete/ ) && ( result[1].exitstatus != 0 )
raise(Chef::Exceptions::Group, "dscl error: #{result.inspect}") unless result[1].exitstatus == 0
raise(Chef::Exceptions::Group, "dscl error: #{result.inspect}") if result[2] =~ /No such key: /
- return result[2]
+ result[2]
end
def load_current_resource
- @current_resource = Chef::Resource::Group.new(@new_resource.name)
- @current_resource.group_name(@new_resource.group_name)
+ @current_resource = Chef::Resource::Group.new(new_resource.name)
+ current_resource.group_name(new_resource.group_name)
group_info = nil
begin
- group_info = safe_dscl("read /Groups/#{@new_resource.group_name}")
+ group_info = safe_dscl("read", "/Groups/#{new_resource.group_name}")
rescue Chef::Exceptions::Group
@group_exists = false
- Chef::Log.debug("#{@new_resource} group does not exist")
+ logger.trace("#{new_resource} group does not exist")
end
if group_info
@@ -57,21 +60,21 @@ class Chef
val.strip! if val
case key.downcase
when "primarygroupid"
- @new_resource.gid(val) unless @new_resource.gid
- @current_resource.gid(val)
+ new_resource.gid(val) unless new_resource.gid
+ current_resource.gid(val)
when "groupmembership"
- @current_resource.members(val.split(" "))
+ current_resource.members(val.split(" "))
end
end
end
- @current_resource
+ current_resource
end
# get a free GID greater than 200
def get_free_gid(search_limit = 1000)
gid = nil; next_gid_guess = 200
- groups_gids = safe_dscl("list /Groups gid")
+ groups_gids = safe_dscl("list", "/Groups", "gid")
while next_gid_guess < search_limit + 200
if groups_gids =~ Regexp.new("#{Regexp.escape(next_gid_guess.to_s)}\n")
next_gid_guess += 1
@@ -80,51 +83,55 @@ class Chef
break
end
end
- return gid || raise("gid not found. Exhausted. Searched #{search_limit} times")
+ gid || raise("gid not found. Exhausted. Searched #{search_limit} times")
end
def gid_used?(gid)
return false unless gid
- groups_gids = safe_dscl("list /Groups gid")
- !! ( groups_gids =~ Regexp.new("#{Regexp.escape(gid.to_s)}\n") )
+ search_gids = safe_dscl("search", "/Groups", "PrimaryGroupID", gid.to_s)
+
+ # dscl -search should not return anything if the gid doesn't exist,
+ # but on the off-chance that it does, check whether the given gid is
+ # in the output.
+ !!(search_gids =~ /\b#{gid}\b/)
end
def set_gid
- @new_resource.gid(get_free_gid) if [nil, ""].include? @new_resource.gid
- raise(Chef::Exceptions::Group, "gid is already in use") if gid_used?(@new_resource.gid)
- safe_dscl("create /Groups/#{@new_resource.group_name} PrimaryGroupID #{@new_resource.gid}")
+ new_resource.gid(get_free_gid) if [nil, ""].include? new_resource.gid
+ raise(Chef::Exceptions::Group, "gid is already in use") if gid_used?(new_resource.gid)
+ safe_dscl("create", "/Groups/#{new_resource.group_name}", "PrimaryGroupID", new_resource.gid)
end
def set_members
# First reset the memberships if the append is not set
- unless @new_resource.append
- Chef::Log.debug("#{@new_resource} removing group members #{@current_resource.members.join(' ')}") unless @current_resource.members.empty?
- safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembers ''") # clear guid list
- safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembership ''") # clear user list
- @current_resource.members([ ])
+ unless new_resource.append
+ logger.trace("#{new_resource} removing group members #{current_resource.members.join(' ')}") unless current_resource.members.empty?
+ safe_dscl("create", "/Groups/#{new_resource.group_name}", "GroupMembers", "") # clear guid list
+ safe_dscl("create", "/Groups/#{new_resource.group_name}", "GroupMembership", "") # clear user list
+ current_resource.members([ ])
end
# Add any members that need to be added
- if @new_resource.members && !@new_resource.members.empty?
+ if new_resource.members && !new_resource.members.empty?
members_to_be_added = [ ]
- @new_resource.members.each do |member|
- members_to_be_added << member if !@current_resource.members.include?(member)
+ new_resource.members.each do |member|
+ members_to_be_added << member unless current_resource.members.include?(member)
end
unless members_to_be_added.empty?
- Chef::Log.debug("#{@new_resource} setting group members #{members_to_be_added.join(', ')}")
- safe_dscl("append /Groups/#{@new_resource.group_name} GroupMembership #{members_to_be_added.join(' ')}")
+ logger.trace("#{new_resource} setting group members #{members_to_be_added.join(', ')}")
+ safe_dscl("append", "/Groups/#{new_resource.group_name}", "GroupMembership", *members_to_be_added)
end
end
# Remove any members that need to be removed
- if @new_resource.excluded_members && !@new_resource.excluded_members.empty?
+ if new_resource.excluded_members && !new_resource.excluded_members.empty?
members_to_be_removed = [ ]
- @new_resource.excluded_members.each do |member|
- members_to_be_removed << member if @current_resource.members.include?(member)
+ new_resource.excluded_members.each do |member|
+ members_to_be_removed << member if current_resource.members.include?(member)
end
unless members_to_be_removed.empty?
- Chef::Log.debug("#{@new_resource} removing group members #{members_to_be_removed.join(', ')}")
- safe_dscl("delete /Groups/#{@new_resource.group_name} GroupMembership #{members_to_be_removed.join(' ')}")
+ logger.trace("#{new_resource} removing group members #{members_to_be_removed.join(', ')}")
+ safe_dscl("delete", "/Groups/#{new_resource.group_name}", "GroupMembership", *members_to_be_removed)
end
end
end
@@ -132,8 +139,8 @@ class Chef
def define_resource_requirements
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/bin/dscl") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{@new_resource.name}"
+ a.assertion { ::File.exist?("/usr/bin/dscl") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{new_resource.name}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
end
@@ -145,24 +152,24 @@ class Chef
end
def manage_group
- if @new_resource.group_name && (@current_resource.group_name != @new_resource.group_name)
+ if new_resource.group_name && (current_resource.group_name != new_resource.group_name)
dscl_create_group
end
- if @new_resource.gid && (@current_resource.gid != @new_resource.gid)
+ if new_resource.gid && (current_resource.gid != new_resource.gid)
set_gid
end
- if @new_resource.members || @new_resource.excluded_members
+ if new_resource.members || new_resource.excluded_members
set_members
end
end
def dscl_create_group
- safe_dscl("create /Groups/#{@new_resource.group_name}")
- safe_dscl("create /Groups/#{@new_resource.group_name} Password '*'")
+ safe_dscl("create", "/Groups/#{new_resource.group_name}")
+ safe_dscl("create", "/Groups/#{new_resource.group_name}", "Password", "*")
end
def remove_group
- safe_dscl("delete /Groups/#{@new_resource.group_name}")
+ safe_dscl("delete", "/Groups/#{new_resource.group_name}")
end
end
end
diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb
index dcf526b211..d8aff10d5b 100644
--- a/lib/chef/provider/group/gpasswd.rb
+++ b/lib/chef/provider/group/gpasswd.rb
@@ -31,26 +31,26 @@ class Chef
def define_resource_requirements
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/bin/gpasswd") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}"
+ a.assertion { ::File.exist?("/usr/bin/gpasswd") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{new_resource}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
end
def set_members(members)
- unless members.empty?
- shell_out!("gpasswd -M #{members.join(',')} #{@new_resource.group_name}")
+ if members.empty?
+ shell_out_compact!("gpasswd", "-M", "", new_resource.group_name)
else
- shell_out!("gpasswd -M \"\" #{@new_resource.group_name}")
+ shell_out_compact!("gpasswd", "-M", members.join(","), new_resource.group_name)
end
end
def add_member(member)
- shell_out!("gpasswd -a #{member} #{@new_resource.group_name}")
+ shell_out_compact!("gpasswd", "-a", member, new_resource.group_name)
end
def remove_member(member)
- shell_out!("gpasswd -d #{member} #{@new_resource.group_name}")
+ shell_out_compact!("gpasswd", "-d", member, new_resource.group_name)
end
end
end
diff --git a/lib/chef/provider/group/groupadd.rb b/lib/chef/provider/group/groupadd.rb
index bc6b5d0208..7d7fac146c 100644
--- a/lib/chef/provider/group/groupadd.rb
+++ b/lib/chef/provider/group/groupadd.rb
@@ -35,8 +35,8 @@ class Chef
super
required_binaries.each do |required_binary|
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?(required_binary) }
- a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}"
+ a.assertion { ::File.exist?(required_binary) }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{new_resource}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
end
@@ -44,54 +44,49 @@ class Chef
# Create the group
def create_group
- command = "groupadd"
- command << set_options
- command << groupadd_options
- run_command(:command => command)
+ shell_out_compact!("groupadd", set_options, groupadd_options)
modify_group_members
end
# Manage the group when it already exists
def manage_group
- command = "groupmod"
- command << set_options
- run_command(:command => command)
+ shell_out_compact!("groupmod", set_options)
modify_group_members
end
# Remove the group
def remove_group
- run_command(:command => "groupdel #{@new_resource.group_name}")
+ shell_out_compact!("groupdel", new_resource.group_name)
end
def modify_group_members
- if @new_resource.append
- if @new_resource.members && !@new_resource.members.empty?
+ if new_resource.append
+ if new_resource.members && !new_resource.members.empty?
members_to_be_added = [ ]
- @new_resource.members.each do |member|
- members_to_be_added << member if !@current_resource.members.include?(member)
+ new_resource.members.each do |member|
+ members_to_be_added << member unless current_resource.members.include?(member)
end
members_to_be_added.each do |member|
- Chef::Log.debug("#{@new_resource} appending member #{member} to group #{@new_resource.group_name}")
+ logger.trace("#{new_resource} appending member #{member} to group #{new_resource.group_name}")
add_member(member)
end
end
- if @new_resource.excluded_members && !@new_resource.excluded_members.empty?
+ if new_resource.excluded_members && !new_resource.excluded_members.empty?
members_to_be_removed = [ ]
- @new_resource.excluded_members.each do |member|
- members_to_be_removed << member if @current_resource.members.include?(member)
+ new_resource.excluded_members.each do |member|
+ members_to_be_removed << member if current_resource.members.include?(member)
end
members_to_be_removed.each do |member|
- Chef::Log.debug("#{@new_resource} removing member #{member} from group #{@new_resource.group_name}")
+ logger.trace("#{new_resource} removing member #{member} from group #{new_resource.group_name}")
remove_member(member)
end
end
else
- members_description = @new_resource.members.empty? ? "none" : @new_resource.members.join(", ")
- Chef::Log.debug("#{@new_resource} setting group members to: #{members_description}")
- set_members(@new_resource.members)
+ members_description = new_resource.members.empty? ? "none" : new_resource.members.join(", ")
+ logger.trace("#{new_resource} setting group members to: #{members_description}")
+ set_members(new_resource.members)
end
end
@@ -112,22 +107,23 @@ class Chef
# ==== Returns
# <string>:: A string containing the option and then the quoted value
def set_options
- opts = ""
- { :gid => "-g" }.sort { |a, b| a[0] <=> b[0] }.each do |field, option|
- if @current_resource.send(field) != @new_resource.send(field)
- if @new_resource.send(field)
- opts << " #{option} '#{@new_resource.send(field)}'"
- Chef::Log.debug("#{@new_resource} set #{field} to #{@new_resource.send(field)}")
- end
- end
+ opts = []
+ { gid: "-g" }.sort_by { |a| a[0] }.each do |field, option|
+ next unless current_resource.send(field) != new_resource.send(field)
+ next unless new_resource.send(field)
+ opts << option
+ opts << new_resource.send(field)
+ logger.trace("#{new_resource} set #{field} to #{new_resource.send(field)}")
end
- opts << " #{@new_resource.group_name}"
+ opts << new_resource.group_name
+ opts
end
def groupadd_options
- opts = ""
- opts << " -r" if @new_resource.system
- opts << " -o" if @new_resource.non_unique
+ opts = []
+ # Solaris doesn't support system groups.
+ opts << "-r" if new_resource.system && !node.platform?("solaris2")
+ opts << "-o" if new_resource.non_unique
opts
end
diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb
index 295d3b3425..13f83db4c4 100644
--- a/lib/chef/provider/group/groupmod.rb
+++ b/lib/chef/provider/group/groupmod.rb
@@ -26,28 +26,26 @@ class Chef
def load_current_resource
super
%w{group user}.each do |binary|
- raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/#{binary} for #{@new_resource}" unless ::File.exists?("/usr/sbin/#{binary}")
+ raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/#{binary} for #{new_resource}" unless ::File.exist?("/usr/sbin/#{binary}")
end
end
# Create the group
def create_group
- command = "group add"
- command << set_options
- shell_out!(command)
+ shell_out_compact!("group", "add", set_options)
- add_group_members(@new_resource.members)
+ add_group_members(new_resource.members)
end
# Manage the group when it already exists
def manage_group
- if @new_resource.append
+ if new_resource.append
members_to_be_added = [ ]
- if @new_resource.excluded_members && !@new_resource.excluded_members.empty?
+ if new_resource.excluded_members && !new_resource.excluded_members.empty?
# First find out if any member needs to be removed
members_to_be_removed = [ ]
- @new_resource.excluded_members.each do |member|
- members_to_be_removed << member if @current_resource.members.include?(member)
+ new_resource.excluded_members.each do |member|
+ members_to_be_removed << member if current_resource.members.include?(member)
end
unless members_to_be_removed.empty?
@@ -56,39 +54,39 @@ class Chef
# Capture the members we need to add in
# members_to_be_added to be added later on.
- @current_resource.members.each do |member|
+ current_resource.members.each do |member|
members_to_be_added << member unless members_to_be_removed.include?(member)
end
end
end
- if @new_resource.members && !@new_resource.members.empty?
- @new_resource.members.each do |member|
- members_to_be_added << member if !@current_resource.members.include?(member)
+ if new_resource.members && !new_resource.members.empty?
+ new_resource.members.each do |member|
+ members_to_be_added << member unless current_resource.members.include?(member)
end
end
- Chef::Log.debug("#{@new_resource} not changing group members, the group has no members to add") if members_to_be_added.empty?
+ logger.trace("#{new_resource} not changing group members, the group has no members to add") if members_to_be_added.empty?
add_group_members(members_to_be_added)
else
# We are resetting the members of a group so use the same trick
reset_group_membership
- Chef::Log.debug("#{@new_resource} setting group members to: none") if @new_resource.members.empty?
- add_group_members(@new_resource.members)
+ logger.trace("#{new_resource} setting group members to: none") if new_resource.members.empty?
+ add_group_members(new_resource.members)
end
end
# Remove the group
def remove_group
- shell_out!("group del #{@new_resource.group_name}")
+ shell_out_compact!("group", "del", new_resource.group_name)
end
# Adds a list of usernames to the group using `user mod`
def add_group_members(members)
- Chef::Log.debug("#{@new_resource} adding members #{members.join(', ')}") if !members.empty?
+ logger.trace("#{new_resource} adding members #{members.join(', ')}") unless members.empty?
members.each do |user|
- shell_out!("user mod -G #{@new_resource.group_name} #{user}")
+ shell_out_compact!("user", "mod", "-G", new_resource.group_name, user)
end
end
@@ -96,15 +94,11 @@ class Chef
# "<name>_bak", create a new group with the same GID and
# "<name>", then set correct members on that group
def reset_group_membership
- rename = "group mod -n #{@new_resource.group_name}_bak #{@new_resource.group_name}"
- shell_out!(rename)
+ shell_out_compact!("group", "mod", "-n", "#{new_resource.group_name}_bak", new_resource.group_name)
- create = "group add"
- create << set_options(:overwrite_gid => true)
- shell_out!(create)
+ shell_out_compact!("group", "add", set_options(overwrite_gid: true))
- remove = "group del #{@new_resource.group_name}_bak"
- shell_out!(remove)
+ shell_out_compact!("group", "del", "#{new_resource.group_name}_bak")
end
# Little bit of magic as per Adam's useradd provider to pull and assign the command line flags
@@ -112,14 +106,15 @@ class Chef
# ==== Returns
# <string>:: A string containing the option and then the quoted value
def set_options(overwrite_gid = false)
- opts = ""
- if overwrite_gid || @new_resource.gid && (@current_resource.gid != @new_resource.gid)
- opts << " -g '#{@new_resource.gid}'"
+ opts = []
+ if overwrite_gid || new_resource.gid && (current_resource.gid != new_resource.gid)
+ opts << "-g"
+ opts << new_resource.gid
end
if overwrite_gid
- opts << " -o"
+ opts << "-o"
end
- opts << " #{@new_resource.group_name}"
+ opts << new_resource.group_name
opts
end
end
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 4fd78b6b31..b0393a147e 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -30,46 +30,42 @@ class Chef
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/sbin/pw") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{@new_resource}"
+ a.assertion { ::File.exist?("/usr/sbin/pw") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{new_resource}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
end
# Create the group
def create_group
- command = "pw groupadd"
- command << set_options
-
- unless @new_resource.members.empty?
+ command = [ "pw", "groupadd", set_options ]
+ unless new_resource.members.empty?
# pw group[add|mod] -M is used to set the full membership list on a
# new or existing group. Because pw groupadd does not support the -m
# and -d options used by manage_group, we treat group creation as a
# special case and use -M.
- Chef::Log.debug("#{@new_resource} setting group members: #{@new_resource.members.join(',')}")
- command += " -M #{@new_resource.members.join(',')}"
+ logger.trace("#{new_resource} setting group members: #{new_resource.members.join(',')}")
+ command += [ "-M", new_resource.members.join(",") ]
end
- run_command(:command => command)
+ shell_out_compact!(command)
end
# Manage the group when it already exists
def manage_group
- command = "pw groupmod"
- command << set_options
member_options = set_members_options
if member_options.empty?
- run_command(:command => command)
+ shell_out_compact!("pw", "groupmod", set_options)
else
member_options.each do |option|
- run_command(:command => command + option)
+ shell_out_compact!("pw", "groupmod", set_options, option)
end
end
end
# Remove the group
def remove_group
- run_command(:command => "pw groupdel #{@new_resource.group_name}")
+ shell_out_compact!("pw", "groupdel", new_resource.group_name)
end
# Little bit of magic as per Adam's useradd provider to pull and assign the command line flags
@@ -77,10 +73,11 @@ class Chef
# ==== Returns
# <string>:: A string containing the option and then the quoted value
def set_options
- opts = " #{@new_resource.group_name}"
- if @new_resource.gid && (@current_resource.gid != @new_resource.gid)
- Chef::Log.debug("#{@new_resource}: current gid (#{@current_resource.gid}) doesnt match target gid (#{@new_resource.gid}), changing it")
- opts << " -g '#{@new_resource.gid}'"
+ opts = [ new_resource.group_name ]
+ if new_resource.gid && (current_resource.gid != new_resource.gid)
+ logger.trace("#{new_resource}: current gid (#{current_resource.gid}) doesnt match target gid (#{new_resource.gid}), changing it")
+ opts << "-g"
+ opts << new_resource.gid
end
opts
end
@@ -91,26 +88,26 @@ class Chef
members_to_be_added = [ ]
members_to_be_removed = [ ]
- if @new_resource.append
+ if new_resource.append
# Append is set so we will only add members given in the
# members list and remove members given in the
# excluded_members list.
- if @new_resource.members && !@new_resource.members.empty?
- @new_resource.members.each do |member|
- members_to_be_added << member if !@current_resource.members.include?(member)
+ if new_resource.members && !new_resource.members.empty?
+ new_resource.members.each do |member|
+ members_to_be_added << member unless current_resource.members.include?(member)
end
end
- if @new_resource.excluded_members && !@new_resource.excluded_members.empty?
- @new_resource.excluded_members.each do |member|
- members_to_be_removed << member if @current_resource.members.include?(member)
+ if new_resource.excluded_members && !new_resource.excluded_members.empty?
+ new_resource.excluded_members.each do |member|
+ members_to_be_removed << member if current_resource.members.include?(member)
end
end
else
# Append is not set so we're resetting the membership of
# the group to the given members.
- members_to_be_added = @new_resource.members.dup
- @current_resource.members.each do |member|
+ members_to_be_added = new_resource.members.dup
+ current_resource.members.each do |member|
# No need to re-add a member if it's present in the new
# list of members
if members_to_be_added.include? member
@@ -122,13 +119,13 @@ class Chef
end
unless members_to_be_added.empty?
- Chef::Log.debug("#{@new_resource} adding group members: #{members_to_be_added.join(',')}")
- opts << " -m #{members_to_be_added.join(',')}"
+ logger.trace("#{new_resource} adding group members: #{members_to_be_added.join(',')}")
+ opts << [ "-m", members_to_be_added.join(",") ]
end
unless members_to_be_removed.empty?
- Chef::Log.debug("#{@new_resource} removing group members: #{members_to_be_removed.join(',')}")
- opts << " -d #{members_to_be_removed.join(',')}"
+ logger.trace("#{new_resource} removing group members: #{members_to_be_removed.join(',')}")
+ opts << [ "-d", members_to_be_removed.join(",") ]
end
opts
diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb
index a79038e25e..0790d2c2d9 100644
--- a/lib/chef/provider/group/suse.rb
+++ b/lib/chef/provider/group/suse.rb
@@ -17,6 +17,7 @@
#
require "chef/provider/group/groupadd"
+require "etc"
class Chef
class Provider
@@ -32,30 +33,48 @@ class Chef
def define_resource_requirements
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/sbin/groupmod") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{@new_resource.name}"
+ a.assertion { ::File.exist?("/usr/sbin/groupmod") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{new_resource.name}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
+
+ requirements.assert(:create, :manage, :modify) do |a|
+ a.assertion do
+ begin
+ to_add(new_resource.members).all? { |member| Etc.getpwnam(member) }
+ rescue
+ false
+ end
+ end
+ a.failure_message Chef::Exceptions::Group, "Could not add users #{to_add(new_resource.members).join(', ')} to #{new_resource.group_name}: one of these users does not exist"
+ a.whyrun "Could not find one of these users: #{to_add(new_resource.members).join(', ')}. Assuming it will be created by a prior step"
+ end
end
def set_members(members)
- to_delete = @current_resource.members - members
- to_delete.each do |member|
+ to_remove(members).each do |member|
remove_member(member)
end
- to_add = members - @current_resource.members
- to_add.each do |member|
+ to_add(members).each do |member|
add_member(member)
end
end
+ def to_add(members)
+ members - current_resource.members
+ end
+
def add_member(member)
- shell_out!("groupmod -A #{member} #{@new_resource.group_name}")
+ shell_out_compact!("groupmod", "-A", member, new_resource.group_name)
+ end
+
+ def to_remove(members)
+ current_resource.members - members
end
def remove_member(member)
- shell_out!("groupmod -R #{member} #{@new_resource.group_name}")
+ shell_out_compact!("groupmod", "-R", member, new_resource.group_name)
end
end
diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb
index e4b19181aa..3874f7b4de 100644
--- a/lib/chef/provider/group/usermod.rb
+++ b/lib/chef/provider/group/usermod.rb
@@ -34,19 +34,19 @@ class Chef
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/sbin/usermod") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{@new_resource}"
+ a.assertion { ::File.exist?("/usr/sbin/usermod") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{new_resource}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
requirements.assert(:modify, :manage) do |a|
- a.assertion { @new_resource.members.empty? || @new_resource.append }
+ a.assertion { new_resource.members.empty? || new_resource.append }
a.failure_message Chef::Exceptions::Group, "setting group members directly is not supported by #{self}, must set append true in group"
# No whyrun alternative - this action is simply not supported.
end
requirements.assert(:all_actions) do |a|
- a.assertion { @new_resource.excluded_members.empty? }
+ a.assertion { new_resource.excluded_members.empty? }
a.failure_message Chef::Exceptions::Group, "excluded_members is not supported by #{self}"
# No whyrun alternative - this action is simply not supported.
end
@@ -57,17 +57,16 @@ class Chef
# This provider only supports adding members with
# append. Only if the action is create we will go
# ahead and add members.
- if @new_resource.action == :create
- members.each do |member|
- add_member(member)
- end
- else
+ unless new_resource.action.include?(:create)
raise Chef::Exceptions::UnsupportedAction, "Setting members directly is not supported by #{self}"
end
+ members.each do |member|
+ add_member(member)
+ end
end
def add_member(member)
- shell_out!("usermod #{append_flags} #{@new_resource.group_name} #{member}")
+ shell_out_compact!("usermod", append_flags, new_resource.group_name, member)
end
def remove_member(member)
@@ -81,7 +80,7 @@ class Chef
when "openbsd", "netbsd", "aix", "solaris2", "smartos", "omnios"
"-G"
when "solaris", "suse", "opensuse"
- "-a -G"
+ [ "-a", "-G" ]
end
end
diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb
index 5873e42a6b..e0cb3b804b 100644
--- a/lib/chef/provider/group/windows.rb
+++ b/lib/chef/provider/group/windows.rb
@@ -30,26 +30,26 @@ class Chef
def initialize(new_resource, run_context)
super
- @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.group_name)
+ @net_group = Chef::Util::Windows::NetGroup.new(new_resource.group_name)
end
def load_current_resource
- @current_resource = Chef::Resource::Group.new(@new_resource.name)
- @current_resource.group_name(@new_resource.group_name)
+ @current_resource = Chef::Resource::Group.new(new_resource.name)
+ current_resource.group_name(new_resource.group_name)
members = nil
begin
members = @net_group.local_get_members
- rescue => e
+ rescue
@group_exists = false
- Chef::Log.debug("#{@new_resource} group does not exist")
+ logger.trace("#{new_resource} group does not exist")
end
if members
- @current_resource.members(members)
+ current_resource.members(members)
end
- @current_resource
+ current_resource
end
def create_group
@@ -58,10 +58,10 @@ class Chef
end
def manage_group
- if @new_resource.append
+ if new_resource.append
members_to_be_added = [ ]
- @new_resource.members.each do |member|
- members_to_be_added << member if ! has_current_group_member?(member) && validate_member!(member)
+ new_resource.members.each do |member|
+ members_to_be_added << member if !has_current_group_member?(member) && validate_member!(member)
end
# local_add_members will raise ERROR_MEMBER_IN_ALIAS if a
@@ -69,19 +69,19 @@ class Chef
@net_group.local_add_members(members_to_be_added) unless members_to_be_added.empty?
members_to_be_removed = [ ]
- @new_resource.excluded_members.each do |member|
- member_sid = lookup_account_name(member)
+ new_resource.excluded_members.each do |member|
+ lookup_account_name(member)
members_to_be_removed << member if has_current_group_member?(member)
end
@net_group.local_delete_members(members_to_be_removed) unless members_to_be_removed.empty?
else
- @net_group.local_set_members(@new_resource.members)
+ @net_group.local_set_members(new_resource.members)
end
end
def has_current_group_member?(member)
member_sid = lookup_account_name(member)
- @current_resource.members.include?(member_sid)
+ current_resource.members.include?(member_sid)
end
def remove_group
@@ -97,12 +97,10 @@ class Chef
end
def lookup_account_name(account_name)
- begin
- Chef::ReservedNames::Win32::Security.lookup_account_name(locally_qualified_name(account_name))[1].to_s
- rescue Chef::Exceptions::Win32APIError
- Chef::Log.warn("SID for '#{locally_qualified_name(account_name)}' could not be found")
- ""
- end
+ Chef::ReservedNames::Win32::Security.lookup_account_name(locally_qualified_name(account_name))[1].to_s
+ rescue Chef::Exceptions::Win32APIError
+ logger.warn("SID for '#{locally_qualified_name(account_name)}' could not be found")
+ ""
end
end
diff --git a/lib/chef/provider/http_request.rb b/lib/chef/provider/http_request.rb
index e1ee01d9b4..885d473a7a 100644
--- a/lib/chef/provider/http_request.rb
+++ b/lib/chef/provider/http_request.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,83 +27,93 @@ class Chef
attr_accessor :http
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @http = Chef::HTTP::Simple.new(@new_resource.url)
+ @http = Chef::HTTP::Simple.new(new_resource.url)
end
- # Send a HEAD request to @new_resource.url
+ # Send a HEAD request to new_resource.url
def action_head
- message = check_message(@new_resource.message)
+ message = check_message(new_resource.message)
# CHEF-4762: we expect a nil return value from Chef::HTTP for a "200 Success" response
# and false for a "304 Not Modified" response
modified = @http.head(
- "#{@new_resource.url}",
- @new_resource.headers
+ "#{new_resource.url}",
+ new_resource.headers
)
- Chef::Log.info("#{@new_resource} HEAD to #{@new_resource.url} successful")
- Chef::Log.debug("#{@new_resource} HEAD request response: #{modified}")
+ logger.info("#{new_resource} HEAD to #{new_resource.url} successful")
+ logger.trace("#{new_resource} HEAD request response: #{modified}")
# :head is usually used to trigger notifications, which converge_by now does
if modified != false
- converge_by("#{@new_resource} HEAD to #{@new_resource.url} returned modified, trigger notifications") {}
+ converge_by("#{new_resource} HEAD to #{new_resource.url} returned modified, trigger notifications") {}
end
end
- # Send a GET request to @new_resource.url
+ # Send a GET request to new_resource.url
def action_get
- converge_by("#{@new_resource} GET to #{@new_resource.url}") do
+ converge_by("#{new_resource} GET to #{new_resource.url}") do
- message = check_message(@new_resource.message)
+ message = check_message(new_resource.message)
body = @http.get(
- "#{@new_resource.url}",
- @new_resource.headers
+ "#{new_resource.url}",
+ new_resource.headers
+ )
+ logger.info("#{new_resource} GET to #{new_resource.url} successful")
+ logger.trace("#{new_resource} GET request response: #{body}")
+ end
+ end
+
+ # Send a PATCH request to new_resource.url, with the message as the payload
+ def action_patch
+ converge_by("#{new_resource} PATCH to #{new_resource.url}") do
+ message = check_message(new_resource.message)
+ body = @http.patch(
+ "#{new_resource.url}",
+ message,
+ new_resource.headers
)
- Chef::Log.info("#{@new_resource} GET to #{@new_resource.url} successful")
- Chef::Log.debug("#{@new_resource} GET request response: #{body}")
+ logger.info("#{new_resource} PATCH to #{new_resource.url} successful")
+ logger.trace("#{new_resource} PATCH request response: #{body}")
end
end
- # Send a PUT request to @new_resource.url, with the message as the payload
+ # Send a PUT request to new_resource.url, with the message as the payload
def action_put
- converge_by("#{@new_resource} PUT to #{@new_resource.url}") do
- message = check_message(@new_resource.message)
+ converge_by("#{new_resource} PUT to #{new_resource.url}") do
+ message = check_message(new_resource.message)
body = @http.put(
- "#{@new_resource.url}",
+ "#{new_resource.url}",
message,
- @new_resource.headers
+ new_resource.headers
)
- Chef::Log.info("#{@new_resource} PUT to #{@new_resource.url} successful")
- Chef::Log.debug("#{@new_resource} PUT request response: #{body}")
+ logger.info("#{new_resource} PUT to #{new_resource.url} successful")
+ logger.trace("#{new_resource} PUT request response: #{body}")
end
end
- # Send a POST request to @new_resource.url, with the message as the payload
+ # Send a POST request to new_resource.url, with the message as the payload
def action_post
- converge_by("#{@new_resource} POST to #{@new_resource.url}") do
- message = check_message(@new_resource.message)
+ converge_by("#{new_resource} POST to #{new_resource.url}") do
+ message = check_message(new_resource.message)
body = @http.post(
- "#{@new_resource.url}",
+ "#{new_resource.url}",
message,
- @new_resource.headers
+ new_resource.headers
)
- Chef::Log.info("#{@new_resource} POST to #{@new_resource.url} message: #{message.inspect} successful")
- Chef::Log.debug("#{@new_resource} POST request response: #{body}")
+ logger.info("#{new_resource} POST to #{new_resource.url} message: #{message.inspect} successful")
+ logger.trace("#{new_resource} POST request response: #{body}")
end
end
- # Send a DELETE request to @new_resource.url
+ # Send a DELETE request to new_resource.url
def action_delete
- converge_by("#{@new_resource} DELETE to #{@new_resource.url}") do
+ converge_by("#{new_resource} DELETE to #{new_resource.url}") do
body = @http.delete(
- "#{@new_resource.url}",
- @new_resource.headers
+ "#{new_resource.url}",
+ new_resource.headers
)
- @new_resource.updated_by_last_action(true)
- Chef::Log.info("#{@new_resource} DELETE to #{@new_resource.url} successful")
- Chef::Log.debug("#{@new_resource} DELETE request response: #{body}")
+ new_resource.updated_by_last_action(true)
+ logger.info("#{new_resource} DELETE to #{new_resource.url} successful")
+ logger.trace("#{new_resource} DELETE request response: #{body}")
end
end
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 4cfb257bb9..243c8ee9c3 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -17,32 +17,24 @@
#
require "chef/log"
-require "chef/mixin/command"
require "chef/mixin/shell_out"
require "chef/provider"
require "chef/resource/file"
require "chef/exceptions"
require "erb"
-# Recipe example:
-#
-# int = {Hash with your network settings...}
-#
-# ifconfig int['ip'] do
-# ignore_failure true
-# device int['dev']
-# mask int['mask']
-# gateway int['gateway']
-# mtu int['mtu']
-# end
-
class Chef
class Provider
+ # use the ifconfig resource to manage interfaces on *nix systems
+ #
+ # @example set a static ip on eth1
+ # ifconfig '33.33.33.80' do
+ # device 'eth1'
+ # end
class Ifconfig < Chef::Provider
provides :ifconfig
include Chef::Mixin::ShellOut
- include Chef::Mixin::Command
attr_accessor :config_template
attr_accessor :config_path
@@ -53,43 +45,106 @@ class Chef
@config_path = nil
end
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
+ @current_resource = Chef::Resource::Ifconfig.new(new_resource.name)
@ifconfig_success = true
@interfaces = {}
- @status = shell_out("ifconfig")
- @status.stdout.each_line do |line|
- if !line[0..9].strip.empty?
- @int_name = line[0..9].strip
- @interfaces[@int_name] = { "hwaddr" => (line =~ /(HWaddr)/ ? ($') : "nil").strip.chomp }
- else
- @interfaces[@int_name]["inet_addr"] = (line =~ /inet addr:(\S+)/ ? ($1) : "nil") if line =~ /inet addr:/
- @interfaces[@int_name]["bcast"] = (line =~ /Bcast:(\S+)/ ? ($1) : "nil") if line =~ /Bcast:/
- @interfaces[@int_name]["mask"] = (line =~ /Mask:(\S+)/ ? ($1) : "nil") if line =~ /Mask:/
- @interfaces[@int_name]["mtu"] = (line =~ /MTU:(\S+)/ ? ($1) : "nil") if line =~ /MTU:/
- @interfaces[@int_name]["metric"] = (line =~ /Metric:(\S+)/ ? ($1) : "nil") if line =~ /Metric:/
+ @ifconfig_version = nil
+
+ @net_tools_version = shell_out("ifconfig --version")
+ @net_tools_version.stderr.each_line do |line|
+ if line =~ /^net-tools (\d+.\d+)/
+ @ifconfig_version = line.match(/^net-tools (\d+.\d+)/)[1]
+ end
+ end
+
+ if @ifconfig_version.nil?
+ raise "net-tools not found - this is required for ifconfig"
+ elsif @ifconfig_version.to_f < 2.0
+ # Example output for 1.60 is as follows: (sanitized but format intact)
+ # eth0 Link encap:Ethernet HWaddr 00:00:00:00:00:00
+ # inet addr:192.168.1.1 Bcast:192.168.0.1 Mask:255.255.248.0
+ # inet6 addr: 0000::00:0000:0000:0000/64 Scope:Link
+ # UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
+ # RX packets:65158911 errors:0 dropped:0 overruns:0 frame:0
+ # TX packets:41723949 errors:0 dropped:0 overruns:0 carrier:0
+ # collisions:0 txqueuelen:1000
+ # RX bytes:42664658792 (39.7 GiB) TX bytes:52722603938 (49.1 GiB)
+ # Interrupt:30
+ @status = shell_out("ifconfig")
+ @status.stdout.each_line do |line|
+ if !line[0..9].strip.empty?
+ @int_name = line[0..9].strip
+ @interfaces[@int_name] = { "hwaddr" => (line =~ /(HWaddr)/ ? ($') : "nil").strip.chomp }
+ else
+ @interfaces[@int_name]["inet_addr"] = (line =~ /inet addr:(\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /inet addr:/
+ @interfaces[@int_name]["bcast"] = (line =~ /Bcast:(\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /Bcast:/
+ @interfaces[@int_name]["mask"] = (line =~ /Mask:(\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /Mask:/
+ @interfaces[@int_name]["mtu"] = (line =~ /MTU:(\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /MTU:/
+ @interfaces[@int_name]["metric"] = (line =~ /Metric:(\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /Metric:/
+ end
+
+ next unless @interfaces.key?(new_resource.device)
+ @interface = @interfaces.fetch(new_resource.device)
+
+ current_resource.target(new_resource.target)
+ current_resource.device(new_resource.device)
+ current_resource.inet_addr(@interface["inet_addr"])
+ current_resource.hwaddr(@interface["hwaddr"])
+ current_resource.bcast(@interface["bcast"])
+ current_resource.mask(@interface["mask"])
+ current_resource.mtu(@interface["mtu"])
+ current_resource.metric(@interface["metric"])
end
+ elsif @ifconfig_version.to_f >= 2.0
+ # Example output for 2.10-alpha is as follows: (sanitized but format intact)
+ # eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
+ # inet 192.168.1.1 netmask 255.255.240.0 broadcast 192.168.0.1
+ # inet6 0000::0000:000:0000:0000 prefixlen 64 scopeid 0x20<link>
+ # ether 00:00:00:00:00:00 txqueuelen 1000 (Ethernet)
+ # RX packets 2383836 bytes 1642630840 (1.5 GiB)
+ # RX errors 0 dropped 0 overruns 0 frame 0
+ # TX packets 1244218 bytes 977339327 (932.0 MiB)
+ # TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
+ @status = shell_out("ifconfig")
+ @status.stdout.each_line do |line|
+ addr_regex = /^(\w+):?(\d*):?\ .+$/
+ if line =~ addr_regex
+ if line.match(addr_regex).nil?
+ @int_name = "nil"
+ elsif line.match(addr_regex)[2] == ""
+ @int_name = line.match(addr_regex)[1]
+ @interfaces[@int_name] = Hash.new
+ @interfaces[@int_name]["mtu"] = (line =~ /mtu (\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /mtu/ && @interfaces[@int_name]["mtu"].nil?
+ else
+ @int_name = "#{line.match(addr_regex)[1]}:#{line.match(addr_regex)[2]}"
+ @interfaces[@int_name] = Hash.new
+ @interfaces[@int_name]["mtu"] = (line =~ /mtu (\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /mtu/ && @interfaces[@int_name]["mtu"].nil?
+ end
+ else
+ @interfaces[@int_name]["inet_addr"] = (line =~ /inet (\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /inet/ && @interfaces[@int_name]["inet_addr"].nil?
+ @interfaces[@int_name]["bcast"] = (line =~ /broadcast (\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /broadcast/ && @interfaces[@int_name]["bcast"].nil?
+ @interfaces[@int_name]["mask"] = (line =~ /netmask (\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /netmask/ && @interfaces[@int_name]["mask"].nil?
+ @interfaces[@int_name]["hwaddr"] = (line =~ /ether (\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /ether/ && @interfaces[@int_name]["hwaddr"].nil?
+ @interfaces[@int_name]["metric"] = (line =~ /Metric:(\S+)/ ? Regexp.last_match(1) : "nil") if line =~ /Metric:/ && @interfaces[@int_name]["metric"].nil?
+ end
+
+ next unless @interfaces.key?(new_resource.device)
+ @interface = @interfaces.fetch(new_resource.device)
- if @interfaces.has_key?(@new_resource.device)
- @interface = @interfaces.fetch(@new_resource.device)
-
- @current_resource.target(@new_resource.target)
- @current_resource.device(@new_resource.device)
- @current_resource.inet_addr(@interface["inet_addr"])
- @current_resource.hwaddr(@interface["hwaddr"])
- @current_resource.bcast(@interface["bcast"])
- @current_resource.mask(@interface["mask"])
- @current_resource.mtu(@interface["mtu"])
- @current_resource.metric(@interface["metric"])
+ current_resource.target(new_resource.target)
+ current_resource.device(new_resource.device)
+ current_resource.inet_addr(@interface["inet_addr"])
+ current_resource.hwaddr(@interface["hwaddr"])
+ current_resource.bcast(@interface["bcast"])
+ current_resource.mask(@interface["mask"])
+ current_resource.mtu(@interface["mtu"])
+ current_resource.metric(@interface["metric"])
end
end
- @current_resource
+ current_resource
end
def define_resource_requirements
@@ -104,14 +159,12 @@ class Chef
def action_add
# check to see if load_current_resource found interface in ifconfig
- unless @current_resource.inet_addr
- unless @new_resource.device == loopback_device
+ unless current_resource.inet_addr
+ unless new_resource.device == loopback_device
command = add_command
- converge_by ("run #{command} to add #{@new_resource}") do
- run_command(
- :command => command
- )
- Chef::Log.info("#{@new_resource} added")
+ converge_by("run #{command.join(' ')} to add #{new_resource}") do
+ shell_out_compact!(command)
+ logger.info("#{new_resource} added")
end
end
end
@@ -122,31 +175,25 @@ class Chef
def action_enable
# check to see if load_current_resource found ifconfig
# enables, but does not manage config files
- unless @current_resource.inet_addr
- unless @new_resource.device == loopback_device
- command = enable_command
- converge_by ("run #{command} to enable #{@new_resource}") do
- run_command(
- :command => command
- )
- Chef::Log.info("#{@new_resource} enabled")
- end
- end
+ return if current_resource.inet_addr
+ return if new_resource.device == loopback_device
+ command = enable_command
+ converge_by("run #{command.join(' ')} to enable #{new_resource}") do
+ shell_out_compact!(command)
+ logger.info("#{new_resource} enabled")
end
end
def action_delete
# check to see if load_current_resource found the interface
- if @current_resource.device
+ if current_resource.device
command = delete_command
- converge_by ("run #{command} to delete #{@new_resource}") do
- run_command(
- :command => command
- )
- Chef::Log.info("#{@new_resource} deleted")
+ converge_by("run #{command.join(' ')} to delete #{new_resource}") do
+ shell_out_compact!(command)
+ logger.info("#{new_resource} deleted")
end
else
- Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
+ logger.trace("#{new_resource} does not exist - nothing to do")
end
delete_config
end
@@ -154,21 +201,19 @@ class Chef
def action_disable
# check to see if load_current_resource found the interface
# disables, but leaves config files in place.
- if @current_resource.device
+ if current_resource.device
command = disable_command
- converge_by ("run #{command} to disable #{@new_resource}") do
- run_command(
- :command => command
- )
- Chef::Log.info("#{@new_resource} disabled")
+ converge_by("run #{command.join(' ')} to disable #{new_resource}") do
+ shell_out_compact!(command)
+ logger.info("#{new_resource} disabled")
end
else
- Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
+ logger.trace("#{new_resource} does not exist - nothing to do")
end
end
def can_generate_config?
- ! @config_template.nil? && ! @config_path.nil?
+ !@config_template.nil? && !@config_path.nil?
end
def resource_for_config(path)
@@ -182,40 +227,40 @@ class Chef
config = resource_for_config(@config_path)
config.content(template.result(b))
config.run_action(:create)
- @new_resource.updated_by_last_action(true) if config.updated?
+ new_resource.updated_by_last_action(true) if config.updated?
end
def delete_config
return unless can_generate_config?
config = resource_for_config(@config_path)
config.run_action(:delete)
- @new_resource.updated_by_last_action(true) if config.updated?
+ new_resource.updated_by_last_action(true) if config.updated?
end
private
def add_command
- command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
- command << " netmask #{@new_resource.mask}" if @new_resource.mask
- command << " metric #{@new_resource.metric}" if @new_resource.metric
- command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
+ command = [ "ifconfig", new_resource.device, new_resource.target ]
+ command += [ "netmask", new_resource.mask ] if new_resource.mask
+ command += [ "metric", new_resource.metric ] if new_resource.metric
+ command += [ "mtu", new_resource.mtu ] if new_resource.mtu
command
end
def enable_command
- command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
- command << " netmask #{@new_resource.mask}" if @new_resource.mask
- command << " metric #{@new_resource.metric}" if @new_resource.metric
- command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
+ command = [ "ifconfig", new_resource.device, new_resource.target ]
+ command += [ "netmask", new_resource.mask ] if new_resource.mask
+ command += [ "metric", new_resource.metric ] if new_resource.metric
+ command += [ "mtu", new_resource.mtu ] if new_resource.mtu
command
end
def disable_command
- "ifconfig #{@new_resource.device} down"
+ [ "ifconfig", new_resource.device, "down" ]
end
def delete_command
- "ifconfig #{@new_resource.device} down"
+ [ "ifconfig", new_resource.device, "down" ]
end
def loopback_device
diff --git a/lib/chef/provider/ifconfig/aix.rb b/lib/chef/provider/ifconfig/aix.rb
index 81164db304..b68c5d5364 100644
--- a/lib/chef/provider/ifconfig/aix.rb
+++ b/lib/chef/provider/ifconfig/aix.rb
@@ -22,64 +22,59 @@ class Chef
class Provider
class Ifconfig
class Aix < Chef::Provider::Ifconfig
- provides :ifconfig, platform: %w{aix}
+ provides :ifconfig, platform: "aix"
def load_current_resource
- @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
+ @current_resource = Chef::Resource::Ifconfig.new(new_resource.name)
@interface_exists = false
found_interface = false
interface = {}
- @status = shell_out("ifconfig -a")
+ @status = shell_out_compact("ifconfig", "-a")
@status.stdout.each_line do |line|
if !found_interface
if line =~ /^(\S+):\sflags=(\S+)/
- # We have interface name, if this is the interface for @current_resource, load info else skip till next interface is found.
- if $1 == @new_resource.device
+ # We have interface name, if this is the interface for current_resource, load info else skip till next interface is found.
+ if Regexp.last_match(1) == new_resource.device
# Found interface
found_interface = true
@interface_exists = true
- @current_resource.target(@new_resource.target)
- @current_resource.device($1)
- interface[:flags] = $2
- @current_resource.metric($1) if line =~ /metric\s(\S+)/
- end
- end
- else
- # parse interface related information, stop when next interface is found.
- if line =~ /^(\S+):\sflags=(\S+)/
- # we are done parsing interface info and hit another one, so stop.
- found_interface = false
- break
- else
- if found_interface
- # read up interface info
- @current_resource.inet_addr($1) if line =~ /inet\s(\S+)\s/
- @current_resource.bcast($1) if line =~ /broadcast\s(\S+)/
- @current_resource.mask(hex_to_dec_netmask($1)) if line =~ /netmask\s(\S+)\s/
+ current_resource.target(new_resource.target)
+ current_resource.device(Regexp.last_match(1))
+ interface[:flags] = Regexp.last_match(2)
+ current_resource.metric(Regexp.last_match(1)) if line =~ /metric\s(\S+)/
end
end
+ elsif line =~ /^(\S+):\sflags=(\S+)/
+ # we are done parsing interface info and hit another one, so stop.
+ found_interface = false
+ break
+ elsif found_interface
+ # read up interface info
+ current_resource.inet_addr(Regexp.last_match(1)) if line =~ /inet\s(\S+)\s/
+ current_resource.bcast(Regexp.last_match(1)) if line =~ /broadcast\s(\S+)/
+ current_resource.mask(hex_to_dec_netmask(Regexp.last_match(1))) if line =~ /netmask\s(\S+)\s/
end
end
- @current_resource
+ current_resource
end
private
def add_command
# ifconfig changes are temporary, chdev persist across reboots.
- raise Chef::Exceptions::Ifconfig, "interface metric attribute cannot be set for :add action" if @new_resource.metric
- command = "chdev -l #{@new_resource.device} -a netaddr=#{@new_resource.name}"
- command << " -a netmask=#{@new_resource.mask}" if @new_resource.mask
- command << " -a mtu=#{@new_resource.mtu}" if @new_resource.mtu
+ raise Chef::Exceptions::Ifconfig, "interface metric attribute cannot be set for :add action" if new_resource.metric
+ command = [ "chdev", "-l", new_resource.device, "-a", "netaddr=#{new_resource.name}" ]
+ command += [ "-a", "netmask=#{new_resource.mask}" ] if new_resource.mask
+ command += [ "-a", "mtu=#{new_resource.mtu}" ] if new_resource.mtu
command
end
def delete_command
# ifconfig changes are temporary, chdev persist across reboots.
- "chdev -l #{@new_resource.device} -a state=down"
+ [ "chdev", "-l", new_resource.device, "-a", "state=down" ]
end
def loopback_device
diff --git a/lib/chef/provider/ifconfig/debian.rb b/lib/chef/provider/ifconfig/debian.rb
index 872b0db152..aee3ca02dc 100644
--- a/lib/chef/provider/ifconfig/debian.rb
+++ b/lib/chef/provider/ifconfig/debian.rb
@@ -26,32 +26,32 @@ class Chef
provides :ifconfig, platform: %w{ubuntu}, platform_version: ">= 11.10"
provides :ifconfig, platform: %w{debian}, platform_version: ">= 7.0"
- INTERFACES_FILE = "/etc/network/interfaces"
- INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d"
+ INTERFACES_FILE = "/etc/network/interfaces".freeze
+ INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d".freeze
def initialize(new_resource, run_context)
super(new_resource, run_context)
@config_template = %{
-<% if @new_resource.device %>
-<% if @new_resource.onboot == "yes" %>auto <%= @new_resource.device %><% end %>
-<% case @new_resource.bootproto
+<% if new_resource.device %>
+<% if new_resource.onboot == "yes" %>auto <%= new_resource.device %><% end %>
+<% case new_resource.bootproto
when "dhcp" %>
-iface <%= @new_resource.device %> inet dhcp
+iface <%= new_resource.device %> <%= new_resource.family %> dhcp
<% when "bootp" %>
-iface <%= @new_resource.device %> inet bootp
+iface <%= new_resource.device %> <%= new_resource.family %> bootp
<% else %>
-iface <%= @new_resource.device %> inet static
- <% if @new_resource.target %>address <%= @new_resource.target %><% end %>
- <% if @new_resource.mask %>netmask <%= @new_resource.mask %><% end %>
- <% if @new_resource.network %>network <%= @new_resource.network %><% end %>
- <% if @new_resource.bcast %>broadcast <%= @new_resource.bcast %><% end %>
- <% if @new_resource.metric %>metric <%= @new_resource.metric %><% end %>
- <% if @new_resource.hwaddr %>hwaddress <%= @new_resource.hwaddr %><% end %>
- <% if @new_resource.mtu %>mtu <%= @new_resource.mtu %><% end %>
+iface <%= new_resource.device %> <%= new_resource.family %> static
+ <% if new_resource.target %>address <%= new_resource.target %><% end %>
+ <% if new_resource.mask %>netmask <%= new_resource.mask %><% end %>
+ <% if new_resource.network %>network <%= new_resource.network %><% end %>
+ <% if new_resource.bcast %>broadcast <%= new_resource.bcast %><% end %>
+ <% if new_resource.metric %>metric <%= new_resource.metric %><% end %>
+ <% if new_resource.hwaddr %>hwaddress <%= new_resource.hwaddr %><% end %>
+ <% if new_resource.mtu %>mtu <%= new_resource.mtu %><% end %>
<% end %>
<% end %>
}
- @config_path = "#{INTERFACES_DOT_D_DIR}/ifcfg-#{@new_resource.device}"
+ @config_path = "#{INTERFACES_DOT_D_DIR}/ifcfg-#{new_resource.device}"
end
def generate_config
@@ -62,6 +62,8 @@ iface <%= @new_resource.device %> inet static
protected
def enforce_interfaces_dot_d_sanity
+ # on ubuntu 18.04 there's no interfaces file and it uses interfaces.d by default
+ return if ::File.directory?(INTERFACES_DOT_D_DIR) && !::File.exist?(INTERFACES_FILE)
# create /etc/network/interfaces.d via dir resource (to get reporting, etc)
dir = Chef::Resource::Directory.new(INTERFACES_DOT_D_DIR, run_context)
dir.run_action(:create)
@@ -69,12 +71,13 @@ iface <%= @new_resource.device %> inet static
# roll our own file_edit resource, this will not get reported until we have a file_edit resource
interfaces_dot_d_for_regexp = INTERFACES_DOT_D_DIR.gsub(/\./, '\.') # escape dots for the regexp
regexp = %r{^\s*source\s+#{interfaces_dot_d_for_regexp}/\*\s*$}
- unless ::File.exists?(INTERFACES_FILE) && regexp.match(IO.read(INTERFACES_FILE))
- converge_by("modifying #{INTERFACES_FILE} to source #{INTERFACES_DOT_D_DIR}") do
- conf = Chef::Util::FileEdit.new(INTERFACES_FILE)
- conf.insert_line_if_no_match(regexp, "source #{INTERFACES_DOT_D_DIR}/*")
- conf.write_file
- end
+
+ return if ::File.exist?(INTERFACES_FILE) && regexp.match(IO.read(INTERFACES_FILE))
+
+ converge_by("modifying #{INTERFACES_FILE} to source #{INTERFACES_DOT_D_DIR}") do
+ conf = Chef::Util::FileEdit.new(INTERFACES_FILE)
+ conf.insert_line_if_no_match(regexp, "source #{INTERFACES_DOT_D_DIR}/*")
+ conf.write_file
end
end
diff --git a/lib/chef/provider/ifconfig/redhat.rb b/lib/chef/provider/ifconfig/redhat.rb
index 0c28e6407a..bf3d979e86 100644
--- a/lib/chef/provider/ifconfig/redhat.rb
+++ b/lib/chef/provider/ifconfig/redhat.rb
@@ -22,24 +22,28 @@ class Chef
class Provider
class Ifconfig
class Redhat < Chef::Provider::Ifconfig
- provides :ifconfig, platform_family: %w{fedora rhel}
+ provides :ifconfig, platform_family: %w{fedora rhel amazon}
def initialize(new_resource, run_context)
super(new_resource, run_context)
@config_template = %{
-<% if @new_resource.device %>DEVICE=<%= @new_resource.device %><% end %>
-<% if @new_resource.onboot == "yes" %>ONBOOT=<%= @new_resource.onboot %><% end %>
-<% if @new_resource.bootproto %>BOOTPROTO=<%= @new_resource.bootproto %><% end %>
-<% if @new_resource.target %>IPADDR=<%= @new_resource.target %><% end %>
-<% if @new_resource.mask %>NETMASK=<%= @new_resource.mask %><% end %>
-<% if @new_resource.network %>NETWORK=<%= @new_resource.network %><% end %>
-<% if @new_resource.bcast %>BROADCAST=<%= @new_resource.bcast %><% end %>
-<% if @new_resource.onparent %>ONPARENT=<%= @new_resource.onparent %><% end %>
-<% if @new_resource.hwaddr %>HWADDR=<%= @new_resource.hwaddr %><% end %>
-<% if @new_resource.metric %>METRIC=<%= @new_resource.metric %><% end %>
-<% if @new_resource.mtu %>MTU=<%= @new_resource.mtu %><% end %>
+<% if new_resource.device %>DEVICE=<%= new_resource.device %><% end %>
+<% if new_resource.onboot == "yes" %>ONBOOT=<%= new_resource.onboot %><% end %>
+<% if new_resource.bootproto %>BOOTPROTO=<%= new_resource.bootproto %><% end %>
+<% if new_resource.target %>IPADDR=<%= new_resource.target %><% end %>
+<% if new_resource.mask %>NETMASK=<%= new_resource.mask %><% end %>
+<% if new_resource.network %>NETWORK=<%= new_resource.network %><% end %>
+<% if new_resource.bcast %>BROADCAST=<%= new_resource.bcast %><% end %>
+<% if new_resource.onparent %>ONPARENT=<%= new_resource.onparent %><% end %>
+<% if new_resource.hwaddr %>HWADDR=<%= new_resource.hwaddr %><% end %>
+<% if new_resource.metric %>METRIC=<%= new_resource.metric %><% end %>
+<% if new_resource.mtu %>MTU=<%= new_resource.mtu %><% end %>
+<% if new_resource.ethtool_opts %>ETHTOOL_OPTS="<%= new_resource.ethtool_opts %>"<% end %>
+<% if new_resource.bonding_opts %>BONDING_OPTS="<%= new_resource.bonding_opts %>"<% end %>
+<% if new_resource.master %>MASTER=<%= new_resource.master %><% end %>
+<% if new_resource.slave %>SLAVE=<%= new_resource.slave %><% end %>
}
- @config_path = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+ @config_path = "/etc/sysconfig/network-scripts/ifcfg-#{new_resource.device}"
end
end
diff --git a/lib/chef/provider/launchd.rb b/lib/chef/provider/launchd.rb
index c58d4bfa34..0bac995247 100644
--- a/lib/chef/provider/launchd.rb
+++ b/lib/chef/provider/launchd.rb
@@ -17,7 +17,6 @@
#
require "chef/provider"
-require "chef/resource/launchd"
require "chef/resource/file"
require "chef/resource/cookbook_file"
require "chef/resource/macosx_service"
@@ -30,7 +29,7 @@ class Chef
extend Forwardable
provides :launchd, os: "darwin"
- def_delegators :@new_resource, *[
+ def_delegators :new_resource, *[
:backup,
:cookbook,
:group,
@@ -85,7 +84,12 @@ class Chef
manage_service(:disable)
end
+ def action_restart
+ manage_service(:restart)
+ end
+
def manage_plist(action)
+ return unless manage_agent?(action)
if source
res = cookbook_file_resource
else
@@ -97,11 +101,30 @@ class Chef
end
def manage_service(action)
+ return unless manage_agent?(action)
res = service_resource
res.run_action(action)
new_resource.updated_by_last_action(true) if res.updated?
end
+ def manage_agent?(action)
+ # Gets UID of console_user and converts to string.
+ console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
+ root = console_user == "root"
+ agent = type == "agent"
+ invalid_action = [:delete, :disable, :enable, :restart].include?(action)
+ lltstype = ""
+ if new_resource.limit_load_to_session_type
+ lltstype = new_resource.limit_load_to_session_type
+ end
+ invalid_type = lltstype != "LoginWindow"
+ if root && agent && invalid_action && invalid_type
+ logger.trace("#{label}: Aqua LaunchAgents shouldn't be loaded as root")
+ return false
+ end
+ true
+ end
+
def service_resource
res = Chef::Resource::MacosxService.new(label, run_context)
res.name(label) if label
@@ -115,7 +138,7 @@ class Chef
res = Chef::Resource::File.new(@path, run_context)
res.name(@path) if @path
res.backup(backup) if backup
- res.content(content) if content
+ res.content(content) if content?
res.group(group) if group
res.mode(mode) if mode
res.owner(owner) if owner
@@ -150,7 +173,7 @@ class Chef
end
def content
- plist_hash = new_resource.hash || gen_hash
+ plist_hash = new_resource.plist_hash || gen_hash
Plist::Emit.dump(plist_hash) unless plist_hash.nil?
end
diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb
index 5fce97e5b3..783d52d09a 100644
--- a/lib/chef/provider/link.rb
+++ b/lib/chef/provider/link.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -42,46 +42,42 @@ class Chef
private :negative_complement
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Link.new(@new_resource.name)
- @current_resource.target_file(@new_resource.target_file)
- if file_class.symlink?(@current_resource.target_file)
- @current_resource.link_type(:symbolic)
- @current_resource.to(
- canonicalize(file_class.readlink(@current_resource.target_file))
+ @current_resource = Chef::Resource::Link.new(new_resource.name)
+ current_resource.target_file(new_resource.target_file)
+ if file_class.symlink?(current_resource.target_file)
+ current_resource.link_type(:symbolic)
+ current_resource.to(
+ canonicalize(file_class.readlink(current_resource.target_file))
)
else
- @current_resource.link_type(:hard)
- if ::File.exists?(@current_resource.target_file)
- if ::File.exists?(@new_resource.to) &&
- file_class.stat(@current_resource.target_file).ino ==
- file_class.stat(@new_resource.to).ino
- @current_resource.to(canonicalize(@new_resource.to))
+ current_resource.link_type(:hard)
+ if ::File.exists?(current_resource.target_file)
+ if ::File.exists?(new_resource.to) &&
+ file_class.stat(current_resource.target_file).ino ==
+ file_class.stat(new_resource.to).ino
+ current_resource.to(canonicalize(new_resource.to))
else
- @current_resource.to("")
+ current_resource.to("")
end
end
end
- ScanAccessControl.new(@new_resource, @current_resource).set_all!
- @current_resource
+ ScanAccessControl.new(new_resource, current_resource).set_all!
+ current_resource
end
def define_resource_requirements
requirements.assert(:delete) do |a|
a.assertion do
- if @current_resource.to
- @current_resource.link_type == @new_resource.link_type &&
- (@current_resource.link_type == :symbolic || @current_resource.to != "")
+ if current_resource.to
+ current_resource.link_type == new_resource.link_type &&
+ (current_resource.link_type == :symbolic || current_resource.to != "")
else
true
end
end
- a.failure_message Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type} link."
- a.whyrun("Would assume the link at #{@new_resource.target_file} was previously created")
+ a.failure_message Chef::Exceptions::Link, "Cannot delete #{new_resource} at #{new_resource.target_file}! Not a #{new_resource.link_type} link."
+ a.whyrun("Would assume the link at #{new_resource.target_file} was previously created")
end
end
@@ -95,42 +91,48 @@ class Chef
# to - the location to link to
# target_file - the name of the link
- if @current_resource.to != canonicalize(@new_resource.to) ||
- @current_resource.link_type != @new_resource.link_type
+ if current_resource.to != canonicalize(new_resource.to) ||
+ current_resource.link_type != new_resource.link_type
# Handle the case where the symlink already exists and is pointing at a valid to_file
- if @current_resource.to
+ if current_resource.to
# On Windows, to fix a symlink already pointing at a directory we must first
# ::Dir.unlink the symlink (not the directory), while if we have a symlink
# pointing at file we must use ::File.unlink on the symlink.
# However if the new symlink will point to a file and the current symlink is pointing at a
# directory we want to throw an exception and calling ::File.unlink on the directory symlink
# will throw the correct ones.
- if Chef::Platform.windows? && ::File.directory?(@new_resource.to) &&
- ::File.directory?(@current_resource.target_file)
- converge_by("unlink existing windows symlink to dir at #{@new_resource.target_file}") do
- ::Dir.unlink(@new_resource.target_file)
+ if Chef::Platform.windows? && ::File.directory?(new_resource.to) &&
+ ::File.directory?(current_resource.target_file)
+ converge_by("unlink existing windows symlink to dir at #{new_resource.target_file}") do
+ ::Dir.unlink(new_resource.target_file)
end
else
- converge_by("unlink existing symlink to file at #{@new_resource.target_file}") do
- ::File.unlink(@new_resource.target_file)
+ converge_by("unlink existing symlink to file at #{new_resource.target_file}") do
+ ::File.unlink(new_resource.target_file)
end
end
end
- if @new_resource.link_type == :symbolic
- converge_by("create symlink at #{@new_resource.target_file} to #{@new_resource.to}") do
- file_class.symlink(canonicalize(@new_resource.to), @new_resource.target_file)
- Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.target_file} -> #{@new_resource.to}")
- Chef::Log.info("#{@new_resource} created")
+ if new_resource.link_type == :symbolic
+ converge_by("create symlink at #{new_resource.target_file} to #{new_resource.to}") do
+ file_class.symlink(canonicalize(new_resource.to), new_resource.target_file)
+ logger.trace("#{new_resource} created #{new_resource.link_type} link from #{new_resource.target_file} -> #{new_resource.to}")
+ logger.info("#{new_resource} created")
+ # file_class.symlink will create the link with default access controls.
+ # This means that the access controls of the file could be different
+ # than those captured during the initial evaluation of current_resource.
+ # We need to re-evaluate the current_resource to ensure that the desired
+ # access controls are applied.
+ ScanAccessControl.new(new_resource, current_resource).set_all!
end
- elsif @new_resource.link_type == :hard
- converge_by("create hard link at #{@new_resource.target_file} to #{@new_resource.to}") do
- file_class.link(@new_resource.to, @new_resource.target_file)
- Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.target_file} -> #{@new_resource.to}")
- Chef::Log.info("#{@new_resource} created")
+ elsif new_resource.link_type == :hard
+ converge_by("create hard link at #{new_resource.target_file} to #{new_resource.to}") do
+ file_class.link(new_resource.to, new_resource.target_file)
+ logger.trace("#{new_resource} created #{new_resource.link_type} link from #{new_resource.target_file} -> #{new_resource.to}")
+ logger.info("#{new_resource} created")
end
end
end
- if @new_resource.link_type == :symbolic
+ if new_resource.link_type == :symbolic
if access_controls.requires_changes?
converge_by(access_controls.describe_changes) do
access_controls.set_all
@@ -140,10 +142,17 @@ class Chef
end
def action_delete
- if @current_resource.to # Exists
- converge_by("delete link at #{@new_resource.target_file}") do
- ::File.delete(@new_resource.target_file)
- Chef::Log.info("#{@new_resource} deleted")
+ if current_resource.to # Exists
+ if Chef::Platform.windows? && ::File.directory?(current_resource.target_file)
+ converge_by("delete link to dir at #{new_resource.target_file}") do
+ ::Dir.delete(new_resource.target_file)
+ logger.info("#{new_resource} deleted")
+ end
+ else
+ converge_by("delete link to file at #{new_resource.target_file}") do
+ ::File.delete(new_resource.target_file)
+ logger.info("#{new_resource} deleted")
+ end
end
end
end
@@ -152,7 +161,7 @@ class Chef
# access control (e.g., use lchmod instead of chmod) if the resource is a
# symlink.
def manage_symlink_access?
- @new_resource.link_type == :symbolic
+ new_resource.link_type == :symbolic
end
end
end
diff --git a/lib/chef/provider/log.rb b/lib/chef/provider/log.rb
index eef4077c07..8d15883ff6 100644
--- a/lib/chef/provider/log.rb
+++ b/lib/chef/provider/log.rb
@@ -1,6 +1,6 @@
#
# Author:: Cary Penniman (<cary@rightscale.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,41 +17,27 @@
#
class Chef
-
class Provider
-
class Log
-
- # Chef log provider, allows logging to chef's logs from recipes
+ # Chef log provider, allows logging to chef's logs
class ChefLog < Chef::Provider
-
provides :log
- def whyrun_supported?
- true
- end
-
# No concept of a 'current' resource for logs, this is a no-op
#
- # === Return
- # true:: Always return true
+ # @return [true] Always returns true
def load_current_resource
true
end
# Write the log to Chef's log
#
- # === Return
- # true:: Always return true
+ # @return [true] Always returns true
def action_write
- Chef::Log.send(@new_resource.level, @new_resource.message)
- @new_resource.updated_by_last_action(true)
+ logger.send(new_resource.level, new_resource.message)
+ new_resource.updated_by_last_action(true) if Chef::Config[:count_log_resource_updates]
end
-
end
-
end
-
end
-
end
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index f5ba30ba15..70f2af6539 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -34,7 +34,6 @@ class Chef
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
# They are not included by its replacement, Chef::DSL::Recipe, but
# they may be used in existing LWRPs.
- include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
# Allow include_recipe from within LWRP provider code
@@ -53,7 +52,7 @@ class Chef
def build_from_file(cookbook_name, filename, run_context)
if LWRPBase.loaded_lwrps[filename]
- Chef::Log.debug("LWRP provider #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
+ Chef::Log.trace("LWRP provider #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
return loaded_lwrps[filename]
end
@@ -72,22 +71,13 @@ class Chef
define_singleton_method(:inspect) { to_s }
end
- Chef::Log.debug("Loaded contents of #{filename} into provider #{resource_name} (#{provider_class})")
+ Chef::Log.trace("Loaded contents of #{filename} into provider #{resource_name} (#{provider_class})")
LWRPBase.loaded_lwrps[filename] = true
- Chef::Provider.register_deprecated_lwrp_class(provider_class, convert_to_class_name(resource_name))
-
provider_class
end
- # DSL for defining a provider's actions.
- def action(name, &block)
- define_method("action_#{name}") do
- instance_eval(&block)
- end
- end
-
protected
def loaded_lwrps
diff --git a/lib/chef/provider/mdadm.rb b/lib/chef/provider/mdadm.rb
index f8225ff63a..c5f15a851a 100644
--- a/lib/chef/provider/mdadm.rb
+++ b/lib/chef/provider/mdadm.rb
@@ -25,66 +25,58 @@ class Chef
provides :mdadm
- def popen4
- raise Exception, "deprecated"
- end
-
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Mdadm.new(@new_resource.name)
- @current_resource.raid_device(@new_resource.raid_device)
- Chef::Log.debug("#{@new_resource} checking for software raid device #{@current_resource.raid_device}")
+ @current_resource = Chef::Resource::Mdadm.new(new_resource.name)
+ current_resource.raid_device(new_resource.raid_device)
+ logger.trace("#{new_resource} checking for software raid device #{current_resource.raid_device}")
device_not_found = 4
- mdadm = shell_out!("mdadm --detail --test #{@new_resource.raid_device}", :returns => [0, device_not_found])
+ mdadm = shell_out!("mdadm --detail --test #{new_resource.raid_device}", :returns => [0, device_not_found])
exists = (mdadm.status == 0)
- @current_resource.exists(exists)
+ current_resource.exists(exists)
end
def action_create
- unless @current_resource.exists
+ unless current_resource.exists
converge_by("create RAID device #{new_resource.raid_device}") do
- command = "yes | mdadm --create #{@new_resource.raid_device} --level #{@new_resource.level}"
- command << " --chunk=#{@new_resource.chunk}" unless @new_resource.level == 1
- command << " --metadata=#{@new_resource.metadata}"
- command << " --bitmap=#{@new_resource.bitmap}" if @new_resource.bitmap
- command << " --layout=#{@new_resource.layout}" if @new_resource.layout
- command << " --raid-devices #{@new_resource.devices.length} #{@new_resource.devices.join(" ")}"
- Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+ command = "yes | mdadm --create #{new_resource.raid_device} --level #{new_resource.level}"
+ command << " --chunk=#{new_resource.chunk}" unless new_resource.level == 1
+ command << " --metadata=#{new_resource.metadata}"
+ command << " --bitmap=#{new_resource.bitmap}" if new_resource.bitmap
+ command << " --layout=#{new_resource.layout}" if new_resource.layout
+ command << " --raid-devices #{new_resource.devices.length} #{new_resource.devices.join(" ")}"
+ logger.trace("#{new_resource} mdadm command: #{command}")
shell_out!(command)
- Chef::Log.info("#{@new_resource} created raid device (#{@new_resource.raid_device})")
+ logger.info("#{new_resource} created raid device (#{new_resource.raid_device})")
end
else
- Chef::Log.debug("#{@new_resource} raid device already exists, skipping create (#{@new_resource.raid_device})")
+ logger.trace("#{new_resource} raid device already exists, skipping create (#{new_resource.raid_device})")
end
end
def action_assemble
- unless @current_resource.exists
+ unless current_resource.exists
converge_by("assemble RAID device #{new_resource.raid_device}") do
- command = "yes | mdadm --assemble #{@new_resource.raid_device} #{@new_resource.devices.join(" ")}"
- Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+ command = "yes | mdadm --assemble #{new_resource.raid_device} #{new_resource.devices.join(" ")}"
+ logger.trace("#{new_resource} mdadm command: #{command}")
shell_out!(command)
- Chef::Log.info("#{@new_resource} assembled raid device (#{@new_resource.raid_device})")
+ logger.info("#{new_resource} assembled raid device (#{new_resource.raid_device})")
end
else
- Chef::Log.debug("#{@new_resource} raid device already exists, skipping assemble (#{@new_resource.raid_device})")
+ logger.trace("#{new_resource} raid device already exists, skipping assemble (#{new_resource.raid_device})")
end
end
def action_stop
- if @current_resource.exists
+ if current_resource.exists
converge_by("stop RAID device #{new_resource.raid_device}") do
- command = "yes | mdadm --stop #{@new_resource.raid_device}"
- Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+ command = "yes | mdadm --stop #{new_resource.raid_device}"
+ logger.trace("#{new_resource} mdadm command: #{command}")
shell_out!(command)
- Chef::Log.info("#{@new_resource} stopped raid device (#{@new_resource.raid_device})")
+ logger.info("#{new_resource} stopped raid device (#{new_resource.raid_device})")
end
else
- Chef::Log.debug("#{@new_resource} raid device doesn't exist (#{@new_resource.raid_device}) - not stopping")
+ logger.trace("#{new_resource} raid device doesn't exist (#{new_resource.raid_device}) - not stopping")
end
end
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 9e9ee29bde..994d67939b 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -1,7 +1,7 @@
#
# Author:: Joshua Timberman (<joshua@chef.io>)
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software, Inc.
+# Copyright:: Copyright 2009-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,10 +28,6 @@ class Chef
attr_accessor :unmount_retries
- def whyrun_supported?
- true
- end
-
def load_current_resource
true
end
@@ -45,10 +41,10 @@ class Chef
unless current_resource.mounted
converge_by("mount #{current_resource.device} to #{current_resource.mount_point}") do
mount_fs
- Chef::Log.info("#{new_resource} mounted")
+ logger.info("#{new_resource} mounted")
end
else
- Chef::Log.debug("#{new_resource} is already mounted")
+ logger.trace("#{new_resource} is already mounted")
end
end
@@ -56,10 +52,10 @@ class Chef
if current_resource.mounted
converge_by("unmount #{current_resource.device}") do
umount_fs
- Chef::Log.info("#{new_resource} unmounted")
+ logger.info("#{new_resource} unmounted")
end
else
- Chef::Log.debug("#{new_resource} is already unmounted")
+ logger.trace("#{new_resource} is already unmounted")
end
end
@@ -68,32 +64,32 @@ class Chef
if new_resource.supports[:remount]
converge_by("remount #{current_resource.device}") do
remount_fs
- Chef::Log.info("#{new_resource} remounted")
+ logger.info("#{new_resource} remounted")
end
else
converge_by("unmount #{current_resource.device}") do
umount_fs
- Chef::Log.info("#{new_resource} unmounted")
+ logger.info("#{new_resource} unmounted")
end
wait_until_unmounted(unmount_retries)
converge_by("mount #{current_resource.device}") do
mount_fs
- Chef::Log.info("#{new_resource} mounted")
+ logger.info("#{new_resource} mounted")
end
end
else
- Chef::Log.debug("#{new_resource} not mounted, nothing to remount")
+ logger.trace("#{new_resource} not mounted, nothing to remount")
end
end
def action_enable
- unless current_resource.enabled && mount_options_unchanged?
+ unless current_resource.enabled && mount_options_unchanged? && device_unchanged?
converge_by("enable #{current_resource.device}") do
enable_fs
- Chef::Log.info("#{new_resource} enabled")
+ logger.info("#{new_resource} enabled")
end
else
- Chef::Log.debug("#{new_resource} already enabled")
+ logger.trace("#{new_resource} already enabled")
end
end
@@ -101,13 +97,15 @@ class Chef
if current_resource.enabled
converge_by("disable #{current_resource.device}") do
disable_fs
- Chef::Log.info("#{new_resource} disabled")
+ logger.info("#{new_resource} disabled")
end
else
- Chef::Log.debug("#{new_resource} already disabled")
+ logger.trace("#{new_resource} already disabled")
end
end
+ alias :action_unmount :action_umount
+
#
# Abstract Methods to be implemented by subclasses
#
@@ -122,6 +120,14 @@ class Chef
raise Chef::Exceptions::UnsupportedAction, "#{self} does not implement #mount_options_unchanged?"
end
+ # It's entirely plausible that a site might prefer UUIDs or labels, so
+ # we need to be able to update fstab to conform with their wishes
+ # without necessarily needing to remount the device.
+ # See #6851 for more.
+ def device_unchanged?
+ @current_resource.device == @new_resource.device
+ end
+
#
# NOTE: for the following methods, this superclass will already have checked if the filesystem is
# enabled and/or mounted and they will be called in converge_by blocks, so most defensive checking
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 12f0d67e6b..c1ed499957 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -1,6 +1,5 @@
#
-# Author::
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright 2009-2018, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,14 +21,14 @@ class Chef
class Provider
class Mount
class Aix < Chef::Provider::Mount::Mount
- provides :mount, platform: %w{aix}
+ provides :mount, platform: "aix"
# Override for aix specific handling
def initialize(new_resource, run_context)
super
# options and fstype are set to "defaults" and "auto" respectively in the Mount Resource class. These options are not valid for AIX, override them.
if @new_resource.options[0] == "defaults"
- @new_resource.options.clear
+ @new_resource.options([])
end
if @new_resource.fstype == "auto"
@new_resource.send(:clear_fstype)
@@ -40,30 +39,60 @@ class Chef
# Check to see if there is an entry in /etc/filesystems. Last entry for a volume wins. Using command "lsfs" to fetch entries.
enabled = false
+ regex_arr = device_fstab_regex.split(":")
+ if regex_arr.size == 2
+ nodename = regex_arr[0]
+ devicename = regex_arr[1]
+ else
+ devicename = regex_arr[0]
+ end
# lsfs o/p = #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct
# search only for current mount point
shell_out("lsfs -c #{@new_resource.mount_point}").stdout.each_line do |line|
case line
when /^#\s/
next
- when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}:(\S+):(\[\S+\])?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
+ when /^#{Regexp.escape(@new_resource.mount_point)}:#{devicename}:(\S+):#{nodename}:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
# mount point entry with ipv6 address for nodename (ipv6 address use ':')
enabled = true
@current_resource.fstype($1)
- @current_resource.options($5)
- Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems")
+ @current_resource.options($4)
+ logger.trace("Found mount point #{@new_resource.mount_point} :: device_type #{@current_resource.device_type} in /etc/filesystems")
next
- when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}::(\S+):(\S+)?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
+ when /^#{Regexp.escape(@new_resource.mount_point)}:#{nodename}:(\S+)::(\S+)?:(\S+):(\S+):(\S+):(\S+)/
# mount point entry with hostname or ipv4 address
enabled = true
@current_resource.fstype($1)
+ @current_resource.options($4)
+ @current_resource.device("")
+ logger.trace("Found mount point #{@new_resource.mount_point} :: device_type #{@current_resource.device_type} in /etc/filesystems")
+ next
+ when /^#{Regexp.escape(@new_resource.mount_point)}:(\S+)?:(\S+):#{devicename}:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
+ # mount point entry with hostname or ipv4 address
+ enabled = true
+ @current_resource.fstype($2)
@current_resource.options($5)
- Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems")
+ @current_resource.device(devicename + "/")
+ logger.trace("Found mount point #{@new_resource.mount_point} :: device_type #{@current_resource.device_type} in /etc/filesystems")
next
- when /^#{Regexp.escape(@new_resource.mount_point)}/
- enabled = false
- Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/filesystems")
+ when /^#{Regexp.escape(@new_resource.mount_point)}:(.*?):(.*?):(.*?):(.*?):(.*?):(.*?):(.*?):(.*?)/
+ if $3.split("=")[0] == "LABEL" || $1.split("=")[0] == "LABEL"
+ @current_resource.device_type("label")
+ elsif $3.split("=")[0] == "UUID" || $1.split("=")[0] == "UUID"
+ @current_resource.device_type("uuid")
+ else
+ @current_resource.device_type("device")
+ end
+
+ if @current_resource.device_type != @new_resource.device_type
+ enabled = true
+ logger.trace("Found mount point #{@new_resource.mount_point} :: device_type #{@current_resource.device_type} in /etc/filesystems")
+ else
+ enabled = false
+ logger.trace("Found conflicting mount point #{@new_resource.mount_point} in /etc/filesystems")
+ end
end
+
end
@current_resource.enabled(enabled)
end
@@ -80,10 +109,10 @@ class Chef
case line
when /#{search_device}\s+#{Regexp.escape(@new_resource.mount_point)}/
mounted = true
- Chef::Log.debug("Special device #{device_logstring} mounted as #{@new_resource.mount_point}")
+ logger.trace("Special device #{device_logstring} mounted as #{@new_resource.mount_point}")
when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/
mounted = false
- Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
+ logger.trace("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
end
end
@current_resource.mounted(mounted)
@@ -108,23 +137,23 @@ class Chef
end
command << " #{@new_resource.mount_point}"
shell_out!(command)
- Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
def remount_command
if !(@new_resource.options.nil? || @new_resource.options.empty?)
- return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.device} #{@new_resource.mount_point}"
+ "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.device} #{@new_resource.mount_point}"
else
- return "mount -o remount #{@new_resource.device} #{@new_resource.mount_point}"
+ "mount -o remount #{@new_resource.device} #{@new_resource.mount_point}"
end
end
def enable_fs
if @current_resource.enabled && mount_options_unchanged?
- Chef::Log.debug("#{@new_resource} is already enabled - nothing to do")
+ logger.trace("#{@new_resource} is already enabled - nothing to do")
return nil
end
@@ -145,10 +174,21 @@ class Chef
fstab.puts("\tvfs\t\t= #{@new_resource.fstype}")
fstab.puts("\tmount\t\t= false")
fstab.puts "\toptions\t\t= #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty?
- Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is enabled at #{@new_resource.mount_point}")
end
end
+ def mount_options_unchanged?
+ current_resource_options = @current_resource.options.delete_if { |x| x == "rw" }
+
+ @current_resource.device == @new_resource.device &&
+ @current_resource.fsck_device == @new_resource.fsck_device &&
+ @current_resource.fstype == @new_resource.fstype &&
+ current_resource_options == @new_resource.options &&
+ @current_resource.dump == @new_resource.dump &&
+ @current_resource.pass == @new_resource.pass
+ end
+
def disable_fs
contents = []
if @current_resource.enabled
@@ -170,7 +210,7 @@ class Chef
contents.each { |line| fstab.puts line }
end
else
- Chef::Log.debug("#{@new_resource} is not enabled - nothing to do")
+ logger.trace("#{@new_resource} is not enabled - nothing to do")
end
end
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 07da6ac361..d0da30a30a 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -47,7 +47,7 @@ class Chef
elsif @new_resource.mount_point != "none" && !::File.exists?(@new_resource.mount_point)
raise Chef::Exceptions::Mount, "Mount point #{@new_resource.mount_point} does not exist"
end
- return true
+ true
end
def enabled?
@@ -57,17 +57,18 @@ class Chef
case line
when /^[#\s]/
next
- when /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/
+ when /^(#{device_fstab_regex})\s+#{Regexp.escape(@new_resource.mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/
enabled = true
- @current_resource.fstype($1)
- @current_resource.options($2)
- @current_resource.dump($3.to_i)
- @current_resource.pass($4.to_i)
- Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/fstab")
+ @current_resource.device($1)
+ @current_resource.fstype($2)
+ @current_resource.options($3)
+ @current_resource.dump($4.to_i)
+ @current_resource.pass($5.to_i)
+ logger.trace("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/fstab")
next
when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/
enabled = false
- Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
+ logger.trace("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
end
end
@current_resource.enabled(enabled)
@@ -89,10 +90,10 @@ class Chef
case line
when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(real_mount_point)}\s/
mounted = true
- Chef::Log.debug("Special device #{device_logstring} mounted as #{real_mount_point}")
+ logger.trace("Special device #{device_logstring} mounted as #{real_mount_point}")
when /^([\/\w])+\son\s#{Regexp.escape(real_mount_point)}\s+/
mounted = false
- Chef::Log.debug("Special device #{$~[1]} mounted as #{real_mount_point}")
+ logger.trace("Special device #{$~[1]} mounted as #{real_mount_point}")
end
end
@current_resource.mounted(mounted)
@@ -113,42 +114,47 @@ class Chef
end
command << " #{@new_resource.mount_point}"
shell_out!(command)
- Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
def umount_fs
if @current_resource.mounted
shell_out!("umount #{@new_resource.mount_point}")
- Chef::Log.debug("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
end
end
def remount_command
- return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.mount_point}"
+ "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.mount_point}"
end
def remount_fs
if @current_resource.mounted && @new_resource.supports[:remount]
shell_out!(remount_command)
@new_resource.updated_by_last_action(true)
- Chef::Log.debug("#{@new_resource} is remounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is remounted at #{@new_resource.mount_point}")
elsif @current_resource.mounted
umount_fs
sleep 1
mount_fs
else
- Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point} - nothing to do")
+ logger.trace("#{@new_resource} is not mounted at #{@new_resource.mount_point} - nothing to do")
end
end
+ # Return appropriate default mount options according to the given os.
+ def default_mount_options
+ node[:os] == "linux" ? "defaults" : "rw"
+ end
+
def enable_fs
- if @current_resource.enabled && mount_options_unchanged?
- Chef::Log.debug("#{@new_resource} is already enabled - nothing to do")
+ if @current_resource.enabled && mount_options_unchanged? && device_unchanged?
+ logger.trace("#{@new_resource} is already enabled - nothing to do")
return nil
end
@@ -158,8 +164,8 @@ class Chef
disable_fs
end
::File.open("/etc/fstab", "a") do |fstab|
- fstab.puts("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? "defaults" : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}")
- Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}")
+ fstab.puts("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? default_mount_options : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}")
+ logger.trace("#{@new_resource} is enabled at #{@new_resource.mount_point}")
end
end
@@ -171,7 +177,7 @@ class Chef
::File.readlines("/etc/fstab").reverse_each do |line|
if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s/
found = true
- Chef::Log.debug("#{@new_resource} is removed from fstab")
+ logger.trace("#{@new_resource} is removed from fstab")
next
else
contents << line
@@ -182,7 +188,7 @@ class Chef
contents.reverse_each { |line| fstab.puts line }
end
else
- Chef::Log.debug("#{@new_resource} is not enabled - nothing to do")
+ logger.trace("#{@new_resource} is not enabled - nothing to do")
end
end
@@ -193,7 +199,7 @@ class Chef
def device_should_exist?
( @new_resource.device != "none" ) &&
( not network_device? ) &&
- ( not %w{ cgroup tmpfs fuse vboxsf }.include? @new_resource.fstype )
+ ( not %w{ cgroup tmpfs fuse vboxsf zfs }.include? @new_resource.fstype )
end
private
@@ -210,7 +216,7 @@ class Chef
end
def device_real
- if @real_device == nil
+ if @real_device.nil?
if @new_resource.device_type == :device
@real_device = @new_resource.device
else
@@ -253,7 +259,7 @@ class Chef
if @new_resource.device_type == :device
device_mount_regex
else
- device_fstab
+ Regexp.union(device_fstab, device_mount_regex)
end
end
diff --git a/lib/chef/provider/mount/solaris.rb b/lib/chef/provider/mount/solaris.rb
index a5a7a327cb..095c07432a 100644
--- a/lib/chef/provider/mount/solaris.rb
+++ b/lib/chef/provider/mount/solaris.rb
@@ -74,8 +74,8 @@ class Chef
end
def mount_fs
- actual_options = options || []
- actual_options.delete("noauto")
+ actual_options = native_options(options)
+ actual_options.delete("-")
command = "mount -F #{fstype}"
command << " -o #{actual_options.join(',')}" unless actual_options.empty?
command << " #{device} #{mount_point}"
@@ -88,8 +88,8 @@ class Chef
def remount_fs
# FIXME: Should remount always do the remount or only if the options change?
- actual_options = options || []
- actual_options.delete("noauto")
+ actual_options = native_options(options)
+ actual_options.delete("-")
mount_options = actual_options.empty? ? "" : ",#{actual_options.join(',')}"
shell_out!("mount -o remount#{mount_options} #{mount_point}")
end
@@ -112,7 +112,7 @@ class Chef
else
# this is likely some kind of internal error, since we should only call disable_fs when there
# the filesystem we want to disable is enabled.
- Chef::Log.warn("#{new_resource} did not find the mountpoint to disable in the vfstab")
+ logger.warn("#{new_resource} did not find the mountpoint to disable in the vfstab")
end
end
@@ -121,8 +121,8 @@ class Chef
end
def mount_options_unchanged?
- new_options = options_remove_noauto(options)
- current_options = options_remove_noauto(current_resource.nil? ? nil : current_resource.options)
+ new_options = native_options(options)
+ current_options = native_options(current_resource.nil? ? nil : current_resource.options)
current_resource.fsck_device == fsck_device &&
current_resource.fstype == fstype &&
@@ -153,10 +153,10 @@ class Chef
shell_out!("mount -v").stdout.each_line do |line|
case line
when /^#{device_regex}\s+on\s+#{Regexp.escape(mount_point)}\s+/
- Chef::Log.debug("Special device #{device} is mounted as #{mount_point}")
+ logger.trace("Special device #{device} is mounted as #{mount_point}")
mounted = true
when /^([\/\w]+)\son\s#{Regexp.escape(mount_point)}\s+/
- Chef::Log.debug("Special device #{Regexp.last_match[1]} is mounted as #{mount_point}")
+ logger.trace("Special device #{Regexp.last_match[1]} is mounted as #{mount_point}")
mounted = false
end
end
@@ -168,7 +168,8 @@ class Chef
def read_vfstab_status
# Check to see if there is an entry in /etc/vfstab. Last entry for a volume wins.
enabled = false
- fstype = options = pass = nil
+ pass = false
+ fstype = options = nil
::File.foreach(VFSTAB) do |line|
case line
when /^[#\s]/
@@ -190,12 +191,12 @@ class Chef
end
end
pass = (Regexp.last_match[2] == "-") ? 0 : Regexp.last_match[2].to_i
- Chef::Log.debug("Found mount #{device} to #{mount_point} in #{VFSTAB}")
+ logger.trace("Found mount #{device} to #{mount_point} in #{VFSTAB}")
next
when /^[-\/\w]+\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+/
# if we find a mountpoint on top of our mountpoint, then we are not enabled
enabled = false
- Chef::Log.debug("Found conflicting mount point #{mount_point} in #{VFSTAB}")
+ logger.trace("Found conflicting mount point #{mount_point} in #{VFSTAB}")
end
end
[enabled, fstype, options, pass]
@@ -220,11 +221,7 @@ class Chef
end
def vfstab_entry
- actual_options = unless options.nil?
- tempops = options.dup
- tempops.delete("noauto")
- tempops
- end
+ actual_options = native_options(options)
autostr = mount_at_boot? ? "yes" : "no"
passstr = pass == 0 ? "-" : pass
optstr = (actual_options.nil? || actual_options.empty?) ? "-" : actual_options.join(",")
@@ -237,7 +234,7 @@ class Chef
::File.readlines(VFSTAB).reverse_each do |line|
if !found && line =~ /^#{device_regex}\s+\S+\s+#{Regexp.escape(mount_point)}/
found = true
- Chef::Log.debug("#{new_resource} is removed from vfstab")
+ logger.trace("#{new_resource} is removed from vfstab")
next
end
contents << line
@@ -251,11 +248,15 @@ class Chef
contents << vfstab_entry
end
- def options_remove_noauto(temp_options)
- new_options = []
- new_options += temp_options.nil? ? [] : temp_options
- new_options.delete("noauto")
- new_options
+ def native_options(temp_options)
+ if temp_options == %w{defaults}
+ ["-"]
+ else
+ new_options = []
+ new_options += temp_options.nil? ? [] : temp_options.dup
+ new_options.delete("noauto")
+ new_options
+ end
end
def device_regex
diff --git a/lib/chef/provider/mount/windows.rb b/lib/chef/provider/mount/windows.rb
index 0fb5aa7645..1bd932729d 100644
--- a/lib/chef/provider/mount/windows.rb
+++ b/lib/chef/provider/mount/windows.rb
@@ -47,15 +47,15 @@ class Chef
@current_resource = Chef::Resource::Mount.new(@new_resource.name)
@current_resource.mount_point(@new_resource.mount_point)
- Chef::Log.debug("Checking for mount point #{@current_resource.mount_point}")
+ logger.trace("Checking for mount point #{@current_resource.mount_point}")
begin
@current_resource.device(@mount.device)
- Chef::Log.debug("#{@current_resource.device} mounted on #{@new_resource.mount_point}")
+ logger.trace("#{@current_resource.device} mounted on #{@new_resource.mount_point}")
@current_resource.mounted(true)
rescue ArgumentError => e
@current_resource.mounted(false)
- Chef::Log.debug("#{@new_resource.mount_point} is not mounted: #{e.message}")
+ logger.trace("#{@new_resource.mount_point} is not mounted: #{e.message}")
end
end
@@ -65,18 +65,18 @@ class Chef
:username => @new_resource.username,
:domainname => @new_resource.domain,
:password => @new_resource.password)
- Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
def umount_fs
if @current_resource.mounted
@mount.delete
- Chef::Log.debug("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
end
end
diff --git a/lib/chef/provider/noop.rb b/lib/chef/provider/noop.rb
index 207bf7dedb..077da3f0b4 100644
--- a/lib/chef/provider/noop.rb
+++ b/lib/chef/provider/noop.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) 2016-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,7 +27,7 @@ class Chef
def method_missing(method_sym, *arguments, &block)
if method_sym.to_s =~ /^action_/
- Chef::Log.debug("NoOp-ing for #{method_sym}")
+ logger.trace("NoOp-ing for #{method_sym}")
else
super
end
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
index 6b5a605ed5..0b65a1d28a 100644
--- a/lib/chef/provider/ohai.rb
+++ b/lib/chef/provider/ohai.rb
@@ -23,25 +23,21 @@ class Chef
class Ohai < Chef::Provider
provides :ohai
- def whyrun_supported?
- true
- end
-
def load_current_resource
true
end
- def action_reload
+ action :reload do
converge_by("re-run ohai and merge results into node attributes") do
ohai = ::Ohai::System.new
- # If @new_resource.plugin is nil, ohai will reload all the plugins
+ # If new_resource.plugin is nil, ohai will reload all the plugins
# Otherwise it will only reload the specified plugin
# Note that any changes to plugins, or new plugins placed on
# the path are picked up by ohai.
- ohai.all_plugins @new_resource.plugin
+ ohai.all_plugins new_resource.plugin
node.automatic_attrs.merge! ohai.data
- Chef::Log.info("#{@new_resource} reloaded")
+ logger.info("#{new_resource} reloaded")
end
end
end
diff --git a/lib/chef/provider/osx_profile.rb b/lib/chef/provider/osx_profile.rb
index 6ac67e0560..e753f84d86 100644
--- a/lib/chef/provider/osx_profile.rb
+++ b/lib/chef/provider/osx_profile.rb
@@ -25,75 +25,71 @@ require "uuidtools"
class Chef
class Provider
class OsxProfile < Chef::Provider
- include Chef::Mixin::Command
- provides :osx_profile, os: "darwin"
- provides :osx_config_profile, os: "darwin"
-
- def whyrun_supported?
- true
- end
+ provides :osx_profile
+ provides :osx_config_profile
def load_current_resource
- @current_resource = Chef::Resource::OsxProfile.new(@new_resource.name)
- @current_resource.profile_name(@new_resource.profile_name)
+ @current_resource = Chef::Resource::OsxProfile.new(new_resource.name)
+ current_resource.profile_name(new_resource.profile_name)
all_profiles = get_installed_profiles
- @new_resource.profile(
- @new_resource.profile ||
- @new_resource.profile_name
+ new_resource.profile(
+ new_resource.profile ||
+ new_resource.profile_name
)
- @new_profile_hash = get_profile_hash(@new_resource.profile)
- @new_profile_hash["PayloadUUID"] =
- config_uuid(@new_profile_hash) if @new_profile_hash
+ @new_profile_hash = get_profile_hash(new_resource.profile)
+ if @new_profile_hash
+ @new_profile_hash["PayloadUUID"] =
+ config_uuid(@new_profile_hash)
+ end
if @new_profile_hash
@new_profile_identifier = @new_profile_hash["PayloadIdentifier"]
else
- @new_profile_identifier = @new_resource.identifier ||
- @new_resource.profile_name
+ @new_profile_identifier = new_resource.identifier ||
+ new_resource.profile_name
end
- if all_profiles.empty?
- current_profile = nil
- else
+ current_profile = nil
+ if all_profiles && all_profiles.key?("_computerlevel")
current_profile = all_profiles["_computerlevel"].find do |item|
item["ProfileIdentifier"] == @new_profile_identifier
end
end
- @current_resource.profile(current_profile)
+ current_resource.profile(current_profile)
end
def define_resource_requirements
requirements.assert(:remove) do |a|
if @new_profile_identifier
- a.assertion {
+ a.assertion do
!@new_profile_identifier.nil? &&
!@new_profile_identifier.end_with?(".mobileconfig") &&
- /^\w+(?:\.\w+)+$/.match(@new_profile_identifier)
- }
+ /^\w+(?:(\.| )\w+)+$/.match(@new_profile_identifier)
+ end
a.failure_message RuntimeError, "when removing using the identifier attribute, it must match the profile identifier"
else
- new_profile_name = @new_resource.profile_name
- a.assertion {
+ new_profile_name = new_resource.profile_name
+ a.assertion do
!new_profile_name.end_with?(".mobileconfig") &&
- /^\w+(?:\.\w+)+$/.match(new_profile_name)
- }
+ /^\w+(?:(\.| )\w+)+$/.match(new_profile_name)
+ end
a.failure_message RuntimeError, "When removing by resource name, it must match the profile identifier "
end
end
requirements.assert(:install) do |a|
if @new_profile_hash.is_a?(Hash)
- a.assertion {
+ a.assertion do
@new_profile_hash.include?("PayloadIdentifier")
- }
+ end
a.failure_message RuntimeError, "The specified profile does not seem to be valid"
end
if @new_profile_hash.is_a?(String)
- a.assertion {
+ a.assertion do
@new_profile_hash.end_with?(".mobileconfig")
- }
+ end
a.failure_message RuntimeError, "#{new_profile_hash}' is not a valid profile"
end
end
@@ -127,21 +123,21 @@ class Chef
raise Chef::Exceptions::FileNotFound, error_string
end
cookbook_profile = cache_cookbook_profile(new_profile)
- return read_plist(cookbook_profile)
+ read_plist(cookbook_profile)
else
- return nil
+ nil
end
end
def cookbook_file_available?(cookbook_file)
run_context.has_cookbook_file_in_cookbook?(
- @new_resource.cookbook_name, cookbook_file
+ new_resource.cookbook_name, cookbook_file
)
end
def get_cache_dir
cache_dir = Chef::FileCache.create_cache_path(
- "profiles/#{@new_resource.cookbook_name}"
+ "profiles/#{new_resource.cookbook_name}"
)
end
@@ -149,7 +145,7 @@ class Chef
Chef::FileCache.create_cache_path(
::File.join(
"profiles",
- @new_resource.cookbook_name,
+ new_resource.cookbook_name,
::File.dirname(cookbook_file)
)
)
@@ -160,7 +156,7 @@ class Chef
),
run_context
)
- remote_file.cookbook_name = @new_resource.cookbook_name
+ remote_file.cookbook_name = new_resource.cookbook_name
remote_file.source(cookbook_file)
remote_file.backup(false)
remote_file.run_action(:create)
@@ -169,9 +165,9 @@ class Chef
def get_profile_hash(new_profile)
if new_profile.is_a?(Hash)
- return new_profile
+ new_profile
elsif new_profile.is_a?(String)
- return load_profile_hash(new_profile)
+ load_profile_hash(new_profile)
end
end
@@ -184,8 +180,8 @@ class Chef
end
def write_profile_to_disk
- @new_resource.path(Chef::FileCache.create_cache_path("profiles"))
- tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
+ new_resource.path(Chef::FileCache.create_cache_path("profiles"))
+ tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
tempfile.write(@new_profile_hash.to_plist)
tempfile.close
tempfile.path
@@ -193,14 +189,14 @@ class Chef
def install_profile(profile_path)
cmd = "profiles -I -F '#{profile_path}'"
- Chef::Log.debug("cmd: #{cmd}")
+ logger.trace("cmd: #{cmd}")
shellout_results = shell_out(cmd)
shellout_results.exitstatus
end
def remove_profile
cmd = "profiles -R -p '#{@new_profile_identifier}'"
- Chef::Log.debug("cmd: #{cmd}")
+ logger.trace("cmd: #{cmd}")
shellout_results = shell_out(cmd)
shellout_results.exitstatus
end
@@ -218,7 +214,7 @@ class Chef
tempfile = generate_tempfile
write_installed_profiles(tempfile)
installed_profiles = read_plist(tempfile)
- Chef::Log.debug("Saved profiles to run_state")
+ logger.trace("Saved profiles to run_state")
# Clean up the temp file as we do not need it anymore
::File.unlink(tempfile)
installed_profiles
@@ -239,13 +235,13 @@ class Chef
def profile_installed?
# Profile Identifier and UUID must match a currently installed profile
- if @current_resource.profile.nil? || @current_resource.profile.empty?
+ if current_resource.profile.nil? || current_resource.profile.empty?
false
else
- if @new_resource.action.include?(:remove)
+ if new_resource.action.include?(:remove)
true
else
- @current_resource.profile["ProfileUUID"] ==
+ current_resource.profile["ProfileUUID"] ==
@new_profile_hash["PayloadUUID"]
end
end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index ca9b526920..133f87dad9 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright 2008-2018, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,16 +17,16 @@
#
require "chef/mixin/shell_out"
-require "chef/mixin/command"
require "chef/mixin/subclass_directive"
require "chef/log"
require "chef/file_cache"
require "chef/platform"
+require "chef/decorator/lazy_array"
+require "shellwords"
class Chef
class Provider
class Package < Chef::Provider
- include Chef::Mixin::Command
include Chef::Mixin::ShellOut
extend Chef::Mixin::SubclassDirective
@@ -34,6 +34,9 @@ class Chef
subclass_directive :use_multipackage_api
# subclasses declare this if they want sources (filenames) pulled from their package names
subclass_directive :use_package_name_for_source
+ # keeps package_names_for_targets and versions_for_targets indexed the same as package_name at
+ # the cost of having the subclass needing to deal with nils
+ subclass_directive :allow_nils
#
# Hook that subclasses use to populate the candidate_version(s)
@@ -46,28 +49,27 @@ class Chef
@candidate_version = nil
end
- def whyrun_supported?
- true
+ def options
+ new_resource.options
end
def check_resource_semantics!
# FIXME: this is not universally true and subclasses are needing to override this and no-ops it. It should be turned into
# another "subclass_directive" and the apt and yum providers should declare that they need this behavior.
- if new_resource.package_name.is_a?(Array) && new_resource.source != nil
+ if new_resource.package_name.is_a?(Array) && !new_resource.source.nil?
raise Chef::Exceptions::InvalidResourceSpecification, "You may not specify both multipackage and source"
end
end
- def load_current_resource
- end
+ def load_current_resource; end
def define_resource_requirements
# XXX: upgrade with a specific version doesn't make a whole lot of sense, but why don't we throw this anyway if it happens?
# if not, shouldn't we raise to tell the user to use install instead of upgrade if they want to pin a version?
requirements.assert(:install) do |a|
a.assertion { candidates_exist_for_all_forced_changes? }
- a.failure_message(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{forced_packages_missing_candidates.join(", ")}")
- a.whyrun("Assuming a repository that offers #{forced_packages_missing_candidates.join(", ")} would have been configured")
+ a.failure_message(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{forced_packages_missing_candidates.join(', ')}")
+ a.whyrun("Assuming a repository that offers #{forced_packages_missing_candidates.join(', ')} would have been configured")
end
# XXX: Does it make sense to pass in a source with :upgrade? Probably
@@ -75,19 +77,19 @@ class Chef
# so we'll just leave things as-is for now.
requirements.assert(:upgrade, :install) do |a|
a.assertion { candidates_exist_for_all_uninstalled? || new_resource.source }
- a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(", ")}")
- a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(", ")} would have been configured")
+ a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(', ')}")
+ a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(', ')} would have been configured")
end
end
- def action_install
+ action :install do
unless target_version_array.any?
- Chef::Log.debug("#{@new_resource} is already installed - nothing to do")
+ logger.trace("#{new_resource} is already installed - nothing to do")
return
end
# @todo: move the preseed code out of the base class (and complete the fix for Array of preseeds? ugh...)
- if @new_resource.response_file
+ if new_resource.response_file
if preseed_file = get_preseed_file(package_names_for_targets, versions_for_targets)
converge_by("preseed package #{package_names_for_targets}") do
preseed_package(preseed_file)
@@ -99,7 +101,7 @@ class Chef
multipackage_api_adapter(package_names_for_targets, versions_for_targets) do |name, version|
install_package(name, version)
end
- Chef::Log.info("#{@new_resource} installed #{package_names_for_targets} at #{versions_for_targets}")
+ logger.info("#{new_resource} installed #{package_names_for_targets} at #{versions_for_targets}")
end
end
@@ -115,9 +117,9 @@ class Chef
private :install_description
- def action_upgrade
- if !target_version_array.any?
- Chef::Log.debug("#{@new_resource} no versions to upgrade - nothing to do")
+ action :upgrade do
+ unless target_version_array.any?
+ logger.trace("#{new_resource} no versions to upgrade - nothing to do")
return
end
@@ -126,7 +128,7 @@ class Chef
upgrade_package(name, version)
end
log_allow_downgrade = allow_downgrade ? "(allow_downgrade)" : ""
- Chef::Log.info("#{@new_resource} upgraded#{log_allow_downgrade} #{package_names_for_targets} to #{versions_for_targets}")
+ logger.info("#{new_resource} upgraded#{log_allow_downgrade} #{package_names_for_targets} to #{versions_for_targets}")
end
end
@@ -145,17 +147,17 @@ class Chef
private :upgrade_description
- def action_remove
+ action :remove do
if removing_package?
- description = @new_resource.version ? "version #{@new_resource.version} of " : ""
- converge_by("remove #{description}package #{@current_resource.package_name}") do
- multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version|
+ description = new_resource.version ? "version #{new_resource.version} of " : ""
+ converge_by("remove #{description}package #{current_resource.package_name}") do
+ multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
remove_package(name, version)
end
- Chef::Log.info("#{@new_resource} removed")
+ logger.info("#{new_resource} removed")
end
else
- Chef::Log.debug("#{@new_resource} package does not exist - nothing to do")
+ logger.trace("#{new_resource} package does not exist - nothing to do")
end
end
@@ -180,43 +182,86 @@ class Chef
end
end
- def action_purge
+ action :purge do
if removing_package?
- description = @new_resource.version ? "version #{@new_resource.version} of" : ""
- converge_by("purge #{description} package #{@current_resource.package_name}") do
- multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version|
+ description = new_resource.version ? "version #{new_resource.version} of" : ""
+ converge_by("purge #{description} package #{current_resource.package_name}") do
+ multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
purge_package(name, version)
end
- Chef::Log.info("#{@new_resource} purged")
+ logger.info("#{new_resource} purged")
end
end
end
- def action_reconfig
- if @current_resource.version == nil then
- Chef::Log.debug("#{@new_resource} is NOT installed - nothing to do")
+ action :reconfig do
+ if current_resource.version.nil?
+ logger.trace("#{new_resource} is NOT installed - nothing to do")
return
end
- unless @new_resource.response_file then
- Chef::Log.debug("#{@new_resource} no response_file provided - nothing to do")
+ unless new_resource.response_file
+ logger.trace("#{new_resource} no response_file provided - nothing to do")
return
end
- if preseed_file = get_preseed_file(@new_resource.package_name, @current_resource.version)
- converge_by("reconfigure package #{@new_resource.package_name}") do
+ if preseed_file = get_preseed_file(new_resource.package_name, current_resource.version)
+ converge_by("reconfigure package #{new_resource.package_name}") do
preseed_package(preseed_file)
- multipackage_api_adapter(@new_resource.package_name, @current_resource.version) do |name, version|
+ multipackage_api_adapter(new_resource.package_name, current_resource.version) do |name, version|
reconfig_package(name, version)
end
- Chef::Log.info("#{@new_resource} reconfigured")
+ logger.info("#{new_resource} reconfigured")
+ end
+ else
+ logger.trace("#{new_resource} preseeding has not changed - nothing to do")
+ end
+ end
+
+ def action_lock
+ packages_locked = if respond_to?(:packages_all_locked?, true)
+ packages_all_locked?(Array(new_resource.package_name), Array(new_resource.version))
+ else
+ package_locked(new_resource.package_name, new_resource.version)
+ end
+ unless packages_locked
+ description = new_resource.version ? "version #{new_resource.version} of " : ""
+ converge_by("lock #{description}package #{current_resource.package_name}") do
+ multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
+ lock_package(name, version)
+ logger.info("#{new_resource} locked")
+ end
end
else
- Chef::Log.debug("#{@new_resource} preseeding has not changed - nothing to do")
+ logger.trace("#{new_resource} is already locked")
end
end
+ def action_unlock
+ packages_unlocked = if respond_to?(:packages_all_unlocked?, true)
+ packages_all_unlocked?(Array(new_resource.package_name), Array(new_resource.version))
+ else
+ !package_locked(new_resource.package_name, new_resource.version)
+ end
+ unless packages_unlocked
+ description = new_resource.version ? "version #{new_resource.version} of " : ""
+ converge_by("unlock #{description}package #{current_resource.package_name}") do
+ multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
+ unlock_package(name, version)
+ logger.info("#{new_resource} unlocked")
+ end
+ end
+ else
+ logger.trace("#{new_resource} is already unlocked")
+ end
+ end
+
+ # for multipackage just implement packages_all_[un]locked? properly and omit implementing this API
+ def package_locked(name, version)
+ raise Chef::Exceptions::UnsupportedAction, "#{self} has no way to detect if package is locked"
+ end
+
# @todo use composition rather than inheritance
def multipackage_api_adapter(name, version)
@@ -251,21 +296,94 @@ class Chef
raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :reconfig" )
end
+ def lock_package(name, version)
+ raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :lock" )
+ end
+
+ def unlock_package(name, version)
+ raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :unlock" )
+ end
+
# used by subclasses. deprecated. use #a_to_s instead.
def expand_options(options)
- options ? " #{options}" : ""
+ # its deprecated but still work to do to deprecate it fully
+ #Chef.deprecated(:package_misc, "expand_options is deprecated, use shell_out_compact or shell_out_compact_timeout instead")
+ if options
+ " #{options.is_a?(Array) ? Shellwords.join(options) : options}"
+ else
+ ""
+ end
end
- # this is public and overridden by subclasses (rubygems package implements '>=' and '~>' operators)
- def target_version_already_installed?(current_version, new_version)
- new_version == current_version
+ # Check the current_version against either the candidate_version or the new_version
+ #
+ # For some reason the windows provider subclasses this (to implement passing Arrays to
+ # versions for some reason other than multipackage stuff, which is mildly terrifying).
+ #
+ # This MUST have 'equality' semantics -- the exact thing matches the exact thing.
+ #
+ # The name is not just bad, but i find it completely misleading, consider:
+ #
+ # target_version_already_installed?(current_version, new_version)
+ # target_version_already_installed?(current_version, candidate_version)
+ #
+ # Which of those is the 'target_version'? I'd say the new_version and I'm confused when
+ # i see it called with the candidate_version.
+ #
+ # `version_equals?(v1, v2)` would be a better name.
+ #
+ # Note that most likely we need a spaceship operator on versions that subclasses can implement
+ # and we should have `version_compare(v1, v2)` that returns `v1 <=> v2`.
+
+ # This method performs a strict equality check between two strings representing version numbers
+ #
+ # This function will eventually be deprecated in favour of the below version_equals function.
+
+ def target_version_already_installed?(current_version, target_version)
+ version_equals?(current_version, target_version)
+ end
+
+ # Note that most likely we need a spaceship operator on versions that subclasses can implement
+ # and we should have `version_compare(v1, v2)` that returns `v1 <=> v2`.
+
+ # This method performs a strict equality check between two strings representing version numbers
+ #
+ def version_equals?(v1, v2)
+ return false unless v1 && v2
+ v1 == v2
+ end
+
+ # This function compares two version numbers and returns 'spaceship operator' style results, ie:
+ # if v1 < v2 then return -1
+ # if v1 = v2 then return 0
+ # if v1 > v2 then return 1
+ # if v1 and v2 are not comparable then return nil
+ #
+ # By default, this function will use Gem::Version comparison. Subclasses can reimplement this method
+ # for package-management system specific versions.
+ def version_compare(v1, v2)
+ gem_v1 = Gem::Version.new(v1)
+ gem_v2 = Gem::Version.new(v2)
+
+ gem_v1 <=> gem_v2
+ end
+
+ # Check the current_version against the new_resource.version, possibly using fuzzy
+ # matching criteria.
+ #
+ # Subclasses MAY override this to provide fuzzy matching on the resource ('>=' and '~>' stuff)
+ #
+ # `version_satisfied_by?(version, constraint)` might be a better name to make this generic.
+ #
+ def version_requirement_satisfied?(current_version, new_version)
+ target_version_already_installed?(current_version, new_version)
end
# @todo: extract apt/dpkg specific preseeding to a helper class
def get_preseed_file(name, version)
resource = preseed_resource(name, version)
resource.run_action(:create)
- Chef::Log.debug("#{@new_resource} fetched preseed file to #{resource.path}")
+ logger.trace("#{new_resource} fetched preseed file to #{resource.path}")
if resource.updated_by_last_action?
resource.path
@@ -277,26 +395,26 @@ class Chef
# @todo: extract apt/dpkg specific preseeding to a helper class
def preseed_resource(name, version)
# A directory in our cache to store this cookbook's preseed files in
- file_cache_dir = Chef::FileCache.create_cache_path("preseed/#{@new_resource.cookbook_name}")
+ file_cache_dir = Chef::FileCache.create_cache_path("preseed/#{new_resource.cookbook_name}")
# The full path where the preseed file will be stored
cache_seed_to = "#{file_cache_dir}/#{name}-#{version}.seed"
- Chef::Log.debug("#{@new_resource} fetching preseed file to #{cache_seed_to}")
+ logger.trace("#{new_resource} fetching preseed file to #{cache_seed_to}")
- if template_available?(@new_resource.response_file)
- Chef::Log.debug("#{@new_resource} fetching preseed file via Template")
+ if template_available?(new_resource.response_file)
+ logger.trace("#{new_resource} fetching preseed file via Template")
remote_file = Chef::Resource::Template.new(cache_seed_to, run_context)
- remote_file.variables(@new_resource.response_file_variables)
- elsif cookbook_file_available?(@new_resource.response_file)
- Chef::Log.debug("#{@new_resource} fetching preseed file via cookbook_file")
+ remote_file.variables(new_resource.response_file_variables)
+ elsif cookbook_file_available?(new_resource.response_file)
+ logger.trace("#{new_resource} fetching preseed file via cookbook_file")
remote_file = Chef::Resource::CookbookFile.new(cache_seed_to, run_context)
else
- message = "No template or cookbook file found for response file #{@new_resource.response_file}"
+ message = "No template or cookbook file found for response file #{new_resource.response_file}"
raise Chef::Exceptions::FileNotFound, message
end
- remote_file.cookbook_name = @new_resource.cookbook_name
- remote_file.source(@new_resource.response_file)
+ remote_file.cookbook_name = new_resource.cookbook_name
+ remote_file.source(new_resource.response_file)
remote_file.backup(false)
remote_file
end
@@ -319,9 +437,12 @@ class Chef
def package_names_for_targets
package_names_for_targets = []
target_version_array.each_with_index do |target_version, i|
- next if target_version.nil?
- package_name = package_name_array[i]
- package_names_for_targets.push(package_name)
+ if !target_version.nil?
+ package_name = package_name_array[i]
+ package_names_for_targets.push(package_name)
+ else
+ package_names_for_targets.push(nil) if allow_nils?
+ end
end
multipackage? ? package_names_for_targets : package_names_for_targets[0]
end
@@ -336,8 +457,11 @@ class Chef
def versions_for_targets
versions_for_targets = []
target_version_array.each_with_index do |target_version, i|
- next if target_version.nil?
- versions_for_targets.push(target_version)
+ if !target_version.nil?
+ versions_for_targets.push(target_version)
+ else
+ versions_for_targets.push(nil) if allow_nils?
+ end
end
multipackage? ? versions_for_targets : versions_for_targets[0]
end
@@ -354,33 +478,42 @@ class Chef
each_package do |package_name, new_version, current_version, candidate_version|
case action
when :upgrade
-
- if !candidate_version
- Chef::Log.debug("#{new_resource} #{package_name} has no candidate_version to upgrade to")
+ if version_equals?(current_version, new_version)
+ # this is an odd use case
+ logger.trace("#{new_resource} #{package_name} #{new_version} is already installed -- you are equality pinning with an :upgrade action, this may be deprecated in the future")
+ target_version_array.push(nil)
+ elsif version_equals?(current_version, candidate_version)
+ logger.trace("#{new_resource} #{package_name} #{candidate_version} is already installed")
target_version_array.push(nil)
- elsif current_version == candidate_version
- Chef::Log.debug("#{new_resource} #{package_name} the #{candidate_version} is already installed")
+ elsif candidate_version.nil?
+ logger.trace("#{new_resource} #{package_name} has no candidate_version to upgrade to")
+ target_version_array.push(nil)
+ elsif current_version.nil?
+ logger.trace("#{new_resource} has no existing installed version. Installing install #{candidate_version}")
+ target_version_array.push(candidate_version)
+ elsif version_compare(current_version, candidate_version) == 1 && !allow_downgrade
+ logger.trace("#{new_resource} #{package_name} has installed version #{current_version}, which is newer than available version #{candidate_version}. Skipping...)")
target_version_array.push(nil)
else
- Chef::Log.debug("#{new_resource} #{package_name} is out of date, will upgrade to #{candidate_version}")
+ logger.trace("#{new_resource} #{package_name} is out of date, will upgrade to #{candidate_version}")
target_version_array.push(candidate_version)
end
when :install
if new_version
- if target_version_already_installed?(current_version, new_version)
- Chef::Log.debug("#{new_resource} #{package_name} #{current_version} satisifies #{new_version} requirement")
+ if version_requirement_satisfied?(current_version, new_version)
+ logger.trace("#{new_resource} #{package_name} #{current_version} satisifies #{new_version} requirement")
target_version_array.push(nil)
else
- Chef::Log.debug("#{new_resource} #{package_name} #{current_version} needs updating to #{new_version}")
+ logger.trace("#{new_resource} #{package_name} #{current_version} needs updating to #{new_version}")
target_version_array.push(new_version)
end
elsif current_version.nil?
- Chef::Log.debug("#{new_resource} #{package_name} not installed, installing #{candidate_version}")
+ logger.trace("#{new_resource} #{package_name} not installed, installing #{candidate_version}")
target_version_array.push(candidate_version)
else
- Chef::Log.debug("#{new_resource} #{package_name} #{current_version} already installed")
+ logger.trace("#{new_resource} #{package_name} #{current_version} already installed")
target_version_array.push(nil)
end
@@ -411,7 +544,7 @@ class Chef
begin
missing = []
each_package do |package_name, new_version, current_version, candidate_version|
- missing.push(package_name) if candidate_version.nil? && current_version.nil?
+ missing.push(package_name) if current_version.nil? && candidate_version.nil?
end
missing
end
@@ -436,7 +569,7 @@ class Chef
missing = []
each_package do |package_name, new_version, current_version, candidate_version|
next if new_version.nil? || current_version.nil?
- if candidate_version.nil? && !target_version_already_installed?(current_version, new_version)
+ if !version_requirement_satisfied?(current_version, new_version) && candidate_version.nil?
missing.push(package_name)
end
end
@@ -458,27 +591,29 @@ class Chef
# @return [Boolean] if we're doing a multipackage install or not
def multipackage?
- new_resource.package_name.is_a?(Array)
+ @multipackage_bool ||= new_resource.package_name.is_a?(Array)
end
# @return [Array] package_name(s) as an array
def package_name_array
- [ new_resource.package_name ].flatten
+ @package_name_array ||= [ new_resource.package_name ].flatten
end
# @return [Array] candidate_version(s) as an array
def candidate_version_array
- [ candidate_version ].flatten
+ # NOTE: even with use_multipackage_api candidate_version may be a bare nil and need wrapping
+ # ( looking at you, dpkg provider... )
+ Chef::Decorator::LazyArray.new { [ candidate_version ].flatten }
end
# @return [Array] current_version(s) as an array
def current_version_array
- [ current_resource.version ].flatten
+ @current_version_array ||= [ current_resource.version ].flatten
end
# @return [Array] new_version(s) as an array
def new_version_array
- [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
+ @new_version_array ||= [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
end
# TIP: less error prone to simply always call resolved_source_array, even if you
@@ -486,11 +621,14 @@ class Chef
#
# @return [Array] new_resource.source as an array
def source_array
- if new_resource.source.nil?
- package_name_array.map { nil }
- else
- [ new_resource.source ].flatten
- end
+ @source_array ||=
+ begin
+ if new_resource.source.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.source ].flatten
+ end
+ end
end
# Helper to handle use_package_name_for_source to convert names into local packages to install.
@@ -503,7 +641,7 @@ class Chef
package_name = package_name_array[i]
# we require at least one '/' in the package_name to avoid [XXX_]package 'foo' breaking due to a random 'foo' file in cwd
if use_package_name_for_source? && source.nil? && package_name.match(/#{::File::SEPARATOR}/) && ::File.exist?(package_name)
- Chef::Log.debug("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.")
+ logger.trace("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.")
package_name
else
source
@@ -523,8 +661,8 @@ class Chef
end
def allow_downgrade
- if @new_resource.respond_to?("allow_downgrade")
- @new_resource.allow_downgrade
+ if new_resource.respond_to?("allow_downgrade")
+ new_resource.allow_downgrade
else
false
end
@@ -539,27 +677,19 @@ class Chef
end
def add_timeout_option(command_args)
+ # this is deprecated but its not quite done yet
+ #Chef.deprecated(:package_misc, "shell_out_with_timeout and add_timeout_option are deprecated methods, use shell_out_compact_timeout instead")
args = command_args.dup
if args.last.is_a?(Hash)
options = args.pop.dup
options[:timeout] = new_resource.timeout if new_resource.timeout
- options[:timeout] = 900 unless options.has_key?(:timeout)
+ options[:timeout] = 900 unless options.key?(:timeout)
args << options
else
- args << { :timeout => new_resource.timeout ? new_resource.timeout : 900 }
+ args << { timeout: new_resource.timeout ? new_resource.timeout : 900 }
end
args
end
-
- # Helper for sublcasses to convert an array of string args into a string. It
- # will compact nil or empty strings in the array and will join the array elements
- # with spaces, without introducing any double spaces for nil/empty elements.
- #
- # @param args [String] variable number of string arguments
- # @return [String] nicely concatenated string or empty string
- def a_to_s(*args)
- args.reject { |i| i.nil? || i == "" }.join(" ")
- end
end
end
end
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
deleted file mode 100644
index a1709c4af7..0000000000
--- a/lib/chef/provider/package/aix.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-#
-# Author:: Deepali Jagtap
-# Copyright:: Copyright 2013-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/mixin/command"
-require "chef/resource/package"
-require "chef/mixin/get_source_from_package"
-
-class Chef
- class Provider
- class Package
- class Aix < Chef::Provider::Package
-
- provides :package, os: "aix"
- provides :bff_package, os: "aix"
-
- include Chef::Mixin::GetSourceFromPackage
-
- def define_resource_requirements
- super
- requirements.assert(:install) do |a|
- a.assertion { @new_resource.source }
- a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
- end
- requirements.assert(:all_actions) do |a|
- a.assertion { !@new_resource.source || @package_source_found }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- a.whyrun "would assume #{@new_resource.source} would be have previously been made available"
- end
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
-
- if @new_resource.source
- @package_source_found = ::File.exists?(@new_resource.source)
- if @package_source_found
- Chef::Log.debug("#{@new_resource} checking pkg status")
- ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}")
- ret.stdout.each_line do |line|
- case line
- when /#{@new_resource.package_name}:/
- fields = line.split(":")
- @new_resource.version(fields[2])
- end
- end
- raise Chef::Exceptions::Package, "package source #{@new_resource.source} does not provide package #{@new_resource.package_name}" unless @new_resource.version
- end
- end
-
- Chef::Log.debug("#{@new_resource} checking install state")
- ret = shell_out_with_timeout("lslpp -lcq #{@current_resource.package_name}")
- ret.stdout.each_line do |line|
- case line
- when /#{@current_resource.package_name}/
- fields = line.split(":")
- Chef::Log.debug("#{@new_resource} version #{fields[2]} is already installed")
- @current_resource.version(fields[2])
- end
- end
-
- unless ret.exitstatus == 0 || ret.exitstatus == 1
- raise Chef::Exceptions::Package, "lslpp failed - #{ret.format_for_exception}!"
- end
-
- @current_resource
- end
-
- def candidate_version
- return @candidate_version if @candidate_version
- ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}")
- ret.stdout.each_line do |line|
- case line
- when /\w:#{Regexp.escape(@new_resource.package_name)}:(.*)/
- fields = line.split(":")
- @candidate_version = fields[2]
- @new_resource.version(fields[2])
- Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}")
- end
- end
- unless ret.exitstatus == 0
- raise Chef::Exceptions::Package, "installp -L -d #{@new_resource.source} - #{ret.format_for_exception}!"
- end
- @candidate_version
- end
-
- #
- # The install/update action needs to be tested with various kinds of packages
- # on AIX viz. packages with or without licensing file dependencies, packages
- # with dependencies on other packages which will help to test additional
- # options of installp.
- # So far, the code has been tested only with standalone packages.
- #
- def install_package(name, version)
- Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
- if @new_resource.options.nil?
- shell_out_with_timeout!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" )
- Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
- else
- shell_out_with_timeout!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" )
- Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
- end
- end
-
- alias_method :upgrade_package, :install_package
-
- def remove_package(name, version)
- if @new_resource.options.nil?
- shell_out_with_timeout!( "installp -u #{name}" )
- Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
- else
- shell_out_with_timeout!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
- Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index ac730202b8..798abf4680 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,132 +17,82 @@
#
require "chef/provider/package"
-require "chef/mixin/command"
-require "chef/resource/package"
+require "chef/resource/apt_package"
class Chef
class Provider
class Package
class Apt < Chef::Provider::Package
+ use_multipackage_api
provides :package, platform_family: "debian"
- provides :apt_package, os: "linux"
-
- # return [Hash] mapping of package name to Boolean value
- attr_accessor :is_virtual_package
+ provides :apt_package
def initialize(new_resource, run_context)
super
- @is_virtual_package = {}
end
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
- check_all_packages_state(@new_resource.package_name)
- @current_resource
+ @current_resource = Chef::Resource::AptPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(get_current_versions)
+ current_resource
end
def define_resource_requirements
super
requirements.assert(:all_actions) do |a|
- a.assertion { !@new_resource.source }
+ a.assertion { !new_resource.source }
a.failure_message(Chef::Exceptions::Package, "apt package provider cannot handle source attribute. Use dpkg provider instead")
end
end
- def default_release_options
- # Use apt::Default-Release option only if provider supports it
- "-o APT::Default-Release=#{@new_resource.default_release}" if @new_resource.respond_to?(:default_release) && @new_resource.default_release
+ def package_data
+ @package_data ||= Hash.new do |hash, key|
+ hash[key] = package_data_for(key)
+ end
end
- def check_package_state(pkg)
- is_virtual_package = false
- installed = false
- installed_version = nil
- candidate_version = nil
+ def get_current_versions
+ package_name_array.map do |package_name|
+ package_data[package_name][:current_version]
+ end
+ end
- shell_out_with_timeout!("apt-cache#{expand_options(default_release_options)} policy #{pkg}").stdout.each_line do |line|
- case line
- when /^\s{2}Installed: (.+)$/
- installed_version = $1
- if installed_version == "(none)"
- Chef::Log.debug("#{@new_resource} current version is nil")
- installed_version = nil
- else
- Chef::Log.debug("#{@new_resource} current version is #{installed_version}")
- installed = true
- end
- when /^\s{2}Candidate: (.+)$/
- candidate_version = $1
- if candidate_version == "(none)"
- # This may not be an appropriate assumption, but it shouldn't break anything that already worked -- btm
- is_virtual_package = true
- showpkg = shell_out_with_timeout!("apt-cache showpkg #{pkg}").stdout
- providers = Hash.new
- showpkg.rpartition(/Reverse Provides: ?#{$/}/)[2].each_line do |line|
- provider, version = line.split
- providers[provider] = version
- end
- # Check if the package providing this virtual package is installed
- num_providers = providers.length
- raise Chef::Exceptions::Package, "#{@new_resource.package_name} has no candidate in the apt-cache" if num_providers == 0
- # apt will only install a virtual package if there is a single providing package
- raise Chef::Exceptions::Package, "#{@new_resource.package_name} is a virtual package provided by #{num_providers} packages, you must explicitly select one to install" if num_providers > 1
- # Check if the package providing this virtual package is installed
- Chef::Log.info("#{@new_resource} is a virtual package, actually acting on package[#{providers.keys.first}]")
- ret = check_package_state(providers.keys.first)
- installed = ret[:installed]
- installed_version = ret[:installed_version]
- else
- Chef::Log.debug("#{@new_resource} candidate version is #{$1}")
- end
- end
+ def get_candidate_versions
+ package_name_array.map do |package_name|
+ package_data[package_name][:candidate_version]
end
+ end
- return {
- installed_version: installed_version,
- installed: installed,
- candidate_version: candidate_version,
- is_virtual_package: is_virtual_package,
- }
+ def candidate_version
+ @candidate_version ||= get_candidate_versions
end
- def check_all_packages_state(package)
- installed_version = {}
- candidate_version = {}
- installed = {}
+ def packages_all_locked?(names, versions)
+ names.all? { |n| locked_packages.include? n }
+ end
- [package].flatten.each do |pkg|
- ret = check_package_state(pkg)
- is_virtual_package[pkg] = ret[:is_virtual_package]
- installed[pkg] = ret[:installed]
- installed_version[pkg] = ret[:installed_version]
- candidate_version[pkg] = ret[:candidate_version]
- end
+ def packages_all_unlocked?(names, versions)
+ names.all? { |n| !locked_packages.include? n }
+ end
- if package.is_a?(Array)
- @candidate_version = []
- final_installed_version = []
- [package].flatten.each do |pkg|
- @candidate_version << candidate_version[pkg]
- final_installed_version << installed_version[pkg]
+ def locked_packages
+ @locked_packages ||=
+ begin
+ locked = shell_out_compact_timeout!("apt-mark", "showhold")
+ locked.stdout.each_line.map do |line|
+ line.strip
+ end
end
- @current_resource.version(final_installed_version)
- else
- @candidate_version = candidate_version[package]
- @current_resource.version(installed_version[package])
- end
end
def install_package(name, version)
- name_array = [ name ].flatten
- version_array = [ version ].flatten
- package_name = name_array.zip(version_array).map do |n, v|
- is_virtual_package[n] ? n : "#{n}=#{v}"
- end.join(" ")
- run_noninteractive("apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}")
+ package_name = name.zip(version).map do |n, v|
+ package_data[n][:virtual] ? n : "#{n}=#{v}"
+ end
+ run_noninteractive("apt-get", "-q", "-y", config_file_options, default_release_options, options, "install", package_name)
end
def upgrade_package(name, version)
@@ -150,33 +100,135 @@ class Chef
end
def remove_package(name, version)
- package_name = [ name ].flatten.join(" ")
- run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}")
+ package_name = name.map do |n|
+ package_data[n][:virtual] ? resolve_virtual_package_name(n) : n
+ end
+ run_noninteractive("apt-get", "-q", "-y", options, "remove", package_name)
end
def purge_package(name, version)
- package_name = [ name ].flatten.join(" ")
- run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} purge #{package_name}")
+ package_name = name.map do |n|
+ package_data[n][:virtual] ? resolve_virtual_package_name(n) : n
+ end
+ run_noninteractive("apt-get", "-q", "-y", options, "purge", package_name)
end
def preseed_package(preseed_file)
- Chef::Log.info("#{@new_resource} pre-seeding package installation instructions")
- run_noninteractive("debconf-set-selections #{preseed_file}")
+ logger.info("#{new_resource} pre-seeding package installation instructions")
+ run_noninteractive("debconf-set-selections", preseed_file)
end
def reconfig_package(name, version)
- package_name = [ name ].flatten.join(" ")
- Chef::Log.info("#{@new_resource} reconfiguring")
- run_noninteractive("dpkg-reconfigure #{package_name}")
+ logger.info("#{new_resource} reconfiguring")
+ run_noninteractive("dpkg-reconfigure", name)
+ end
+
+ def lock_package(name, version)
+ run_noninteractive("apt-mark", options, "hold", name)
+ end
+
+ def unlock_package(name, version)
+ run_noninteractive("apt-mark", options, "unhold", name)
end
private
+ # compare 2 versions to each other to see which is newer.
+ # this differs from the standard package method because we
+ # need to be able to parse debian version strings which contain
+ # tildes which Gem cannot properly parse
+ #
+ # @return [Integer] 1 if v1 > v2. 0 if they're equal. -1 if v1 < v2
+ def version_compare(v1, v2)
+ if !shell_out_compact_timeout("dpkg", "--compare-versions", v1.to_s, "gt", v2.to_s).error?
+ 1
+ elsif !shell_out_compact_timeout("dpkg", "--compare-versions", v1.to_s, "eq", v2.to_s).error?
+ 0
+ else
+ -1
+ end
+ end
+
# Runs command via shell_out with magic environment to disable
# interactive prompts. Command is run with default localization rather
# than forcing locale to "C", so command output may not be stable.
- def run_noninteractive(command)
- shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+ def run_noninteractive(*args)
+ shell_out_compact_timeout!(*args, env: { "DEBIAN_FRONTEND" => "noninteractive" })
+ end
+
+ def default_release_options
+ # Use apt::Default-Release option only if provider supports it
+ if new_resource.respond_to?(:default_release) && new_resource.default_release
+ [ "-o", "APT::Default-Release=#{new_resource.default_release}" ]
+ end
+ end
+
+ def config_file_options
+ # If the user has specified config file options previously, respect those.
+ return if Array(options).any? { |opt| opt =~ /--force-conf/ }
+
+ # It doesn't make sense to install packages in a scenario that can
+ # result in a prompt. Have users decide up-front whether they want to
+ # forcibly overwrite the config file, otherwise preserve it.
+ if new_resource.overwrite_config_files
+ [ "-o", "Dpkg::Options::=--force-confnew" ]
+ else
+ [ "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold" ]
+ end
+ end
+
+ def resolve_package_versions(pkg)
+ current_version = nil
+ candidate_version = nil
+ run_noninteractive("apt-cache", default_release_options, "policy", pkg).stdout.each_line do |line|
+ case line
+ when /^\s{2}Installed: (.+)$/
+ current_version = ( $1 != "(none)" ) ? $1 : nil
+ logger.trace("#{new_resource} installed version for #{pkg} is #{$1}")
+ when /^\s{2}Candidate: (.+)$/
+ candidate_version = ( $1 != "(none)" ) ? $1 : nil
+ logger.trace("#{new_resource} candidate version for #{pkg} is #{$1}")
+ end
+ end
+ [ current_version, candidate_version ]
+ end
+
+ def resolve_virtual_package_name(pkg)
+ showpkg = run_noninteractive("apt-cache", "showpkg", pkg).stdout
+ partitions = showpkg.rpartition(/Reverse Provides: ?#{$/}/)
+ return nil if partitions[0] == "" && partitions[1] == "" # not found in output
+ set = partitions[2].lines.each_with_object(Set.new) do |line, acc|
+ # there may be multiple reverse provides for a single package
+ acc.add(line.split[0])
+ end
+ if set.size > 1
+ raise Chef::Exceptions::Package, "#{new_resource.package_name} is a virtual package provided by multiple packages, you must explicitly select one"
+ end
+ set.to_a.first
+ end
+
+ def package_data_for(pkg)
+ virtual = false
+ current_version = nil
+ candidate_version = nil
+
+ current_version, candidate_version = resolve_package_versions(pkg)
+
+ if candidate_version.nil?
+ newpkg = resolve_virtual_package_name(pkg)
+
+ if newpkg
+ virtual = true
+ logger.info("#{new_resource} is a virtual package, actually acting on package[#{newpkg}]")
+ current_version, candidate_version = resolve_package_versions(newpkg)
+ end
+ end
+
+ {
+ current_version: current_version,
+ candidate_version: candidate_version,
+ virtual: virtual,
+ }
end
end
diff --git a/lib/chef/provider/package/bff.rb b/lib/chef/provider/package/bff.rb
new file mode 100644
index 0000000000..44fadd92df
--- /dev/null
+++ b/lib/chef/provider/package/bff.rb
@@ -0,0 +1,142 @@
+#
+# Author:: Deepali Jagtap
+# Copyright:: Copyright 2013-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/package"
+require "chef/mixin/get_source_from_package"
+
+class Chef
+ class Provider
+ class Package
+ class Bff < Chef::Provider::Package
+
+ provides :package, os: "aix"
+ provides :bff_package
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def define_resource_requirements
+ super
+ requirements.assert(:install) do |a|
+ a.assertion { new_resource.source }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{new_resource.name} required for action install"
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !new_resource.source || package_source_found? }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.name} not found: #{new_resource.source}"
+ a.whyrun "would assume #{new_resource.source} would be have previously been made available"
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+
+ if package_source_found?
+ logger.trace("#{new_resource} checking pkg status")
+ ret = shell_out_compact_timeout("installp", "-L", "-d", new_resource.source)
+ ret.stdout.each_line do |line|
+ case line
+ when /:#{new_resource.package_name}:/
+ fields = line.split(":")
+ new_resource.version(fields[2])
+ when /^#{new_resource.package_name}:/
+ logger.warn("You are installing a bff package by product name. For idempotent installs, please install individual filesets")
+ fields = line.split(":")
+ new_resource.version(fields[2])
+ end
+ end
+ raise Chef::Exceptions::Package, "package source #{new_resource.source} does not provide package #{new_resource.package_name}" unless new_resource.version
+ end
+
+ logger.trace("#{new_resource} checking install state")
+ ret = shell_out_compact_timeout("lslpp", "-lcq", current_resource.package_name)
+ ret.stdout.each_line do |line|
+ case line
+ when /#{current_resource.package_name}/
+ fields = line.split(":")
+ logger.trace("#{new_resource} version #{fields[2]} is already installed")
+ current_resource.version(fields[2])
+ end
+ end
+
+ unless ret.exitstatus == 0 || ret.exitstatus == 1
+ raise Chef::Exceptions::Package, "lslpp failed - #{ret.format_for_exception}!"
+ end
+
+ current_resource
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+ if package_source_found?
+ ret = shell_out_compact_timeout("installp", "-L", "-d", new_resource.source)
+ ret.stdout.each_line do |line|
+ case line
+ when /\w:#{Regexp.escape(new_resource.package_name)}:(.*)/
+ fields = line.split(":")
+ @candidate_version = fields[2]
+ new_resource.version(fields[2])
+ logger.trace("#{new_resource} setting install candidate version to #{@candidate_version}")
+ end
+ end
+ unless ret.exitstatus == 0
+ raise Chef::Exceptions::Package, "installp -L -d #{new_resource.source} - #{ret.format_for_exception}!"
+ end
+ end
+ @candidate_version
+ end
+
+ #
+ # The install/update action needs to be tested with various kinds of packages
+ # on AIX viz. packages with or without licensing file dependencies, packages
+ # with dependencies on other packages which will help to test additional
+ # options of installp.
+ # So far, the code has been tested only with standalone packages.
+ #
+ def install_package(name, version)
+ logger.trace("#{new_resource} package install options: #{options}")
+ if options.nil?
+ shell_out_compact_timeout!("installp", "-aYF", "-d", new_resource.source, new_resource.package_name)
+ logger.trace("#{new_resource} installed version #{new_resource.version} from: #{new_resource.source}")
+ else
+ shell_out_compact_timeout!("installp", "-aYF", options, "-d", new_resource.source, new_resource.package_name)
+ logger.trace("#{new_resource} installed version #{new_resource.version} from: #{new_resource.source}")
+ end
+ end
+
+ alias upgrade_package install_package
+
+ def remove_package(name, version)
+ if options.nil?
+ shell_out_compact_timeout!("installp", "-u", name)
+ logger.trace("#{new_resource} removed version #{new_resource.version}")
+ else
+ shell_out_compact_timeout!("installp", "-u", options, name)
+ logger.trace("#{new_resource} removed version #{new_resource.version}")
+ end
+ end
+
+ def package_source_found?
+ @package_source_found ||= new_resource.source && ::File.exist?(new_resource.source)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/cab.rb b/lib/chef/provider/package/cab.rb
new file mode 100644
index 0000000000..79292293d2
--- /dev/null
+++ b/lib/chef/provider/package/cab.rb
@@ -0,0 +1,183 @@
+#
+# Author:: Vasundhara Jagdale (<vasundhara.jagdale@msystechnologies.com>)
+# Copyright:: Copyright 2015-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/cab_package"
+require "chef/mixin/shell_out"
+require "chef/mixin/uris"
+require "chef/mixin/checksum"
+
+class Chef
+ class Provider
+ class Package
+ class Cab < Chef::Provider::Package
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::Uris
+ include Chef::Mixin::Checksum
+
+ provides :cab_package, os: "windows"
+
+ def load_current_resource
+ @current_resource = Chef::Resource::CabPackage.new(new_resource.name)
+ current_resource.source(cab_file_source)
+ new_resource.version(package_version)
+ current_resource.version(installed_version)
+ current_resource
+ end
+
+ def cab_file_source
+ @cab_file_source ||= uri_scheme?(new_resource.source) ? download_source_file : new_resource.source
+ end
+
+ def download_source_file
+ source_resource.run_action(:create)
+ logger.trace("#{new_resource} fetched source file to #{source_resource.path}")
+ source_resource.path
+ end
+
+ def source_resource
+ @source_resource ||= declare_resource(:remote_file, new_resource.name) do
+ path default_download_cache_path
+ source new_resource.source
+ backup false
+ end
+ end
+
+ def default_download_cache_path
+ uri = ::URI.parse(new_resource.source)
+ filename = ::File.basename(::URI.unescape(uri.path))
+ file_cache_dir = Chef::FileCache.create_cache_path("package/")
+ Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+ end
+
+ def install_package(name, version)
+ dism_command("/Add-Package /PackagePath:\"#{cab_file_source}\"")
+ end
+
+ def remove_package(name, version)
+ dism_command("/Remove-Package /PackagePath:\"#{cab_file_source}\"")
+ end
+
+ def dism_command(command)
+ shellout = Mixlib::ShellOut.new("dism.exe /Online /English #{command} /NoRestart", timeout: new_resource.timeout)
+ with_os_architecture(nil) do
+ shellout.run_command
+ end
+ end
+
+ def installed_version
+ # e.g. Package_for_KB2975719~31bf3856ad364e35~amd64~~6.3.1.8
+ package = new_cab_identity
+ # Search for just the package name to catch a different version being installed
+ logger.trace("#{new_resource} searching for installed package #{package['name']}")
+ existing_package_identities = installed_packages.map do |p|
+ split_package_identity(p["package_identity"])
+ end
+ found_packages = existing_package_identities.select do |existing_package_ident|
+ existing_package_ident["name"] == package["name"]
+ end
+ if found_packages.empty?
+ nil
+ elsif found_packages.length == 1
+ found_packages.first["version"]
+ else
+ # Presuming this won't happen, otherwise we need to handle it
+ raise Chef::Exceptions::Package, "Found multiple packages installed matching name #{package['name']}, found: #{found_packages.length} matches"
+ end
+ end
+
+ def cab_identity_from_cab_file
+ stdout = dism_command("/Get-PackageInfo /PackagePath:\"#{cab_file_source}\"").stdout
+ package_info = parse_dism_get_package_info(stdout)
+ split_package_identity(package_info["package_information"]["package_identity"])
+ end
+
+ def new_cab_identity
+ logger.trace("#{new_resource} getting product version for package at #{cab_file_source}")
+ @new_cab_identity ||= cab_identity_from_cab_file
+ end
+
+ def package_version
+ new_cab_identity["version"].chomp
+ end
+
+ # returns a hash of package state information given the output of dism /get-packages
+ # expected keys: package_identity
+ def parse_dism_get_packages(text)
+ packages = []
+ text.each_line do |line|
+ key, value = line.split(":") if line.start_with?("Package Identity")
+ next if key.nil? || value.nil?
+ package = {}
+ package[key.downcase.strip.tr(" ", "_")] = value.strip.chomp
+ packages << package
+ end
+ packages
+ end
+
+ # returns a hash of package information given the output of dism /get-packageinfo
+ def parse_dism_get_package_info(text)
+ package_data = {}
+ errors = []
+ in_section = false
+ section_headers = [ "Package information", "Custom Properties", "Features" ]
+ text.each_line do |line|
+ if line =~ /Error: (.*)/
+ errors << $1.strip
+ elsif section_headers.any? { |header| line =~ /^(#{header})/ }
+ in_section = $1.downcase.tr(" ", "_")
+ elsif line =~ /(.*) ?: (.*)/
+ v = $2 # has to be first or the gsub below replaces this variable
+ k = $1.downcase.strip.tr(" ", "_")
+ if in_section
+ package_data[in_section] = {} unless package_data[in_section]
+ package_data[in_section][k] = v
+ else
+ package_data[k] = v
+ end
+ end
+ end
+ unless errors.empty?
+ if errors.include?("0x80070003") || errors.include?("0x80070002")
+ raise Chef::Exceptions::Package, "DISM: The system cannot find the path or file specified."
+ elsif errors.include?("740")
+ raise Chef::Exceptions::Package, "DISM: Error 740: Elevated permissions are required to run DISM."
+ else
+ raise Chef::Exceptions::Package, "Unknown errors encountered parsing DISM output: #{errors}"
+ end
+ end
+ package_data
+ end
+
+ def split_package_identity(identity)
+ data = {}
+ data["name"], data["publisher"], data["arch"], data["resource_id"], data["version"] = identity.split("~")
+ data
+ end
+
+ def installed_packages
+ @packages ||= begin
+ output = dism_command("/Get-Packages").stdout
+ packages = parse_dism_get_packages(output)
+ packages
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/chocolatey.rb b/lib/chef/provider/package/chocolatey.rb
index 44fb1de235..a6abdd5b46 100644
--- a/lib/chef/provider/package/chocolatey.rb
+++ b/lib/chef/provider/package/chocolatey.rb
@@ -25,13 +25,13 @@ class Chef
class Chocolatey < Chef::Provider::Package
include Chef::Mixin::PowershellOut
- provides :chocolatey_package, os: "windows"
+ provides :chocolatey_package
# Declare that our arguments should be arrays
use_multipackage_api
- PATHFINDING_POWERSHELL_COMMAND = "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')"
- CHOCO_MISSING_MSG = <<-EOS
+ PATHFINDING_POWERSHELL_COMMAND = "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')".freeze
+ CHOCO_MISSING_MSG = <<-EOS.freeze
Could not locate your Chocolatey install. To install chocolatey, we recommend
the 'chocolatey' cookbook (https://github.com/chocolatey/chocolatey-cookbook).
If Chocolatey is installed, ensure that the 'ChocolateyInstall' environment
@@ -59,8 +59,8 @@ EOS
# so we want to assert candidates exist for the alternate source
requirements.assert(:upgrade, :install) do |a|
a.assertion { candidates_exist_for_all_uninstalled? }
- a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(", ")}")
- a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(", ")} would have been configured")
+ a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(', ')}")
+ a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(', ')} would have been configured")
end
end
@@ -84,7 +84,7 @@ EOS
# choco does not support installing multiple packages with version pins
name_has_versions.each do |name, version|
- choco_command("install -y -version", version, cmd_args, name)
+ choco_command("install -y --version", version, cmd_args, name)
end
# but we can do all the ones without version pins at once
@@ -106,7 +106,7 @@ EOS
# choco does not support installing multiple packages with version pins
name_has_versions.each do |name, version|
- choco_command("upgrade -y -version", version, cmd_args, name)
+ choco_command("upgrade -y --version", version, cmd_args, name)
end
# but we can do all the ones without version pins at once
@@ -124,24 +124,26 @@ EOS
choco_command("uninstall -y", cmd_args(include_source: false), *names)
end
- # Support :uninstall as an action in order for users to easily convert
- # from the `chocolatey` provider in the cookbook. It is, however,
- # already deprecated.
- def action_uninstall
- Chef::Log.deprecation "The use of action :uninstall on the chocolatey_package provider is deprecated, please use :remove"
- action_remove
- end
-
# Choco does not have dpkg's distinction between purge and remove
- alias_method :purge_package, :remove_package
+ alias purge_package remove_package
# Override the superclass check. The semantics for our new_resource.source is not files to
# install from, but like the rubygem provider's sources which are more like repos.
- def check_resource_semantics!
- end
+ def check_resource_semantics!; end
private
+ def version_compare(v1, v2)
+ if v1 == "latest" || v2 == "latest"
+ return 0
+ end
+
+ gem_v1 = Gem::Version.new(v1)
+ gem_v2 = Gem::Version.new(v2)
+
+ gem_v1 <=> gem_v2
+ end
+
# Magic to find where chocolatey is installed in the system, and to
# return the full path of choco.exe
#
@@ -160,7 +162,7 @@ EOS
def choco_install_path
@choco_install_path ||= powershell_out!(
PATHFINDING_POWERSHELL_COMMAND
- ).stdout.chomp
+ ).stdout.chomp
end
# Helper to dispatch a choco command through shell_out using the timeout
@@ -169,7 +171,7 @@ EOS
# @param args [String] variable number of string arguments
# @return [Mixlib::ShellOut] object returned from shell_out!
def choco_command(*args)
- shell_out_with_timeout!(args_to_string(choco_exe, *args))
+ shell_out_with_timeout!(args_to_string(choco_exe, *args), returns: new_resource.returns)
end
# Use the available_packages Hash helper to create an array suitable for
@@ -227,17 +229,21 @@ EOS
#
# @return [Hash] name-to-version mapping of available packages
def available_packages
- @available_packages ||=
- begin
- cmd = [ "list -ar #{package_name_array.join ' '}" ]
- cmd.push( "-source #{new_resource.source}" ) if new_resource.source
- parse_list_output(*cmd).each_with_object({}) do |name_version, available|
- name, version = name_version
- if desired_name_versions[name].nil? || desired_name_versions[name] == version
- available[name] = version
+ return @available_packages if @available_packages
+ @available_packages = {}
+ package_name_array.each do |pkg|
+ available_versions =
+ begin
+ cmd = [ "list -r #{pkg}" ]
+ cmd.push( "-source #{new_resource.source}" ) if new_resource.source
+ raw = parse_list_output(*cmd)
+ raw.keys.each_with_object({}) do |name, available|
+ available[name] = desired_name_versions[name] || raw[name]
end
end
- end
+ @available_packages.merge! available_versions
+ end
+ @available_packages
end
# Installed packages in chocolatey as a Hash of names mapped to versions
@@ -246,20 +252,22 @@ EOS
# @return [Hash] name-to-version mapping of installed packages
def installed_packages
@installed_packages ||= Hash[*parse_list_output("list -l -r").flatten]
+ @installed_packages
end
# Helper to convert choco.exe list output to a Hash
# (names are downcased for case-insenstive matching)
#
# @param cmd [String] command to run
- # @return [Array] list output converted to ruby Hash
+ # @return [Hash] list output converted to ruby Hash
def parse_list_output(*args)
- list = []
+ hash = {}
choco_command(*args).stdout.each_line do |line|
+ next if line.start_with?("Chocolatey v")
name, version = line.split("|")
- list << [ name.downcase, version.chomp ]
+ hash[name.downcase] = version&.chomp
end
- list
+ hash
end
# Helper to downcase all names in an array
@@ -267,7 +275,7 @@ EOS
# @param names [Array] original mixed case names
# @return [Array] same names in lower case
def lowercase_names(names)
- names.map { |name| name.downcase }
+ names.map(&:downcase)
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..90a5596727
--- /dev/null
+++ b/lib/chef/provider/package/dnf.rb
@@ -0,0 +1,196 @@
+#
+# Copyright:: Copyright 2016-2017, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/provider/package"
+require "chef/resource/dnf_package"
+require "chef/mixin/which"
+require "chef/mixin/shell_out"
+require "chef/mixin/get_source_from_package"
+require "chef/provider/package/dnf/python_helper"
+require "chef/provider/package/dnf/version"
+
+class Chef
+ class Provider
+ class Package
+ class Dnf < Chef::Provider::Package
+ extend Chef::Mixin::Which
+ extend Chef::Mixin::ShellOut
+ include Chef::Mixin::GetSourceFromPackage
+
+ allow_nils
+ use_multipackage_api
+ use_package_name_for_source
+
+ # all rhel variants >= 8 will use DNF
+ provides :package, platform_family: "rhel", platform_version: ">= 8"
+
+ # fedora >= 22 uses DNF
+ provides :package, platform: "fedora", platform_version: ">= 22"
+
+ # amazon will eventually use DNF
+ provides :package, platform: "amazon" do
+ which("dnf")
+ end
+
+ provides :dnf_package
+
+ #
+ # Most of the magic in this class happens in the python helper script. The ruby side of this
+ # provider knows only enough to translate Chef-style new_resource name+package+version into
+ # a request to the python side. The python side is then responsible for knowing everything
+ # about RPMs and what is installed and what is available. The ruby side of this class should
+ # remain a lightweight translation layer to translate Chef requests into RPC requests to
+ # python. This class knows nothing about how to compare RPM versions, and does not maintain
+ # any cached state of installed/available versions and should be kept that way.
+ #
+ def python_helper
+ @python_helper ||= PythonHelper.instance
+ end
+
+ def load_current_resource
+ flushcache if new_resource.flush_cache[:before]
+
+ @current_resource = Chef::Resource::DnfPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(get_current_versions)
+
+ current_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:install, :upgrade, :remove, :purge) do |a|
+ a.assertion { !new_resource.source || ::File.exist?(new_resource.source) }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "assuming #{new_resource.source} would have previously been created"
+ end
+
+ super
+ end
+
+ def candidate_version
+ package_name_array.each_with_index.map do |pkg, i|
+ available_version(i).version_with_arch
+ end
+ end
+
+ def get_current_versions
+ package_name_array.each_with_index.map do |pkg, i|
+ installed_version(i).version_with_arch
+ end
+ end
+
+ def install_package(names, versions)
+ if new_resource.source
+ dnf(options, "-y install", new_resource.source)
+ else
+ resolved_names = names.each_with_index.map { |name, i| available_version(i).to_s unless name.nil? }
+ dnf(options, "-y install", resolved_names)
+ end
+ flushcache
+ end
+
+ # dnf upgrade does not work on uninstalled packaged, while install will upgrade
+ alias upgrade_package install_package
+
+ def remove_package(names, versions)
+ resolved_names = names.each_with_index.map { |name, i| installed_version(i).to_s unless name.nil? }
+ dnf(options, "-y remove", resolved_names)
+ flushcache
+ end
+
+ alias purge_package remove_package
+
+ action :flush_cache do
+ flushcache
+ end
+
+ private
+
+ def resolve_source_to_version_obj
+ shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n' #{new_resource.source}").stdout.each_line do |line|
+ # this is another case of committing the sin of doing some lightweight mangling of RPM versions in ruby -- but the output of the rpm command
+ # does not match what the dnf library accepts.
+ case line
+ when /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/
+ return Version.new($1, "#{$2 == '(none)' ? '0' : $2}:#{$3}-#{$4}", $5)
+ end
+ end
+ end
+
+ def version_compare(v1, v2)
+ python_helper.compare_versions(v1, v2)
+ end
+
+ # @returns Array<Version>
+ def available_version(index)
+ @available_version ||= []
+
+ @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])
+ end
+
+ @available_version[index]
+ end
+
+ # @return [Array<Version>]
+ 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])
+ else
+ python_helper.query(:whatinstalled, package_name_array[index], safe_version_array[index], safe_arch_array[index])
+ end
+ @installed_version[index]
+ end
+
+ # cache flushing is accomplished by simply restarting the python helper. this produces a roughly
+ # 15% hit to the runtime of installing/removing/upgrading packages. correctly using multipackage
+ # array installs (and the multipackage cookbook) can produce 600% improvements in runtime.
+ def flushcache
+ python_helper.restart
+ end
+
+ def dnf(*args)
+ shell_out_with_timeout!(a_to_s("dnf", *args))
+ end
+
+ def safe_version_array
+ if new_resource.version.is_a?(Array)
+ new_resource.version
+ elsif new_resource.version.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.version ]
+ end
+ end
+
+ def safe_arch_array
+ if new_resource.arch.is_a?(Array)
+ new_resource.arch
+ elsif new_resource.arch.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.arch ]
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dnf/dnf_helper.py b/lib/chef/provider/package/dnf/dnf_helper.py
new file mode 100644
index 0000000000..501d6fceee
--- /dev/null
+++ b/lib/chef/provider/package/dnf/dnf_helper.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+
+import sys
+import dnf
+import hawkey
+import signal
+import os
+import json
+
+base = None
+
+def get_sack():
+ global base
+ if base is None:
+ base = dnf.Base()
+ base.read_all_repos()
+ base.fill_sack()
+ return base.sack
+
+# FIXME: leaks memory and does not work
+def flushcache():
+ try:
+ os.remove('/var/cache/dnf/@System.solv')
+ except OSError:
+ pass
+ get_sack().load_system_repo(build_cache=True)
+
+def versioncompare(versions):
+ sack = get_sack()
+ if (versions[0] is None) or (versions[1] is None):
+ sys.stdout.write('0\n')
+ else:
+ evr_comparison = sack.evr_cmp(versions[0], versions[1])
+ sys.stdout.write('{}\n'.format(evr_comparison))
+
+def query(command):
+ sack = get_sack()
+
+ subj = dnf.subject.Subject(command['provides'])
+ q = subj.get_best_query(sack, with_provides=True)
+
+ if command['action'] == "whatinstalled":
+ q = q.installed()
+
+ if command['action'] == "whatavailable":
+ q = q.available()
+
+ if 'epoch' in command:
+ q = q.filterm(epoch=int(command['epoch']))
+ if 'version' in command:
+ q = q.filterm(version__glob=command['version'])
+ if 'release' in command:
+ q = q.filterm(release__glob=command['release'])
+
+ if 'arch' in command:
+ q = q.filterm(arch__glob=command['arch'])
+
+ # only apply the default arch query filter if it returns something
+ archq = q.filter(arch=[ 'noarch', hawkey.detect_arch() ])
+ if len(archq.run()) > 0:
+ q = archq
+
+ pkgs = q.latest(1).run()
+
+ if not pkgs:
+ sys.stdout.write('{} nil nil\n'.format(command['provides'].split().pop(0)))
+ else:
+ # make sure we picked the package with the highest version
+ pkgs.sort
+ pkg = pkgs.pop()
+ sys.stdout.write('{} {}:{}-{} {}\n'.format(pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch))
+
+# the design of this helper is that it should try to be 'brittle' and fail hard and exit in order
+# to keep process tables clean. additional error handling should probably be added to the retry loop
+# on the ruby side.
+def exit_handler(signal, frame):
+ sys.exit(0)
+
+signal.signal(signal.SIGINT, exit_handler)
+signal.signal(signal.SIGHUP, exit_handler)
+signal.signal(signal.SIGPIPE, exit_handler)
+
+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")
diff --git a/lib/chef/provider/package/dnf/python_helper.rb b/lib/chef/provider/package/dnf/python_helper.rb
new file mode 100644
index 0000000000..5524740fc4
--- /dev/null
+++ b/lib/chef/provider/package/dnf/python_helper.rb
@@ -0,0 +1,172 @@
+#
+# Copyright:: Copyright 2016-2017, 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/mixin/which"
+require "chef/mixin/shell_out"
+require "chef/provider/package/dnf/version"
+require "timeout"
+
+class Chef
+ class Provider
+ class Package
+ class Dnf < Chef::Provider::Package
+ class PythonHelper
+ include Singleton
+ include Chef::Mixin::Which
+ include Chef::Mixin::ShellOut
+
+ 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
+
+ def dnf_command
+ @dnf_command ||= which("python", "python3", "python2", "python2.7") do |f|
+ shell_out("#{f} -c 'import dnf'").exitstatus == 0
+ end + " #{DNF_HELPER}"
+ end
+
+ def start
+ ENV["PYTHONUNBUFFERED"] = "1"
+ @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(dnf_command)
+ end
+
+ def reap
+ unless wait_thr.nil?
+ Process.kill("KILL", wait_thr.pid) rescue nil
+ stdin.close unless stdin.nil?
+ stdout.close unless stdout.nil?
+ stderr.close unless stderr.nil?
+ wait_thr.value # this calls waitpit()
+ end
+ end
+
+ def check
+ start if stdin.nil?
+ end
+
+ 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
+ end
+ end
+
+ # @returns Array<Version>
+ 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
+ end
+
+ def restart
+ reap
+ start
+ end
+
+ private
+
+ # i couldn't figure out how to decompose an evr on the python side, it seems reasonably
+ # painless to do it in ruby (generally massaging nevras in the ruby side is HIGHLY
+ # discouraged -- this is an "every rule has an exception" exception -- any additional
+ # functionality should probably trigger moving this regexp logic into python)
+ def add_version(hash, version)
+ epoch = nil
+ if version =~ /(\S+):(\S+)/
+ epoch = $1
+ version = $2
+ end
+ if version =~ /(\S+)-(\S+)/
+ version = $1
+ release = $2
+ end
+ hash["epoch"] = epoch unless epoch.nil?
+ hash["release"] = release unless release.nil?
+ hash["version"] = version
+ end
+
+ def build_query(action, provides, version, arch)
+ hash = { "action" => action }
+ hash["provides"] = provides
+ add_version(hash, version) unless version.nil?
+ hash["arch" ] = arch unless arch.nil?
+ FFI_Yajl::Encoder.encode(hash)
+ end
+
+ def build_version_query(action, versions)
+ hash = { "action" => action }
+ hash["versions"] = versions
+ FFI_Yajl::Encoder.encode(hash)
+ end
+
+ def parse_response(output)
+ array = output.split.map { |x| x == "nil" ? nil : x }
+ array.each_slice(3).map { |x| Version.new(*x) }.first
+ end
+
+ def drain_stderr
+ output = ""
+ until IO.select([stderr], nil, nil, 0).nil?
+ output += stderr.sysread(4096).chomp
+ end
+ output
+ rescue
+ # we must rescue EOFError, and we don't much care about errors on stderr anyway
+ output
+ end
+
+ def with_helper
+ max_retries ||= 5
+ ret = nil
+ Timeout.timeout(600) do
+ check
+ ret = yield
+ end
+ output = drain_stderr
+ unless output.empty?
+ Chef::Log.trace "discarding output on stderr from python helper: #{output}"
+ end
+ ret
+ rescue EOFError, Errno::EPIPE, Timeout::Error, Errno::ESRCH => e
+ output = drain_stderr
+ if ( max_retries -= 1 ) > 0
+ unless output.empty?
+ Chef::Log.trace "discarding output on stderr from python helper: #{output}"
+ end
+ restart
+ retry
+ else
+ raise e if output.empty?
+ raise "dnf-helper.py had stderr output:\n\n#{output}"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dnf/version.rb b/lib/chef/provider/package/dnf/version.rb
new file mode 100644
index 0000000000..3cff5b0437
--- /dev/null
+++ b/lib/chef/provider/package/dnf/version.rb
@@ -0,0 +1,56 @@
+#
+# Copyright:: Copyright 2016, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Provider
+ class Package
+ class Dnf < Chef::Provider::Package
+
+ # helper class to assist in passing around name/version/arch triples
+ class Version
+ attr_accessor :name
+ attr_accessor :version
+ attr_accessor :arch
+
+ def initialize(name, version, arch)
+ @name = name
+ @version = version
+ @arch = arch
+ end
+
+ def to_s
+ "#{name}-#{version}.#{arch}"
+ end
+
+ def version_with_arch
+ "#{version}.#{arch}" unless version.nil?
+ end
+
+ def matches_name_and_arch?(other)
+ other.version == version && other.arch == arch
+ end
+
+ def ==(other)
+ name == other.name && version == other.version && arch == other.arch
+ end
+
+ alias eql? ==
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index a5a80e14d6..aa53f6145f 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -23,11 +23,11 @@ class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package
- DPKG_REMOVED = /^Status: deinstall ok config-files/
+ DPKG_REMOVED = /^Status: deinstall ok config-files/
DPKG_INSTALLED = /^Status: install ok installed/
- DPKG_VERSION = /^Version: (.+)$/
+ DPKG_VERSION = /^Version: (.+)$/
- provides :dpkg_package, os: "linux"
+ provides :dpkg_package
use_multipackage_api
use_package_name_for_source
@@ -73,18 +73,18 @@ class Chef
def install_package(name, version)
sources = name.map { |n| name_sources[n] }
- Chef::Log.info("#{new_resource} installing package(s): #{name.join(' ')}")
- run_noninteractive("dpkg -i", new_resource.options, *sources)
+ logger.info("#{new_resource} installing package(s): #{name.join(' ')}")
+ run_noninteractive("dpkg", "-i", *options, *sources)
end
def remove_package(name, version)
- Chef::Log.info("#{new_resource} removing package(s): #{name.join(' ')}")
- run_noninteractive("dpkg -r", new_resource.options, *name)
+ logger.info("#{new_resource} removing package(s): #{name.join(' ')}")
+ run_noninteractive("dpkg", "-r", *options, *name)
end
def purge_package(name, version)
- Chef::Log.info("#{new_resource} purging packages(s): #{name.join(' ')}")
- run_noninteractive("dpkg -P", new_resource.options, *name)
+ logger.info("#{new_resource} purging packages(s): #{name.join(' ')}")
+ run_noninteractive("dpkg", "-P", *options, *name)
end
def upgrade_package(name, version)
@@ -92,24 +92,39 @@ class Chef
end
def preseed_package(preseed_file)
- Chef::Log.info("#{new_resource} pre-seeding package installation instructions")
+ logger.info("#{new_resource} pre-seeding package installation instructions")
run_noninteractive("debconf-set-selections", *preseed_file)
end
def reconfig_package(name, version)
- Chef::Log.info("#{new_resource} reconfiguring")
+ logger.info("#{new_resource} reconfiguring")
run_noninteractive("dpkg-reconfigure", *name)
end
# Override the superclass check. Multiple sources are required here.
- def check_resource_semantics!
- end
+ def check_resource_semantics!; end
private
+ # compare 2 versions to each other to see which is newer.
+ # this differs from the standard package method because we
+ # need to be able to parse debian version strings which contain
+ # tildes which Gem cannot properly parse
+ #
+ # @return [Integer] 1 if v1 > v2. 0 if they're equal. -1 if v1 < v2
+ def version_compare(v1, v2)
+ if !shell_out_compact_timeout("dpkg", "--compare-versions", v1.to_s, "gt", v2.to_s).error?
+ 1
+ elsif !shell_out_compact_timeout("dpkg", "--compare-versions", v1.to_s, "eq", v2.to_s).error?
+ 0
+ else
+ -1
+ end
+ end
+
def read_current_version_of_package(package_name)
- Chef::Log.debug("#{new_resource} checking install state of #{package_name}")
- status = shell_out_with_timeout!("dpkg -s #{package_name}", returns: [0, 1])
+ logger.trace("#{new_resource} checking install state of #{package_name}")
+ status = shell_out_compact_timeout!("dpkg", "-s", package_name, returns: [0, 1])
package_installed = false
status.stdout.each_line do |line|
case line
@@ -120,12 +135,12 @@ class Chef
package_installed = true
when DPKG_VERSION
if package_installed
- Chef::Log.debug("#{new_resource} current version is #{$1}")
+ logger.trace("#{new_resource} current version is #{$1}")
return $1
end
end
end
- return nil
+ nil
end
def get_current_version_from(array)
@@ -137,7 +152,7 @@ class Chef
# Runs command via shell_out_with_timeout with magic environment to disable
# interactive prompts.
def run_noninteractive(*command)
- shell_out_with_timeout!(a_to_s(*command), :env => { "DEBIAN_FRONTEND" => "noninteractive" })
+ shell_out_compact_timeout!(*command, env: { "DEBIAN_FRONTEND" => "noninteractive" })
end
# Returns true if all sources exist. Returns false if any do not, or if no
@@ -176,8 +191,8 @@ class Chef
@name_pkginfo ||=
begin
pkginfos = resolved_source_array.map do |src|
- Chef::Log.debug("#{new_resource} checking #{src} dpkg status")
- status = shell_out_with_timeout!("dpkg-deb -W #{src}")
+ logger.trace("#{new_resource} checking #{src} dpkg status")
+ status = shell_out_compact_timeout!("dpkg-deb", "-W", src)
status.stdout
end
Hash[*package_name_array.zip(pkginfos).flatten]
diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb
deleted file mode 100644
index 989f2ab9d2..0000000000
--- a/lib/chef/provider/package/easy_install.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-#
-# Author:: Joe Williams (<joe@joetify.com>)
-# Copyright:: Copyright 2009-2016, Joe Williams
-# 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/mixin/command"
-require "chef/resource/package"
-
-class Chef
- class Provider
- class Package
- class EasyInstall < Chef::Provider::Package
-
- provides :easy_install_package
-
- def install_check(name)
- check = false
-
- begin
- # first check to see if we can import it
- output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{name}\"", :returns => [0, 1]).stderr
- if output.include? "ImportError"
- # then check to see if its on the path
- output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns => [0, 1]).stdout
- if output.downcase.include? "#{name.downcase}"
- check = true
- end
- else
- check = true
- end
- rescue
- # it's probably not installed
- end
-
- check
- end
-
- def easy_install_binary_path
- path = @new_resource.easy_install_binary
- path ? path : "easy_install"
- end
-
- def python_binary_path
- path = @new_resource.python_binary
- path ? path : "python"
- end
-
- def module_name
- m = @new_resource.module_name
- m ? m : @new_resource.name
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
-
- # get the currently installed version if installed
- package_version = nil
- if install_check(module_name)
- begin
- output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
- package_version = output.strip
- rescue
- output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns => [0, 1]).stdout
-
- output_array = output.gsub(/[\[\]]/, "").split(/\s*,\s*/)
- package_path = ""
-
- output_array.each do |entry|
- if entry.downcase.include?(@new_resource.package_name)
- package_path = entry
- end
- end
-
- package_path[/\S\S(.*)\/(.*)-(.*)-py(.*).egg\S/]
- package_version = $3
- end
- end
-
- if package_version == @new_resource.version
- Chef::Log.debug("#{@new_resource} at version #{@new_resource.version}")
- @current_resource.version(@new_resource.version)
- else
- Chef::Log.debug("#{@new_resource} at version #{package_version}")
- @current_resource.version(package_version)
- end
-
- @current_resource
- end
-
- def candidate_version
- return @candidate_version if @candidate_version
-
- # do a dry run to get the latest version
- result = shell_out_with_timeout!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns => [0, 1])
- @candidate_version = result.stdout[/(.*)Best match: (.*) (.*)$/, 3]
- @candidate_version
- end
-
- def install_package(name, version)
- Chef.log_deprecation("The easy_install package provider is deprecated and will be removed in Chef 13.")
- run_command(:command => "#{easy_install_binary_path}#{expand_options(@new_resource.options)} \"#{name}==#{version}\"")
- end
-
- def upgrade_package(name, version)
- install_package(name, version)
- end
-
- def remove_package(name, version)
- Chef.log_deprecation("The easy_install package provider is deprecated and will be removed in Chef 13.")
- run_command(:command => "#{easy_install_binary_path }#{expand_options(@new_resource.options)} -m #{name}")
- end
-
- def purge_package(name, version)
- remove_package(name, version)
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/package/freebsd/base.rb b/lib/chef/provider/package/freebsd/base.rb
index 7104a71f70..fc62fa7cc0 100644
--- a/lib/chef/provider/package/freebsd/base.rb
+++ b/lib/chef/provider/package/freebsd/base.rb
@@ -47,7 +47,7 @@ class Chef
# Otherwise look up the path to the ports directory using 'whereis'
else
- whereis = shell_out_with_timeout!("whereis -s #{port}", :env => nil)
+ whereis = shell_out_compact_timeout!("whereis", "-s", port, env: nil)
unless path = whereis.stdout[/^#{Regexp.escape(port)}:\s+(.+)$/, 1]
raise Chef::Exceptions::Package, "Could not find port with the name #{port}"
end
@@ -56,9 +56,9 @@ class Chef
end
def makefile_variable_value(variable, dir = nil)
- options = dir ? { :cwd => dir } : {}
- make_v = shell_out_with_timeout!("make -V #{variable}", options.merge!(:env => nil, :returns => [0, 1]))
- make_v.exitstatus.zero? ? make_v.stdout.strip.split($\).first : nil # $\ is the line separator, i.e. newline.
+ options = dir ? { cwd: dir } : {}
+ make_v = shell_out_compact_timeout!("make", "-V", variable, options.merge!(env: nil, returns: [0, 1]))
+ make_v.exitstatus == 0 ? make_v.stdout.strip.split($OUTPUT_RECORD_SEPARATOR).first : nil # $\ is the line separator, i.e. newline.
end
end
@@ -67,19 +67,19 @@ class Chef
def initialize(*args)
super
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
end
def load_current_resource
- @current_resource.package_name(@new_resource.package_name)
+ current_resource.package_name(new_resource.package_name)
- @current_resource.version(current_installed_version)
- Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version
+ current_resource.version(current_installed_version)
+ logger.trace("#{new_resource} current version is #{current_resource.version}") if current_resource.version
@candidate_version = candidate_version
- Chef::Log.debug("#{@new_resource} candidate version is #{@candidate_version}") if @candidate_version
+ logger.trace("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version
- @current_resource
+ current_resource
end
end
diff --git a/lib/chef/provider/package/freebsd/pkg.rb b/lib/chef/provider/package/freebsd/pkg.rb
index 78d9449454..04e6e5c427 100644
--- a/lib/chef/provider/package/freebsd/pkg.rb
+++ b/lib/chef/provider/package/freebsd/pkg.rb
@@ -30,28 +30,28 @@ class Chef
include PortsHelper
def install_package(name, version)
- unless @current_resource.version
- case @new_resource.source
+ unless current_resource.version
+ case new_resource.source
when /^http/, /^ftp/
- if @new_resource.source =~ /\/$/
- shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, "LC_ALL" => nil }).status
+ if new_resource.source =~ /\/$/
+ shell_out_compact_timeout!("pkg_add", "-r", package_name, env: { "PACKAGESITE" => new_resource.source, "LC_ALL" => nil }).status
else
- shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, "LC_ALL" => nil }).status
+ shell_out_compact_timeout!("pkg_add", "-r", package_name, env: { "PACKAGEROOT" => new_resource.source, "LC_ALL" => nil }).status
end
- Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
+ logger.trace("#{new_resource} installed from: #{new_resource.source}")
when /^\//
- shell_out_with_timeout!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , "LC_ALL" => nil }).status
- Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
+ shell_out_compact_timeout!("pkg_add", file_candidate_version_path, env: { "PKG_PATH" => new_resource.source, "LC_ALL" => nil }).status
+ logger.trace("#{new_resource} installed from: #{new_resource.source}")
else
- shell_out_with_timeout!("pkg_add -r #{latest_link_name}", :env => nil).status
+ shell_out_compact_timeout!("pkg_add", "-r", latest_link_name, env: nil).status
end
end
end
def remove_package(name, version)
- shell_out_with_timeout!("pkg_delete #{package_name}-#{version || @current_resource.version}", :env => nil).status
+ shell_out_compact_timeout!("pkg_delete", "#{package_name}-#{version || current_resource.version}", env: nil).status
end
# The name of the package (without the version number) as understood by pkg_add and pkg_info.
@@ -63,7 +63,7 @@ class Chef
raise Chef::Exceptions::Package, "Unexpected form for PKGNAME variable in #{port_path}/Makefile"
end
else
- @new_resource.package_name
+ new_resource.package_name
end
end
@@ -72,12 +72,12 @@ class Chef
end
def current_installed_version
- pkg_info = shell_out_with_timeout!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0, 1])
+ pkg_info = shell_out_compact_timeout!("pkg_info", "-E", "#{package_name}*", env: nil, returns: [0, 1])
pkg_info.stdout[/^#{Regexp.escape(package_name)}-(.+)/, 1]
end
def candidate_version
- case @new_resource.source
+ case new_resource.source
when /^http/, /^ftp/
repo_candidate_version
when /^\//
@@ -88,7 +88,7 @@ class Chef
end
def file_candidate_version_path
- Dir[Chef::Util::PathHelper.escape_glob_dir("#{@new_resource.source}/#{@current_resource.package_name}") + "*"][-1].to_s
+ Dir[Chef::Util::PathHelper.escape_glob_dir("#{new_resource.source}/#{current_resource.package_name}") + "*"][-1].to_s
end
def file_candidate_version
@@ -104,7 +104,7 @@ class Chef
end
def port_path
- port_dir @new_resource.package_name
+ port_dir new_resource.package_name
end
end
diff --git a/lib/chef/provider/package/freebsd/pkgng.rb b/lib/chef/provider/package/freebsd/pkgng.rb
index de7bea6387..c9c0947f9b 100644
--- a/lib/chef/provider/package/freebsd/pkgng.rb
+++ b/lib/chef/provider/package/freebsd/pkgng.rb
@@ -25,46 +25,44 @@ class Chef
class Pkgng < Base
def install_package(name, version)
- unless @current_resource.version
- case @new_resource.source
+ unless current_resource.version
+ case new_resource.source
when /^(http|ftp|\/)/
- shell_out_with_timeout!("pkg add#{expand_options(@new_resource.options)} #{@new_resource.source}", :env => { "LC_ALL" => nil }).status
- Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
-
+ shell_out_compact_timeout!("pkg", "add", options, new_resource.source, env: { "LC_ALL" => nil }).status
+ logger.trace("#{new_resource} installed from: #{new_resource.source}")
else
- shell_out_with_timeout!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { "LC_ALL" => nil }).status
+ shell_out_compact_timeout!("pkg", "install", "-y", options, name, env: { "LC_ALL" => nil }).status
end
end
end
def remove_package(name, version)
- options = @new_resource.options && @new_resource.options.sub(repo_regex, "")
- options && !options.empty? || options = nil
- shell_out_with_timeout!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status
+ options_dup = options && options.map { |str| str.sub(repo_regex, "") }.reject!(&:empty?)
+ shell_out_compact_timeout!("pkg", "delete", "-y", options_dup, "#{name}#{version ? '-' + version : ''}", env: nil).status
end
def current_installed_version
- pkg_info = shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0, 70])
+ pkg_info = shell_out_compact_timeout!("pkg", "info", new_resource.package_name, env: nil, returns: [0, 70])
pkg_info.stdout[/^Version +: (.+)$/, 1]
end
def candidate_version
- @new_resource.source ? file_candidate_version : repo_candidate_version
+ new_resource.source ? file_candidate_version : repo_candidate_version
end
private
def file_candidate_version
- @new_resource.source[/#{Regexp.escape(@new_resource.package_name)}-(.+)\.txz/, 1]
+ new_resource.source[/#{Regexp.escape(new_resource.package_name)}-(.+)\.txz/, 1]
end
def repo_candidate_version
- if @new_resource.options && @new_resource.options.match(repo_regex)
- options = $1
+ if options && options.join(" ").match(repo_regex)
+ options = $1.split(" ")
end
- pkg_query = shell_out_with_timeout!("pkg rquery#{expand_options(options)} '%v' #{@new_resource.package_name}", :env => nil)
- pkg_query.exitstatus.zero? ? pkg_query.stdout.strip.split(/\n/).last : nil
+ pkg_query = shell_out_compact_timeout!("pkg", "rquery", options, "%v", new_resource.package_name, env: nil)
+ pkg_query.exitstatus == 0 ? pkg_query.stdout.strip.split(/\n/).last : nil
end
def repo_regex
diff --git a/lib/chef/provider/package/freebsd/port.rb b/lib/chef/provider/package/freebsd/port.rb
index 3eb3c5ab01..e87be4d304 100644
--- a/lib/chef/provider/package/freebsd/port.rb
+++ b/lib/chef/provider/package/freebsd/port.rb
@@ -26,20 +26,20 @@ class Chef
include PortsHelper
def install_package(name, version)
- shell_out_with_timeout!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status
+ shell_out_compact_timeout!("make", "-DBATCH", "install", "clean", timeout: 1800, env: nil, cwd: port_dir).status
end
def remove_package(name, version)
- shell_out_with_timeout!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status
+ shell_out_compact_timeout!("make", "deinstall", timeout: 300, env: nil, cwd: port_dir).status
end
def current_installed_version
- pkg_info = if @new_resource.supports_pkgng?
- shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0, 70])
+ pkg_info = if new_resource.supports_pkgng?
+ shell_out_compact_timeout!("pkg", "info", new_resource.package_name, env: nil, returns: [0, 70])
else
- shell_out_with_timeout!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0, 1])
+ shell_out_compact_timeout!("pkg_info", "-E", "#{new_resource.package_name}*", env: nil, returns: [0, 1])
end
- pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+)/, 1]
+ pkg_info.stdout[/^#{Regexp.escape(new_resource.package_name)}-(.+)/, 1]
end
def candidate_version
@@ -51,7 +51,7 @@ class Chef
end
def port_dir
- super(@new_resource.package_name)
+ super(new_resource.package_name)
end
end
end
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index a105f6d7d0..643faf23c6 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -32,21 +32,21 @@ class Chef
include Chef::Mixin::HomebrewUser
def load_current_resource
- self.current_resource = Chef::Resource::Package.new(new_resource.name)
+ self.current_resource = Chef::Resource::HomebrewPackage.new(new_resource.name)
current_resource.package_name(new_resource.package_name)
current_resource.version(current_installed_version)
- Chef::Log.debug("#{new_resource} current version is #{current_resource.version}") if current_resource.version
+ logger.trace("#{new_resource} current version is #{current_resource.version}") if current_resource.version
@candidate_version = candidate_version
- Chef::Log.debug("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version
+ logger.trace("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version
current_resource
end
def install_package(name, version)
unless current_resource.version == version
- brew("install", new_resource.options, name)
+ brew("install", options, name)
end
end
@@ -56,24 +56,25 @@ class Chef
if current_version.nil? || current_version.empty?
install_package(name, version)
elsif current_version != version
- brew("upgrade", new_resource.options, name)
+ brew("upgrade", options, name)
end
end
def remove_package(name, version)
if current_resource.version
- brew("uninstall", new_resource.options, name)
+ brew("uninstall", options, name)
end
end
# Homebrew doesn't really have a notion of purging, do a "force remove"
def purge_package(name, version)
- new_resource.options((new_resource.options || "") << " --force").strip
- remove_package(name, version)
+ if current_resource.version
+ brew("uninstall", "--force", options, name)
+ end
end
def brew(*args)
- get_response_from_command("brew #{args.join(' ')}")
+ get_response_from_command("brew", *args)
end
# We implement a querying method that returns the JSON-as-Hash
@@ -121,13 +122,13 @@ class Chef
private
- def get_response_from_command(command)
+ def get_response_from_command(*command)
homebrew_uid = find_homebrew_uid(new_resource.respond_to?(:homebrew_user) && new_resource.homebrew_user)
homebrew_user = Etc.getpwuid(homebrew_uid)
- Chef::Log.debug "Executing '#{command}' as user '#{homebrew_user.name}'"
+ logger.trace "Executing '#{command.join(' ')}' as user '#{homebrew_user.name}'"
# FIXME: this 1800 second default timeout should be deprecated
- output = shell_out_with_timeout!(command, :timeout => 1800, :user => homebrew_uid, :environment => { "HOME" => homebrew_user.dir, "RUBYOPT" => nil, "TMPDIR" => nil })
+ output = shell_out_compact_timeout!(*command, timeout: 1800, user: homebrew_uid, environment: { "HOME" => homebrew_user.dir, "RUBYOPT" => nil, "TMPDIR" => nil })
output.stdout.chomp
end
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
index 85053d47f2..cabc7fc68b 100644
--- a/lib/chef/provider/package/ips.rb
+++ b/lib/chef/provider/package/ips.rb
@@ -1,7 +1,7 @@
#
# Author:: Jason J. W. Williams (<williamsjj@digitar.com>)
# Author:: Stephen Nelson-Smith (<sns@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright 2011-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,7 +19,6 @@
require "open3"
require "chef/provider/package"
-require "chef/mixin/command"
require "chef/resource/package"
class Chef
@@ -28,7 +27,7 @@ class Chef
class Ips < Chef::Provider::Package
provides :package, platform: %w{openindiana opensolaris omnios solaris2}
- provides :ips_package, os: "solaris2"
+ provides :ips_package
attr_accessor :virtual
@@ -36,45 +35,40 @@ class Chef
super
requirements.assert(:all_actions) do |a|
- a.assertion { ! @candidate_version.nil? }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.package_name} not found"
- a.whyrun "Assuming package #{@new_resource.package_name} would have been made available."
+ a.assertion { !@candidate_version.nil? }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found"
+ a.whyrun "Assuming package #{new_resource.package_name} would have been made available."
end
end
def get_current_version
- shell_out_with_timeout("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
+ shell_out_compact_timeout("pkg", "info", new_resource.package_name).stdout.each_line do |line|
return $1.split[0] if line =~ /^\s+Version: (.*)/
end
- return nil
+ nil
end
def get_candidate_version
- shell_out_with_timeout!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
+ shell_out_compact_timeout!("pkg", "info", "-r", new_resource.package_name).stdout.each_line do |line|
return $1.split[0] if line =~ /Version: (.*)/
end
- return nil
+ nil
end
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
- Chef::Log.debug("Checking package status for #{@new_resource.name}")
- @current_resource.version(get_current_version)
+ @current_resource = Chef::Resource::IpsPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ logger.trace("Checking package status for #{new_resource.name}")
+ current_resource.version(get_current_version)
@candidate_version = get_candidate_version
- @current_resource
+ current_resource
end
def install_package(name, version)
- package_name = "#{name}@#{version}"
- normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}"
- command =
- if @new_resource.respond_to?(:accept_license) && @new_resource.accept_license
- normal_command.gsub("-q", "-q --accept")
- else
- normal_command
- end
- shell_out_with_timeout(command)
+ command = [ "pkg", options, "install", "-q" ]
+ command << "--accept" if new_resource.accept_license
+ command << "#{name}@#{version}"
+ shell_out_compact_timeout!(command)
end
def upgrade_package(name, version)
@@ -83,7 +77,7 @@ class Chef
def remove_package(name, version)
package_name = "#{name}@#{version}"
- shell_out_with_timeout!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
+ shell_out_compact_timeout!( "pkg", options, "uninstall", "-q", package_name )
end
end
end
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
index 7bbc68aba8..ddaf19a76f 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -7,25 +7,25 @@ class Chef
provides :macports_package
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- @current_resource.version(current_installed_version)
- Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version
+ current_resource.version(current_installed_version)
+ logger.trace("#{new_resource} current version is #{current_resource.version}") if current_resource.version
@candidate_version = macports_candidate_version
- if !@new_resource.version && !@candidate_version
- raise Chef::Exceptions::Package, "Could not get a candidate version for this package -- #{@new_resource.name} does not seem to be a valid package!"
+ if !new_resource.version && !@candidate_version
+ raise Chef::Exceptions::Package, "Could not get a candidate version for this package -- #{new_resource.name} does not seem to be a valid package!"
end
- Chef::Log.debug("#{@new_resource} candidate version is #{@candidate_version}") if @candidate_version
+ logger.trace("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version
- @current_resource
+ current_resource
end
def current_installed_version
- command = "port installed #{@new_resource.package_name}"
+ command = [ "port", "installed", new_resource.package_name ]
output = get_response_from_command(command)
response = nil
@@ -37,7 +37,7 @@ class Chef
end
def macports_candidate_version
- command = "port info --version #{@new_resource.package_name}"
+ command = [ "port", "info", "--version", new_resource.package_name ]
output = get_response_from_command(command)
match = output.match(/^version: (.+)$/)
@@ -46,37 +46,37 @@ class Chef
end
def install_package(name, version)
- unless @current_resource.version == version
- command = "port#{expand_options(@new_resource.options)} install #{name}"
- command << " @#{version}" if version && !version.empty?
- shell_out_with_timeout!(command)
+ unless current_resource.version == version
+ command = [ "port", options, "install", name ]
+ command << "@#{version}" if version && !version.empty?
+ shell_out_compact_timeout!(command)
end
end
def purge_package(name, version)
- command = "port#{expand_options(@new_resource.options)} uninstall #{name}"
- command << " @#{version}" if version && !version.empty?
- shell_out_with_timeout!(command)
+ command = [ "port", options, "uninstall", name ]
+ command << "@#{version}" if version && !version.empty?
+ shell_out_compact_timeout!(command)
end
def remove_package(name, version)
- command = "port#{expand_options(@new_resource.options)} deactivate #{name}"
- command << " @#{version}" if version && !version.empty?
+ command = [ "port", options, "deactivate", name ]
+ command << "@#{version}" if version && !version.empty?
- shell_out_with_timeout!(command)
+ shell_out_compact_timeout!(command)
end
def upgrade_package(name, version)
# Saving this to a variable -- weird rSpec behavior
# happens otherwise...
- current_version = @current_resource.version
+ current_version = current_resource.version
if current_version.nil? || current_version.empty?
# Macports doesn't like when you upgrade a package
# that hasn't been installed.
install_package(name, version)
elsif current_version != version
- shell_out_with_timeout!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
+ shell_out_compact_timeout!( "port", options, "upgrade", name, "@#{version}" )
end
end
@@ -84,14 +84,14 @@ class Chef
def get_response_from_command(command)
output = nil
- status = shell_out_with_timeout(command)
+ status = shell_out_compact_timeout(command)
begin
output = status.stdout
rescue Exception
raise Chef::Exceptions::Package, "Could not read from STDOUT on command: #{command}"
end
unless status.exitstatus == 0 || status.exitstatus == 1
- raise Chef::Exceptions::Package, "#{command} failed - #{status.insect}!"
+ raise Chef::Exceptions::Package, "#{command} failed - #{status.inspect}!"
end
output
end
diff --git a/lib/chef/provider/package/msu.rb b/lib/chef/provider/package/msu.rb
new file mode 100644
index 0000000000..c4e53a0fdf
--- /dev/null
+++ b/lib/chef/provider/package/msu.rb
@@ -0,0 +1,161 @@
+#
+# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
+# Copyright:: Copyright 2015-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.
+#
+
+# msu_package leverages cab_package
+# The contents of msu file are extracted, which contains one or more cab files.
+# The extracted cab files are installed using Chef::Resource::Package::CabPackage
+# Reference: https://support.microsoft.com/en-in/kb/934307
+require "chef/provider/package"
+require "chef/resource/msu_package"
+require "chef/mixin/shell_out"
+require "chef/provider/package/cab"
+require "chef/util/path_helper"
+require "chef/mixin/uris"
+require "chef/mixin/checksum"
+
+class Chef
+ class Provider
+ class Package
+ class Msu < Chef::Provider::Package
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::Uris
+ include Chef::Mixin::Checksum
+
+ provides :msu_package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::MsuPackage.new(new_resource.name)
+
+ # download file if source is a url
+ msu_file = uri_scheme?(new_resource.source) ? download_source_file : new_resource.source
+
+ # temp directory where the contents of msu file get extracted
+ @temp_directory = Dir.mktmpdir("chef")
+ extract_msu_contents(msu_file, @temp_directory)
+ @cab_files = read_cab_files_from_xml(@temp_directory)
+
+ if @cab_files.empty?
+ raise Chef::Exceptions::Package, "Corrupt MSU package: MSU package XML does not contain any cab file"
+ else
+ current_resource.version(get_current_versions)
+ end
+ current_resource
+ end
+
+ def get_current_versions
+ @cab_files.map do |cabfile|
+ cab_pkg = get_cab_package(cabfile)
+ cab_pkg.installed_version
+ end
+ end
+
+ def get_candidate_versions
+ @cab_files.map do |cabfile|
+ cab_pkg = get_cab_package(cabfile)
+ cab_pkg.package_version
+ end
+ end
+
+ def candidate_version
+ @candidate_version ||= get_candidate_versions
+ end
+
+ def get_cab_package(cab_file)
+ cab_resource = new_resource
+ cab_resource.source = cab_file
+ Chef::Provider::Package::Cab.new(cab_resource, nil)
+ end
+
+ def download_source_file
+ source_resource.run_action(:create)
+ logger.trace("#{new_resource} fetched source file to #{source_resource.path}")
+ source_resource.path
+ end
+
+ def source_resource
+ @source_resource ||= declare_resource(:remote_file, new_resource.name) do
+ path default_download_cache_path
+ source new_resource.source
+ checksum new_resource.checksum
+ backup false
+ end
+ end
+
+ def default_download_cache_path
+ uri = ::URI.parse(new_resource.source)
+ filename = ::File.basename(::URI.unescape(uri.path))
+ file_cache_dir = Chef::FileCache.create_cache_path("package/")
+ Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+ end
+
+ def install_package(name, version)
+ # use cab_package resource to install the extracted cab packages
+ @cab_files.each do |cab_file|
+ declare_resource(:cab_package, new_resource.name) do
+ source cab_file
+ action :install
+ end
+ end
+ end
+
+ def remove_package(name, version)
+ # use cab_package provider to remove the extracted cab packages
+ @cab_files.each do |cab_file|
+ declare_resource(:cab_package, new_resource.name) do
+ source cab_file
+ action :remove
+ end
+ end
+ end
+
+ def extract_msu_contents(msu_file, destination)
+ with_os_architecture(nil) do
+ shell_out_with_timeout!("#{ENV['SYSTEMROOT']}\\system32\\expand.exe -f:* #{msu_file} #{destination}")
+ end
+ end
+
+ # msu package can contain multiple cab files
+ # Reading cab files from xml to ensure the order of installation in case of multiple cab files
+ def read_cab_files_from_xml(msu_dir)
+ # get the file with .xml extension
+ xml_files = Dir.glob("#{msu_dir}/*.xml")
+ cab_files = []
+
+ if xml_files.empty?
+ raise Chef::Exceptions::Package, "Corrupt MSU package: MSU package doesn't contain any xml file"
+ else
+ # msu package contains only single xml file. So using xml_files.first is sufficient
+ doc = ::File.open(xml_files.first.to_s) { |f| REXML::Document.new f }
+ locations = doc.elements.each("unattend/servicing/package/source") { |element| element.attributes["location"] }
+ locations.each do |loc|
+ cab_files << msu_dir + "/" + loc.attribute("location").value.split("\\")[1]
+ end
+
+ cab_files
+ end
+ cab_files
+ end
+
+ def cleanup_after_converge
+ # delete the temp directory where the contents of msu file are extracted
+ FileUtils.rm_rf(@temp_directory) if Dir.exist?(@temp_directory)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb
index 2120b9aa48..f528c48f08 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -42,9 +42,9 @@ class Chef
end
def load_current_resource
- @current_resource.package_name(new_resource.package_name)
- @current_resource.version(installed_version)
- @current_resource
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(installed_version)
+ current_resource
end
def define_resource_requirements
@@ -68,12 +68,12 @@ class Chef
end
def install_package(name, version)
- unless @current_resource.version
+ unless current_resource.version
if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add
name = parts[1]
end
- shell_out_with_timeout!("pkg_add -r #{name}#{version_string(version)}", :env => { "PKG_PATH" => pkg_path }).status
- Chef::Log.debug("#{new_resource.package_name} installed")
+ shell_out_compact_timeout!("pkg_add", "-r", package_string(name, version), env: { "PKG_PATH" => pkg_path }).status
+ logger.trace("#{new_resource.package_name} installed")
end
end
@@ -81,35 +81,35 @@ class Chef
if parts = name.match(/^(.+?)--(.+)/)
name = parts[1]
end
- shell_out_with_timeout!("pkg_delete #{name}#{version_string(version)}", :env => nil).status
+ shell_out_compact_timeout!("pkg_delete", package_string(name, version), env: nil).status
end
private
def installed_version
- if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
- name = parts[1]
- else
- name = new_resource.package_name
- end
- pkg_info = shell_out_with_timeout!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0, 1])
+ name = if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
+ parts[1]
+ else
+ new_resource.package_name
+ end
+ pkg_info = shell_out_compact_timeout!("pkg_info", "-e", "#{name}->0", env: nil, returns: [0, 1])
result = pkg_info.stdout[/^inst:#{Regexp.escape(name)}-(.+?)\s/, 1]
- Chef::Log.debug("installed_version of '#{new_resource.package_name}' is '#{result}'")
+ logger.trace("installed_version of '#{new_resource.package_name}' is '#{result}'")
result
end
def candidate_version
@candidate_version ||= begin
results = []
- shell_out_with_timeout!("pkg_info -I \"#{new_resource.package_name}#{version_string(new_resource.version)}\"", :env => nil, :returns => [0, 1]).stdout.each_line do |line|
- if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
- results << line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
- else
- results << line[/^#{Regexp.escape(new_resource.package_name)}-(.+?)\s/, 1]
- end
+ shell_out_compact_timeout!("pkg_info", "-I", package_string(new_resource.package_name, new_resource.version), env: nil, returns: [0, 1]).stdout.each_line do |line|
+ results << if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
+ line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
+ else
+ line[/^#{Regexp.escape(new_resource.package_name)}-(.+?)\s/, 1]
+ end
end
results = results.reject(&:nil?)
- Chef::Log.debug("Candidate versions of '#{new_resource.package_name}' are '#{results}'")
+ logger.trace("Candidate versions of '#{new_resource.package_name}' are '#{results}'")
case results.length
when 0
[]
@@ -121,13 +121,16 @@ class Chef
end
end
- def version_string(version)
- ver = ""
- ver += "-#{version}" if version
+ def package_string(name, version)
+ if version
+ "#{name}-#{version}"
+ else
+ name
+ end
end
def pkg_path
- ENV["PKG_PATH"] || "http://ftp.OpenBSD.org/pub/#{node.kernel.name}/#{node.kernel.release}/packages/#{node.kernel.machine}/"
+ ENV["PKG_PATH"] || "http://ftp.OpenBSD.org/pub/#{node['kernel']['name']}/#{node['kernel']['release']}/packages/#{node['kernel']['machine']}/"
end
end
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index bd8028d881..f6dde66219 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -17,7 +17,6 @@
#
require "chef/provider/package"
-require "chef/mixin/command"
require "chef/resource/package"
class Chef
@@ -26,19 +25,19 @@ class Chef
class Pacman < Chef::Provider::Package
provides :package, platform: "arch"
- provides :pacman_package, os: "linux"
+ provides :pacman_package
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- Chef::Log.debug("#{@new_resource} checking pacman for #{@new_resource.package_name}")
- status = shell_out_with_timeout("pacman -Qi #{@new_resource.package_name}")
+ logger.trace("#{new_resource} checking pacman for #{new_resource.package_name}")
+ status = shell_out_compact_timeout("pacman", "-Qi", new_resource.package_name)
status.stdout.each_line do |line|
case line
when /^Version(\s?)*: (.+)$/
- Chef::Log.debug("#{@new_resource} current version is #{$2}")
- @current_resource.version($2)
+ logger.trace("#{new_resource} current version is #{$2}")
+ current_resource.version($2)
end
end
@@ -46,7 +45,7 @@ class Chef
raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!"
end
- @current_resource
+ current_resource
end
def candidate_version
@@ -54,20 +53,20 @@ class Chef
repos = %w{extra core community}
- if ::File.exists?("/etc/pacman.conf")
+ if ::File.exist?("/etc/pacman.conf")
pacman = ::File.read("/etc/pacman.conf")
repos = pacman.scan(/\[(.+)\]/).flatten
end
package_repos = repos.map { |r| Regexp.escape(r) }.join("|")
- status = shell_out_with_timeout("pacman -Sl")
+ status = shell_out_compact_timeout("pacman", "-Sl")
status.stdout.each_line do |line|
case line
- when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/
- # $2 contains a string like "4.4.0-1" or "3.10-4 [installed]"
- # simply split by space and use first token
- @candidate_version = $2.split(" ").first
+ when /^(#{package_repos}) #{Regexp.escape(new_resource.package_name)} (.+)$/
+ # $2 contains a string like "4.4.0-1" or "3.10-4 [installed]"
+ # simply split by space and use first token
+ @candidate_version = $2.split(" ").first
end
end
@@ -76,14 +75,14 @@ class Chef
end
unless @candidate_version
- raise Chef::Exceptions::Package, "pacman does not have a version of package #{@new_resource.package_name}"
+ raise Chef::Exceptions::Package, "pacman does not have a version of package #{new_resource.package_name}"
end
@candidate_version
end
def install_package(name, version)
- shell_out_with_timeout!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_compact_timeout!( "pacman", "--sync", "--noconfirm", "--noprogressbar", options, name)
end
def upgrade_package(name, version)
@@ -91,7 +90,7 @@ class Chef
end
def remove_package(name, version)
- shell_out_with_timeout!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_compact_timeout!( "pacman", "--remove", "--noconfirm", "--noprogressbar", options, name )
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb
index 557e7ebc22..f6274d7553 100644
--- a/lib/chef/provider/package/paludis.rb
+++ b/lib/chef/provider/package/paludis.rb
@@ -25,41 +25,40 @@ class Chef
class Paludis < Chef::Provider::Package
provides :package, platform: "exherbo"
- provides :paludis_package, os: "linux"
+ provides :paludis_package
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.package_name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.package_name)
+ current_resource.package_name(new_resource.package_name)
- Chef::Log.debug("Checking package status for #{@new_resource.package_name}")
+ logger.trace("Checking package status for #{new_resource.package_name}")
installed = false
re = Regexp.new("(.*)[[:blank:]](.*)[[:blank:]](.*)$")
- shell_out!("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line|
+ shell_out_compact!("cave", "-L", "warning", "print-ids", "-M", "none", "-m", new_resource.package_name, "-f", "%c/%p %v %r\n").stdout.each_line do |line|
res = re.match(line)
- unless res.nil?
- case res[3]
- when "accounts", "installed-accounts"
- next
- when "installed"
- installed = true
- @current_resource.version(res[2])
- else
- @candidate_version = res[2]
- end
+ next if res.nil?
+ case res[3]
+ when "accounts", "installed-accounts"
+ next
+ when "installed"
+ installed = true
+ current_resource.version(res[2])
+ else
+ @candidate_version = res[2]
end
end
- @current_resource
+ current_resource
end
def install_package(name, version)
- if version
- pkg = "=#{name}-#{version}"
- else
- pkg = "#{@new_resource.package_name}"
- end
- shell_out!("cave -L warning resolve -x#{expand_options(@new_resource.options)} \"#{pkg}\"", :timeout => @new_resource.timeout)
+ pkg = if version
+ "=#{name}-#{version}"
+ else
+ new_resource.package_name.to_s
+ end
+ shell_out_compact_timeout!("cave", "-L", "warning", "resolve", "-x", options, pkg)
end
def upgrade_package(name, version)
@@ -67,13 +66,13 @@ class Chef
end
def remove_package(name, version)
- if version
- pkg = "=#{@new_resource.package_name}-#{version}"
- else
- pkg = "#{@new_resource.package_name}"
- end
+ pkg = if version
+ "=#{new_resource.package_name}-#{version}"
+ else
+ new_resource.package_name.to_s
+ end
- shell_out!("cave -L warning uninstall -x#{expand_options(@new_resource.options)} \"#{pkg}\"")
+ shell_out_compact!("cave", "-L", "warning", "uninstall", "-x", options, pkg)
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index 7c52e43bff..fecbba9dc9 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -17,8 +17,7 @@
#
require "chef/provider/package"
-require "chef/mixin/command"
-require "chef/resource/package"
+require "chef/resource/portage_package"
require "chef/util/path_helper"
class Chef
@@ -32,74 +31,68 @@ class Chef
PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- category, pkg = %r{^#{PACKAGE_NAME_PATTERN}$}.match(@new_resource.package_name)[1, 2]
+ category, pkg = /^#{PACKAGE_NAME_PATTERN}$/.match(new_resource.package_name)[1, 2]
globsafe_category = category ? Chef::Util::PathHelper.escape_glob_dir(category) : nil
globsafe_pkg = Chef::Util::PathHelper.escape_glob_dir(pkg)
- possibilities = Dir["/var/db/pkg/#{globsafe_category || "*"}/#{globsafe_pkg}-*"].map { |d| d.sub(%r{/var/db/pkg/}, "") }
+ possibilities = Dir["/var/db/pkg/#{globsafe_category || '*'}/#{globsafe_pkg}-*"].map { |d| d.sub(%r{/var/db/pkg/}, "") }
versions = possibilities.map do |entry|
- if entry =~ %r{[^/]+/#{Regexp.escape(pkg)}\-(\d[\.\d]*((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)}
+ if entry =~ %r{[^/]+/#{Regexp.escape(pkg)}\-(\d[\.\d]*[a-z]?((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)}
[$&, $1]
end
end.compact
if versions.size > 1
- atoms = versions.map { |v| v.first }.sort
+ atoms = versions.map(&:first).sort
categories = atoms.map { |v| v.split("/")[0] }.uniq
if !category && categories.size > 1
- raise Chef::Exceptions::Package, "Multiple packages found for #{@new_resource.package_name}: #{atoms.join(" ")}. Specify a category."
+ raise Chef::Exceptions::Package, "Multiple packages found for #{new_resource.package_name}: #{atoms.join(' ')}. Specify a category."
end
elsif versions.size == 1
- @current_resource.version(versions.first.last)
- Chef::Log.debug("#{@new_resource} current version #{$1}")
+ current_resource.version(versions.first.last)
+ logger.trace("#{new_resource} current version #{$1}")
end
- @current_resource
+ current_resource
end
- def parse_emerge(package, txt)
- availables = {}
- found_package_name = nil
+ def raise_error_for_query(msg)
+ raise Chef::Exceptions::Package, "Query for '#{new_resource.package_name}' #{msg}"
+ end
- txt.each_line do |line|
- if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/
- found_package_name = $&.delete("*").strip
- if package =~ /\// #the category is specified
- if found_package_name == package
- availables[found_package_name] = nil
- end
- else #the category is not specified
- if found_package_name.split("/").last == package
- availables[found_package_name] = nil
- end
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ pkginfo = shell_out_compact("portageq", "best_visible", "/", new_resource.package_name)
+
+ if pkginfo.exitstatus != 0
+ pkginfo.stderr.each_line do |line|
+ if line =~ /[Uu]nqualified atom .*match.* multiple/
+ raise_error_for_query("matched multiple packages (please specify a category):\n#{pkginfo.inspect}")
end
end
- if line =~ /Latest version available: (.*)/ && availables.has_key?(found_package_name)
- availables[found_package_name] = $1.strip
+ if pkginfo.stdout.strip.empty?
+ raise_error_for_query("did not find a matching package:\n#{pkginfo.inspect}")
end
- end
- if availables.size > 1
- # shouldn't happen if a category is specified so just use `package`
- raise Chef::Exceptions::Package, "Multiple emerge results found for #{package}: #{availables.keys.join(" ")}. Specify a category."
+ raise_error_for_query("resulted in an unknown error:\n#{pkginfo.inspect}")
end
- availables.values.first
- end
-
- def candidate_version
- return @candidate_version if @candidate_version
-
- status = shell_out("emerge --color n --nospinner --search #{@new_resource.package_name.split('/').last}")
- available, installed = parse_emerge(@new_resource.package_name, status.stdout)
- @candidate_version = available
+ if pkginfo.stdout.lines.count > 1
+ raise_error_for_query("produced unexpected output (multiple lines):\n#{pkginfo.inspect}")
+ end
- unless status.exitstatus == 0
- raise Chef::Exceptions::Package, "emerge --search failed - #{status.inspect}!"
+ pkginfo.stdout.chomp!
+ if pkginfo.stdout =~ /-r\d+$/
+ # Latest/Best version of the package is a revision (-rX).
+ @candidate_version = pkginfo.stdout.split(/(?<=-)/).last(2).join
+ else
+ # Latest/Best version of the package is NOT a revision (-rX).
+ @candidate_version = pkginfo.stdout.split("-").last
end
@candidate_version
@@ -113,7 +106,7 @@ class Chef
pkg = "~#{name}-#{$1}"
end
- shell_out!( "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" )
+ shell_out_compact!( "emerge", "-g", "--color", "n", "--nospinner", "--quiet", options, pkg )
end
def upgrade_package(name, version)
@@ -121,13 +114,13 @@ class Chef
end
def remove_package(name, version)
- if version
- pkg = "=#{@new_resource.package_name}-#{version}"
- else
- pkg = "#{@new_resource.package_name}"
- end
+ pkg = if version
+ "=#{new_resource.package_name}-#{version}"
+ else
+ new_resource.package_name.to_s
+ end
- shell_out!( "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" )
+ shell_out_compact!( "emerge", "--unmerge", "--color", "n", "--nospinner", "--quiet", options, pkg )
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/powershell.rb b/lib/chef/provider/package/powershell.rb
new file mode 100644
index 0000000000..44b6e69a00
--- /dev/null
+++ b/lib/chef/provider/package/powershell.rb
@@ -0,0 +1,129 @@
+# Author:: Dheeraj Dubey(dheeraj.dubey@msystechnologies.com)
+# Copyright:: Copyright 2015-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/powershell_package"
+require "chef/mixin/powershell_out"
+
+class Chef
+ class Provider
+ class Package
+ class Powershell < Chef::Provider::Package
+ include Chef::Mixin::PowershellOut
+
+ provides :powershell_package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::PowershellPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(build_current_versions)
+ current_resource
+ end
+
+ def define_resource_requirements
+ super
+ if powershell_out("$PSVersionTable.PSVersion.Major").stdout.strip.to_i < 5
+ raise "Minimum installed Powershell Version required is 5"
+ end
+ requirements.assert(:install) do |a|
+ a.assertion { candidates_exist_for_all_uninstalled? }
+ a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(', ')}")
+ a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(', ')} would have been configured")
+ end
+ end
+
+ def candidate_version
+ @candidate_version ||= build_candidate_versions
+ end
+
+ # Installs the package specified with the version passed else latest version will be installed
+ def install_package(names, versions)
+ names.each_with_index do |name, index|
+ powershell_out(build_powershell_package_command("Install-Package '#{name}'", versions[index]), timeout: new_resource.timeout)
+ end
+ end
+
+ # Removes the package for the version passed and if no version is passed, then all installed versions of the package are removed
+ def remove_package(names, versions)
+ names.each_with_index do |name, index|
+ if versions && !versions[index].nil?
+ powershell_out(build_powershell_package_command("Uninstall-Package '#{name}'", versions[index]), timeout: new_resource.timeout)
+ else
+ version = "0"
+ until version.empty?
+ version = powershell_out(build_powershell_package_command("Uninstall-Package '#{name}'"), timeout: new_resource.timeout).stdout.strip
+ unless version.empty?
+ logger.info("Removed package '#{name}' with version #{version}")
+ end
+ end
+ end
+ end
+ end
+
+ # Returns array of available available online
+ def build_candidate_versions
+ versions = []
+ new_resource.package_name.each_with_index do |name, index|
+ version = if new_resource.version && !new_resource.version[index].nil?
+ powershell_out(build_powershell_package_command("Find-Package '#{name}'", new_resource.version[index]), timeout: new_resource.timeout).stdout.strip
+ else
+ powershell_out(build_powershell_package_command("Find-Package '#{name}'"), timeout: new_resource.timeout).stdout.strip
+ end
+ if version.empty?
+ version = nil
+ end
+ versions.push(version)
+ end
+ versions
+ end
+
+ # Returns version array of installed version on the system
+ def build_current_versions
+ version_list = []
+ new_resource.package_name.each_with_index do |name, index|
+ version = if new_resource.version && !new_resource.version[index].nil?
+ powershell_out(build_powershell_package_command("Get-Package '#{name}'", new_resource.version[index]), timeout: new_resource.timeout).stdout.strip
+ else
+ powershell_out(build_powershell_package_command("Get-Package '#{name}'"), timeout: new_resource.timeout).stdout.strip
+ end
+ if version.empty?
+ version = nil
+ end
+ version_list.push(version)
+ end
+ version_list
+ end
+
+ def build_powershell_package_command(command, version = nil)
+ command = [command] unless command.is_a?(Array)
+ command.unshift("(")
+ %w{-Force -ForceBootstrap}.each do |arg|
+ command.push(arg)
+ end
+ command.push("-RequiredVersion #{version}") if version
+ command.push("-Source #{new_resource.source}") if new_resource.source && command[1] =~ Regexp.union(/Install-Package/, /Find-Package/)
+ command.push(").Version")
+ command.join(" ")
+ end
+
+ def check_resource_semantics!
+ # This validation method from Chef::Provider::Package does not apply here, so no-op it.
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index 777cc6d209..9d4f2f3c23 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -16,16 +16,15 @@
# limitations under the License.
#
require "chef/provider/package"
-require "chef/mixin/command"
require "chef/resource/package"
require "chef/mixin/get_source_from_package"
+require "chef/provider/package/yum/rpm_utils"
class Chef
class Provider
class Package
class Rpm < Chef::Provider::Package
-
- provides :rpm_package, os: %w{linux aix}
+ provides :rpm_package
include Chef::Mixin::GetSourceFromPackage
@@ -34,13 +33,13 @@ class Chef
requirements.assert(:all_actions) do |a|
a.assertion { @package_source_exists }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- a.whyrun "Assuming package #{@new_resource.name} would have been made available."
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.name} not found: #{new_resource.source}"
+ a.whyrun "Assuming package #{new_resource.name} would have been made available."
end
requirements.assert(:all_actions) do |a|
a.assertion { !@rpm_status.nil? && (@rpm_status.exitstatus == 0 || @rpm_status.exitstatus == 1) }
a.failure_message Chef::Exceptions::Package, "Unable to determine current version due to RPM failure. Detail: #{@rpm_status.inspect}"
- a.whyrun "Assuming current version would have been determined for package#{@new_resource.name}."
+ a.whyrun "Assuming current version would have been determined for package#{new_resource.name}."
end
end
@@ -48,74 +47,78 @@ class Chef
@package_source_provided = true
@package_source_exists = true
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- if @new_resource.source
- unless uri_scheme?(@new_resource.source) || ::File.exists?(@new_resource.source)
+ if new_resource.source
+ unless uri_scheme?(new_resource.source) || ::File.exist?(new_resource.source)
@package_source_exists = false
return
end
- Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
+ logger.trace("#{new_resource} checking rpm status")
+ shell_out_compact_timeout!("rpm", "-qp", "--queryformat", "%{NAME} %{VERSION}-%{RELEASE}\n", new_resource.source).stdout.each_line do |line|
case line
when /^(\S+)\s(\S+)$/
- @current_resource.package_name($1)
- @new_resource.version($2)
+ current_resource.package_name($1)
+ new_resource.version($2)
@candidate_version = $2
end
end
else
- if Array(@new_resource.action).include?(:install)
+ if Array(new_resource.action).include?(:install)
@package_source_exists = false
return
end
end
- Chef::Log.debug("#{@new_resource} checking install state")
- @rpm_status = shell_out_with_timeout("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
+ logger.trace("#{new_resource} checking install state")
+ @rpm_status = shell_out_compact_timeout("rpm", "-q", "--queryformat", "%{NAME} %{VERSION}-%{RELEASE}\n", current_resource.package_name)
@rpm_status.stdout.each_line do |line|
case line
when /^(\S+)\s(\S+)$/
- Chef::Log.debug("#{@new_resource} current version is #{$2}")
- @current_resource.version($2)
+ logger.trace("#{new_resource} current version is #{$2}")
+ current_resource.version($2)
end
end
- @current_resource
+ current_resource
end
def install_package(name, version)
- unless @current_resource.version
- shell_out_with_timeout!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
- else
+ if current_resource.version
if allow_downgrade
- shell_out_with_timeout!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" )
+ shell_out_compact_timeout!("rpm", options, "-U", "--oldpackage", new_resource.source)
else
- shell_out_with_timeout!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
+ shell_out_compact_timeout!("rpm", options, "-U", new_resource.source)
end
+ else
+ shell_out_compact_timeout!("rpm", options, "-i", new_resource.source)
end
end
- alias_method :upgrade_package, :install_package
+ alias upgrade_package install_package
def remove_package(name, version)
if version
- shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
+ shell_out_compact_timeout!("rpm", options, "-e", "#{name}-#{version}")
else
- shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}" )
+ shell_out_compact_timeout!("rpm", options, "-e", name)
end
end
private
+ def version_compare(v1, v2)
+ Chef::Provider::Package::Yum::RPMVersion.parse(v1) <=> Chef::Provider::Package::Yum::RPMVersion.parse(v2)
+ end
+
def uri_scheme?(str)
scheme = URI.split(str).first
return false unless scheme
%w{http https ftp file}.include?(scheme.downcase)
rescue URI::InvalidURIError
- return false
+ false
end
end
end
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index a15ee1bdea..6b04af547b 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, 2010-2016 Chef Software, Inc.
+# Copyright:: Copyright 2008-2016, 2010-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,9 +19,9 @@
require "uri"
require "chef/provider/package"
-require "chef/mixin/command"
require "chef/resource/package"
require "chef/mixin/get_source_from_package"
+require "chef/mixin/which"
# Class methods on Gem are defined in rubygems
require "rubygems"
@@ -47,7 +47,7 @@ class Chef
# alternate value and overwrite it with the defaults.
Gem.configuration
- DEFAULT_UNINSTALLER_OPTS = { :ignore => true, :executables => true }
+ DEFAULT_UNINSTALLER_OPTS = { ignore: true, executables: true }.freeze
##
# The paths where rubygems should search for installed gems.
@@ -86,7 +86,22 @@ class Chef
# === Returns
# [Gem::Specification] an array of Gem::Specification objects
def installed_versions(gem_dep)
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0")
+ rubygems_version = Gem::Version.new(Gem::VERSION)
+ if rubygems_version >= Gem::Version.new("2.7")
+ # In newer Rubygems, bundler is now a "default gem" which means
+ # even with AlternateGemEnvironment when you try to get the
+ # installed versions, you get the one from Chef's Ruby's default
+ # gems. This workaround ignores default gems entirely so we see
+ # only the installed gems.
+ stubs = gem_specification.send(:installed_stubs, gem_specification.dirs, "#{gem_dep.name}-*.gemspec")
+ # Filter down to only to only stubs we actually want. The name
+ # filter is needed in case of things like `foo-*.gemspec` also
+ # matching a gem named `foo-bar`.
+ stubs.select! { |stub| stub.name == gem_dep.name && gem_dep.requirement.satisfied_by?(stub.version) }
+ # This isn't sorting before returning beacuse the only code that
+ # uses this method calls `max_by` so it doesn't need to be sorted.
+ stubs
+ elsif rubygems_version >= Gem::Version.new("1.8.0")
gem_specification.find_all_by_name(gem_dep.name, gem_dep.requirement)
else
gem_source_index.search(gem_dep)
@@ -133,11 +148,11 @@ class Chef
def candidate_version_from_file(gem_dependency, source)
spec = spec_from_file(source)
if spec.satisfies_requirement?(gem_dependency)
- logger.debug { "#{@new_resource} found candidate gem version #{spec.version} from local gem package #{source}" }
+ logger.trace { "found candidate gem version #{spec.version} from local gem package #{source}" }
spec.version
else
# This is probably going to end badly...
- logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency}" }
+ logger.warn { "gem package #{source} does not satisfy the requirements #{gem_dependency}" }
nil
end
end
@@ -159,27 +174,30 @@ class Chef
# Find the newest gem version available from Gem.sources that satisfies
# the constraints of +gem_dependency+
def find_newest_remote_version(gem_dependency, *sources)
- available_gems = dependency_installer.find_gems_with_sources(gem_dependency)
- spec, source = if available_gems.respond_to?(:last)
- # DependencyInstaller sorts the results such that the last one is
- # always the one it considers best.
- spec_with_source = available_gems.last
- spec_with_source && spec_with_source
- else
- # Rubygems 2.0 returns a Gem::Available set, which is a
- # collection of AvailableSet::Tuple structs
- available_gems.pick_best!
- best_gem = available_gems.set.first
- best_gem && [best_gem.spec, best_gem.source]
- end
+ spec, source =
+ if Chef::Config[:rubygems_cache_enabled]
+ # This code caches every gem on rubygems.org and uses lots of RAM
+ available_gems = dependency_installer.find_gems_with_sources(gem_dependency)
+ available_gems.pick_best!
+ best_gem = available_gems.set.first
+ best_gem && [best_gem.spec, best_gem.source]
+ else
+ # Use the API that 'gem install' calls which does not pull down the rubygems universe
+ begin
+ rs = dependency_installer.resolve_dependencies gem_dependency.name, gem_dependency.requirement
+ rs.specs.select { |s| s.name == gem_dependency.name }.first
+ rescue Gem::UnsatisfiableDependencyError
+ nil
+ end
+ end
version = spec && spec.version
if version
- logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{source}" }
+ logger.trace { "found gem #{spec.name} version #{version} for platform #{spec.platform} from #{source}" }
version
else
source_list = sources.compact.empty? ? "[#{Gem.sources.to_a.join(', ')}]" : "[#{sources.join(', ')}]"
- logger.warn { "#{@new_resource} failed to find gem #{gem_dependency} from #{source_list}" }
+ logger.warn { "failed to find gem #{gem_dependency} from #{source_list}" }
nil
end
end
@@ -214,7 +232,7 @@ class Chef
# Set rubygems' user interaction to ConsoleUI or SilentUI depending
# on our current debug level
def with_correct_verbosity
- Gem::DefaultUserInteraction.ui = Chef::Log.debug? ? Gem::ConsoleUI.new : Gem::SilentUI.new
+ Gem::DefaultUserInteraction.ui = logger.trace? ? Gem::ConsoleUI.new : Gem::SilentUI.new
yield
end
@@ -229,7 +247,7 @@ class Chef
private
def logger
- Chef::Log.logger
+ Chef::Log.with_child({ subsytem: "gem_installer_environment" })
end
end
@@ -282,7 +300,7 @@ class Chef
# shellout! is a fork/exec which won't work on windows
shell_style_paths = shell_out!("#{@gem_binary_location} env gempath").stdout
# on windows, the path separator is (usually? always?) semicolon
- paths = shell_style_paths.split(::File::PATH_SEPARATOR).map { |path| path.strip }
+ paths = shell_style_paths.split(::File::PATH_SEPARATOR).map(&:strip)
self.class.gempath_cache[@gem_binary_location] = paths
end
end
@@ -319,11 +337,11 @@ class Chef
self.class.platform_cache[@gem_binary_location]
else
gem_environment = shell_out!("#{@gem_binary_location} env").stdout
- if jruby = gem_environment[JRUBY_PLATFORM]
- self.class.platform_cache[@gem_binary_location] = ["ruby", Gem::Platform.new(jruby)]
- else
- self.class.platform_cache[@gem_binary_location] = Gem.platforms
- end
+ self.class.platform_cache[@gem_binary_location] = if jruby = gem_environment[JRUBY_PLATFORM]
+ ["ruby", Gem::Platform.new(jruby)]
+ else
+ Gem.platforms
+ end
end
end
@@ -349,30 +367,27 @@ class Chef
attr_reader :gem_env
attr_reader :cleanup_gem_env
- def logger
- Chef::Log.logger
- end
-
provides :chef_gem
provides :gem_package
include Chef::Mixin::GetSourceFromPackage
+ include Chef::Mixin::Which
def initialize(new_resource, run_context = nil)
super
@cleanup_gem_env = true
if new_resource.gem_binary
- if new_resource.options && new_resource.options.kind_of?(Hash)
+ if new_resource.options && new_resource.options.is_a?(Hash)
msg = "options cannot be given as a hash when using an explicit gem_binary\n"
msg << "in #{new_resource} from #{new_resource.source_line}"
raise ArgumentError, msg
end
@gem_env = AlternateGemEnvironment.new(new_resource.gem_binary)
- Chef::Log.debug("#{@new_resource} using gem '#{new_resource.gem_binary}'")
- elsif is_omnibus? && (!@new_resource.instance_of? Chef::Resource::ChefGem)
+ logger.trace("#{new_resource} using gem '#{new_resource.gem_binary}'")
+ elsif is_omnibus? && (!new_resource.instance_of? Chef::Resource::ChefGem)
# Opscode Omnibus - The ruby that ships inside omnibus is only used for Chef
# Default to installing somewhere more functional
- if new_resource.options && new_resource.options.kind_of?(Hash)
+ if new_resource.options && new_resource.options.is_a?(Hash)
msg = [
"Gem options must be passed to gem_package as a string instead of a hash when",
"using this installation of Chef because it runs with its own packaged Ruby. A hash",
@@ -383,23 +398,23 @@ class Chef
raise ArgumentError, msg
end
gem_location = find_gem_by_path
- @new_resource.gem_binary gem_location
+ new_resource.gem_binary gem_location
@gem_env = AlternateGemEnvironment.new(gem_location)
- Chef::Log.debug("#{@new_resource} using gem '#{gem_location}'")
+ logger.trace("#{new_resource} using gem '#{gem_location}'")
else
@gem_env = CurrentGemEnvironment.new
@cleanup_gem_env = false
- Chef::Log.debug("#{@new_resource} using gem from running ruby environment")
+ logger.trace("#{new_resource} using gem from running ruby environment")
end
end
def is_omnibus?
if RbConfig::CONFIG["bindir"] =~ %r{/(opscode|chef|chefdk)/embedded/bin}
- Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
+ logger.trace("#{new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
# Omnibus installs to a static path because of linking on unix, find it.
true
elsif RbConfig::CONFIG["bindir"].sub(/^[\w]:/, "") == "/opscode/chef/embedded/bin"
- Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
+ logger.trace("#{new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
# windows, with the drive letter removed
true
else
@@ -408,44 +423,40 @@ class Chef
end
def find_gem_by_path
- Chef::Log.debug("#{@new_resource} searching for 'gem' binary in path: #{ENV['PATH']}")
- separator = ::File::ALT_SEPARATOR ? ::File::ALT_SEPARATOR : ::File::SEPARATOR
- path_to_first_gem = ENV["PATH"].split(::File::PATH_SEPARATOR).find { |path| ::File.exists?(path + separator + "gem") }
- raise Chef::Exceptions::FileNotFound, "Unable to find 'gem' binary in path: #{ENV['PATH']}" if path_to_first_gem.nil?
- path_to_first_gem + separator + "gem"
+ which("gem", extra_path: RbConfig::CONFIG["bindir"])
end
def gem_dependency
- Gem::Dependency.new(@new_resource.package_name, @new_resource.version)
+ Gem::Dependency.new(new_resource.package_name, new_resource.version)
end
def source_is_remote?
- return true if @new_resource.source.nil?
- scheme = URI.parse(@new_resource.source).scheme
+ return true if new_resource.source.nil?
+ return true if new_resource.source.is_a?(Array)
+ scheme = URI.parse(new_resource.source).scheme
# URI.parse gets confused by MS Windows paths with forward slashes.
scheme = nil if scheme =~ /^[a-z]$/
%w{http https}.include?(scheme)
rescue URI::InvalidURIError
- Chef::Log.debug("#{@new_resource} failed to parse source '#{@new_resource.source}' as a URI, assuming a local path")
+ logger.trace("#{new_resource} failed to parse source '#{new_resource.source}' as a URI, assuming a local path")
false
end
def current_version
- #raise 'todo'
# If one or more matching versions are installed, the newest of them
# is the current version
if !matching_installed_versions.empty?
- gemspec = matching_installed_versions.last
- logger.debug { "#{@new_resource} found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}" }
+ gemspec = matching_installed_versions.max_by(&:version)
+ logger.trace { "#{new_resource} found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}" }
gemspec
# If no version matching the requirements exists, the latest installed
# version is the current version.
elsif !all_installed_versions.empty?
- gemspec = all_installed_versions.last
- logger.debug { "#{@new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" }
+ gemspec = all_installed_versions.max_by(&:version)
+ logger.trace { "#{new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" }
gemspec
else
- logger.debug { "#{@new_resource} no installed version found for #{gem_dependency}" }
+ logger.trace { "#{new_resource} no installed version found for #{gem_dependency}" }
nil
end
end
@@ -461,41 +472,39 @@ class Chef
end
def gem_sources
- @new_resource.source ? Array(@new_resource.source) : nil
+ srcs = [ new_resource.source ]
+ srcs << Chef::Config[:rubygems_url] if new_resource.include_default_source
+ srcs.flatten.compact
end
def load_current_resource
- @current_resource = Chef::Resource::Package::GemPackage.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package::GemPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
if current_spec = current_version
- @current_resource.version(current_spec.version.to_s)
+ current_resource.version(current_spec.version.to_s)
end
- @current_resource
+ current_resource
end
def cleanup_after_converge
if @cleanup_gem_env
- logger.debug { "#{@new_resource} resetting gem environment to default" }
+ logger.trace { "#{new_resource} resetting gem environment to default" }
Gem.clear_paths
end
end
def candidate_version
@candidate_version ||= begin
- if target_version_already_installed?(@current_resource.version, @new_resource.version)
- nil
- elsif source_is_remote?
- @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
- else
- @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s
- end
- end
+ if source_is_remote?
+ @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
+ else
+ @gem_env.candidate_version_from_file(gem_dependency, new_resource.source).to_s
+ end
+ end
end
- def target_version_already_installed?(current_version, new_version)
- return false unless current_version
- return false if new_version.nil?
-
+ def version_requirement_satisfied?(current_version, new_version)
+ return false unless current_version && new_version
Gem::Requirement.new(new_version).satisfied_by?(Gem::Version.new(current_version))
end
@@ -506,20 +515,18 @@ class Chef
# 2. shell out to `gem install` when a String of options is given
# 3. use gems API with options if a hash of options is given
def install_package(name, version)
- if source_is_remote? && @new_resource.gem_binary.nil?
- if @new_resource.options.nil?
- @gem_env.install(gem_dependency, :sources => gem_sources)
- elsif @new_resource.options.kind_of?(Hash)
- options = @new_resource.options
+ if source_is_remote? && new_resource.gem_binary.nil?
+ if new_resource.options.nil?
+ @gem_env.install(gem_dependency, sources: gem_sources)
+ elsif new_resource.options.is_a?(Hash)
+ options = new_resource.options
options[:sources] = gem_sources
@gem_env.install(gem_dependency, options)
else
install_via_gem_command(name, version)
end
- elsif @new_resource.gem_binary.nil?
- # domain is used by Gem::DependencyInstaller rather than by Chef code
- # domain can be :local, :remote or :both
- @gem_env.install(@new_resource.source, domain: :local)
+ elsif new_resource.gem_binary.nil?
+ @gem_env.install(new_resource.source)
else
install_via_gem_command(name, version)
end
@@ -527,23 +534,22 @@ class Chef
end
def gem_binary_path
- @new_resource.gem_binary || "gem"
+ new_resource.gem_binary || "gem"
end
def install_via_gem_command(name, version)
- if @new_resource.source =~ /\.gem$/i
- name = @new_resource.source
- src = " --local" unless source_is_remote?
- elsif @new_resource.clear_sources
- src = " --clear-sources"
- src << (@new_resource.source && " --source=#{@new_resource.source}" || "")
+ src = []
+ if new_resource.source.is_a?(String) && new_resource.source =~ /\.gem$/i
+ name = new_resource.source
else
- src = @new_resource.source && " --source=#{@new_resource.source} --source=#{Chef::Config[:rubygems_url]}"
+ src << "--clear-sources" if new_resource.clear_sources
+ src += gem_sources.map { |s| "--source=#{s}" }
end
- if !version.nil? && version.length > 0
- shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env => nil)
+ src_str = src.empty? ? "" : " #{src.join(" ")}"
+ if !version.nil? && !version.empty?
+ shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src_str}#{opts}", env: nil)
else
- shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env => nil)
+ shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src_str}#{opts}", env: nil)
end
end
@@ -552,11 +558,11 @@ class Chef
end
def remove_package(name, version)
- if @new_resource.gem_binary.nil?
- if @new_resource.options.nil?
+ if new_resource.gem_binary.nil?
+ if new_resource.options.nil?
@gem_env.uninstall(name, version)
- elsif @new_resource.options.kind_of?(Hash)
- @gem_env.uninstall(name, version, @new_resource.options)
+ elsif new_resource.options.is_a?(Hash)
+ @gem_env.uninstall(name, version, new_resource.options)
else
uninstall_via_gem_command(name, version)
end
@@ -567,9 +573,9 @@ class Chef
def uninstall_via_gem_command(name, version)
if version
- shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env => nil)
+ shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", env: nil)
else
- shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env => nil)
+ shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", env: nil)
end
end
@@ -580,7 +586,7 @@ class Chef
private
def opts
- expand_options(@new_resource.options)
+ expand_options(new_resource.options)
end
end
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index 3f09bef212..5c637814a6 100644
--- a/lib/chef/provider/package/smartos.rb
+++ b/lib/chef/provider/package/smartos.rb
@@ -3,7 +3,7 @@
# Bryan McLellan (btm@loftninjas.org)
# Matthew Landauer (matthew@openaustralia.org)
# Ben Rockwood (benr@joyent.com)
-# Copyright:: Copyright 2009-2016, Bryan McLellan, Matthew Landauer
+# Copyright:: Copyright 2009-2018, Bryan McLellan, Matthew Landauer
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,27 +30,27 @@ class Chef
attr_accessor :is_virtual_package
provides :package, platform: "smartos"
- provides :smartos_package, os: "solaris2", platform_family: "smartos"
+ provides :smartos_package
def load_current_resource
- Chef::Log.debug("#{@new_resource} loading current resource")
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
- check_package_state(@new_resource.package_name)
- @current_resource # modified by check_package_state
+ logger.trace("#{new_resource} loading current resource")
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ check_package_state(new_resource.package_name)
+ current_resource # modified by check_package_state
end
def check_package_state(name)
- Chef::Log.debug("#{@new_resource} checking package #{name}")
+ logger.trace("#{new_resource} checking package #{name}")
version = nil
- info = shell_out_with_timeout!("/opt/local/sbin/pkg_info", "-E", "#{name}*", :env => nil, :returns => [0, 1])
+ info = shell_out_compact_timeout!("/opt/local/sbin/pkg_info", "-E", "#{name}*", env: nil, returns: [0, 1])
if info.stdout
- version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
+ version = info.stdout[/^#{new_resource.package_name}-(.+)/, 1]
end
if version
- @current_resource.version(version)
+ current_resource.version(version)
end
end
@@ -58,7 +58,7 @@ class Chef
return @candidate_version if @candidate_version
name = nil
version = nil
- pkg = shell_out_with_timeout!("/opt/local/bin/pkgin", "se", new_resource.package_name, :env => nil, :returns => [0, 1])
+ pkg = shell_out_compact_timeout!("/opt/local/bin/pkgin", "se", new_resource.package_name, env: nil, returns: [0, 1])
pkg.stdout.each_line do |line|
case line
when /^#{new_resource.package_name}/
@@ -70,20 +70,20 @@ class Chef
end
def install_package(name, version)
- Chef::Log.debug("#{@new_resource} installing package #{name} version #{version}")
+ logger.trace("#{new_resource} installing package #{name} version #{version}")
package = "#{name}-#{version}"
- out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "install", package, :env => nil)
+ out = shell_out_compact_timeout!("/opt/local/bin/pkgin", "-y", "install", package, env: nil)
end
def upgrade_package(name, version)
- Chef::Log.debug("#{@new_resource} upgrading package #{name} version #{version}")
+ logger.trace("#{new_resource} upgrading package #{name} version #{version}")
install_package(name, version)
end
def remove_package(name, version)
- Chef::Log.debug("#{@new_resource} removing package #{name} version #{version}")
- package = "#{name}"
- out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "remove", package, :env => nil)
+ logger.trace("#{new_resource} removing package #{name} version #{version}")
+ package = name.to_s
+ out = shell_out_compact_timeout!("/opt/local/bin/pkgin", "-y", "remove", package, env: nil)
end
end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index 1c393e6a20..9c75c76929 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -16,7 +16,6 @@
# limitations under the License.
#
require "chef/provider/package"
-require "chef/mixin/command"
require "chef/resource/package"
require "chef/mixin/get_source_from_package"
@@ -29,49 +28,49 @@ class Chef
provides :package, platform: "nexentacore"
provides :package, platform: "solaris2", platform_version: "< 5.11"
- provides :solaris_package, os: "solaris2"
+ provides :solaris_package
# def initialize(*args)
# super
- # @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ # @current_resource = Chef::Resource::Package.new(new_resource.name)
# end
def define_resource_requirements
super
requirements.assert(:install) do |a|
- a.assertion { @new_resource.source }
- a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+ a.assertion { new_resource.source }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{new_resource.name} required for action install"
end
requirements.assert(:all_actions) do |a|
- a.assertion { !@new_resource.source || @package_source_found }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- a.whyrun "would assume #{@new_resource.source} would be have previously been made available"
+ a.assertion { !new_resource.source || @package_source_found }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.name} not found: #{new_resource.source}"
+ a.whyrun "would assume #{new_resource.source} would be have previously been made available"
end
end
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- if @new_resource.source
- @package_source_found = ::File.exists?(@new_resource.source)
+ if new_resource.source
+ @package_source_found = ::File.exist?(new_resource.source)
if @package_source_found
- Chef::Log.debug("#{@new_resource} checking pkg status")
- shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line|
+ logger.trace("#{new_resource} checking pkg status")
+ shell_out_compact_timeout("pkginfo", "-l", "-d", new_resource.source, new_resource.package_name).stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
- @new_resource.version($1)
+ new_resource.version($1)
end
end
end
end
- Chef::Log.debug("#{@new_resource} checking install state")
- status = shell_out_with_timeout("pkginfo -l #{@current_resource.package_name}")
+ logger.trace("#{new_resource} checking install state")
+ status = shell_out_compact_timeout("pkginfo", "-l", current_resource.package_name)
status.stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
- Chef::Log.debug("#{@new_resource} version #{$1} is already installed")
- @current_resource.version($1)
+ logger.trace("#{new_resource} version #{$1} is already installed")
+ current_resource.version($1)
end
end
@@ -79,56 +78,56 @@ class Chef
raise Chef::Exceptions::Package, "pkginfo failed - #{status.inspect}!"
end
- @current_resource
+ current_resource
end
def candidate_version
return @candidate_version if @candidate_version
- status = shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}")
+ status = shell_out_compact_timeout("pkginfo", "-l", "-d", new_resource.source, new_resource.package_name)
status.stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
@candidate_version = $1
- @new_resource.version($1)
- Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}")
+ new_resource.version($1)
+ logger.trace("#{new_resource} setting install candidate version to #{@candidate_version}")
end
end
unless status.exitstatus == 0
- raise Chef::Exceptions::Package, "pkginfo -l -d #{@new_resource.source} - #{status.inspect}!"
+ raise Chef::Exceptions::Package, "pkginfo -l -d #{new_resource.source} - #{status.inspect}!"
end
@candidate_version
end
def install_package(name, version)
- Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
- if @new_resource.options.nil?
- if ::File.directory?(@new_resource.source) # CHEF-4469
- command = "pkgadd -n -d #{@new_resource.source} #{@new_resource.package_name}"
- else
- command = "pkgadd -n -d #{@new_resource.source} all"
- end
- shell_out_with_timeout!(command)
- Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
+ logger.trace("#{new_resource} package install options: #{options}")
+ if options.nil?
+ command = if ::File.directory?(new_resource.source) # CHEF-4469
+ [ "pkgadd", "-n", "-d", new_resource.source, new_resource.package_name ]
+ else
+ [ "pkgadd", "-n", "-d", new_resource.source, "all" ]
+ end
+ shell_out_compact_timeout!(command)
+ logger.trace("#{new_resource} installed version #{new_resource.version} from: #{new_resource.source}")
else
- if ::File.directory?(@new_resource.source) # CHEF-4469
- command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}"
- else
- command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
- end
- shell_out_with_timeout!(command)
- Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
+ command = if ::File.directory?(new_resource.source) # CHEF-4469
+ [ "pkgadd", "-n", options, "-d", new_resource.source, new_resource.package_name ]
+ else
+ [ "pkgadd", "-n", options, "-d", new_resource.source, "all" ]
+ end
+ shell_out_compact_timeout!(*command)
+ logger.trace("#{new_resource} installed version #{new_resource.version} from: #{new_resource.source}")
end
end
- alias_method :upgrade_package, :install_package
+ alias upgrade_package install_package
def remove_package(name, version)
- if @new_resource.options.nil?
- shell_out_with_timeout!( "pkgrm -n #{name}" )
- Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+ if options.nil?
+ shell_out_compact_timeout!( "pkgrm", "-n", name )
+ logger.trace("#{new_resource} removed version #{new_resource.version}")
else
- shell_out_with_timeout!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
- Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+ shell_out_compact_timeout!( "pkgrm", "-n", options, name )
+ logger.trace("#{new_resource} removed version #{new_resource.version}")
end
end
diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb
index a5f3ff7191..0fea32dcf3 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -30,7 +30,7 @@ class Chef
include Chef::Mixin::Checksum
provides :package, os: "windows"
- provides :windows_package, os: "windows"
+ provides :windows_package
require "chef/provider/package/windows/registry_uninstall_entry.rb"
@@ -45,7 +45,7 @@ class Chef
def load_current_resource
@current_resource = Chef::Resource::WindowsPackage.new(new_resource.name)
if downloadable_file_missing?
- Chef::Log.debug("We do not know the version of #{new_resource.source} because the file is not downloaded")
+ logger.trace("We do not know the version of #{new_resource.source} because the file is not downloaded")
current_resource.version(:unknown.to_s)
else
current_resource.version(package_provider.installed_version)
@@ -59,11 +59,11 @@ class Chef
@package_provider ||= begin
case installer_type
when :msi
- Chef::Log.debug("#{new_resource} is MSI")
+ logger.trace("#{new_resource} is MSI")
require "chef/provider/package/windows/msi"
Chef::Provider::Package::Windows::MSI.new(resource_for_provider, uninstall_registry_entries)
else
- Chef::Log.debug("#{new_resource} is EXE with type '#{installer_type}'")
+ logger.trace("#{new_resource} is EXE with type '#{installer_type}'")
require "chef/provider/package/windows/exe"
Chef::Provider::Package::Windows::Exe.new(resource_for_provider, installer_type, uninstall_registry_entries)
end
@@ -104,8 +104,8 @@ class Chef
return :nsis
end
- if io.tell() < filesize
- io.seek(io.tell() - overlap)
+ if io.tell < filesize
+ io.seek(io.tell - overlap)
end
end
@@ -165,7 +165,11 @@ class Chef
#
# @return [Boolean] true if new_version is equal to or included in current_version
def target_version_already_installed?(current_version, new_version)
- Chef::Log.debug("Checking if #{new_resource} version '#{new_version}' is already installed. #{current_version} is currently installed")
+ version_equals?(current_version, new_version)
+ end
+
+ def version_equals?(current_version, new_version)
+ logger.trace("Checking if #{new_resource} version '#{new_version}' is already installed. #{current_version} is currently installed")
if current_version.is_a?(Array)
current_version.include?(new_version)
else
@@ -179,6 +183,17 @@ class Chef
private
+ def version_compare(v1, v2)
+ if v1 == "latest" || v2 == "latest"
+ return 0
+ end
+
+ gem_v1 = Gem::Version.new(v1)
+ gem_v2 = Gem::Version.new(v2)
+
+ gem_v1 <=> gem_v2
+ end
+
def uninstall_registry_entries
@uninstall_registry_entries ||= Chef::Provider::Package::Windows::RegistryUninstallEntry.find_entries(new_resource.package_name)
end
@@ -195,12 +210,13 @@ class Chef
end
def downloadable_file_missing?
- !new_resource.source.nil? && uri_scheme?(new_resource.source) && !::File.exists?(source_location)
+ !new_resource.source.nil? && uri_scheme?(new_resource.source) && !::File.exist?(source_location)
end
def resource_for_provider
@resource_for_provider = Chef::Resource::WindowsPackage.new(new_resource.name).tap do |r|
r.source(Chef::Util::PathHelper.validate_path(source_location)) unless source_location.nil?
+ r.cookbook_name = new_resource.cookbook_name
r.version(new_resource.version)
r.timeout(new_resource.timeout)
r.returns(new_resource.returns)
@@ -210,12 +226,13 @@ class Chef
def download_source_file
source_resource.run_action(:create)
- Chef::Log.debug("#{new_resource} fetched source file to #{source_resource.path}")
+ logger.trace("#{new_resource} fetched source file to #{source_resource.path}")
end
def source_resource
@source_resource ||= Chef::Resource::RemoteFile.new(default_download_cache_path, run_context).tap do |r|
r.source(new_resource.source)
+ r.cookbook_name = new_resource.cookbook_name
r.checksum(new_resource.checksum)
r.backup(false)
@@ -248,7 +265,7 @@ class Chef
def validate_content!
if new_resource.checksum
source_checksum = checksum(source_location)
- if new_resource.checksum != source_checksum
+ if new_resource.checksum.downcase != source_checksum
raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(source_checksum))
end
end
@@ -260,7 +277,7 @@ class Chef
if source_location.nil?
inferred_registry_type == :msi
else
- ::File.extname(source_location).casecmp(".msi").zero?
+ ::File.extname(source_location).casecmp(".msi") == 0
end
end
end
diff --git a/lib/chef/provider/package/windows/exe.rb b/lib/chef/provider/package/windows/exe.rb
index 70c9879845..6499d0cfeb 100644
--- a/lib/chef/provider/package/windows/exe.rb
+++ b/lib/chef/provider/package/windows/exe.rb
@@ -28,11 +28,13 @@ class Chef
def initialize(resource, installer_type, uninstall_entries)
@new_resource = resource
+ @logger = new_resource.logger
@installer_type = installer_type
@uninstall_entries = uninstall_entries
end
attr_reader :new_resource
+ attr_reader :logger
attr_reader :installer_type
attr_reader :uninstall_entries
@@ -43,7 +45,7 @@ class Chef
# Returns a version if the package is installed or nil if it is not.
def installed_version
- Chef::Log.debug("#{new_resource} checking package version")
+ logger.trace("#{new_resource} checking package version")
current_installed_version
end
@@ -52,7 +54,7 @@ class Chef
end
def install_package
- Chef::Log.debug("#{new_resource} installing #{new_resource.installer_type} package '#{new_resource.source}'")
+ logger.trace("#{new_resource} installing #{new_resource.installer_type} package '#{new_resource.source}'")
shell_out!(
[
"start",
@@ -69,32 +71,30 @@ class Chef
def remove_package
uninstall_version = new_resource.version || current_installed_version
uninstall_entries.select { |entry| [uninstall_version].flatten.include?(entry.display_version) }
- .map { |version| version.uninstall_string }.uniq.each do |uninstall_string|
- Chef::Log.debug("Registry provided uninstall string for #{new_resource} is '#{uninstall_string}'")
- shell_out!(uninstall_command(uninstall_string), { returns: new_resource.returns })
- end
+ .map(&:uninstall_string).uniq.each do |uninstall_string|
+ logger.trace("Registry provided uninstall string for #{new_resource} is '#{uninstall_string}'")
+ shell_out!(uninstall_command(uninstall_string), timeout: new_resource.timeout, returns: new_resource.returns)
+ end
end
private
def uninstall_command(uninstall_string)
- uninstall_string.delete!('"')
+ uninstall_string = "\"#{uninstall_string}\"" if ::File.exist?(uninstall_string)
uninstall_string = [
- %q{/d"},
- ::File.dirname(uninstall_string),
- %q{" },
- ::File.basename(uninstall_string),
+ uninstall_string,
expand_options(new_resource.options),
" ",
unattended_flags,
].join
- %Q{start "" /wait #{uninstall_string} & exit %%%%ERRORLEVEL%%%%}
+ %{start "" /wait #{uninstall_string} & exit %%%%ERRORLEVEL%%%%}
end
def current_installed_version
- @current_installed_version ||= uninstall_entries.count == 0 ? nil : begin
- uninstall_entries.map { |entry| entry.display_version }.uniq
- end
+ @current_installed_version ||=
+ if uninstall_entries.count != 0
+ uninstall_entries.map(&:display_version).uniq
+ end
end
# http://unattended.sourceforge.net/installers.php
diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb
index ac771688e7..51afcab2a2 100644
--- a/lib/chef/provider/package/windows/msi.rb
+++ b/lib/chef/provider/package/windows/msi.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright 2014-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,7 @@
# limitations under the License.
#
-# TODO: Allow @new_resource.source to be a Product Code as a GUID for uninstall / network install
+# TODO: Allow new_resource.source to be a Product Code as a GUID for uninstall / network install
require "chef/win32/api/installer" if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi?
require "chef/mixin/shell_out"
@@ -31,10 +31,12 @@ class Chef
def initialize(resource, uninstall_entries)
@new_resource = resource
+ @logger = new_resource.logger
@uninstall_entries = uninstall_entries
end
attr_reader :new_resource
+ attr_reader :logger
attr_reader :uninstall_entries
# From Chef::Provider::Package
@@ -45,13 +47,13 @@ class Chef
# Returns a version if the package is installed or nil if it is not.
def installed_version
if !new_resource.source.nil? && ::File.exist?(new_resource.source)
- Chef::Log.debug("#{new_resource} getting product code for package at #{new_resource.source}")
+ logger.trace("#{new_resource} getting product code for package at #{new_resource.source}")
product_code = get_product_property(new_resource.source, "ProductCode")
- Chef::Log.debug("#{new_resource} checking package status and version for #{product_code}")
+ logger.trace("#{new_resource} checking package status and version for #{product_code}")
get_installed_version(product_code)
else
- uninstall_entries.count == 0 ? nil : begin
- uninstall_entries.map { |entry| entry.display_version }.uniq
+ if uninstall_entries.count != 0
+ uninstall_entries.map(&:display_version).uniq
end
end
end
@@ -59,30 +61,31 @@ class Chef
def package_version
return new_resource.version if new_resource.version
if !new_resource.source.nil? && ::File.exist?(new_resource.source)
- Chef::Log.debug("#{new_resource} getting product version for package at #{new_resource.source}")
+ logger.trace("#{new_resource} getting product version for package at #{new_resource.source}")
get_product_property(new_resource.source, "ProductVersion")
end
end
def install_package
# We could use MsiConfigureProduct here, but we'll start off with msiexec
- Chef::Log.debug("#{new_resource} installing MSI package '#{new_resource.source}'")
- shell_out!("msiexec /qn /i \"#{new_resource.source}\" #{expand_options(new_resource.options)}", { :timeout => new_resource.timeout, :returns => new_resource.returns })
+ logger.trace("#{new_resource} installing MSI package '#{new_resource.source}'")
+ shell_out!("msiexec /qn /i \"#{new_resource.source}\" #{expand_options(new_resource.options)}", timeout: new_resource.timeout, returns: new_resource.returns)
end
def remove_package
# We could use MsiConfigureProduct here, but we'll start off with msiexec
if !new_resource.source.nil? && ::File.exist?(new_resource.source)
- Chef::Log.debug("#{new_resource} removing MSI package '#{new_resource.source}'")
- shell_out!("msiexec /qn /x \"#{new_resource.source}\" #{expand_options(new_resource.options)}", { :timeout => new_resource.timeout, :returns => new_resource.returns })
+ logger.trace("#{new_resource} removing MSI package '#{new_resource.source}'")
+ shell_out!("msiexec /qn /x \"#{new_resource.source}\" #{expand_options(new_resource.options)}", timeout: new_resource.timeout, returns: new_resource.returns)
else
uninstall_version = new_resource.version || installed_version
uninstall_entries.select { |entry| [uninstall_version].flatten.include?(entry.display_version) }
- .map { |version| version.uninstall_string }.uniq.each do |uninstall_string|
- Chef::Log.debug("#{new_resource} removing MSI package version using '#{uninstall_string}'")
+ .map(&:uninstall_string).uniq.each do |uninstall_string|
+ uninstall_string = "msiexec /x #{uninstall_string.match(/{.*}/)}"
uninstall_string += expand_options(new_resource.options)
- uninstall_string += " /Q" unless uninstall_string =~ / \/Q\b/
- shell_out!(uninstall_string, { :timeout => new_resource.timeout, :returns => new_resource.returns })
+ uninstall_string += " /q" unless uninstall_string.downcase =~ / \/q/
+ logger.trace("#{new_resource} removing MSI package version using '#{uninstall_string}'")
+ shell_out!(uninstall_string, timeout: new_resource.timeout, returns: new_resource.returns)
end
end
end
diff --git a/lib/chef/provider/package/windows/registry_uninstall_entry.rb b/lib/chef/provider/package/windows/registry_uninstall_entry.rb
index 3fa00b6081..d57f700799 100644
--- a/lib/chef/provider/package/windows/registry_uninstall_entry.rb
+++ b/lib/chef/provider/package/windows/registry_uninstall_entry.rb
@@ -26,7 +26,7 @@ class Chef
class RegistryUninstallEntry
def self.find_entries(package_name)
- Chef::Log.debug("Finding uninstall entries for #{package_name}")
+ logger.trace("Finding uninstall entries for #{package_name}")
entries = []
[
[::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0100)],
@@ -40,36 +40,47 @@ class Chef
begin
entry = reg.open(key, desired)
display_name = read_registry_property(entry, "DisplayName")
- if display_name == package_name
- entries.push(RegistryUninstallEntry.new(hkey, key, entry))
+ if display_name.to_s.rstrip == package_name
+ quiet_uninstall_string = RegistryUninstallEntry.read_registry_property(entry, "QuietUninstallString")
+ entries.push(quiet_uninstall_string_key?(quiet_uninstall_string, hkey, key, entry))
end
rescue ::Win32::Registry::Error => ex
- Chef::Log.debug("Registry error opening key '#{key}' on node #{desired}: #{ex}")
+ logger.trace("Registry error opening key '#{key}' on node #{desired}: #{ex}")
end
end
end
rescue ::Win32::Registry::Error => ex
- Chef::Log.debug("Registry error opening hive '#{hkey[0]}' :: #{desired}: #{ex}")
+ logger.trace("Registry error opening hive '#{hkey[0]}' :: #{desired}: #{ex}")
end
end
entries
end
+ def self.quiet_uninstall_string_key?(quiet_uninstall_string, hkey, key, entry)
+ return RegistryUninstallEntry.new(hkey, key, entry) if quiet_uninstall_string.nil?
+ RegistryUninstallEntry.new(hkey, key, entry, "QuietUninstallString")
+ end
+
def self.read_registry_property(data, property)
data[property]
- rescue ::Win32::Registry::Error => ex
- Chef::Log.debug("Failure to read property '#{property}'")
+ rescue ::Win32::Registry::Error
+ logger.trace("Failure to read property '#{property}'")
nil
end
- def initialize(hive, key, registry_data)
- Chef::Log.debug("Creating uninstall entry for #{hive}::#{key}")
+ def self.logger
+ Chef::Log
+ end
+
+ def initialize(hive, key, registry_data, uninstall_key = "UninstallString")
+ @logger = Chef::Log.with_child({ subsystem: "registry_uninstall_entry" })
+ logger.trace("Creating uninstall entry for #{hive}::#{key}")
@hive = hive
@key = key
@data = registry_data
@display_name = RegistryUninstallEntry.read_registry_property(registry_data, "DisplayName")
@display_version = RegistryUninstallEntry.read_registry_property(registry_data, "DisplayVersion")
- @uninstall_string = RegistryUninstallEntry.read_registry_property(registry_data, "UninstallString")
+ @uninstall_string = RegistryUninstallEntry.read_registry_property(registry_data, uninstall_key)
end
attr_reader :hive
@@ -78,8 +89,7 @@ class Chef
attr_reader :display_version
attr_reader :uninstall_string
attr_reader :data
-
- private
+ attr_reader :logger
UNINSTALL_SUBKEY = 'Software\Microsoft\Windows\CurrentVersion\Uninstall'.freeze
end
diff --git a/lib/chef/provider/package/yum-dump.py b/lib/chef/provider/package/yum-dump.py
deleted file mode 100644
index 6183460195..0000000000
--- a/lib/chef/provider/package/yum-dump.py
+++ /dev/null
@@ -1,307 +0,0 @@
-#
-# Author:: Matthew Kent (<mkent@magoazul.com>)
-# Copyright:: Copyright 2009-2016, Matthew Kent
-# 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.
-#
-
-# yum-dump.py
-# Inspired by yumhelper.py by David Lutterkort
-#
-# Produce a list of installed, available and re-installable packages using yum
-# and dump the results to stdout.
-#
-# yum-dump invokes yum similarly to the command line interface which makes it
-# subject to most of the configuration parameters in yum.conf. yum-dump will
-# also load yum plugins in the same manor as yum - these can affect the output.
-#
-# Can be run as non root, but that won't update the cache.
-#
-# Intended to support yum 2.x and 3.x
-
-import os
-import sys
-import time
-import yum
-import re
-import errno
-
-from yum import Errors
-from optparse import OptionParser
-from distutils import version
-
-YUM_PID_FILE='/var/run/yum.pid'
-
-YUM_VER = version.StrictVersion(yum.__version__)
-YUM_MAJOR = YUM_VER.version[0]
-
-if YUM_MAJOR > 3 or YUM_MAJOR < 2:
- print >> sys.stderr, "yum-dump Error: Can't match supported yum version" \
- " (%s)" % yum.__version__
- sys.exit(1)
-
-# Required for Provides output
-if YUM_MAJOR == 2:
- import rpm
- import rpmUtils.miscutils
-
-def setup(yb, options):
- # Only want our output
- #
- if YUM_MAJOR == 3:
- try:
- if YUM_VER >= version.StrictVersion("3.2.22"):
- yb.preconf.errorlevel=0
- yb.preconf.debuglevel=0
-
- # initialize the config
- yb.conf
- else:
- yb.doConfigSetup(errorlevel=0, debuglevel=0)
- except yum.Errors.ConfigError, e:
- # suppresses an ignored exception at exit
- yb.preconf = None
- print >> sys.stderr, "yum-dump Config Error: %s" % e
- return 1
- except ValueError, e:
- yb.preconf = None
- print >> sys.stderr, "yum-dump Options Error: %s" % e
- return 1
- elif YUM_MAJOR == 2:
- yb.doConfigSetup()
-
- def __log(a,b): pass
-
- yb.log = __log
- yb.errorlog = __log
-
- # Give Chef every possible package version, it can decide what to do with them
- if YUM_MAJOR == 3:
- yb.conf.showdupesfromrepos = True
- elif YUM_MAJOR == 2:
- yb.conf.setConfigOption('showdupesfromrepos', True)
-
- # Optionally run only on cached repositories, but non root must use the cache
- if os.geteuid() != 0:
- if YUM_MAJOR == 3:
- yb.conf.cache = True
- elif YUM_MAJOR == 2:
- yb.conf.setConfigOption('cache', True)
- else:
- if YUM_MAJOR == 3:
- yb.conf.cache = options.cache
- elif YUM_MAJOR == 2:
- yb.conf.setConfigOption('cache', options.cache)
-
- # Handle repo toggle via id or glob exactly like yum
- for opt, repos in options.repo_control:
- for repo in repos:
- if opt == '--enablerepo':
- yb.repos.enableRepo(repo)
- elif opt == '--disablerepo':
- yb.repos.disableRepo(repo)
-
- return 0
-
-def dump_packages(yb, list, output_provides):
- packages = {}
-
- if YUM_MAJOR == 2:
- yb.doTsSetup()
- yb.doRepoSetup()
- yb.doSackSetup()
-
- db = yb.doPackageLists(list)
-
- for pkg in db.installed:
- pkg.type = 'i'
- packages[str(pkg)] = pkg
-
- if YUM_VER >= version.StrictVersion("3.2.21"):
- for pkg in db.available:
- pkg.type = 'a'
- packages[str(pkg)] = pkg
-
- # These are both installed and available
- for pkg in db.reinstall_available:
- pkg.type = 'r'
- packages[str(pkg)] = pkg
- else:
- # Old style method - no reinstall list
- for pkg in yb.pkgSack.returnPackages():
-
- if str(pkg) in packages:
- if packages[str(pkg)].type == "i":
- packages[str(pkg)].type = 'r'
- continue
-
- pkg.type = 'a'
- packages[str(pkg)] = pkg
-
- unique_packages = packages.values()
-
- unique_packages.sort(lambda x, y: cmp(x.name, y.name))
-
- for pkg in unique_packages:
- if output_provides == "all" or \
- (output_provides == "installed" and (pkg.type == "i" or pkg.type == "r")):
-
- # yum 2 doesn't have provides_print, implement it ourselves using methods
- # based on requires gathering in packages.py
- if YUM_MAJOR == 2:
- provlist = []
-
- # Installed and available are gathered in different ways
- if pkg.type == 'i' or pkg.type == 'r':
- names = pkg.hdr[rpm.RPMTAG_PROVIDENAME]
- flags = pkg.hdr[rpm.RPMTAG_PROVIDEFLAGS]
- ver = pkg.hdr[rpm.RPMTAG_PROVIDEVERSION]
- if names is not None:
- tmplst = zip(names, flags, ver)
-
- for (n, f, v) in tmplst:
- prov = rpmUtils.miscutils.formatRequire(n, v, f)
- provlist.append(prov)
- # This is slow :(
- elif pkg.type == 'a':
- for prcoTuple in pkg.returnPrco('provides'):
- prcostr = pkg.prcoPrintable(prcoTuple)
- provlist.append(prcostr)
-
- provides = provlist
- else:
- provides = pkg.provides_print
- else:
- provides = "[]"
-
- print '%s %s %s %s %s %s %s %s' % (
- pkg.name,
- pkg.epoch,
- pkg.version,
- pkg.release,
- pkg.arch,
- provides,
- pkg.type,
- pkg.repoid )
-
- return 0
-
-def yum_dump(options):
- lock_obtained = False
-
- yb = yum.YumBase()
-
- status = setup(yb, options)
- if status != 0:
- return status
-
- if options.output_options:
- print "[option installonlypkgs] %s" % " ".join(yb.conf.installonlypkgs)
-
- # Non root can't handle locking on rhel/centos 4
- if os.geteuid() != 0:
- return dump_packages(yb, options.package_list, options.output_provides)
-
- # Wrap the collection and output of packages in yum's global lock to prevent
- # any inconsistencies.
- try:
- # Spin up to --yum-lock-timeout option
- countdown = options.yum_lock_timeout
- while True:
- try:
- yb.doLock(YUM_PID_FILE)
- lock_obtained = True
- except Errors.LockError, e:
- time.sleep(1)
- countdown -= 1
- if countdown == 0:
- print >> sys.stderr, "yum-dump Locking Error! Couldn't obtain an " \
- "exclusive yum lock in %d seconds. Giving up." % options.yum_lock_timeout
- return 200
- else:
- break
-
- return dump_packages(yb, options.package_list, options.output_provides)
-
- # Ensure we clear the lock and cleanup any resources
- finally:
- try:
- yb.closeRpmDB()
- if lock_obtained == True:
- yb.doUnlock(YUM_PID_FILE)
- except Errors.LockError, e:
- print >> sys.stderr, "yum-dump Unlock Error: %s" % e
- return 200
-
-# Preserve order of enable/disable repo args like yum does
-def gather_repo_opts(option, opt, value, parser):
- if getattr(parser.values, option.dest, None) is None:
- setattr(parser.values, option.dest, [])
- getattr(parser.values, option.dest).append((opt, value.split(',')))
-
-def main():
- usage = "Usage: %prog [options]\n" + \
- "Output a list of installed, available and re-installable packages via yum"
- parser = OptionParser(usage=usage)
- parser.add_option("-C", "--cache",
- action="store_true", dest="cache", default=False,
- help="run entirely from cache, don't update cache")
- parser.add_option("-o", "--options",
- action="store_true", dest="output_options", default=False,
- help="output select yum options useful to Chef")
- parser.add_option("-p", "--installed-provides",
- action="store_const", const="installed", dest="output_provides", default="none",
- help="output Provides for installed packages, big/wide output")
- parser.add_option("-P", "--all-provides",
- action="store_const", const="all", dest="output_provides", default="none",
- help="output Provides for all package, slow, big/wide output")
- parser.add_option("-i", "--installed",
- action="store_const", const="installed", dest="package_list", default="all",
- help="output only installed packages")
- parser.add_option("-a", "--available",
- action="store_const", const="available", dest="package_list", default="all",
- help="output only available and re-installable packages")
- parser.add_option("--enablerepo",
- action="callback", callback=gather_repo_opts, type="string", dest="repo_control", default=[],
- help="enable disabled repositories by id or glob")
- parser.add_option("--disablerepo",
- action="callback", callback=gather_repo_opts, type="string", dest="repo_control", default=[],
- help="disable repositories by id or glob")
- parser.add_option("--yum-lock-timeout",
- action="store", type="int", dest="yum_lock_timeout", default=30,
- help="Time in seconds to wait for yum process lock")
-
- (options, args) = parser.parse_args()
-
- try:
- return yum_dump(options)
-
- except yum.Errors.RepoError, e:
- print >> sys.stderr, "yum-dump Repository Error: %s" % e
- return 1
-
- except yum.Errors.YumBaseError, e:
- print >> sys.stderr, "yum-dump General Error: %s" % e
- return 1
-
-try:
- status = main()
-# Suppress a nasty broken pipe error when output is piped to utilities like 'head'
-except IOError, e:
- if e.errno == errno.EPIPE:
- sys.exit(1)
- else:
- raise
-
-sys.exit(status)
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index dfd32fde55..805a74d013 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1,6 +1,5 @@
-
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+#
+# Copyright:: Copyright 2016-2018, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,1402 +15,271 @@
# limitations under the License.
#
-require "chef/config"
require "chef/provider/package"
+require "chef/resource/yum_package"
require "chef/mixin/which"
-require "chef/resource/package"
-require "singleton"
+require "chef/mixin/shell_out"
require "chef/mixin/get_source_from_package"
+require "chef/provider/package/yum/python_helper"
+require "chef/provider/package/yum/version"
+# the stubs in the YumCache class are still an external API
+require "chef/provider/package/yum/yum_cache"
class Chef
class Provider
class Package
class Yum < Chef::Provider::Package
+ extend Chef::Mixin::Which
+ extend Chef::Mixin::ShellOut
+ include Chef::Mixin::GetSourceFromPackage
- provides :package, platform_family: %w{rhel fedora}
- provides :yum_package, os: "linux"
-
- class RPMUtils
- class << self
-
- # RPM::Version version_parse equivalent
- def version_parse(evr)
- return if evr.nil?
-
- epoch = nil
- # assume this is a version
- version = evr
- release = nil
-
- lead = 0
- tail = evr.size
-
- if evr =~ %r{^([\d]+):}
- epoch = $1.to_i
- lead = $1.length + 1
- elsif evr[0].ord == ":".ord
- epoch = 0
- lead = 1
- end
-
- if evr =~ %r{:?.*-(.*)$}
- release = $1
- tail = evr.length - release.length - lead - 1
-
- if release.empty?
- release = nil
- end
- end
-
- version = evr[lead, tail]
- if version.empty?
- version = nil
- end
-
- [ epoch, version, release ]
- end
-
- # verify
- def isalnum(x)
- isalpha(x) || isdigit(x)
- end
-
- def isalpha(x)
- v = x.ord
- (v >= 65 && v <= 90) || (v >= 97 && v <= 122)
- end
-
- def isdigit(x)
- v = x.ord
- v >= 48 && v <= 57
- end
-
- # based on the reference spec in lib/rpmvercmp.c in rpm 4.9.0
- def rpmvercmp(x, y)
- # easy! :)
- return 0 if x == y
-
- if x.nil?
- x = ""
- end
-
- if y.nil?
- y = ""
- end
-
- # not so easy :(
- #
- # takes 2 strings like
- #
- # x = "1.20.b18.el5"
- # y = "1.20.b17.el5"
- #
- # breaks into purely alpha and numeric segments and compares them using
- # some rules
- #
- # * 10 > 1
- # * 1 > a
- # * z > a
- # * Z > A
- # * z > Z
- # * leading zeros are ignored
- # * separators (periods, commas) are ignored
- # * "1.20.b18.el5.extrastuff" > "1.20.b18.el5"
-
- x_pos = 0 # overall string element reference position
- x_pos_max = x.length - 1 # number of elements in string, starting from 0
- x_seg_pos = 0 # segment string element reference position
- x_comp = nil # segment to compare
-
- y_pos = 0
- y_seg_pos = 0
- y_pos_max = y.length - 1
- y_comp = nil
-
- while x_pos <= x_pos_max && y_pos <= y_pos_max
- # first we skip over anything non alphanumeric
- while (x_pos <= x_pos_max) && (isalnum(x[x_pos]) == false)
- x_pos += 1 # +1 over pos_max if end of string
- end
- while (y_pos <= y_pos_max) && (isalnum(y[y_pos]) == false)
- y_pos += 1
- end
-
- # if we hit the end of either we are done matching segments
- if (x_pos == x_pos_max + 1) || (y_pos == y_pos_max + 1)
- break
- end
-
- # we are now at the start of a alpha or numeric segment
- x_seg_pos = x_pos
- y_seg_pos = y_pos
-
- # grab segment so we can compare them
- if isdigit(x[x_seg_pos].ord)
- x_seg_is_num = true
-
- # already know it's a digit
- x_seg_pos += 1
-
- # gather up our digits
- while (x_seg_pos <= x_pos_max) && isdigit(x[x_seg_pos])
- x_seg_pos += 1
- end
- # copy the segment but not the unmatched character that x_seg_pos will
- # refer to
- x_comp = x[x_pos, x_seg_pos - x_pos]
-
- while (y_seg_pos <= y_pos_max) && isdigit(y[y_seg_pos])
- y_seg_pos += 1
- end
- y_comp = y[y_pos, y_seg_pos - y_pos]
- else
- # we are comparing strings
- x_seg_is_num = false
-
- while (x_seg_pos <= x_pos_max) && isalpha(x[x_seg_pos])
- x_seg_pos += 1
- end
- x_comp = x[x_pos, x_seg_pos - x_pos]
-
- while (y_seg_pos <= y_pos_max) && isalpha(y[y_seg_pos])
- y_seg_pos += 1
- end
- y_comp = y[y_pos, y_seg_pos - y_pos]
- end
-
- # if y_seg_pos didn't advance in the above loop it means the segments are
- # different types
- if y_pos == y_seg_pos
- # numbers always win over letters
- return x_seg_is_num ? 1 : -1
- end
-
- # move the ball forward before we mess with the segments
- x_pos += x_comp.length # +1 over pos_max if end of string
- y_pos += y_comp.length
-
- # we are comparing numbers - simply convert them
- if x_seg_is_num
- x_comp = x_comp.to_i
- y_comp = y_comp.to_i
- end
-
- # compares ints or strings
- # don't return if equal - try the next segment
- if x_comp > y_comp
- return 1
- elsif x_comp < y_comp
- return -1
- end
-
- # if we've reached here than the segments are the same - try again
- end
-
- # we must have reached the end of one or both of the strings and they
- # matched up until this point
-
- # segments matched completely but the segment separators were different -
- # rpm reference code treats these as equal.
- if (x_pos == x_pos_max + 1) && (y_pos == y_pos_max + 1)
- return 0
- end
-
- # the most unprocessed characters left wins
- if (x_pos_max - x_pos) > (y_pos_max - y_pos)
- return 1
- else
- return -1
- end
- end
-
- end # self
- end # RPMUtils
-
- class RPMVersion
- include Comparable
+ allow_nils
+ use_multipackage_api
+ use_package_name_for_source
- def initialize(*args)
- if args.size == 1
- @e, @v, @r = RPMUtils.version_parse(args[0])
- elsif args.size == 3
- @e = args[0].to_i
- @v = args[1]
- @r = args[2]
- else
- raise ArgumentError, "Expecting either 'epoch-version-release' or 'epoch, " +
- "version, release'"
- end
- end
- attr_reader :e, :v, :r
- alias :epoch :e
- alias :version :v
- alias :release :r
+ provides :package, platform_family: %w{fedora amazon rhel}
- def self.parse(*args)
- self.new(*args)
- end
+ provides :yum_package
- def <=>(y)
- compare_versions(y)
- end
+ #
+ # Most of the magic in this class happens in the python helper script. The ruby side of this
+ # provider knows only enough to translate Chef-style new_resource name+package+version into
+ # a request to the python side. The python side is then responsible for knowing everything
+ # about RPMs and what is installed and what is available. The ruby side of this class should
+ # remain a lightweight translation layer to translate Chef requests into RPC requests to
+ # python. This class knows nothing about how to compare RPM versions, and does not maintain
+ # any cached state of installed/available versions and should be kept that way.
+ #
+ def python_helper
+ @python_helper ||= PythonHelper.instance
+ end
- def compare(y)
- compare_versions(y, false)
- end
+ def load_current_resource
+ flushcache if new_resource.flush_cache[:before]
- def partial_compare(y)
- compare_versions(y, true)
- end
+ @current_resource = Chef::Resource::YumPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(get_current_versions)
- # RPM::Version rpm_version_to_s equivalent
- def to_s
- if @r.nil?
- @v
- else
- "#{@v}-#{@r}"
- end
- end
+ current_resource
+ end
- def evr
- "#{@e}:#{@v}-#{@r}"
+ def define_resource_requirements
+ requirements.assert(:install, :upgrade, :remove, :purge) do |a|
+ a.assertion { !new_resource.source || ::File.exist?(new_resource.source) }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "assuming #{new_resource.source} would have previously been created"
end
- private
-
- # Rough RPM::Version rpm_version_cmp equivalent - except much slower :)
- #
- # partial lets epoch and version segment equality be good enough to return equal, eg:
- #
- # 2:1.2-1 == 2:1.2
- # 2:1.2-1 == 2:
- #
- def compare_versions(y, partial = false)
- x = self
-
- # compare epoch
- if (x.e.nil? == false && x.e > 0) && y.e.nil?
- return 1
- elsif x.e.nil? && (y.e.nil? == false && y.e > 0)
- return -1
- elsif x.e.nil? == false && y.e.nil? == false
- if x.e < y.e
- return -1
- elsif x.e > y.e
- return 1
- end
- end
-
- # compare version
- if partial && (x.v.nil? || y.v.nil?)
- return 0
- elsif x.v.nil? == false && y.v.nil?
- return 1
- elsif x.v.nil? && y.v.nil? == false
- return -1
- elsif x.v.nil? == false && y.v.nil? == false
- cmp = RPMUtils.rpmvercmp(x.v, y.v)
- return cmp if cmp != 0
- end
-
- # compare release
- if partial && (x.r.nil? || y.r.nil?)
- return 0
- elsif x.r.nil? == false && y.r.nil?
- return 1
- elsif x.r.nil? && y.r.nil? == false
- return -1
- elsif x.r.nil? == false && y.r.nil? == false
- cmp = RPMUtils.rpmvercmp(x.r, y.r)
- return cmp
- end
+ super
+ end
- return 0
+ def candidate_version
+ package_name_array.each_with_index.map do |pkg, i|
+ available_version(i).version_with_arch
end
end
- class RPMPackage
- include Comparable
-
- def initialize(*args)
- if args.size == 4
- @n = args[0]
- @version = RPMVersion.new(args[1])
- @a = args[2]
- @provides = args[3]
- elsif args.size == 6
- @n = args[0]
- e = args[1].to_i
- v = args[2]
- r = args[3]
- @version = RPMVersion.new(e, v, r)
- @a = args[4]
- @provides = args[5]
- else
- raise ArgumentError, "Expecting either 'name, epoch-version-release, arch, provides' " +
- "or 'name, epoch, version, release, arch, provides'"
- end
-
- # We always have one, ourselves!
- if @provides.empty?
- @provides = [ RPMProvide.new(@n, @version.evr, :==) ]
- end
+ def get_current_versions
+ package_name_array.each_with_index.map do |pkg, i|
+ installed_version(i).version_with_arch
end
- attr_reader :n, :a, :version, :provides
- alias :name :n
- alias :arch :a
+ end
- def <=>(y)
- compare(y)
- end
+ def install_package(names, versions)
+ method = nil
+ methods = []
+ names.each_with_index do |n, i|
+ next if n.nil?
- def compare(y)
- x = self
+ av = available_version(i)
- # easy! :)
- return 0 if x.nevra == y.nevra
+ name = av.name # resolve the name via the available/candidate version
- # compare name
- if x.n.nil? == false && y.n.nil?
- return 1
- elsif x.n.nil? && y.n.nil? == false
- return -1
- elsif x.n.nil? == false && y.n.nil? == false
- if x.n < y.n
- return -1
- elsif x.n > y.n
- return 1
- end
- end
+ iv = python_helper.package_query(:whatinstalled, name)
- # compare version
- if x.version > y.version
- return 1
- elsif x.version < y.version
- return -1
- end
+ method = "install"
- # compare arch
- if x.a.nil? == false && y.a.nil?
- return 1
- elsif x.a.nil? && y.a.nil? == false
- return -1
- elsif x.a.nil? == false && y.a.nil? == false
- if x.a < y.a
- return -1
- elsif x.a > y.a
- return 1
- end
+ # If this is a package like the kernel that can be installed multiple times, we'll skip over this logic
+ if new_resource.allow_downgrade && version_gt?(iv.version_with_arch, av.version_with_arch) && !python_helper.install_only_packages(name)
+ # We allow downgrading only in the evenit of single-package
+ # rules where the user explicitly allowed it
+ method = "downgrade"
end
- return 0
+ methods << method
end
- def to_s
- nevra
+ # We could split this up into two commands if we wanted to, but
+ # for now, just don't support this.
+ if methods.uniq.length > 1
+ raise Chef::Exceptions::Package, "Multipackage rule has a mix of upgrade and downgrade packages. Cannot proceed."
end
- def nevra
- "#{@n}-#{@version.evr}.#{@a}"
+ if new_resource.source
+ yum(options, "-y #{method}", new_resource.source)
+ else
+ resolved_names = names.each_with_index.map { |name, i| available_version(i).to_s unless name.nil? }
+ yum(options, "-y #{method}", resolved_names)
end
+ flushcache
end
- # Simple implementation from rpm and ruby-rpm reference code
- class RPMDependency
- def initialize(*args)
- if args.size == 3
- @name = args[0]
- @version = RPMVersion.new(args[1])
- # Our requirement to other dependencies
- @flag = args[2] || :==
- elsif args.size == 5
- @name = args[0]
- e = args[1].to_i
- v = args[2]
- r = args[3]
- @version = RPMVersion.new(e, v, r)
- @flag = args[4] || :==
- else
- raise ArgumentError, "Expecting either 'name, epoch-version-release, flag' or " +
- "'name, epoch, version, release, flag'"
- end
- end
- attr_reader :name, :version, :flag
-
- # Parses 2 forms:
- #
- # "mtr >= 2:0.71-3.0"
- # "mta"
- def self.parse(string)
- if string =~ %r{^(\S+)\s+(>|>=|=|==|<=|<)\s+(\S+)$}
- name = $1
- if $2 == "="
- flag = :==
- else
- flag = :"#{$2}"
- end
- version = $3
-
- return self.new(name, version, flag)
- else
- name = string
- return self.new(name, nil, nil)
- end
- end
-
- # Test if another RPMDependency satisfies our requirements
- def satisfy?(y)
- unless y.kind_of?(RPMDependency)
- raise ArgumentError, "Expecting an RPMDependency object"
- end
-
- x = self
-
- # Easy!
- if x.name != y.name
- return false
- end
-
- # Partial compare
- #
- # eg: x.version 2.3 == y.version 2.3-1
- sense = x.version.partial_compare(y.version)
+ # yum upgrade does not work on uninstalled packaged, while install will upgrade
+ alias upgrade_package install_package
- # Thanks to rpmdsCompare() rpmds.c
- if (sense < 0) && ((x.flag == :> || x.flag == :>=) || (y.flag == :<= || y.flag == :<))
- return true
- elsif (sense > 0) && ((x.flag == :< || x.flag == :<=) || (y.flag == :>= || y.flag == :>))
- return true
- elsif sense == 0 && (
- ((x.flag == :== || x.flag == :<= || x.flag == :>=) && (y.flag == :== || y.flag == :<= || y.flag == :>=)) ||
- (x.flag == :< && y.flag == :<) ||
- (x.flag == :> && y.flag == :>)
- )
- return true
- end
-
- return false
- end
+ def remove_package(names, versions)
+ resolved_names = names.each_with_index.map { |name, i| installed_version(i).to_s unless name.nil? }
+ yum(options, "-y remove", resolved_names)
+ flushcache
end
- class RPMProvide < RPMDependency; end
- class RPMRequire < RPMDependency; end
+ alias purge_package remove_package
- class RPMDbPackage < RPMPackage
- # <rpm parts>, installed, available
- def initialize(*args)
- @repoid = args.pop
- # state
- @available = args.pop
- @installed = args.pop
- super(*args)
- end
- attr_reader :repoid, :available, :installed
+ action :flush_cache do
+ flushcache
end
- # Simple storage for RPMPackage objects - keeps them unique and sorted
- class RPMDb
- def initialize
- # package name => [ RPMPackage, RPMPackage ] of different versions
- @rpms = Hash.new
- # package nevra => RPMPackage for lookups
- @index = Hash.new
- # provide name (aka feature) => [RPMPackage, RPMPackage] each providing this feature
- @provides = Hash.new
- # RPMPackages listed as available
- @available = Set.new
- # RPMPackages listed as installed
- @installed = Set.new
- end
-
- def [](package_name)
- self.lookup(package_name)
- end
-
- # Lookup package_name and return a descending array of package objects
- def lookup(package_name)
- pkgs = @rpms[package_name]
- if pkgs
- return pkgs.sort.reverse
- else
- return nil
- end
- end
-
- def lookup_provides(provide_name)
- @provides[provide_name]
- end
+ # NB: the yum_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 `yum versionlock | grep '^pattern`` kind of approach
+ def lock_package(names, versions)
+ yum("-d0 -e0 -y", options, "versionlock add", resolved_package_lock_names(names))
+ end
- # Using the package name as a key, and nevra for an index, keep a unique list of packages.
- # The available/installed state can be overwritten for existing packages.
- def push(*args)
- args.flatten.each do |new_rpm|
- unless new_rpm.kind_of?(RPMDbPackage)
- raise ArgumentError, "Expecting an RPMDbPackage object"
- end
+ # NB: the yum_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 `yum versionlock | grep '^pattern`` kind of approach
+ def unlock_package(names, versions)
+ # yum versionlock delete on rhel6 needs the glob nonsense in the following command
+ yum("-d0 -e0 -y", options, "versionlock delete", resolved_package_lock_names(names).map { |n| "'*:#{n}-*'" })
+ end
- @rpms[new_rpm.n] ||= Array.new
+ private
- # we may already have this one, like when the installed list is refreshed
- idx = @index[new_rpm.nevra]
- if idx
- # grab the existing package if it's not
- curr_rpm = idx
+ # 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|
+ if !name.nil?
+ if installed_version(i).version.nil?
+ available_version(i).name
else
- @rpms[new_rpm.n] << new_rpm
-
- new_rpm.provides.each do |provide|
- @provides[provide.name] ||= Array.new
- @provides[provide.name] << new_rpm
- end
-
- curr_rpm = new_rpm
- end
-
- # Track the nevra -> RPMPackage association to avoid having to compare versions
- # with @rpms[new_rpm.n] on the next round
- @index[new_rpm.nevra] = curr_rpm
-
- # these are overwritten for existing packages
- if new_rpm.available
- @available << curr_rpm
- end
- if new_rpm.installed
- @installed << curr_rpm
+ installed_version(i).name
end
end
end
-
- def <<(*args)
- self.push(args)
- end
-
- def clear
- @rpms.clear
- @index.clear
- @provides.clear
- clear_available
- clear_installed
- end
-
- def clear_available
- @available.clear
- end
-
- def clear_installed
- @installed.clear
- end
-
- def size
- @rpms.size
- end
- alias :length :size
-
- def available_size
- @available.size
- end
-
- def installed_size
- @installed.size
- end
-
- def available?(package)
- @available.include?(package)
- end
-
- def installed?(package)
- @installed.include?(package)
- end
-
- def whatprovides(rpmdep)
- unless rpmdep.kind_of?(RPMDependency)
- raise ArgumentError, "Expecting an RPMDependency object"
- end
-
- what = []
-
- packages = lookup_provides(rpmdep.name)
- if packages
- packages.each do |pkg|
- pkg.provides.each do |provide|
- if provide.satisfy?(rpmdep)
- what << pkg
- end
- end
- end
- end
-
- return what
- end
end
- # Cache for our installed and available packages, pulled in from yum-dump.py
- class YumCache
- include Chef::Mixin::Which
- include Chef::Mixin::ShellOut
- include Singleton
-
- attr_accessor :yum_binary
-
- def initialize
- @rpmdb = RPMDb.new
-
- # Next time @rpmdb is accessed:
- # :all - Trigger a run of "yum-dump.py --options --installed-provides", updates
- # yum's cache and parses options from /etc/yum.conf. Pulls in Provides
- # dependency data for installed packages only - this data is slow to
- # gather.
- # :provides - Same as :all but pulls in Provides data for available packages as well.
- # Used as a last resort when we can't find a Provides match.
- # :installed - Trigger a run of "yum-dump.py --installed", only reads the local rpm
- # db. Used between client runs for a quick refresh.
- # :none - Do nothing, a call to one of the reload methods is required.
- @next_refresh = :all
-
- @allow_multi_install = []
-
- @extra_repo_control = nil
-
- # these are for subsequent runs if we are on an interval
- Chef::Client.when_run_starts do
- YumCache.instance.reload
- end
- end
-
- attr_reader :extra_repo_control
-
- # Cache management
- #
-
- def refresh
- case @next_refresh
- when :none
- return nil
- when :installed
- reset_installed
- # fast
- opts = " --installed"
- when :all
- reset
- # medium
- opts = " --options --installed-provides"
- when :provides
- reset
- # slow!
- opts = " --options --all-provides"
- else
- raise ArgumentError, "Unexpected value in next_refresh: #{@next_refresh}"
- end
-
- if @extra_repo_control
- opts << " #{@extra_repo_control}"
- end
-
- opts << " --yum-lock-timeout #{Chef::Config[:yum_lock_timeout]}"
-
- one_line = false
- error = nil
-
- helper = ::File.join(::File.dirname(__FILE__), "yum-dump.py")
- status = nil
-
+ def locked_packages
+ @locked_packages ||=
begin
- status = shell_out!("#{python_bin} #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
- status.stdout.each_line do |line|
- one_line = true
-
- line.chomp!
- if line =~ %r{\[option (.*)\] (.*)}
- if $1 == "installonlypkgs"
- @allow_multi_install = $2.split
- else
- raise Chef::Exceptions::Package, "Strange, unknown option line '#{line}' from yum-dump.py"
- end
- next
- end
-
- if line =~ %r{^(\S+) ([0-9]+) (\S+) (\S+) (\S+) \[(.*)\] ([i,a,r]) (\S+)$}
- name = $1
- epoch = $2
- version = $3
- release = $4
- arch = $5
- provides = parse_provides($6)
- type = $7
- repoid = $8
- else
- Chef::Log.warn("Problem parsing line '#{line}' from yum-dump.py! " +
- "Please check your yum configuration.")
- next
- end
-
- case type
- when "i"
- # if yum-dump was called with --installed this may not be true, but it's okay
- # since we don't touch the @available Set in reload_installed
- available = false
- installed = true
- when "a"
- available = true
- installed = false
- when "r"
- available = true
- installed = true
- end
-
- pkg = RPMDbPackage.new(name, epoch, version, release, arch, provides, installed, available, repoid)
- @rpmdb << pkg
- end
-
- error = status.stderr
- rescue Mixlib::ShellOut::CommandTimeout => e
- Chef::Log.error("#{helper} exceeded timeout #{Chef::Config[:yum_timeout]}")
- raise(e)
- end
-
- if status.exitstatus != 0
- raise Chef::Exceptions::Package, "Yum failed - #{status.inspect} - returns: #{error}"
- else
- unless one_line
- Chef::Log.warn("Odd, no output from yum-dump.py. Please check " +
- "your yum configuration.")
- end
- end
-
- # A reload method must be called before the cache is altered
- @next_refresh = :none
- end
-
- def python_bin
- yum_executable = which(yum_binary)
- if yum_executable && shabang?(yum_executable)
- shabang_or_fallback(extract_interpreter(yum_executable))
- else
- Chef::Log.warn("Yum executable not found or doesn't start with #!. Using default python.")
- "/usr/bin/python"
- end
- rescue StandardError => e
- Chef::Log.warn("An error occurred attempting to determine correct python executable. Using default.")
- Chef::Log.debug(e)
- "/usr/bin/python"
- end
-
- def extract_interpreter(file)
- ::File.open(file, "r", &:readline)[2..-1].strip
- end
-
- # dnf based systems have a yum shim that has /bin/bash as the interpreter. Don't use this.
- def shabang_or_fallback(interpreter)
- if interpreter == "/bin/bash"
- Chef::Log.warn("Yum executable interpreter is /bin/bash. Falling back to default python.")
- "/usr/bin/python"
- else
- interpreter
- end
- end
-
- def shabang?(file)
- ::File.open(file, "r") do |f|
- f.read(2) == '#!'
- end
- rescue Errno::ENOENT
- false
- end
-
- def reload
- @next_refresh = :all
- end
-
- def reload_installed
- @next_refresh = :installed
- end
-
- def reload_provides
- @next_refresh = :provides
- end
-
- def reset
- @rpmdb.clear
- end
-
- def reset_installed
- @rpmdb.clear_installed
- end
-
- # Querying the cache
- #
-
- # Check for package by name or name+arch
- def package_available?(package_name)
- refresh
-
- if @rpmdb.lookup(package_name)
- return true
- else
- if package_name =~ %r{^(.*)\.(.*)$}
- pkg_name = $1
- pkg_arch = $2
-
- if matches = @rpmdb.lookup(pkg_name)
- matches.each do |m|
- return true if m.arch == pkg_arch
- end
- end
+ locked = shell_out_with_timeout!("yum versionlock list")
+ locked.stdout.each_line.map do |line|
+ line.sub(/-[^-]*-[^-]*$/, "").split(":").last.strip
end
end
-
- return false
- end
-
- # Returns a array of packages satisfying an RPMDependency
- def packages_from_require(rpmdep)
- refresh
- @rpmdb.whatprovides(rpmdep)
- end
-
- # Check if a package-version.arch is available to install
- def version_available?(package_name, desired_version, arch = nil)
- version(package_name, arch, true, false) do |v|
- return true if desired_version == v
- end
-
- return false
- end
-
- # Return the source repository for a package-version.arch
- def package_repository(package_name, desired_version, arch = nil)
- package(package_name, arch, true, false) do |pkg|
- return pkg.repoid if desired_version == pkg.version.to_s
- end
-
- return nil
- end
-
- # Return the latest available version for a package.arch
- def available_version(package_name, arch = nil)
- version(package_name, arch, true, false)
- end
- alias :candidate_version :available_version
-
- # Return the currently installed version for a package.arch
- def installed_version(package_name, arch = nil)
- version(package_name, arch, false, true)
- end
-
- # Return an array of packages allowed to be installed multiple times, such as the kernel
- def allow_multi_install
- refresh
- @allow_multi_install
- end
-
- def enable_extra_repo_control(arg)
- # Don't touch cache if it's the same repos as the last load
- unless @extra_repo_control == arg
- @extra_repo_control = arg
- reload
- end
- end
-
- def disable_extra_repo_control
- # Only force reload when set
- if @extra_repo_control
- @extra_repo_control = nil
- reload
- end
- end
-
- private
-
- def version(package_name, arch = nil, is_available = false, is_installed = false)
- package(package_name, arch, is_available, is_installed) do |pkg|
- if block_given?
- yield pkg.version.to_s
- else
- # first match is latest version
- return pkg.version.to_s
- end
- end
-
- if block_given?
- return self
- else
- return nil
- end
- end
-
- def package(package_name, arch = nil, is_available = false, is_installed = false)
- refresh
- packages = @rpmdb[package_name]
- if packages
- packages.each do |pkg|
- if is_available
- next unless @rpmdb.available?(pkg)
- end
- if is_installed
- next unless @rpmdb.installed?(pkg)
- end
- if arch
- next unless pkg.arch == arch
- end
-
- if block_given?
- yield pkg
- else
- # first match is latest version
- return pkg
- end
- end
- end
-
- if block_given?
- return self
- else
- return nil
- end
- end
-
- # Parse provides from yum-dump.py output
- def parse_provides(string)
- ret = []
- # ['atk = 1.12.2-1.fc6', 'libatk-1.0.so.0']
- string.split(", ").each do |seg|
- # 'atk = 1.12.2-1.fc6'
- if seg =~ %r{^'(.*)'$}
- ret << RPMProvide.parse($1)
- end
- end
-
- return ret
- end
-
- end # YumCache
-
- include Chef::Mixin::GetSourceFromPackage
-
- def initialize(new_resource, run_context)
- super
-
- @yum = YumCache.instance
- @yum.yum_binary = yum_binary
end
- def yum_binary
- @yum_binary ||=
- begin
- yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
- yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
- end
+ def packages_all_locked?(names, versions)
+ resolved_package_lock_names(names).all? { |n| locked_packages.include? n }
end
- # Extra attributes
- #
-
- def arch_for_name(n)
- if @new_resource.respond_to?("arch")
- @new_resource.arch
- elsif @arch
- idx = package_name_array.index(n)
- as_array(@arch)[idx]
- else
- nil
- end
+ def packages_all_unlocked?(names, versions)
+ !resolved_package_lock_names(names).any? { |n| locked_packages.include? n }
end
- def arch
- if @new_resource.respond_to?("arch")
- @new_resource.arch
- else
- nil
- end
+ def version_gt?(v1, v2)
+ return false if v1.nil? || v2.nil?
+ python_helper.compare_versions(v1, v2) == 1
end
- def set_arch(arch)
- if @new_resource.respond_to?("arch")
- @new_resource.arch(arch)
- end
+ def version_equals?(v1, v2)
+ return false if v1.nil? || v2.nil?
+ python_helper.compare_versions(v1, v2) == 0
end
- def flush_cache
- if @new_resource.respond_to?("flush_cache")
- @new_resource.flush_cache
- else
- { :before => false, :after => false }
- end
+ def version_compare(v1, v2)
+ return false if v1.nil? || v2.nil?
+ python_helper.compare_versions(v1, v2)
end
- # Helpers
- #
-
- def yum_arch(arch)
- arch ? ".#{arch}" : nil
+ # 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 yum_command(command)
- command = "#{yum_binary} #{command}"
- Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
- status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
-
- # This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
- # considered fatal - meaning the rpm is still successfully installed. These issue
- # cause yum to emit a non fatal warning but still exit(1). As there's currently no
- # way to suppress this behavior and an exit(1) will break a Chef run we make an
- # effort to trap these and re-run the same install command - it will either fail a
- # second time or succeed.
- #
- # A cleaner solution would have to be done in python and better hook into
- # yum/rpm to handle exceptions as we see fit.
- if status.exitstatus == 1
- status.stdout.each_line do |l|
- # rpm-4.4.2.3 lib/psm.c line 2182
- if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
- Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
- "so running install again to verify.")
- status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
- break
- end
+ def resolve_source_to_version_obj
+ shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n' #{new_resource.source}").stdout.each_line do |line|
+ # this is another case of committing the sin of doing some lightweight mangling of RPM versions in ruby -- but the output of the rpm command
+ # does not match what the 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
-
- if status.exitstatus > 0
- command_output = "STDOUT: #{status.stdout}\nSTDERR: #{status.stderr}"
- raise Chef::Exceptions::Exec, "#{command} returned #{status.exitstatus}:\n#{command_output}"
- end
end
- # Standard Provider methods for Parent
- #
-
- def load_current_resource
- if flush_cache[:before]
- @yum.reload
- end
-
- if @new_resource.options
- repo_control = []
- @new_resource.options.split.each do |opt|
- if opt =~ %r{--(enable|disable)repo=.+}
- repo_control << opt
- end
- end
-
- if repo_control.size > 0
- @yum.enable_extra_repo_control(repo_control.join(" "))
- else
- @yum.disable_extra_repo_control
- end
- else
- @yum.disable_extra_repo_control
- end
-
- # At this point package_name could be:
- #
- # 1) a package name, eg: "foo"
- # 2) a package name.arch, eg: "foo.i386"
- # 3) or a dependency, eg: "foo >= 1.1"
-
- # Check if we have name or name+arch which has a priority over a dependency
- package_name_array.each_with_index do |n, index|
- unless @yum.package_available?(n)
- # If they aren't in the installed packages they could be a dependency
- dep = parse_dependency(n, new_version_array[index])
- if dep
- if @new_resource.package_name.is_a?(Array)
- @new_resource.package_name(package_name_array - [n] + [dep.first])
- @new_resource.version(new_version_array - [new_version_array[index]] + [dep.last]) if dep.last
- else
- @new_resource.package_name(dep.first)
- @new_resource.version(dep.last) if dep.last
- end
- end
- end
- end
-
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
-
- installed_version = []
- @candidate_version = []
- @arch = []
- if @new_resource.source
- unless ::File.exists?(@new_resource.source)
- raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- end
-
- Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
- case line
- when /([\w\d_.-]+)\s([\w\d_.-]+)/
- @current_resource.package_name($1)
- @new_resource.version($2)
- end
- end
- @candidate_version << @new_resource.version
- installed_version << @yum.installed_version(@current_resource.package_name, arch)
- else
-
- package_name_array.each_with_index do |pkg, idx|
- # Don't overwrite an existing arch
- if arch
- name, parch = pkg, arch
- else
- name, parch = parse_arch(pkg)
- # if we parsed an arch from the name, update the name
- # to be just the package name.
- if parch
- if @new_resource.package_name.is_a?(Array)
- @new_resource.package_name[idx] = name
- else
- @new_resource.package_name(name)
- # only set the arch if it's a single package
- set_arch(parch)
- end
- end
- end
+ # @returns Array<Version>
+ def available_version(index)
+ @available_version ||= []
- if @new_resource.version
- new_resource =
- "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}"
- else
- new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}"
- end
- Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
- installed_version << @yum.installed_version(name, parch)
- @candidate_version << @yum.candidate_version(name, parch)
- @arch << parch
- end
- end
+ @available_version[index] ||= if new_resource.source
+ resolve_source_to_version_obj
+ else
+ python_helper.package_query(:whatavailable, package_name_array[index], version: safe_version_array[index], arch: safe_arch_array[index], options: options)
+ end
- if installed_version.size == 1
- @current_resource.version(installed_version[0])
- @candidate_version = @candidate_version[0]
- @arch = @arch[0]
- else
- @current_resource.version(installed_version)
- end
-
- Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
- "#{@candidate_version || "(none)"}")
-
- @current_resource
+ @available_version[index]
end
- def install_remote_package(name, version)
- # Work around yum not exiting with an error if a package doesn't exist
- # for CHEF-2062
- all_avail = as_array(name).zip(as_array(version)).any? do |n, v|
- @yum.version_available?(n, v, arch_for_name(n))
- end
- method = log_method = nil
- methods = []
- if all_avail
- # More Yum fun:
- #
- # yum install of an old name+version will exit(1)
- # yum install of an old name+version+arch will exit(0) for some reason
- #
- # Some packages can be installed multiple times like the kernel
- as_array(name).zip(as_array(version)).each do |n, v|
- method = "install"
- log_method = "installing"
- idx = package_name_array.index(n)
- unless @yum.allow_multi_install.include?(n)
- if RPMVersion.parse(current_version_array[idx]) > RPMVersion.parse(v)
- # We allow downgrading only in the evenit of single-package
- # rules where the user explicitly allowed it
- if allow_downgrade
- method = "downgrade"
- log_method = "downgrading"
- else
- # we bail like yum when the package is older
- raise Chef::Exceptions::Package, "Installed package #{n}-#{current_version_array[idx]} is newer " +
- "than candidate package #{n}-#{v}"
- end
- end
- end
- # methods don't count for packages we won't be touching
- next if RPMVersion.parse(current_version_array[idx]) == RPMVersion.parse(v)
- methods << method
- end
-
- # We could split this up into two commands if we wanted to, but
- # for now, just don't support this.
- if methods.uniq.length > 1
- raise Chef::Exceptions::Package, "Multipackage rule #{name} has a mix of upgrade and downgrade packages. Cannot proceed."
- end
-
- repos = []
- pkg_string_bits = []
- as_array(name).zip(as_array(version)).each do |n, v|
- idx = package_name_array.index(n)
- a = arch_for_name(n)
- s = ""
- unless v == current_version_array[idx]
- s = "#{n}-#{v}#{yum_arch(a)}"
- repo = @yum.package_repository(n, v, a)
- repos << "#{s} from #{repo} repository"
- pkg_string_bits << s
- end
- end
- pkg_string = pkg_string_bits.join(" ")
- Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
- else
- raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
- "and release? (version-release, e.g. 1.84-10.fc6)"
- end
+ # @returns Array<Version>
+ def installed_version(index)
+ @installed_version ||= []
+ @installed_version[index] ||= if new_resource.source
+ python_helper.package_query(:whatinstalled, available_version(index).name, version: safe_version_array[index], arch: safe_arch_array[index])
+ else
+ python_helper.package_query(:whatinstalled, package_name_array[index], version: safe_version_array[index], arch: safe_arch_array[index])
+ end
+ @installed_version[index]
end
- def install_package(name, version)
- if @new_resource.source
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
- else
- install_remote_package(name, version)
- end
-
- if flush_cache[:after]
- @yum.reload
- else
- @yum.reload_installed
- end
+ # cache flushing is accomplished by simply restarting the python helper. this produces a roughly
+ # 15% hit to the runtime of installing/removing/upgrading packages. correctly using multipackage
+ # array installs (and the multipackage cookbook) can produce 600% improvements in runtime.
+ def flushcache
+ python_helper.restart
end
- # Keep upgrades from trying to install an older candidate version. Can happen when a new
- # version is installed then removed from a repository, now the older available version
- # shows up as a viable install candidate.
- #
- # Can be done in upgrade_package but an upgraded from->to log message slips out
- #
- # Hacky - better overall solution? Custom compare in Package provider?
- def action_upgrade
- # Could be uninstalled or have no candidate
- if @current_resource.version.nil? || !candidate_version_array.any?
- super
- elsif candidate_version_array.zip(current_version_array).any? do |c, i|
- RPMVersion.parse(c) > RPMVersion.parse(i)
- end
- super
- else
- Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
- end
+ def yum_binary
+ @yum_binary ||=
+ begin
+ yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
+ yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
+ end
end
- def upgrade_package(name, version)
- install_package(name, version)
+ def yum(*args)
+ shell_out_with_timeout!(a_to_s(yum_binary, *args))
end
- def remove_package(name, version)
- if version
- remove_str = as_array(name).zip(as_array(version)).map do |n, v|
- a = arch_for_name(n)
- "#{[n, v].join('-')}#{yum_arch(a)}"
- end.join(" ")
+ def safe_version_array
+ if new_resource.version.is_a?(Array)
+ new_resource.version
+ elsif new_resource.version.nil?
+ package_name_array.map { nil }
else
- remove_str = as_array(name).map do |n|
- a = arch_for_name(n)
- "#{n}#{yum_arch(a)}"
- end.join(" ")
+ [ new_resource.version ]
end
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
-
- if flush_cache[:after]
- @yum.reload
- else
- @yum.reload_installed
- end
- end
-
- def purge_package(name, version)
- remove_package(name, version)
end
- private
-
- def parse_arch(package_name)
- # Allow for foo.x86_64 style package_name like yum uses in it's output
- #
- if package_name =~ %r{^(.*)\.(.*)$}
- new_package_name = $1
- new_arch = $2
- # foo.i386 and foo.beta1 are both valid package names or expressions of an arch.
- # Ensure we don't have an existing package matching package_name, then ensure we at
- # least have a match for the new_package+new_arch before we overwrite. If neither
- # then fall through to standard package handling.
- old_installed = @yum.installed_version(package_name)
- old_candidate = @yum.candidate_version(package_name)
- new_installed = @yum.installed_version(new_package_name, new_arch)
- new_candidate = @yum.candidate_version(new_package_name, new_arch)
- if (old_installed.nil? && old_candidate.nil?) && (new_installed || new_candidate)
- Chef::Log.debug("Parsed out arch #{new_arch}, new package name is #{new_package_name}")
- return new_package_name, new_arch
- end
- end
- return package_name, nil
- end
-
- # If we don't have the package we could have been passed a 'whatprovides' feature
- #
- # eg: yum install "perl(Config)"
- # yum install "mtr = 2:0.71-3.1"
- # yum install "mtr > 2:0.71"
- #
- # We support resolving these out of the Provides data imported from yum-dump.py and
- # matching them up with an actual package so the standard resource handling can apply.
- #
- # There is currently no support for filename matching.
- def parse_dependency(name, version)
- # Transform the package_name into a requirement
-
- # If we are passed a version or a version constraint we have to assume it's a requirement first. If it can't be
- # parsed only yum_require.name will be set and @new_resource.version will be left intact
- if version
- require_string = "#{name} #{version}"
+ def safe_arch_array
+ if new_resource.arch.is_a?(Array)
+ new_resource.arch
+ elsif new_resource.arch.nil?
+ package_name_array.map { nil }
else
- # Transform the package_name into a requirement, might contain a version, could just be
- # a match for virtual provides
- require_string = name
- end
- yum_require = RPMRequire.parse(require_string)
- # and gather all the packages that have a Provides feature satisfying the requirement.
- # It could be multiple be we can only manage one
- packages = @yum.packages_from_require(yum_require)
-
- if packages.empty?
- # Don't bother if we are just ensuring a package is removed - we don't need Provides data
- actions = Array(@new_resource.action)
- unless actions.size == 1 && (actions[0] == :remove || actions[0] == :purge)
- Chef::Log.debug("#{@new_resource} couldn't match #{@new_resource.package_name} in " +
- "installed Provides, loading available Provides - this may take a moment")
- @yum.reload_provides
- packages = @yum.packages_from_require(yum_require)
- end
- end
-
- unless packages.empty?
- new_package_name = packages.first.name
- new_package_version = packages.first.version.to_s
- debug_msg = "#{name}: Unable to match package '#{name}' but matched #{packages.size} "
- debug_msg << (packages.size == 1 ? "package" : "packages")
- debug_msg << ", selected '#{new_package_name}' version '#{new_package_version}'"
- Chef::Log.debug(debug_msg)
-
- # Ensure it's not the same package under a different architecture
- unique_names = []
- packages.each do |pkg|
- unique_names << "#{pkg.name}-#{pkg.version.evr}"
- end
- unique_names.uniq!
-
- if unique_names.size > 1
- Chef::Log.warn("#{@new_resource} matched multiple Provides for #{@new_resource.package_name} " +
- "but we can only use the first match: #{new_package_name}. Please use a more " +
- "specific version.")
- end
-
- if yum_require.version.to_s.nil?
- new_package_version = nil
- end
-
- [new_package_name, new_package_version]
+ [ new_resource.arch ]
end
end
diff --git a/lib/chef/provider/package/yum/python_helper.rb b/lib/chef/provider/package/yum/python_helper.rb
new file mode 100644
index 0000000000..0a09e12c3d
--- /dev/null
+++ b/lib/chef/provider/package/yum/python_helper.rb
@@ -0,0 +1,221 @@
+#
+# Copyright:: Copyright 2016-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/mixin/which"
+require "chef/mixin/shell_out"
+require "chef/provider/package/yum/version"
+require "singleton"
+require "timeout"
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+ class PythonHelper
+ include Singleton
+ include Chef::Mixin::Which
+ include Chef::Mixin::ShellOut
+
+ attr_accessor :stdin
+ attr_accessor :stdout
+ attr_accessor :stderr
+ attr_accessor :inpipe
+ attr_accessor :outpipe
+ attr_accessor :wait_thr
+
+ YUM_HELPER = ::File.expand_path(::File.join(::File.dirname(__FILE__), "yum_helper.py")).freeze
+
+ def yum_command
+ @yum_command ||= which("python", "python2", "python2.7") do |f|
+ shell_out("#{f} -c 'import yum'").exitstatus == 0
+ end + " #{YUM_HELPER}"
+ end
+
+ def start
+ ENV["PYTHONUNBUFFERED"] = "1"
+ @inpipe, inpipe_write = IO.pipe
+ outpipe_read, @outpipe = IO.pipe
+ @stdin, @stdout, @stderr, @wait_thr = Open3.popen3("#{yum_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("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?
+ inpipe.close unless inpipe.nil?
+ outpipe.close unless outpipe.nil?
+ end
+ end
+
+ def check
+ start if stdin.nil?
+ end
+
+ def compare_versions(version1, version2)
+ query("versioncompare", { "versions" => [version1, version2] }).to_i
+ end
+
+ def install_only_packages(name)
+ query_output = query("installonlypkgs", { "package" => name })
+ if query_output == "False"
+ return false
+ elsif query_output == "True"
+ return true
+ end
+ 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
+
+ # @returns Array<Version>
+ # NB: "options" here is the yum_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)
+ query_output = query(action, parameters)
+ version = parse_response(query_output.lines.last)
+ Chef::Log.trace "parsed #{version} from python helper"
+ # XXX: for now we restart after every query with an enablerepo/disablerepo to clean the helpers internal state
+ restart unless repo_opts.empty?
+ version
+ end
+
+ def restart
+ reap
+ start
+ end
+
+ private
+
+ # i couldn't figure out how to decompose an evr on the python side, it seems reasonably
+ # painless to do it in ruby (generally massaging nevras in the ruby side is HIGHLY
+ # discouraged -- this is an "every rule has an exception" exception -- any additional
+ # functionality should probably trigger moving this regexp logic into python)
+ def add_version(hash, version)
+ epoch = nil
+ if version =~ /(\S+):(\S+)/
+ epoch = $1
+ version = $2
+ end
+ if version =~ /(\S+)-(\S+)/
+ version = $1
+ release = $2
+ end
+ hash["epoch"] = epoch unless epoch.nil?
+ hash["release"] = release unless release.nil?
+ hash["version"] = version
+ end
+
+ 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_query(action, parameters)
+ hash = { "action" => action }
+ 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 [:whatinstalled, :whatavailable].include?(action)
+ add_version(hash, parameters["version"]) unless parameters["version"].nil?
+ end
+
+ FFI_Yajl::Encoder.encode(hash)
+ end
+
+ def parse_response(output)
+ array = output.split.map { |x| x == "nil" ? nil : x }
+ array.each_slice(3).map { |x| Version.new(*x) }.first
+ end
+
+ def drain_fds
+ output = ""
+ 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 => e
+ output
+ end
+
+ def with_helper
+ max_retries ||= 5
+ ret = nil
+ Timeout.timeout(600) do
+ check
+ ret = yield
+ end
+ output = drain_fds
+ unless output.empty?
+ 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_fds
+ if ( max_retries -= 1 ) > 0
+ unless output.empty?
+ Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
+ end
+ restart
+ retry
+ else
+ raise e if output.empty?
+ raise "yum-helper.py had stderr/stdout output:\n\n#{output}"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum/rpm_utils.rb b/lib/chef/provider/package/yum/rpm_utils.rb
new file mode 100644
index 0000000000..eefc0b95b2
--- /dev/null
+++ b/lib/chef/provider/package/yum/rpm_utils.rb
@@ -0,0 +1,651 @@
+
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright 2008-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/provider/package"
+
+#
+# BUGGY AND DEPRECATED: This ruby code is known to not match the python implementation for version comparisons.
+# The APIs here should probably be converted to talk to the PythonHelper or just abandonded completely.
+#
+# e.g. this should just use Chef::Provider::Package::Yum::PythonHelper.instance.compare_versions(x,y)
+#
+# The python_helper could be extended to support additional APIs in here to remove the ruby code entirely.
+#
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+ class RPMUtils
+ class << self
+
+ # RPM::Version version_parse equivalent
+ def version_parse(evr)
+ return if evr.nil?
+
+ epoch = nil
+ # assume this is a version
+ version = evr
+ release = nil
+
+ lead = 0
+ tail = evr.size
+
+ if /^([\d]+):/.match(evr) # rubocop:disable Performance/RedundantMatch
+ epoch = $1.to_i
+ lead = $1.length + 1
+ elsif evr[0].ord == ":".ord
+ epoch = 0
+ lead = 1
+ end
+
+ if /:?.*-(.*)$/.match(evr) # rubocop:disable Performance/RedundantMatch
+ release = $1
+ tail = evr.length - release.length - lead - 1
+
+ if release.empty?
+ release = nil
+ end
+ end
+
+ version = evr[lead, tail]
+ if version.empty?
+ version = nil
+ end
+
+ [ epoch, version, release ]
+ end
+
+ # verify
+ def isalnum(x)
+ isalpha(x) || isdigit(x)
+ end
+
+ def isalpha(x)
+ v = x.ord
+ (v >= 65 && v <= 90) || (v >= 97 && v <= 122)
+ end
+
+ def isdigit(x)
+ v = x.ord
+ v >= 48 && v <= 57
+ end
+
+ # based on the reference spec in lib/rpmvercmp.c in rpm 4.9.0
+ def rpmvercmp(x, y)
+ # easy! :)
+ return 0 if x == y
+
+ if x.nil?
+ x = ""
+ end
+
+ if y.nil?
+ y = ""
+ end
+
+ # not so easy :(
+ #
+ # takes 2 strings like
+ #
+ # x = "1.20.b18.el5"
+ # y = "1.20.b17.el5"
+ #
+ # breaks into purely alpha and numeric segments and compares them using
+ # some rules
+ #
+ # * 10 > 1
+ # * 1 > a
+ # * z > a
+ # * Z > A
+ # * z > Z
+ # * leading zeros are ignored
+ # * separators (periods, commas) are ignored
+ # * "1.20.b18.el5.extrastuff" > "1.20.b18.el5"
+
+ x_pos = 0 # overall string element reference position
+ x_pos_max = x.length - 1 # number of elements in string, starting from 0
+ x_seg_pos = 0 # segment string element reference position
+ x_comp = nil # segment to compare
+
+ y_pos = 0
+ y_seg_pos = 0
+ y_pos_max = y.length - 1
+ y_comp = nil
+
+ while x_pos <= x_pos_max && y_pos <= y_pos_max
+ # first we skip over anything non alphanumeric
+ while (x_pos <= x_pos_max) && (isalnum(x[x_pos]) == false)
+ x_pos += 1 # +1 over pos_max if end of string
+ end
+ while (y_pos <= y_pos_max) && (isalnum(y[y_pos]) == false)
+ y_pos += 1
+ end
+
+ # if we hit the end of either we are done matching segments
+ if (x_pos == x_pos_max + 1) || (y_pos == y_pos_max + 1)
+ break
+ end
+
+ # we are now at the start of a alpha or numeric segment
+ x_seg_pos = x_pos
+ y_seg_pos = y_pos
+
+ # grab segment so we can compare them
+ if isdigit(x[x_seg_pos].ord)
+ x_seg_is_num = true
+
+ # already know it's a digit
+ x_seg_pos += 1
+
+ # gather up our digits
+ while (x_seg_pos <= x_pos_max) && isdigit(x[x_seg_pos])
+ x_seg_pos += 1
+ end
+ # copy the segment but not the unmatched character that x_seg_pos will
+ # refer to
+ x_comp = x[x_pos, x_seg_pos - x_pos]
+
+ while (y_seg_pos <= y_pos_max) && isdigit(y[y_seg_pos])
+ y_seg_pos += 1
+ end
+ y_comp = y[y_pos, y_seg_pos - y_pos]
+ else
+ # we are comparing strings
+ x_seg_is_num = false
+
+ while (x_seg_pos <= x_pos_max) && isalpha(x[x_seg_pos])
+ x_seg_pos += 1
+ end
+ x_comp = x[x_pos, x_seg_pos - x_pos]
+
+ while (y_seg_pos <= y_pos_max) && isalpha(y[y_seg_pos])
+ y_seg_pos += 1
+ end
+ y_comp = y[y_pos, y_seg_pos - y_pos]
+ end
+
+ # if y_seg_pos didn't advance in the above loop it means the segments are
+ # different types
+ if y_pos == y_seg_pos
+ # numbers always win over letters
+ return x_seg_is_num ? 1 : -1
+ end
+
+ # move the ball forward before we mess with the segments
+ x_pos += x_comp.length # +1 over pos_max if end of string
+ y_pos += y_comp.length
+
+ # we are comparing numbers - simply convert them
+ if x_seg_is_num
+ x_comp = x_comp.to_i
+ y_comp = y_comp.to_i
+ end
+
+ # compares ints or strings
+ # don't return if equal - try the next segment
+ if x_comp > y_comp
+ return 1
+ elsif x_comp < y_comp
+ return -1
+ end
+
+ # if we've reached here than the segments are the same - try again
+ end
+
+ # we must have reached the end of one or both of the strings and they
+ # matched up until this point
+
+ # segments matched completely but the segment separators were different -
+ # rpm reference code treats these as equal.
+ if (x_pos == x_pos_max + 1) && (y_pos == y_pos_max + 1)
+ return 0
+ end
+
+ # the most unprocessed characters left wins
+ if (x_pos_max - x_pos) > (y_pos_max - y_pos)
+ return 1
+ else
+ return -1
+ end
+ end
+
+ end # self
+ end # RPMUtils
+
+ class RPMVersion
+ include Comparable
+
+ def initialize(*args)
+ if args.size == 1
+ @e, @v, @r = RPMUtils.version_parse(args[0])
+ elsif args.size == 3
+ @e = args[0].to_i
+ @v = args[1]
+ @r = args[2]
+ else
+ raise ArgumentError, "Expecting either 'epoch-version-release' or 'epoch, " \
+ "version, release'"
+ end
+ end
+ attr_reader :e, :v, :r
+ alias epoch e
+ alias version v
+ alias release r
+
+ def self.parse(*args)
+ new(*args)
+ end
+
+ def <=>(other)
+ compare_versions(other)
+ end
+
+ def compare(other)
+ compare_versions(other, false)
+ end
+
+ def partial_compare(other)
+ compare_versions(other, true)
+ end
+
+ # RPM::Version rpm_version_to_s equivalent
+ def to_s
+ if @r.nil?
+ @v
+ else
+ "#{@v}-#{@r}"
+ end
+ end
+
+ def evr
+ "#{@e}:#{@v}-#{@r}"
+ end
+
+ private
+
+ # Rough RPM::Version rpm_version_cmp equivalent - except much slower :)
+ #
+ # partial lets epoch and version segment equality be good enough to return equal, eg:
+ #
+ # 2:1.2-1 == 2:1.2
+ # 2:1.2-1 == 2:
+ #
+ def compare_versions(y, partial = false)
+ x = self
+
+ # compare epoch
+ if (x.e.nil? == false && x.e > 0) && y.e.nil?
+ return 1
+ elsif x.e.nil? && (y.e.nil? == false && y.e > 0)
+ return -1
+ elsif x.e.nil? == false && y.e.nil? == false
+ if x.e < y.e
+ return -1
+ elsif x.e > y.e
+ return 1
+ end
+ end
+
+ # compare version
+ if partial && (x.v.nil? || y.v.nil?)
+ return 0
+ elsif x.v.nil? == false && y.v.nil?
+ return 1
+ elsif x.v.nil? && y.v.nil? == false
+ return -1
+ elsif x.v.nil? == false && y.v.nil? == false
+ cmp = RPMUtils.rpmvercmp(x.v, y.v)
+ return cmp if cmp != 0
+ end
+
+ # compare release
+ if partial && (x.r.nil? || y.r.nil?)
+ return 0
+ elsif x.r.nil? == false && y.r.nil?
+ return 1
+ elsif x.r.nil? && y.r.nil? == false
+ return -1
+ elsif x.r.nil? == false && y.r.nil? == false
+ cmp = RPMUtils.rpmvercmp(x.r, y.r)
+ return cmp
+ end
+
+ 0
+ end
+ end
+
+ class RPMPackage
+ include Comparable
+
+ def initialize(*args)
+ if args.size == 4
+ @n = args[0]
+ @version = RPMVersion.new(args[1])
+ @a = args[2]
+ @provides = args[3]
+ elsif args.size == 6
+ @n = args[0]
+ e = args[1].to_i
+ v = args[2]
+ r = args[3]
+ @version = RPMVersion.new(e, v, r)
+ @a = args[4]
+ @provides = args[5]
+ else
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, arch, provides' " \
+ "or 'name, epoch, version, release, arch, provides'"
+ end
+
+ # We always have one, ourselves!
+ if @provides.empty?
+ @provides = [ RPMProvide.new(@n, @version.evr, :==) ]
+ end
+ end
+ attr_reader :n, :a, :version, :provides
+ alias name n
+ alias arch a
+
+ def <=>(other)
+ compare(other)
+ end
+
+ def compare(y)
+ x = self
+
+ # easy! :)
+ return 0 if x.nevra == y.nevra
+
+ # compare name
+ if x.n.nil? == false && y.n.nil?
+ return 1
+ elsif x.n.nil? && y.n.nil? == false
+ return -1
+ elsif x.n.nil? == false && y.n.nil? == false
+ if x.n < y.n
+ return -1
+ elsif x.n > y.n
+ return 1
+ end
+ end
+
+ # compare version
+ if x.version > y.version
+ return 1
+ elsif x.version < y.version
+ return -1
+ end
+
+ # compare arch
+ if x.a.nil? == false && y.a.nil?
+ return 1
+ elsif x.a.nil? && y.a.nil? == false
+ return -1
+ elsif x.a.nil? == false && y.a.nil? == false
+ if x.a < y.a
+ return -1
+ elsif x.a > y.a
+ return 1
+ end
+ end
+
+ 0
+ end
+
+ def to_s
+ nevra
+ end
+
+ def nevra
+ "#{@n}-#{@version.evr}.#{@a}"
+ end
+ end
+
+ # Simple implementation from rpm and ruby-rpm reference code
+ class RPMDependency
+ def initialize(*args)
+ if args.size == 3
+ @name = args[0]
+ @version = RPMVersion.new(args[1])
+ # Our requirement to other dependencies
+ @flag = args[2] || :==
+ elsif args.size == 5
+ @name = args[0]
+ e = args[1].to_i
+ v = args[2]
+ r = args[3]
+ @version = RPMVersion.new(e, v, r)
+ @flag = args[4] || :==
+ else
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, flag' or " \
+ "'name, epoch, version, release, flag'"
+ end
+ end
+ attr_reader :name, :version, :flag
+
+ # Parses 2 forms:
+ #
+ # "mtr >= 2:0.71-3.0"
+ # "mta"
+ def self.parse(string)
+ if /^(\S+)\s+(>|>=|=|==|<=|<)\s+(\S+)$/.match(string) # rubocop:disable Performance/RedundantMatch
+ name = $1
+ flag = if $2 == "="
+ :==
+ else
+ :"#{$2}"
+ end
+ version = $3
+
+ new(name, version, flag)
+ else
+ name = string
+ new(name, nil, nil)
+ end
+ end
+
+ # Test if another RPMDependency satisfies our requirements
+ def satisfy?(y)
+ unless y.is_a?(RPMDependency)
+ raise ArgumentError, "Expecting an RPMDependency object"
+ end
+
+ x = self
+
+ # Easy!
+ if x.name != y.name
+ return false
+ end
+
+ # Partial compare
+ #
+ # eg: x.version 2.3 == y.version 2.3-1
+ sense = x.version.partial_compare(y.version)
+
+ # Thanks to rpmdsCompare() rpmds.c
+ if (sense < 0) && ((x.flag == :> || x.flag == :>=) || (y.flag == :<= || y.flag == :<))
+ return true
+ elsif (sense > 0) && ((x.flag == :< || x.flag == :<=) || (y.flag == :>= || y.flag == :>))
+ return true
+ elsif sense == 0 && (
+ ((x.flag == :== || x.flag == :<= || x.flag == :>=) && (y.flag == :== || y.flag == :<= || y.flag == :>=)) ||
+ (x.flag == :< && y.flag == :<) ||
+ (x.flag == :> && y.flag == :>)
+ )
+ return true
+ end
+
+ false
+ end
+ end
+
+ class RPMProvide < RPMDependency; end
+ class RPMRequire < RPMDependency; end
+
+ class RPMDbPackage < RPMPackage
+ # <rpm parts>, installed, available
+ def initialize(*args)
+ @repoid = args.pop
+ # state
+ @available = args.pop
+ @installed = args.pop
+ super(*args)
+ end
+ attr_reader :repoid, :available, :installed
+ end
+
+ # Simple storage for RPMPackage objects - keeps them unique and sorted
+ class RPMDb
+ def initialize
+ # package name => [ RPMPackage, RPMPackage ] of different versions
+ @rpms = {}
+ # package nevra => RPMPackage for lookups
+ @index = {}
+ # provide name (aka feature) => [RPMPackage, RPMPackage] each providing this feature
+ @provides = {}
+ # RPMPackages listed as available
+ @available = Set.new
+ # RPMPackages listed as installed
+ @installed = Set.new
+ end
+
+ def [](package_name)
+ lookup(package_name)
+ end
+
+ # Lookup package_name and return a descending array of package objects
+ def lookup(package_name)
+ pkgs = @rpms[package_name]
+ if pkgs
+ return pkgs.sort.reverse
+ else
+ return nil
+ end
+ end
+
+ def lookup_provides(provide_name)
+ @provides[provide_name]
+ end
+
+ # Using the package name as a key, and nevra for an index, keep a unique list of packages.
+ # The available/installed state can be overwritten for existing packages.
+ def push(*args)
+ args.flatten.each do |new_rpm|
+ unless new_rpm.is_a?(RPMDbPackage)
+ raise ArgumentError, "Expecting an RPMDbPackage object"
+ end
+
+ @rpms[new_rpm.n] ||= []
+
+ # we may already have this one, like when the installed list is refreshed
+ idx = @index[new_rpm.nevra]
+ if idx
+ # grab the existing package if it's not
+ curr_rpm = idx
+ else
+ @rpms[new_rpm.n] << new_rpm
+
+ new_rpm.provides.each do |provide|
+ @provides[provide.name] ||= []
+ @provides[provide.name] << new_rpm
+ end
+
+ curr_rpm = new_rpm
+ end
+
+ # Track the nevra -> RPMPackage association to avoid having to compare versions
+ # with @rpms[new_rpm.n] on the next round
+ @index[new_rpm.nevra] = curr_rpm
+
+ # these are overwritten for existing packages
+ if new_rpm.available
+ @available << curr_rpm
+ end
+ if new_rpm.installed
+ @installed << curr_rpm
+ end
+ end
+ end
+
+ def <<(*args)
+ push(args)
+ end
+
+ def clear
+ @rpms.clear
+ @index.clear
+ @provides.clear
+ clear_available
+ clear_installed
+ end
+
+ def clear_available
+ @available.clear
+ end
+
+ def clear_installed
+ @installed.clear
+ end
+
+ def size
+ @rpms.size
+ end
+ alias length size
+
+ def available_size
+ @available.size
+ end
+
+ def installed_size
+ @installed.size
+ end
+
+ def available?(package)
+ @available.include?(package)
+ end
+
+ def installed?(package)
+ @installed.include?(package)
+ end
+
+ def whatprovides(rpmdep)
+ unless rpmdep.is_a?(RPMDependency)
+ raise ArgumentError, "Expecting an RPMDependency object"
+ end
+
+ what = []
+
+ packages = lookup_provides(rpmdep.name)
+ if packages
+ packages.each do |pkg|
+ pkg.provides.each do |provide|
+ if provide.satisfy?(rpmdep)
+ what << pkg
+ end
+ end
+ end
+ end
+
+ what
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum/simplejson/LICENSE.txt b/lib/chef/provider/package/yum/simplejson/LICENSE.txt
new file mode 100644
index 0000000000..e05f49c3fd
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/LICENSE.txt
@@ -0,0 +1,79 @@
+simplejson is dual-licensed software. It is available under the terms
+of the MIT license, or the Academic Free License version 2.1. The full
+text of each license agreement is included below. This code is also
+licensed to the Python Software Foundation (PSF) under a Contributor
+Agreement.
+
+MIT License
+===========
+
+Copyright (c) 2006 Bob Ippolito
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Academic Free License v. 2.1
+============================
+
+Copyright (c) 2006 Bob Ippolito. All rights reserved.
+
+This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following notice immediately following the copyright notice for the Original Work:
+
+Licensed under the Academic Free License version 2.1
+
+1) Grant of Copyright License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license to do the following:
+
+a) to reproduce the Original Work in copies;
+
+b) to prepare derivative works ("Derivative Works") based upon the Original Work;
+
+c) to distribute copies of the Original Work and Derivative Works to the public;
+
+d) to perform the Original Work publicly; and
+
+e) to display the Original Work publicly.
+
+2) Grant of Patent License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, to make, use, sell and offer for sale the Original Work and Derivative Works.
+
+3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor hereby agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work, and by publishing the address of that information repository in a notice immediately following the copyright notice that applies to the Original Work.
+
+4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior written permission of the Licensor. Nothing in this License shall be deemed to grant any rights to trademarks, copyrights, patents, trade secrets or any other intellectual property of Licensor except as expressly stated herein. No patent license is granted to make, use, sell or offer to sell embodiments of any patent claims other than the licensed claims defined in Section 2. No right is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under different terms from this License any Original Work that Licensor otherwise would have a right to license.
+
+5) This section intentionally omitted.
+
+6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
+
+7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately proceeding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to Original Work is granted hereunder except under this disclaimer.
+
+8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to any person for any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to liability for death or personal injury resulting from Licensor's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
+
+9) Acceptance and Termination. If You distribute copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. Nothing else but this License (or another written agreement between Licensor and You) grants You permission to create Derivative Works based upon the Original Work or to exercise any of the rights granted in Section 1 herein, and any attempt to do so except under the terms of this License (or another written agreement between Licensor and You) is expressly prohibited by U.S. copyright law, the equivalent laws of other countries, and by international treaty. Therefore, by exercising any of the rights granted to You in Section 1 herein, You indicate Your acceptance of this License and all of its terms and conditions.
+
+10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
+
+11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et seq., the equivalent laws of other countries, and international treaty. This section shall survive the termination of this License.
+
+12) Attorneys Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
+
+13) Miscellaneous. This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
+
+14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
+
+This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. Permission is hereby granted to copy and distribute this license without modification. This license may not be modified without the express written permission of its copyright owner.
diff --git a/lib/chef/provider/package/yum/simplejson/__init__.py b/lib/chef/provider/package/yum/simplejson/__init__.py
new file mode 100644
index 0000000000..d5b4d39913
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/__init__.py
@@ -0,0 +1,318 @@
+r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
+JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
+interchange format.
+
+:mod:`simplejson` exposes an API familiar to users of the standard library
+:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
+version of the :mod:`json` library contained in Python 2.6, but maintains
+compatibility with Python 2.4 and Python 2.5 and (currently) has
+significant performance advantages, even without using the optional C
+extension for speedups.
+
+Encoding basic Python object hierarchies::
+
+ >>> import simplejson as json
+ >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
+ '["foo", {"bar": ["baz", null, 1.0, 2]}]'
+ >>> print json.dumps("\"foo\bar")
+ "\"foo\bar"
+ >>> print json.dumps(u'\u1234')
+ "\u1234"
+ >>> print json.dumps('\\')
+ "\\"
+ >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
+ {"a": 0, "b": 0, "c": 0}
+ >>> from StringIO import StringIO
+ >>> io = StringIO()
+ >>> json.dump(['streaming API'], io)
+ >>> io.getvalue()
+ '["streaming API"]'
+
+Compact encoding::
+
+ >>> import simplejson as json
+ >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
+ '[1,2,3,{"4":5,"6":7}]'
+
+Pretty printing::
+
+ >>> import simplejson as json
+ >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
+ >>> print '\n'.join([l.rstrip() for l in s.splitlines()])
+ {
+ "4": 5,
+ "6": 7
+ }
+
+Decoding JSON::
+
+ >>> import simplejson as json
+ >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
+ >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
+ True
+ >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
+ True
+ >>> from StringIO import StringIO
+ >>> io = StringIO('["streaming API"]')
+ >>> json.load(io)[0] == 'streaming API'
+ True
+
+Specializing JSON object decoding::
+
+ >>> import simplejson as json
+ >>> def as_complex(dct):
+ ... if '__complex__' in dct:
+ ... return complex(dct['real'], dct['imag'])
+ ... return dct
+ ...
+ >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
+ ... object_hook=as_complex)
+ (1+2j)
+ >>> import decimal
+ >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1')
+ True
+
+Specializing JSON object encoding::
+
+ >>> import simplejson as json
+ >>> def encode_complex(obj):
+ ... if isinstance(obj, complex):
+ ... return [obj.real, obj.imag]
+ ... raise TypeError(repr(o) + " is not JSON serializable")
+ ...
+ >>> json.dumps(2 + 1j, default=encode_complex)
+ '[2.0, 1.0]'
+ >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
+ '[2.0, 1.0]'
+ >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
+ '[2.0, 1.0]'
+
+
+Using simplejson.tool from the shell to validate and pretty-print::
+
+ $ echo '{"json":"obj"}' | python -m simplejson.tool
+ {
+ "json": "obj"
+ }
+ $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+ Expecting property name: line 1 column 2 (char 2)
+"""
+__version__ = '2.0.9'
+__all__ = [
+ 'dump', 'dumps', 'load', 'loads',
+ 'JSONDecoder', 'JSONEncoder',
+]
+
+__author__ = 'Bob Ippolito <bob@redivi.com>'
+
+from decoder import JSONDecoder
+from encoder import JSONEncoder
+
+_default_encoder = JSONEncoder(
+ skipkeys=False,
+ ensure_ascii=True,
+ check_circular=True,
+ allow_nan=True,
+ indent=None,
+ separators=None,
+ encoding='utf-8',
+ default=None,
+)
+
+def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=None,
+ encoding='utf-8', default=None, **kw):
+ """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
+ ``.write()``-supporting file-like object).
+
+ If ``skipkeys`` is true then ``dict`` keys that are not basic types
+ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+ will be skipped instead of raising a ``TypeError``.
+
+ If ``ensure_ascii`` is false, then the some chunks written to ``fp``
+ may be ``unicode`` instances, subject to normal Python ``str`` to
+ ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
+ understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
+ to cause an error.
+
+ If ``check_circular`` is false, then the circular reference check
+ for container types will be skipped and a circular reference will
+ result in an ``OverflowError`` (or worse).
+
+ If ``allow_nan`` is false, then it will be a ``ValueError`` to
+ serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
+ in strict compliance of the JSON specification, instead of using the
+ JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+ If ``indent`` is a non-negative integer, then JSON array elements and object
+ members will be pretty-printed with that indent level. An indent level
+ of 0 will only insert newlines. ``None`` is the most compact representation.
+
+ If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+ then it will be used instead of the default ``(', ', ': ')`` separators.
+ ``(',', ':')`` is the most compact JSON representation.
+
+ ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+ ``default(obj)`` is a function that should return a serializable version
+ of obj or raise TypeError. The default simply raises TypeError.
+
+ To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+ ``.default()`` method to serialize additional types), specify it with
+ the ``cls`` kwarg.
+
+ """
+ # cached encoder
+ if (not skipkeys and ensure_ascii and
+ check_circular and allow_nan and
+ cls is None and indent is None and separators is None and
+ encoding == 'utf-8' and default is None and not kw):
+ iterable = _default_encoder.iterencode(obj)
+ else:
+ if cls is None:
+ cls = JSONEncoder
+ iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+ check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+ separators=separators, encoding=encoding,
+ default=default, **kw).iterencode(obj)
+ # could accelerate with writelines in some versions of Python, at
+ # a debuggability cost
+ for chunk in iterable:
+ fp.write(chunk)
+
+
+def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=None,
+ encoding='utf-8', default=None, **kw):
+ """Serialize ``obj`` to a JSON formatted ``str``.
+
+ If ``skipkeys`` is false then ``dict`` keys that are not basic types
+ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+ will be skipped instead of raising a ``TypeError``.
+
+ If ``ensure_ascii`` is false, then the return value will be a
+ ``unicode`` instance subject to normal Python ``str`` to ``unicode``
+ coercion rules instead of being escaped to an ASCII ``str``.
+
+ If ``check_circular`` is false, then the circular reference check
+ for container types will be skipped and a circular reference will
+ result in an ``OverflowError`` (or worse).
+
+ If ``allow_nan`` is false, then it will be a ``ValueError`` to
+ serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
+ strict compliance of the JSON specification, instead of using the
+ JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+ If ``indent`` is a non-negative integer, then JSON array elements and
+ object members will be pretty-printed with that indent level. An indent
+ level of 0 will only insert newlines. ``None`` is the most compact
+ representation.
+
+ If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+ then it will be used instead of the default ``(', ', ': ')`` separators.
+ ``(',', ':')`` is the most compact JSON representation.
+
+ ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+ ``default(obj)`` is a function that should return a serializable version
+ of obj or raise TypeError. The default simply raises TypeError.
+
+ To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+ ``.default()`` method to serialize additional types), specify it with
+ the ``cls`` kwarg.
+
+ """
+ # cached encoder
+ if (not skipkeys and ensure_ascii and
+ check_circular and allow_nan and
+ cls is None and indent is None and separators is None and
+ encoding == 'utf-8' and default is None and not kw):
+ return _default_encoder.encode(obj)
+ if cls is None:
+ cls = JSONEncoder
+ return cls(
+ skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+ check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+ separators=separators, encoding=encoding, default=default,
+ **kw).encode(obj)
+
+
+_default_decoder = JSONDecoder(encoding=None, object_hook=None)
+
+
+def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, **kw):
+ """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
+ a JSON document) to a Python object.
+
+ If the contents of ``fp`` is encoded with an ASCII based encoding other
+ than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
+ be specified. Encodings that are not ASCII based (such as UCS-2) are
+ not allowed, and should be wrapped with
+ ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
+ object and passed to ``loads()``
+
+ ``object_hook`` is an optional function that will be called with the
+ result of any object literal decode (a ``dict``). The return value of
+ ``object_hook`` will be used instead of the ``dict``. This feature
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+ kwarg.
+
+ """
+ return loads(fp.read(),
+ encoding=encoding, cls=cls, object_hook=object_hook,
+ parse_float=parse_float, parse_int=parse_int,
+ parse_constant=parse_constant, **kw)
+
+
+def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, **kw):
+ """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
+ document) to a Python object.
+
+ If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
+ other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
+ must be specified. Encodings that are not ASCII based (such as UCS-2)
+ are not allowed and should be decoded to ``unicode`` first.
+
+ ``object_hook`` is an optional function that will be called with the
+ result of any object literal decode (a ``dict``). The return value of
+ ``object_hook`` will be used instead of the ``dict``. This feature
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+ ``parse_float``, if specified, will be called with the string
+ of every JSON float to be decoded. By default this is equivalent to
+ float(num_str). This can be used to use another datatype or parser
+ for JSON floats (e.g. decimal.Decimal).
+
+ ``parse_int``, if specified, will be called with the string
+ of every JSON int to be decoded. By default this is equivalent to
+ int(num_str). This can be used to use another datatype or parser
+ for JSON integers (e.g. float).
+
+ ``parse_constant``, if specified, will be called with one of the
+ following strings: -Infinity, Infinity, NaN, null, true, false.
+ This can be used to raise an exception if invalid JSON numbers
+ are encountered.
+
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+ kwarg.
+
+ """
+ if (cls is None and encoding is None and object_hook is None and
+ parse_int is None and parse_float is None and
+ parse_constant is None and not kw):
+ return _default_decoder.decode(s)
+ if cls is None:
+ cls = JSONDecoder
+ if object_hook is not None:
+ kw['object_hook'] = object_hook
+ if parse_float is not None:
+ kw['parse_float'] = parse_float
+ if parse_int is not None:
+ kw['parse_int'] = parse_int
+ if parse_constant is not None:
+ kw['parse_constant'] = parse_constant
+ return cls(encoding=encoding, **kw).decode(s)
diff --git a/lib/chef/provider/package/yum/simplejson/__init__.pyc b/lib/chef/provider/package/yum/simplejson/__init__.pyc
new file mode 100644
index 0000000000..10679d3b04
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/__init__.pyc
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/decoder.py b/lib/chef/provider/package/yum/simplejson/decoder.py
new file mode 100644
index 0000000000..d921ce0b97
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/decoder.py
@@ -0,0 +1,354 @@
+"""Implementation of JSONDecoder
+"""
+import re
+import sys
+import struct
+
+from simplejson.scanner import make_scanner
+try:
+ from simplejson._speedups import scanstring as c_scanstring
+except ImportError:
+ c_scanstring = None
+
+__all__ = ['JSONDecoder']
+
+FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
+
+def _floatconstants():
+ _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
+ if sys.byteorder != 'big':
+ _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
+ nan, inf = struct.unpack('dd', _BYTES)
+ return nan, inf, -inf
+
+NaN, PosInf, NegInf = _floatconstants()
+
+
+def linecol(doc, pos):
+ lineno = doc.count('\n', 0, pos) + 1
+ if lineno == 1:
+ colno = pos
+ else:
+ colno = pos - doc.rindex('\n', 0, pos)
+ return lineno, colno
+
+
+def errmsg(msg, doc, pos, end=None):
+ # Note that this function is called from _speedups
+ lineno, colno = linecol(doc, pos)
+ if end is None:
+ #fmt = '{0}: line {1} column {2} (char {3})'
+ #return fmt.format(msg, lineno, colno, pos)
+ fmt = '%s: line %d column %d (char %d)'
+ return fmt % (msg, lineno, colno, pos)
+ endlineno, endcolno = linecol(doc, end)
+ #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
+ #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
+ fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
+ return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
+
+
+_CONSTANTS = {
+ '-Infinity': NegInf,
+ 'Infinity': PosInf,
+ 'NaN': NaN,
+}
+
+STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
+BACKSLASH = {
+ '"': u'"', '\\': u'\\', '/': u'/',
+ 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
+}
+
+DEFAULT_ENCODING = "utf-8"
+
+def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
+ """Scan the string s for a JSON string. End is the index of the
+ character in s after the quote that started the JSON string.
+ Unescapes all valid JSON string escape sequences and raises ValueError
+ on attempt to decode an invalid string. If strict is False then literal
+ control characters are allowed in the string.
+
+ Returns a tuple of the decoded string and the index of the character in s
+ after the end quote."""
+ if encoding is None:
+ encoding = DEFAULT_ENCODING
+ chunks = []
+ _append = chunks.append
+ begin = end - 1
+ while 1:
+ chunk = _m(s, end)
+ if chunk is None:
+ raise ValueError(
+ errmsg("Unterminated string starting at", s, begin))
+ end = chunk.end()
+ content, terminator = chunk.groups()
+ # Content is contains zero or more unescaped string characters
+ if content:
+ if not isinstance(content, unicode):
+ content = unicode(content, encoding)
+ _append(content)
+ # Terminator is the end of string, a literal control character,
+ # or a backslash denoting that an escape sequence follows
+ if terminator == '"':
+ break
+ elif terminator != '\\':
+ if strict:
+ msg = "Invalid control character %r at" % (terminator,)
+ #msg = "Invalid control character {0!r} at".format(terminator)
+ raise ValueError(errmsg(msg, s, end))
+ else:
+ _append(terminator)
+ continue
+ try:
+ esc = s[end]
+ except IndexError:
+ raise ValueError(
+ errmsg("Unterminated string starting at", s, begin))
+ # If not a unicode escape sequence, must be in the lookup table
+ if esc != 'u':
+ try:
+ char = _b[esc]
+ except KeyError:
+ msg = "Invalid \\escape: " + repr(esc)
+ raise ValueError(errmsg(msg, s, end))
+ end += 1
+ else:
+ # Unicode escape sequence
+ esc = s[end + 1:end + 5]
+ next_end = end + 5
+ if len(esc) != 4:
+ msg = "Invalid \\uXXXX escape"
+ raise ValueError(errmsg(msg, s, end))
+ uni = int(esc, 16)
+ # Check for surrogate pair on UCS-4 systems
+ if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
+ msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
+ if not s[end + 5:end + 7] == '\\u':
+ raise ValueError(errmsg(msg, s, end))
+ esc2 = s[end + 7:end + 11]
+ if len(esc2) != 4:
+ raise ValueError(errmsg(msg, s, end))
+ uni2 = int(esc2, 16)
+ uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
+ next_end += 6
+ char = unichr(uni)
+ end = next_end
+ # Append the unescaped character
+ _append(char)
+ return u''.join(chunks), end
+
+
+# Use speedup if available
+scanstring = c_scanstring or py_scanstring
+
+WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
+WHITESPACE_STR = ' \t\n\r'
+
+def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+ pairs = {}
+ # Use a slice to prevent IndexError from being raised, the following
+ # check will raise a more specific ValueError if the string is empty
+ nextchar = s[end:end + 1]
+ # Normally we expect nextchar == '"'
+ if nextchar != '"':
+ if nextchar in _ws:
+ end = _w(s, end).end()
+ nextchar = s[end:end + 1]
+ # Trivial empty object
+ if nextchar == '}':
+ return pairs, end + 1
+ elif nextchar != '"':
+ raise ValueError(errmsg("Expecting property name", s, end))
+ end += 1
+ while True:
+ key, end = scanstring(s, end, encoding, strict)
+
+ # To skip some function call overhead we optimize the fast paths where
+ # the JSON key separator is ": " or just ":".
+ if s[end:end + 1] != ':':
+ end = _w(s, end).end()
+ if s[end:end + 1] != ':':
+ raise ValueError(errmsg("Expecting : delimiter", s, end))
+
+ end += 1
+
+ try:
+ if s[end] in _ws:
+ end += 1
+ if s[end] in _ws:
+ end = _w(s, end + 1).end()
+ except IndexError:
+ pass
+
+ try:
+ value, end = scan_once(s, end)
+ except StopIteration:
+ raise ValueError(errmsg("Expecting object", s, end))
+ pairs[key] = value
+
+ try:
+ nextchar = s[end]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end]
+ except IndexError:
+ nextchar = ''
+ end += 1
+
+ if nextchar == '}':
+ break
+ elif nextchar != ',':
+ raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
+
+ try:
+ nextchar = s[end]
+ if nextchar in _ws:
+ end += 1
+ nextchar = s[end]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end]
+ except IndexError:
+ nextchar = ''
+
+ end += 1
+ if nextchar != '"':
+ raise ValueError(errmsg("Expecting property name", s, end - 1))
+
+ if object_hook is not None:
+ pairs = object_hook(pairs)
+ return pairs, end
+
+def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+ values = []
+ nextchar = s[end:end + 1]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end:end + 1]
+ # Look-ahead for trivial empty array
+ if nextchar == ']':
+ return values, end + 1
+ _append = values.append
+ while True:
+ try:
+ value, end = scan_once(s, end)
+ except StopIteration:
+ raise ValueError(errmsg("Expecting object", s, end))
+ _append(value)
+ nextchar = s[end:end + 1]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end:end + 1]
+ end += 1
+ if nextchar == ']':
+ break
+ elif nextchar != ',':
+ raise ValueError(errmsg("Expecting , delimiter", s, end))
+
+ try:
+ if s[end] in _ws:
+ end += 1
+ if s[end] in _ws:
+ end = _w(s, end + 1).end()
+ except IndexError:
+ pass
+
+ return values, end
+
+class JSONDecoder(object):
+ """Simple JSON <http://json.org> decoder
+
+ Performs the following translations in decoding by default:
+
+ +---------------+-------------------+
+ | JSON | Python |
+ +===============+===================+
+ | object | dict |
+ +---------------+-------------------+
+ | array | list |
+ +---------------+-------------------+
+ | string | unicode |
+ +---------------+-------------------+
+ | number (int) | int, long |
+ +---------------+-------------------+
+ | number (real) | float |
+ +---------------+-------------------+
+ | true | True |
+ +---------------+-------------------+
+ | false | False |
+ +---------------+-------------------+
+ | null | None |
+ +---------------+-------------------+
+
+ It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
+ their corresponding ``float`` values, which is outside the JSON spec.
+
+ """
+
+ def __init__(self, encoding=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, strict=True):
+ """``encoding`` determines the encoding used to interpret any ``str``
+ objects decoded by this instance (utf-8 by default). It has no
+ effect when decoding ``unicode`` objects.
+
+ Note that currently only encodings that are a superset of ASCII work,
+ strings of other encodings should be passed in as ``unicode``.
+
+ ``object_hook``, if specified, will be called with the result
+ of every JSON object decoded and its return value will be used in
+ place of the given ``dict``. This can be used to provide custom
+ deserializations (e.g. to support JSON-RPC class hinting).
+
+ ``parse_float``, if specified, will be called with the string
+ of every JSON float to be decoded. By default this is equivalent to
+ float(num_str). This can be used to use another datatype or parser
+ for JSON floats (e.g. decimal.Decimal).
+
+ ``parse_int``, if specified, will be called with the string
+ of every JSON int to be decoded. By default this is equivalent to
+ int(num_str). This can be used to use another datatype or parser
+ for JSON integers (e.g. float).
+
+ ``parse_constant``, if specified, will be called with one of the
+ following strings: -Infinity, Infinity, NaN.
+ This can be used to raise an exception if invalid JSON numbers
+ are encountered.
+
+ """
+ self.encoding = encoding
+ self.object_hook = object_hook
+ self.parse_float = parse_float or float
+ self.parse_int = parse_int or int
+ self.parse_constant = parse_constant or _CONSTANTS.__getitem__
+ self.strict = strict
+ self.parse_object = JSONObject
+ self.parse_array = JSONArray
+ self.parse_string = scanstring
+ self.scan_once = make_scanner(self)
+
+ def decode(self, s, _w=WHITESPACE.match):
+ """Return the Python representation of ``s`` (a ``str`` or ``unicode``
+ instance containing a JSON document)
+
+ """
+ obj, end = self.raw_decode(s, idx=_w(s, 0).end())
+ end = _w(s, end).end()
+ if end != len(s):
+ raise ValueError(errmsg("Extra data", s, end, len(s)))
+ return obj
+
+ def raw_decode(self, s, idx=0):
+ """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
+ with a JSON document) and return a 2-tuple of the Python
+ representation and the index in ``s`` where the document ended.
+
+ This can be used to decode a JSON document from a string that may
+ have extraneous data at the end.
+
+ """
+ try:
+ obj, end = self.scan_once(s, idx)
+ except StopIteration:
+ raise ValueError("No JSON object could be decoded")
+ return obj, end
diff --git a/lib/chef/provider/package/yum/simplejson/decoder.pyc b/lib/chef/provider/package/yum/simplejson/decoder.pyc
new file mode 100644
index 0000000000..d402901870
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/decoder.pyc
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/encoder.py b/lib/chef/provider/package/yum/simplejson/encoder.py
new file mode 100644
index 0000000000..cf58290366
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/encoder.py
@@ -0,0 +1,440 @@
+"""Implementation of JSONEncoder
+"""
+import re
+
+try:
+ from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii
+except ImportError:
+ c_encode_basestring_ascii = None
+try:
+ from simplejson._speedups import make_encoder as c_make_encoder
+except ImportError:
+ c_make_encoder = None
+
+ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
+ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
+HAS_UTF8 = re.compile(r'[\x80-\xff]')
+ESCAPE_DCT = {
+ '\\': '\\\\',
+ '"': '\\"',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+}
+for i in range(0x20):
+ #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
+ ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
+
+# Assume this produces an infinity on all machines (probably not guaranteed)
+INFINITY = float('1e66666')
+FLOAT_REPR = repr
+
+def encode_basestring(s):
+ """Return a JSON representation of a Python string
+
+ """
+ def replace(match):
+ return ESCAPE_DCT[match.group(0)]
+ return '"' + ESCAPE.sub(replace, s) + '"'
+
+
+def py_encode_basestring_ascii(s):
+ """Return an ASCII-only JSON representation of a Python string
+
+ """
+ if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+ s = s.decode('utf-8')
+ def replace(match):
+ s = match.group(0)
+ try:
+ return ESCAPE_DCT[s]
+ except KeyError:
+ n = ord(s)
+ if n < 0x10000:
+ #return '\\u{0:04x}'.format(n)
+ return '\\u%04x' % (n,)
+ else:
+ # surrogate pair
+ n -= 0x10000
+ s1 = 0xd800 | ((n >> 10) & 0x3ff)
+ s2 = 0xdc00 | (n & 0x3ff)
+ #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
+ return '\\u%04x\\u%04x' % (s1, s2)
+ return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+
+encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii
+
+class JSONEncoder(object):
+ """Extensible JSON <http://json.org> encoder for Python data structures.
+
+ Supports the following objects and types by default:
+
+ +-------------------+---------------+
+ | Python | JSON |
+ +===================+===============+
+ | dict | object |
+ +-------------------+---------------+
+ | list, tuple | array |
+ +-------------------+---------------+
+ | str, unicode | string |
+ +-------------------+---------------+
+ | int, long, float | number |
+ +-------------------+---------------+
+ | True | true |
+ +-------------------+---------------+
+ | False | false |
+ +-------------------+---------------+
+ | None | null |
+ +-------------------+---------------+
+
+ To extend this to recognize other objects, subclass and implement a
+ ``.default()`` method with another method that returns a serializable
+ object for ``o`` if possible, otherwise it should call the superclass
+ implementation (to raise ``TypeError``).
+
+ """
+ item_separator = ', '
+ key_separator = ': '
+ def __init__(self, skipkeys=False, ensure_ascii=True,
+ check_circular=True, allow_nan=True, sort_keys=False,
+ indent=None, separators=None, encoding='utf-8', default=None):
+ """Constructor for JSONEncoder, with sensible defaults.
+
+ If skipkeys is false, then it is a TypeError to attempt
+ encoding of keys that are not str, int, long, float or None. If
+ skipkeys is True, such items are simply skipped.
+
+ If ensure_ascii is true, the output is guaranteed to be str
+ objects with all incoming unicode characters escaped. If
+ ensure_ascii is false, the output will be unicode object.
+
+ If check_circular is true, then lists, dicts, and custom encoded
+ objects will be checked for circular references during encoding to
+ prevent an infinite recursion (which would cause an OverflowError).
+ Otherwise, no such check takes place.
+
+ If allow_nan is true, then NaN, Infinity, and -Infinity will be
+ encoded as such. This behavior is not JSON specification compliant,
+ but is consistent with most JavaScript based encoders and decoders.
+ Otherwise, it will be a ValueError to encode such floats.
+
+ If sort_keys is true, then the output of dictionaries will be
+ sorted by key; this is useful for regression tests to ensure
+ that JSON serializations can be compared on a day-to-day basis.
+
+ If indent is a non-negative integer, then JSON array
+ elements and object members will be pretty-printed with that
+ indent level. An indent level of 0 will only insert newlines.
+ None is the most compact representation.
+
+ If specified, separators should be a (item_separator, key_separator)
+ tuple. The default is (', ', ': '). To get the most compact JSON
+ representation you should specify (',', ':') to eliminate whitespace.
+
+ If specified, default is a function that gets called for objects
+ that can't otherwise be serialized. It should return a JSON encodable
+ version of the object or raise a ``TypeError``.
+
+ If encoding is not None, then all input strings will be
+ transformed into unicode using that encoding prior to JSON-encoding.
+ The default is UTF-8.
+
+ """
+
+ self.skipkeys = skipkeys
+ self.ensure_ascii = ensure_ascii
+ self.check_circular = check_circular
+ self.allow_nan = allow_nan
+ self.sort_keys = sort_keys
+ self.indent = indent
+ if separators is not None:
+ self.item_separator, self.key_separator = separators
+ if default is not None:
+ self.default = default
+ self.encoding = encoding
+
+ def default(self, o):
+ """Implement this method in a subclass such that it returns
+ a serializable object for ``o``, or calls the base implementation
+ (to raise a ``TypeError``).
+
+ For example, to support arbitrary iterators, you could
+ implement default like this::
+
+ def default(self, o):
+ try:
+ iterable = iter(o)
+ except TypeError:
+ pass
+ else:
+ return list(iterable)
+ return JSONEncoder.default(self, o)
+
+ """
+ raise TypeError(repr(o) + " is not JSON serializable")
+
+ def encode(self, o):
+ """Return a JSON string representation of a Python data structure.
+
+ >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
+ '{"foo": ["bar", "baz"]}'
+
+ """
+ # This is for extremely simple cases and benchmarks.
+ if isinstance(o, basestring):
+ if isinstance(o, str):
+ _encoding = self.encoding
+ if (_encoding is not None
+ and not (_encoding == 'utf-8')):
+ o = o.decode(_encoding)
+ if self.ensure_ascii:
+ return encode_basestring_ascii(o)
+ else:
+ return encode_basestring(o)
+ # This doesn't pass the iterator directly to ''.join() because the
+ # exceptions aren't as detailed. The list call should be roughly
+ # equivalent to the PySequence_Fast that ''.join() would do.
+ chunks = self.iterencode(o, _one_shot=True)
+ if not isinstance(chunks, (list, tuple)):
+ chunks = list(chunks)
+ return ''.join(chunks)
+
+ def iterencode(self, o, _one_shot=False):
+ """Encode the given object and yield each string
+ representation as available.
+
+ For example::
+
+ for chunk in JSONEncoder().iterencode(bigobject):
+ mysocket.write(chunk)
+
+ """
+ if self.check_circular:
+ markers = {}
+ else:
+ markers = None
+ if self.ensure_ascii:
+ _encoder = encode_basestring_ascii
+ else:
+ _encoder = encode_basestring
+ if self.encoding != 'utf-8':
+ def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
+ if isinstance(o, str):
+ o = o.decode(_encoding)
+ return _orig_encoder(o)
+
+ def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
+ # Check for specials. Note that this type of test is processor- and/or
+ # platform-specific, so do tests which don't depend on the internals.
+
+ if o != o:
+ text = 'NaN'
+ elif o == _inf:
+ text = 'Infinity'
+ elif o == _neginf:
+ text = '-Infinity'
+ else:
+ return _repr(o)
+
+ if not allow_nan:
+ raise ValueError(
+ "Out of range float values are not JSON compliant: " +
+ repr(o))
+
+ return text
+
+
+ if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys:
+ _iterencode = c_make_encoder(
+ markers, self.default, _encoder, self.indent,
+ self.key_separator, self.item_separator, self.sort_keys,
+ self.skipkeys, self.allow_nan)
+ else:
+ _iterencode = _make_iterencode(
+ markers, self.default, _encoder, self.indent, floatstr,
+ self.key_separator, self.item_separator, self.sort_keys,
+ self.skipkeys, _one_shot)
+ return _iterencode(o, 0)
+
+def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
+ ## HACK: hand-optimized bytecode; turn globals into locals
+ False=False,
+ True=True,
+ ValueError=ValueError,
+ basestring=basestring,
+ dict=dict,
+ float=float,
+ id=id,
+ int=int,
+ isinstance=isinstance,
+ list=list,
+ long=long,
+ str=str,
+ tuple=tuple,
+ ):
+
+ def _iterencode_list(lst, _current_indent_level):
+ if not lst:
+ yield '[]'
+ return
+ if markers is not None:
+ markerid = id(lst)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = lst
+ buf = '['
+ if _indent is not None:
+ _current_indent_level += 1
+ newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+ separator = _item_separator + newline_indent
+ buf += newline_indent
+ else:
+ newline_indent = None
+ separator = _item_separator
+ first = True
+ for value in lst:
+ if first:
+ first = False
+ else:
+ buf = separator
+ if isinstance(value, basestring):
+ yield buf + _encoder(value)
+ elif value is None:
+ yield buf + 'null'
+ elif value is True:
+ yield buf + 'true'
+ elif value is False:
+ yield buf + 'false'
+ elif isinstance(value, (int, long)):
+ yield buf + str(value)
+ elif isinstance(value, float):
+ yield buf + _floatstr(value)
+ else:
+ yield buf
+ if isinstance(value, (list, tuple)):
+ chunks = _iterencode_list(value, _current_indent_level)
+ elif isinstance(value, dict):
+ chunks = _iterencode_dict(value, _current_indent_level)
+ else:
+ chunks = _iterencode(value, _current_indent_level)
+ for chunk in chunks:
+ yield chunk
+ if newline_indent is not None:
+ _current_indent_level -= 1
+ yield '\n' + (' ' * (_indent * _current_indent_level))
+ yield ']'
+ if markers is not None:
+ del markers[markerid]
+
+ def _iterencode_dict(dct, _current_indent_level):
+ if not dct:
+ yield '{}'
+ return
+ if markers is not None:
+ markerid = id(dct)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = dct
+ yield '{'
+ if _indent is not None:
+ _current_indent_level += 1
+ newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+ item_separator = _item_separator + newline_indent
+ yield newline_indent
+ else:
+ newline_indent = None
+ item_separator = _item_separator
+ first = True
+ if _sort_keys:
+ items = dct.items()
+ items.sort(key=lambda kv: kv[0])
+ else:
+ items = dct.iteritems()
+ for key, value in items:
+ if isinstance(key, basestring):
+ pass
+ # JavaScript is weakly typed for these, so it makes sense to
+ # also allow them. Many encoders seem to do something like this.
+ elif isinstance(key, float):
+ key = _floatstr(key)
+ elif key is True:
+ key = 'true'
+ elif key is False:
+ key = 'false'
+ elif key is None:
+ key = 'null'
+ elif isinstance(key, (int, long)):
+ key = str(key)
+ elif _skipkeys:
+ continue
+ else:
+ raise TypeError("key " + repr(key) + " is not a string")
+ if first:
+ first = False
+ else:
+ yield item_separator
+ yield _encoder(key)
+ yield _key_separator
+ if isinstance(value, basestring):
+ yield _encoder(value)
+ elif value is None:
+ yield 'null'
+ elif value is True:
+ yield 'true'
+ elif value is False:
+ yield 'false'
+ elif isinstance(value, (int, long)):
+ yield str(value)
+ elif isinstance(value, float):
+ yield _floatstr(value)
+ else:
+ if isinstance(value, (list, tuple)):
+ chunks = _iterencode_list(value, _current_indent_level)
+ elif isinstance(value, dict):
+ chunks = _iterencode_dict(value, _current_indent_level)
+ else:
+ chunks = _iterencode(value, _current_indent_level)
+ for chunk in chunks:
+ yield chunk
+ if newline_indent is not None:
+ _current_indent_level -= 1
+ yield '\n' + (' ' * (_indent * _current_indent_level))
+ yield '}'
+ if markers is not None:
+ del markers[markerid]
+
+ def _iterencode(o, _current_indent_level):
+ if isinstance(o, basestring):
+ yield _encoder(o)
+ elif o is None:
+ yield 'null'
+ elif o is True:
+ yield 'true'
+ elif o is False:
+ yield 'false'
+ elif isinstance(o, (int, long)):
+ yield str(o)
+ elif isinstance(o, float):
+ yield _floatstr(o)
+ elif isinstance(o, (list, tuple)):
+ for chunk in _iterencode_list(o, _current_indent_level):
+ yield chunk
+ elif isinstance(o, dict):
+ for chunk in _iterencode_dict(o, _current_indent_level):
+ yield chunk
+ else:
+ if markers is not None:
+ markerid = id(o)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = o
+ o = _default(o)
+ for chunk in _iterencode(o, _current_indent_level):
+ yield chunk
+ if markers is not None:
+ del markers[markerid]
+
+ return _iterencode
diff --git a/lib/chef/provider/package/yum/simplejson/encoder.pyc b/lib/chef/provider/package/yum/simplejson/encoder.pyc
new file mode 100644
index 0000000000..207bce5cfb
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/encoder.pyc
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/scanner.py b/lib/chef/provider/package/yum/simplejson/scanner.py
new file mode 100644
index 0000000000..adbc6ec979
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/scanner.py
@@ -0,0 +1,65 @@
+"""JSON token scanner
+"""
+import re
+try:
+ from simplejson._speedups import make_scanner as c_make_scanner
+except ImportError:
+ c_make_scanner = None
+
+__all__ = ['make_scanner']
+
+NUMBER_RE = re.compile(
+ r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
+ (re.VERBOSE | re.MULTILINE | re.DOTALL))
+
+def py_make_scanner(context):
+ parse_object = context.parse_object
+ parse_array = context.parse_array
+ parse_string = context.parse_string
+ match_number = NUMBER_RE.match
+ encoding = context.encoding
+ strict = context.strict
+ parse_float = context.parse_float
+ parse_int = context.parse_int
+ parse_constant = context.parse_constant
+ object_hook = context.object_hook
+
+ def _scan_once(string, idx):
+ try:
+ nextchar = string[idx]
+ except IndexError:
+ raise StopIteration
+
+ if nextchar == '"':
+ return parse_string(string, idx + 1, encoding, strict)
+ elif nextchar == '{':
+ return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook)
+ elif nextchar == '[':
+ return parse_array((string, idx + 1), _scan_once)
+ elif nextchar == 'n' and string[idx:idx + 4] == 'null':
+ return None, idx + 4
+ elif nextchar == 't' and string[idx:idx + 4] == 'true':
+ return True, idx + 4
+ elif nextchar == 'f' and string[idx:idx + 5] == 'false':
+ return False, idx + 5
+
+ m = match_number(string, idx)
+ if m is not None:
+ integer, frac, exp = m.groups()
+ if frac or exp:
+ res = parse_float(integer + (frac or '') + (exp or ''))
+ else:
+ res = parse_int(integer)
+ return res, m.end()
+ elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
+ return parse_constant('NaN'), idx + 3
+ elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
+ return parse_constant('Infinity'), idx + 8
+ elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
+ return parse_constant('-Infinity'), idx + 9
+ else:
+ raise StopIteration
+
+ return _scan_once
+
+make_scanner = c_make_scanner or py_make_scanner
diff --git a/lib/chef/provider/package/yum/simplejson/scanner.pyc b/lib/chef/provider/package/yum/simplejson/scanner.pyc
new file mode 100644
index 0000000000..12df070e44
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/scanner.pyc
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/tool.py b/lib/chef/provider/package/yum/simplejson/tool.py
new file mode 100644
index 0000000000..90443317b2
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/tool.py
@@ -0,0 +1,37 @@
+r"""Command-line tool to validate and pretty-print JSON
+
+Usage::
+
+ $ echo '{"json":"obj"}' | python -m simplejson.tool
+ {
+ "json": "obj"
+ }
+ $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+ Expecting property name: line 1 column 2 (char 2)
+
+"""
+import sys
+import simplejson
+
+def main():
+ if len(sys.argv) == 1:
+ infile = sys.stdin
+ outfile = sys.stdout
+ elif len(sys.argv) == 2:
+ infile = open(sys.argv[1], 'rb')
+ outfile = sys.stdout
+ elif len(sys.argv) == 3:
+ infile = open(sys.argv[1], 'rb')
+ outfile = open(sys.argv[2], 'wb')
+ else:
+ raise SystemExit(sys.argv[0] + " [infile [outfile]]")
+ try:
+ obj = simplejson.load(infile)
+ except ValueError, e:
+ raise SystemExit(e)
+ simplejson.dump(obj, outfile, sort_keys=True, indent=4)
+ outfile.write('\n')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/chef/provider/package/yum/version.rb b/lib/chef/provider/package/yum/version.rb
new file mode 100644
index 0000000000..b19f52fe09
--- /dev/null
+++ b/lib/chef/provider/package/yum/version.rb
@@ -0,0 +1,56 @@
+#
+# Copyright:: Copyright 2016-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+
+ # helper class to assist in passing around name/version/arch triples
+ class Version
+ attr_accessor :name
+ attr_accessor :version
+ attr_accessor :arch
+
+ def initialize(name, version, arch)
+ @name = name
+ @version = version
+ @arch = arch
+ end
+
+ def to_s
+ "#{name}-#{version}.#{arch}" unless version.nil?
+ end
+
+ def version_with_arch
+ "#{version}.#{arch}" unless version.nil?
+ end
+
+ def matches_name_and_arch?(other)
+ other.version == version && other.arch == arch
+ end
+
+ def ==(other)
+ name == other.name && version == other.version && arch == other.arch
+ end
+
+ alias eql? ==
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum/yum_cache.rb b/lib/chef/provider/package/yum/yum_cache.rb
new file mode 100644
index 0000000000..fa0930109f
--- /dev/null
+++ b/lib/chef/provider/package/yum/yum_cache.rb
@@ -0,0 +1,93 @@
+
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright 2008-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/provider/package/yum/python_helper"
+require "chef/provider/package"
+require "singleton"
+
+#
+# These are largely historical APIs, the YumCache object no longer exists and this is a
+# fascade over the python helper class. It should be considered deprecated-lite and
+# no new APIs should be added and should be added to the python_helper instead.
+#
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+ class YumCache
+ include Singleton
+
+ def refresh
+ python_helper.restart
+ end
+
+ def reload
+ python_helper.restart
+ end
+
+ def reload_installed
+ python_helper.restart
+ end
+
+ def reload_provides
+ python_helper.restart
+ end
+
+ def reset
+ python_helper.restart
+ end
+
+ def reset_installed
+ python_helper.restart
+ end
+
+ def available_version(name, arch = nil)
+ p = python_helper.package_query(:whatavailable, name, arch: arch)
+ "#{p.version}.#{p.arch}" unless p.version.nil?
+ end
+
+ def installed_version(name, arch = nil)
+ p = python_helper.package_query(:whatinstalled, name, arch: arch)
+ "#{p.version}.#{p.arch}" unless p.version.nil?
+ end
+
+ def package_available?(name, arch = nil)
+ p = python_helper.package_query(:whatavailable, name, arch: arch)
+ !p.version.nil?
+ end
+
+ # NOTE that it is the responsibility of the python_helper to get these APIs correct and
+ # we do not do any validation here that the e.g. version or arch matches the requested value
+ # (because the bigger issue there is a buggy+broken python_helper -- so don't try to fix those
+ # kinds of bugs here)
+ def version_available?(name, version, arch = nil)
+ p = python_helper.package_query(:whatavailable, name, version: version, arch: arch)
+ !p.version.nil?
+ end
+
+ # @api private
+ def python_helper
+ @python_helper ||= PythonHelper.instance
+ end
+
+ end # YumCache
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum/yum_helper.py b/lib/chef/provider/package/yum/yum_helper.py
new file mode 100644
index 0000000000..d2c04c72db
--- /dev/null
+++ b/lib/chef/provider/package/yum/yum_helper.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python3
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+
+#
+# NOTE: this actually needs to run under python2.4 and centos 5.x through python3 and centos 7.x
+# please manually test changes on centos5 boxes or you will almost certainly break things.
+#
+
+import sys
+import yum
+import signal
+import os
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'simplejson'))
+try: import json
+except ImportError: import simplejson as json
+import re
+from rpmUtils.miscutils import stringToVersion,compareEVR
+from rpmUtils.arch import getBaseArch, getArchList
+
+
+try: from yum.misc import string_to_prco_tuple
+except ImportError:
+ # RHEL5 compat
+ def string_to_prco_tuple(prcoString):
+ prco_split = prcoString.split()
+ n, f, v = prco_split
+ (prco_e, prco_v, prco_r) = stringToVersion(v)
+ return (n, f, (prco_e, prco_v, prco_r))
+
+# hack to work around https://github.com/chef/chef/issues/7126
+# see https://bugzilla.redhat.com/show_bug.cgi?id=1396248
+if not hasattr(yum.packages.FakeRepository, 'compare_providers_priority'):
+ yum.packages.FakeRepository.compare_providers_priority = 99
+
+base = None
+
+def get_base():
+ global base
+ if base is None:
+ base = yum.YumBase()
+ setup_exit_handler()
+ return base
+
+def versioncompare(versions):
+ arch_list = getArchList()
+ candidate_arch1 = versions[0].split(".")[-1]
+ candidate_arch2 = versions[1].split(".")[-1]
+
+ # The first version number passed to this method is always a valid nevra (the current version)
+ # If the second version number looks like it does not contain a valid arch
+ # then we'll chop the arch component (assuming it *is* a valid one) from the first version string
+ # so we're only comparing the evr portions.
+ if (candidate_arch2 not in arch_list) and (candidate_arch1 in arch_list):
+ final_version1 = versions[0].replace("." + candidate_arch1,"")
+ else:
+ final_version1 = versions[0]
+
+ final_version2 = versions[1]
+
+ (e1, v1, r1) = stringToVersion(final_version1)
+ (e2, v2, r2) = stringToVersion(final_version2)
+
+ evr_comparison = compareEVR((e1, v1, r1), (e2, v2, r2))
+ outpipe.write("%(e)s\n" % { 'e': evr_comparison })
+ outpipe.flush()
+
+def install_only_packages(name):
+ base = get_base()
+ if name in base.conf.installonlypkgs:
+ outpipe.write('True')
+ else:
+ outpipe.write('False')
+ outpipe.flush()
+
+# python2.4 / centos5 compat
+try:
+ any
+except NameError:
+ def any(s):
+ for v in s:
+ if v:
+ return True
+ return False
+
+def query(command):
+ base = get_base()
+
+ enabled_repos = base.repos.listEnabled()
+
+ # Handle any repocontrols passed in with our options
+
+ if 'repos' in command:
+ for repo in command['repos']:
+ if 'enable' in repo:
+ base.repos.enableRepo(repo['enable'])
+ if 'disable' in repo:
+ base.repos.disableRepo(repo['disable'])
+
+ args = { 'name': command['provides'] }
+ do_nevra = False
+ if 'epoch' in command:
+ args['epoch'] = command['epoch']
+ do_nevra = True
+ if 'version' in command:
+ args['ver'] = command['version']
+ do_nevra = True
+ if 'release' in command:
+ args['rel'] = command['release']
+ do_nevra = True
+ if 'arch' in command:
+ desired_arch = command['arch']
+ args['arch'] = command['arch']
+ do_nevra = True
+ else:
+ desired_arch = getBaseArch()
+
+ obj = None
+ if command['action'] == "whatinstalled":
+ obj = base.rpmdb
+ else:
+ obj = base.pkgSack
+
+ # if we are given "name == 1.2.3" then we must use the getProvides() API.
+ # - this means that we ignore arch and version properties when given prco tuples as a package_name
+ # - in order to fix this, something would have to happen where getProvides was called first and
+ # then the result was searchNevra'd. please be extremely careful if attempting to fix that
+ # since searchNevra does not support prco tuples.
+ if any(elem in command['provides'] for elem in r"<=>"):
+ # handles flags (<, >, =, etc) and versions, but no wildcareds
+ pkgs = obj.getProvides(*string_to_prco_tuple(command['provides']))
+ elif do_nevra:
+ # now if we're given version or arch properties explicitly, then we do a SearchNevra.
+ # - this means that wildcard version in the package_name with an arch property will not work correctly
+ # - again don't try to fix this just by pushing bugs around in the code, you would need to call
+ # returnPackages and searchProvides and then apply the Nevra filters to those results.
+ pkgs = obj.searchNevra(**args)
+ if (command['action'] == "whatinstalled") and (not pkgs):
+ pkgs = obj.searchNevra(name=args['name'], arch=desired_arch)
+ else:
+ pats = [command['provides']]
+ pkgs = obj.returnPackages(patterns=pats)
+
+ if not pkgs:
+ # handles wildcards
+ pkgs = obj.searchProvides(command['provides'])
+
+ if not pkgs:
+ outpipe.write(command['provides'].split().pop(0)+' nil nil\n')
+ outpipe.flush()
+ else:
+ # make sure we picked the package with the highest version
+ pkgs = base.bestPackagesFromList(pkgs,single_name=True)
+ pkg = pkgs.pop(0)
+ outpipe.write("%(n)s %(e)s:%(v)s-%(r)s %(a)s\n" % { 'n': pkg.name, 'e': pkg.epoch, 'v': pkg.version, 'r': pkg.release, 'a': pkg.arch })
+ outpipe.flush()
+
+ # Reset any repos we were passed in enablerepo/disablerepo to the original state in enabled_repos
+ if 'repos' in command:
+ for repo in command['repos']:
+ if 'enable' in repo:
+ if base.repos.getRepo(repo['enable']) not in enabled_repos:
+ base.repos.disableRepo(repo['enable'])
+ if 'disable' in repo:
+ if base.repos.getRepo(repo['disable']) in enabled_repos:
+ base.repos.enableRepo(repo['disable'])
+
+# 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):
+ base.closeRpmDB()
+ sys.exit(0)
+
+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")
+
+while 1:
+ # kill self if we get orphaned (tragic)
+ ppid = os.getppid()
+ if ppid == 1:
+ sys.exit(0)
+ setup_exit_handler()
+ line = inpipe.readline()
+
+ try:
+ command = json.loads(line)
+ except ValueError, e:
+ base.closeRpmDB()
+ sys.exit(0)
+
+ if command['action'] == "whatinstalled":
+ query(command)
+ elif command['action'] == "whatavailable":
+ query(command)
+ elif command['action'] == "versioncompare":
+ versioncompare(command['versions'])
+ elif command['action'] == "installonlypkgs":
+ install_only_packages(command['package'])
+ else:
+ raise RuntimeError("bad command")
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index 5ee1dbea8e..c2638fbfc1 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -2,7 +2,7 @@
#
# Authors:: Adam Jacob (<adam@chef.io>)
# Ionuț Arțăriși (<iartarisi@suse.cz>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# Copyright 2013-2016, SUSE Linux GmbH
# License:: Apache License, Version 2.0
#
@@ -29,24 +29,24 @@ class Chef
use_multipackage_api
provides :package, platform_family: "suse"
- provides :zypper_package, os: "linux"
+ provides :zypper_package
def get_versions(package_name)
candidate_version = current_version = nil
is_installed = false
- Chef::Log.debug("#{new_resource} checking zypper")
- status = shell_out_with_timeout!("zypper --non-interactive info #{package_name}")
+ logger.trace("#{new_resource} checking zypper")
+ status = shell_out_compact_timeout!("zypper", "--non-interactive", "info", package_name)
status.stdout.each_line do |line|
case line
- when /^Version: (.+)$/
- candidate_version = $1
- Chef::Log.debug("#{new_resource} version #{$1}")
- when /^Installed: Yes$/
+ when /^Version *: (.+) *$/
+ candidate_version = $1.strip
+ logger.trace("#{new_resource} version #{candidate_version}")
+ when /^Installed *: Yes.*$/ # http://rubular.com/r/9StcAMjOn6
is_installed = true
- Chef::Log.debug("#{new_resource} is installed")
- when /^Status: out-of-date \(version (.+) installed\)$/
- current_version = $1
- Chef::Log.debug("#{new_resource} out of date version #{$1}")
+ logger.trace("#{new_resource} is installed")
+ when /^Status *: out-of-date \(version (.+) installed\) *$/
+ current_version = $1.strip
+ logger.trace("#{new_resource} out of date version #{current_version}")
end
end
current_version = candidate_version if is_installed
@@ -75,6 +75,24 @@ class Chef
end
end
+ def packages_all_locked?(names, versions)
+ names.all? { |n| locked_packages.include? n }
+ end
+
+ def packages_all_unlocked?(names, versions)
+ names.all? { |n| !locked_packages.include? n }
+ end
+
+ def locked_packages
+ @locked_packages ||=
+ begin
+ locked = shell_out_compact_timeout!("zypper", "locks")
+ locked.stdout.each_line.map do |line|
+ line.split("|").shift(2).last.strip
+ end
+ end
+ end
+
def load_current_resource
@current_resource = Chef::Resource::ZypperPackage.new(new_resource.name)
current_resource.package_name(new_resource.package_name)
@@ -91,7 +109,7 @@ class Chef
end
def install_package(name, version)
- zypper_package("install --auto-agree-with-licenses", name, version)
+ zypper_package("install", *options, "--auto-agree-with-licenses", allow_downgrade, name, version)
end
def upgrade_package(name, version)
@@ -100,11 +118,19 @@ class Chef
end
def remove_package(name, version)
- zypper_package("remove", name, version)
+ zypper_package("remove", *options, name, version)
end
def purge_package(name, version)
- zypper_package("remove --clean-deps", name, version)
+ zypper_package("remove", *options, "--clean-deps", name, version)
+ end
+
+ def lock_package(name, version)
+ zypper_package("addlock", *options, name, version)
+ end
+
+ def unlock_package(name, version)
+ zypper_package("removelock", *options, name, version)
end
private
@@ -115,27 +141,21 @@ class Chef
end
end
- def zypper_package(command, names, versions)
+ def zypper_package(command, *options, names, versions)
zipped_names = zip(names, versions)
if zypper_version < 1.0
- shell_out_with_timeout!(a_to_s("zypper", gpg_checks, command, "-y", names))
+ shell_out_compact_timeout!("zypper", gpg_checks, command, *options, "-y", names)
else
- shell_out_with_timeout!(a_to_s("zypper --non-interactive", gpg_checks, command, zipped_names))
+ shell_out_compact_timeout!("zypper", "--non-interactive", gpg_checks, command, *options, zipped_names)
end
end
- def gpg_checks()
- case Chef::Config[:zypper_check_gpg]
- when true
- ""
- when false
- "--no-gpg-checks"
- when nil
- Chef::Log.warn("Chef::Config[:zypper_check_gpg] was not set. " +
- "All packages will be installed without gpg signature checks. " +
- "This is a security hazard.")
- "--no-gpg-checks"
- end
+ def gpg_checks
+ "--no-gpg-checks" unless new_resource.gpg_check
+ end
+
+ def allow_downgrade
+ "--oldpackage" if new_resource.allow_downgrade
end
end
end
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index 6365f6a171..5af73b8b69 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -23,7 +23,7 @@ class Chef
class Provider
class PowershellScript < Chef::Provider::WindowsScript
- provides :powershell_script, os: "windows"
+ provides :powershell_script
def initialize(new_resource, run_context)
super(new_resource, run_context, ".ps1")
@@ -36,7 +36,7 @@ class Chef
end
def command
- basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+ basepath = is_forced_32bit ? wow64_directory : run_context.node["kernel"]["os_info"]["system_directory"]
# Powershell.exe is always in "v1.0" folder (for backwards compatibility)
interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter)
@@ -60,8 +60,8 @@ class Chef
def flags
interpreter_flags = [*default_interpreter_flags].join(" ")
- if ! (@new_resource.flags.nil?)
- interpreter_flags = [@new_resource.flags, interpreter_flags].join(" ")
+ if ! (new_resource.flags.nil?)
+ interpreter_flags = [new_resource.flags, interpreter_flags].join(" ")
end
interpreter_flags
@@ -73,8 +73,8 @@ class Chef
# special handling to cover common use cases.
def add_exit_status_wrapper
self.code = wrapper_script
- Chef::Log.debug("powershell_script provider called with script code:\n\n#{@new_resource.code}\n")
- Chef::Log.debug("powershell_script provider will execute transformed code:\n\n#{self.code}\n")
+ logger.trace("powershell_script provider called with script code:\n\n#{new_resource.code}\n")
+ logger.trace("powershell_script provider will execute transformed code:\n\n#{code}\n")
end
def validate_script_syntax!
@@ -87,7 +87,7 @@ class Chef
# actually running the script.
user_code_wrapped_in_powershell_script_block = <<-EOH
{
- #{@new_resource.code}
+ #{new_resource.code}
}
EOH
user_script_file.puts user_code_wrapped_in_powershell_script_block
@@ -149,6 +149,14 @@ EOH
<<-EOH
# Chef Client wrapper for powershell_script resources
+# In rare cases, such as when PowerShell is executed
+# as an alternate user, the new-variable cmdlet is not
+# available, so import it just in case
+if ( get-module -ListAvailable Microsoft.PowerShell.Utility )
+{
+ Import-Module Microsoft.PowerShell.Utility
+}
+
# LASTEXITCODE can be uninitialized -- make it explictly 0
# to avoid incorrect detection of failure (non-zero) codes
$global:LASTEXITCODE = 0
@@ -159,7 +167,7 @@ $global:LASTEXITCODE = 0
trap [Exception] {write-error ($_.Exception.Message);exit 1}
# Variable state that should not be accessible to the user code
-new-variable -name interpolatedexitcode -visibility private -value $#{@new_resource.convert_boolean_return}
+new-variable -name interpolatedexitcode -visibility private -value $#{new_resource.convert_boolean_return}
new-variable -name chefscriptresult -visibility private
# Initialize a variable we use to capture $? inside a block
@@ -168,7 +176,7 @@ $global:lastcmdlet = $null
# Execute the user's code in a script block --
$chefscriptresult =
{
- #{@new_resource.code}
+ #{new_resource.code}
# This assignment doesn't affect the block's return value
$global:lastcmdlet = $?
diff --git a/lib/chef/provider/reboot.rb b/lib/chef/provider/reboot.rb
index 34eee9236d..f054af0567 100644
--- a/lib/chef/provider/reboot.rb
+++ b/lib/chef/provider/reboot.rb
@@ -21,39 +21,47 @@ require "chef/provider"
class Chef
class Provider
+ # Use the reboot resource to reboot a node, a necessary step with some
+ # installations on certain platforms. This resource is supported for use on
+ # the Microsoft Windows, macOS, and Linux platforms.
+ #
+ # In using this resource via notifications, it's important to *only* use
+ # immediate notifications. Delayed notifications produce unintuitive and
+ # probably undesired results.
+ #
+ # @since 12.0.0
class Reboot < Chef::Provider
provides :reboot
- def whyrun_supported?
- true
- end
-
+ # @return [void]
def load_current_resource
- @current_resource ||= Chef::Resource::Reboot.new(@new_resource.name)
- @current_resource.reason(@new_resource.reason)
- @current_resource.delay_mins(@new_resource.delay_mins)
- @current_resource
+ @current_resource ||= Chef::Resource::Reboot.new(new_resource.name)
+ current_resource.reason(new_resource.reason)
+ current_resource.delay_mins(new_resource.delay_mins)
+ current_resource
end
+ # add a reboot to the node run_context
+ # @return [void]
def request_reboot
node.run_context.request_reboot(
- :delay_mins => @new_resource.delay_mins,
- :reason => @new_resource.reason,
+ :delay_mins => new_resource.delay_mins,
+ :reason => new_resource.reason,
:timestamp => Time.now,
- :requested_by => @new_resource.name
+ :requested_by => new_resource.name
)
end
def action_request_reboot
converge_by("request a system reboot to occur if the run succeeds") do
- Chef::Log.warn "Reboot requested:'#{@new_resource.name}'"
+ logger.warn "Reboot requested:'#{new_resource.name}'"
request_reboot
end
end
def action_reboot_now
converge_by("rebooting the system immediately") do
- Chef::Log.warn "Rebooting system immediately, requested by '#{@new_resource.name}'"
+ logger.warn "Rebooting system immediately, requested by '#{new_resource.name}'"
request_reboot
throw :end_client_run_early
end
@@ -61,7 +69,7 @@ class Chef
def action_cancel
converge_by("cancel any existing end-of-run reboot request") do
- Chef::Log.warn "Reboot canceled: '#{@new_resource.name}'"
+ logger.warn "Reboot canceled: '#{new_resource.name}'"
node.run_context.cancel_reboot
end
end
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index e516433ac8..a4a0465e11 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -2,7 +2,7 @@
# Author:: Prajakta Purohit (<prajakta@chef.io>)
# Author:: Lamont Granquist (<lamont@chef.io>)
#
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright 2011-2017, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -35,10 +35,6 @@ class Chef
include Chef::Mixin::Checksum
- def whyrun_supported?
- true
- end
-
def running_on_windows!
unless Chef::Platform.windows?
raise Chef::Exceptions::Win32NotWindows, "Attempt to manipulate the windows registry on a non-windows node"
@@ -47,19 +43,19 @@ class Chef
def load_current_resource
running_on_windows!
- @current_resource ||= Chef::Resource::RegistryKey.new(@new_resource.key, run_context)
- @current_resource.key(@new_resource.key)
- @current_resource.architecture(@new_resource.architecture)
- @current_resource.recursive(@new_resource.recursive)
- if registry.key_exists?(@new_resource.key)
- @current_resource.values(registry.get_values(@new_resource.key))
- end
- values_to_hash(@current_resource.unscrubbed_values)
- @current_resource
+ @current_resource ||= Chef::Resource::RegistryKey.new(new_resource.key, run_context)
+ current_resource.key(new_resource.key)
+ current_resource.architecture(new_resource.architecture)
+ current_resource.recursive(new_resource.recursive)
+ if registry.key_exists?(new_resource.key)
+ current_resource.values(registry.get_values(new_resource.key))
+ end
+ values_to_hash(current_resource.unscrubbed_values)
+ current_resource
end
def registry
- @registry ||= Chef::Win32::Registry.new(@run_context, @new_resource.architecture)
+ @registry ||= Chef::Win32::Registry.new(@run_context, new_resource.architecture)
end
def values_to_hash(values)
@@ -70,72 +66,111 @@ class Chef
end
end
+ def key_missing?(values, name)
+ values.each do |v|
+ return true unless v.has_key?(name)
+ end
+ false
+ end
+
def define_resource_requirements
requirements.assert(:create, :create_if_missing, :delete, :delete_key) do |a|
- a.assertion { registry.hive_exists?(@new_resource.key) }
- a.failure_message(Chef::Exceptions::Win32RegHiveMissing, "Hive #{@new_resource.key.split("\\").shift} does not exist")
+ a.assertion { registry.hive_exists?(new_resource.key) }
+ a.failure_message(Chef::Exceptions::Win32RegHiveMissing, "Hive #{new_resource.key.split("\\").shift} does not exist")
end
+
requirements.assert(:create) do |a|
- a.assertion { registry.key_exists?(@new_resource.key) }
- a.whyrun("Key #{@new_resource.key} does not exist. Unless it would have been created before, attempt to modify its values would fail.")
+ a.assertion { registry.key_exists?(new_resource.key) }
+ a.whyrun("Key #{new_resource.key} does not exist. Unless it would have been created before, attempt to modify its values would fail.")
end
+
requirements.assert(:create, :create_if_missing) do |a|
- #If keys missing in the path and recursive == false
- a.assertion { !registry.keys_missing?(@current_resource.key) || @new_resource.recursive }
+ # If keys missing in the path and recursive == false
+ a.assertion { !registry.keys_missing?(current_resource.key) || new_resource.recursive }
a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "Intermediate keys missing but recursive is set to false")
- a.whyrun("Intermediate keys in #{@new_resource.key} do not exist. Unless they would have been created earlier, attempt to modify them would fail.")
+ a.whyrun("Intermediate keys in #{new_resource.key} do not exist. Unless they would have been created earlier, attempt to modify them would fail.")
end
+
requirements.assert(:delete_key) do |a|
- #If key to be deleted has subkeys but recurssive == false
- a.assertion { !registry.key_exists?(@new_resource.key) || !registry.has_subkeys?(@new_resource.key) || @new_resource.recursive }
- a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "#{@new_resource.key} has subkeys but recursive is set to false.")
- a.whyrun("#{@current_resource.key} has subkeys, but recursive is set to false. attempt to delete would fails unless subkeys were deleted prior to this action.")
+ # If key to be deleted has subkeys but recurssive == false
+ a.assertion { !registry.key_exists?(new_resource.key) || !registry.has_subkeys?(new_resource.key) || new_resource.recursive }
+ a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "#{new_resource.key} has subkeys but recursive is set to false.")
+ a.whyrun("#{current_resource.key} has subkeys, but recursive is set to false. attempt to delete would fails unless subkeys were deleted prior to this action.")
+ end
+
+ requirements.assert(:create, :create_if_missing) do |a|
+ # If type key missing in the RegistryKey values hash
+ a.assertion { !key_missing?(new_resource.values, :type) }
+ a.failure_message(Chef::Exceptions::RegKeyValuesTypeMissing, "Missing type key in RegistryKey values hash")
+ a.whyrun("Type key does not exist. Attempt would fail unless the complete values hash containing all the keys does not exist for registry_key resource's create action.")
+ end
+
+ requirements.assert(:create, :create_if_missing) do |a|
+ # If data key missing in the RegistryKey values hash
+ a.assertion { !key_missing?(new_resource.values, :data) }
+ a.failure_message(Chef::Exceptions::RegKeyValuesDataMissing, "Missing data key in RegistryKey values hash")
+ a.whyrun("Data key does not exist. Attempt would fail unless the complete values hash containing all the keys does not exist for registry_key resource's create action.")
end
end
def action_create
- unless registry.key_exists?(@current_resource.key)
- converge_by("create key #{@new_resource.key}") do
- registry.create_key(@new_resource.key, @new_resource.recursive)
+ unless registry.key_exists?(current_resource.key)
+ converge_by("create key #{new_resource.key}") do
+ registry.create_key(new_resource.key, new_resource.recursive)
end
end
- @new_resource.unscrubbed_values.each do |value|
+ new_resource.unscrubbed_values.each do |value|
if @name_hash.has_key?(value[:name].downcase)
current_value = @name_hash[value[:name].downcase]
+ if [:dword, :dword_big_endian, :qword].include? value[:type]
+ value[:data] = value[:data].to_i
+ end
unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
- converge_by("set value #{value}") do
- registry.set_value(@new_resource.key, value)
+ converge_by_value = value
+ converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive
+
+ converge_by("set value #{converge_by_value}") do
+ registry.set_value(new_resource.key, value)
end
end
else
- converge_by("set value #{value}") do
- registry.set_value(@new_resource.key, value)
+ converge_by_value = value
+ converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive
+
+ converge_by("set value #{converge_by_value}") do
+ registry.set_value(new_resource.key, value)
end
end
end
end
def action_create_if_missing
- unless registry.key_exists?(@new_resource.key)
- converge_by("create key #{@new_resource.key}") do
- registry.create_key(@new_resource.key, @new_resource.recursive)
+ unless registry.key_exists?(new_resource.key)
+ converge_by("create key #{new_resource.key}") do
+ registry.create_key(new_resource.key, new_resource.recursive)
end
end
- @new_resource.unscrubbed_values.each do |value|
+ new_resource.unscrubbed_values.each do |value|
unless @name_hash.has_key?(value[:name].downcase)
- converge_by("create value #{value}") do
- registry.set_value(@new_resource.key, value)
+ converge_by_value = value
+ converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive
+
+ converge_by("create value #{converge_by_value}") do
+ registry.set_value(new_resource.key, value)
end
end
end
end
def action_delete
- if registry.key_exists?(@new_resource.key)
- @new_resource.unscrubbed_values.each do |value|
+ if registry.key_exists?(new_resource.key)
+ new_resource.unscrubbed_values.each do |value|
if @name_hash.has_key?(value[:name].downcase)
- converge_by("delete value #{value}") do
- registry.delete_value(@new_resource.key, value)
+ converge_by_value = value
+ converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive
+
+ converge_by("delete value #{converge_by_value}") do
+ registry.delete_value(new_resource.key, value)
end
end
end
@@ -143,9 +178,9 @@ class Chef
end
def action_delete_key
- if registry.key_exists?(@new_resource.key)
- converge_by("delete key #{@new_resource.key}") do
- registry.delete_key(@new_resource.key, @new_resource.recursive)
+ if registry.key_exists?(new_resource.key)
+ converge_by("delete key #{new_resource.key}") do
+ registry.delete_key(new_resource.key, new_resource.recursive)
end
end
end
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
index e3bc579107..94de68c557 100644
--- a/lib/chef/provider/remote_directory.rb
+++ b/lib/chef/provider/remote_directory.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright 2008-2018, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,8 +23,6 @@ require "chef/resource/cookbook_file"
require "chef/mixin/file_class"
require "chef/platform/query_helpers"
require "chef/util/path_helper"
-require "chef/deprecation/warnings"
-require "chef/deprecation/provider/remote_directory"
require "forwardable"
@@ -36,9 +34,9 @@ class Chef
provides :remote_directory
- def_delegators :@new_resource, :purge, :path, :source, :cookbook, :cookbook_name
- def_delegators :@new_resource, :files_rights, :files_mode, :files_group, :files_owner, :files_backup
- def_delegators :@new_resource, :rights, :mode, :group, :owner
+ def_delegators :new_resource, :purge, :path, :source, :cookbook, :cookbook_name
+ def_delegators :new_resource, :files_rights, :files_mode, :files_group, :files_owner, :files_backup
+ def_delegators :new_resource, :rights, :mode, :group, :owner
# The overwrite property on the resource. Delegates to new_resource but can be mutated.
#
@@ -49,8 +47,6 @@ class Chef
!!@overwrite
end
- attr_accessor :managed_files
-
# Hash containing keys of the paths for all the files that we sync, plus all their
# parent directories.
#
@@ -155,7 +151,7 @@ class Chef
#
# FIXME: it should do breadth-first, see CHEF-5080 (please use a performant sort)
#
- # @return Array<String> The list of files to transfer
+ # @return [Array<String>] The list of files to transfer
# @api private
#
def files_to_transfer
@@ -209,6 +205,8 @@ class Chef
def cookbook_file_resource(target_path, relative_source_path)
res = Chef::Resource::CookbookFile.new(target_path, run_context)
res.cookbook_name = resource_cookbook
+ # Set the sensitivity level
+ res.sensitive(new_resource.sensitive)
res.source(::File.join(source, relative_source_path))
if Chef::Platform.windows? && files_rights
files_rights.each_pair do |permission, *args|
@@ -266,16 +264,6 @@ class Chef
res
end
- #
- # Add back deprecated methods and aliases that are internally unused and should be removed in Chef-13
- #
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::RemoteDirectory
- add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteDirectory.instance_methods)
-
- alias_method :resource_for_directory, :directory_resource
- add_deprecation_warnings_for([:resource_for_directory])
-
end
end
end
diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb
index 9207e62ac6..d2de3d0b5f 100644
--- a/lib/chef/provider/remote_file.rb
+++ b/lib/chef/provider/remote_file.rb
@@ -18,33 +18,46 @@
#
require "chef/provider/file"
-require "chef/deprecation/provider/remote_file"
-require "chef/deprecation/warnings"
class Chef
class Provider
class RemoteFile < Chef::Provider::File
provides :remote_file
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::RemoteFile
- add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteFile.instance_methods)
-
def initialize(new_resource, run_context)
@content_class = Chef::Provider::RemoteFile::Content
super
end
+ def define_resource_requirements
+ [ new_resource.remote_user, new_resource.remote_domain,
+ new_resource.remote_password ].each do |prop|
+ requirements.assert(:all_actions) do |a|
+ a.assertion do
+ if prop
+ node[:platform_family] == "windows"
+ else
+ true
+ end
+ end
+ a.failure_message Chef::Exceptions::UnsupportedPlatform, "'remote_user', 'remote_domain' and 'remote_password' properties are supported only for Windows platform"
+ a.whyrun("Assuming that the platform is Windows while passing 'remote_user', 'remote_domain' and 'remote_password' properties")
+ end
+ end
+
+ super
+ end
+
def load_current_resource
- @current_resource = Chef::Resource::RemoteFile.new(@new_resource.name)
+ @current_resource = Chef::Resource::RemoteFile.new(new_resource.name)
super
end
private
def managing_content?
- return true if @new_resource.checksum
- return true if !@new_resource.source.nil? && @action != :create_if_missing
+ return true if new_resource.checksum
+ return true if !new_resource.source.nil? && @action != :create_if_missing
false
end
diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb
index 8d7de5c370..32c1542d57 100644
--- a/lib/chef/provider/remote_file/cache_control_data.rb
+++ b/lib/chef/provider/remote_file/cache_control_data.rb
@@ -153,7 +153,7 @@ class Chef
if Chef::FileCache.has_key?(old_path)
# We found an old cache control data file. We started using sha256 instead of md5
# to name these. Upgrade the file to the new name.
- Chef::Log.debug("Found old cache control data file at #{old_path}. Moving to #{path}.")
+ Chef::Log.trace("Found old cache control data file at #{old_path}. Moving to #{path}.")
Chef::FileCache.load(old_path).tap do |data|
Chef::FileCache.store(path, data)
Chef::FileCache.delete(old_path)
diff --git a/lib/chef/provider/remote_file/content.rb b/lib/chef/provider/remote_file/content.rb
index e44096428b..4cf2c32287 100644
--- a/lib/chef/provider/remote_file/content.rb
+++ b/lib/chef/provider/remote_file/content.rb
@@ -32,10 +32,10 @@ class Chef
include Chef::Mixin::Uris
def file_for_provider
- Chef::Log.debug("#{@new_resource} checking for changes")
+ logger.trace("#{@new_resource} checking for changes")
if current_resource_matches_target_checksum?
- Chef::Log.debug("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating")
+ logger.trace("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating")
else
sources = @new_resource.source
raw_file = try_multiple_sources(sources)
@@ -54,10 +54,10 @@ class Chef
as_uri(source)
end
raw_file = grab_file_from_uri(uri)
- rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPServerException, Net::HTTPFatalError, Net::FTPError => e
- Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e}")
+ rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPServerException, Net::HTTPFatalError, Net::FTPError, Errno::ETIMEDOUT => e
+ logger.warn("#{@new_resource} cannot be downloaded from #{source}: #{e}")
if source = sources.shift
- Chef::Log.info("#{@new_resource} trying to download from another mirror")
+ logger.info("#{@new_resource} trying to download from another mirror")
retry
else
raise e
diff --git a/lib/chef/provider/remote_file/fetcher.rb b/lib/chef/provider/remote_file/fetcher.rb
index 563d135d6a..3011dd80a0 100644
--- a/lib/chef/provider/remote_file/fetcher.rb
+++ b/lib/chef/provider/remote_file/fetcher.rb
@@ -24,6 +24,9 @@ class Chef
def self.for_resource(uri, new_resource, current_resource)
if network_share?(uri)
+ if !Chef::Platform.windows?
+ raise Exceptions::UnsupportedPlatform, "Fetching the file on a network share is supported only on the Windows platform. Please change your source: #{uri}"
+ end
Chef::Provider::RemoteFile::NetworkFile.new(uri, new_resource, current_resource)
else
case uri.scheme
diff --git a/lib/chef/provider/remote_file/ftp.rb b/lib/chef/provider/remote_file/ftp.rb
index 5935e83301..b382c20c31 100644
--- a/lib/chef/provider/remote_file/ftp.rb
+++ b/lib/chef/provider/remote_file/ftp.rb
@@ -153,9 +153,9 @@ class Chef
def parse_path
path = uri.path.sub(%r{\A/}, "%2F") # re-encode the beginning slash because uri library decodes it.
directories = path.split(%r{/}, -1)
- directories.each {|d|
+ directories.each do |d|
d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
- }
+ end
unless filename = directories.pop
raise ArgumentError, "no filename: #{path.inspect}"
end
diff --git a/lib/chef/provider/remote_file/http.rb b/lib/chef/provider/remote_file/http.rb
index ad044f9e3c..2122142608 100644
--- a/lib/chef/provider/remote_file/http.rb
+++ b/lib/chef/provider/remote_file/http.rb
@@ -31,12 +31,14 @@ class Chef
attr_reader :uri
attr_reader :new_resource
attr_reader :current_resource
+ attr_reader :logger
# Parse the uri into instance variables
- def initialize(uri, new_resource, current_resource)
+ def initialize(uri, new_resource, current_resource, logger = Chef::Log.with_child)
@uri = uri
@new_resource = new_resource
@current_resource = current_resource
+ @logger = logger
end
def events
@@ -55,22 +57,28 @@ class Chef
if (etag = cache_control_data.etag) && want_etag_cache_control?
cache_control_headers["if-none-match"] = etag
end
- Chef::Log.debug("Cache control headers: #{cache_control_headers.inspect}")
+ logger.trace("Cache control headers: #{cache_control_headers.inspect}")
cache_control_headers
end
def fetch
http = Chef::HTTP::Simple.new(uri, http_client_opts)
+ orig_tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
if want_progress?
- tempfile = http.streaming_request_with_progress(uri, headers) do |size, total|
+ tempfile = http.streaming_request_with_progress(uri, headers, orig_tempfile) do |size, total|
events.resource_update_progress(new_resource, size, total, progress_interval)
end
else
- tempfile = http.streaming_request(uri, headers)
+ tempfile = http.streaming_request(uri, headers, orig_tempfile)
end
if tempfile
update_cache_control_data(tempfile, http.last_response)
tempfile.close
+ else
+ # cache_control shows the file is unchanged, so we got back nil from the streaming_request above, and it is
+ # now our responsibility to unlink the tempfile we created
+ orig_tempfile.close
+ orig_tempfile.unlink
end
tempfile
end
@@ -123,7 +131,7 @@ class Chef
# case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
# which is not what you wanted.
if uri.to_s =~ /gz$/
- Chef::Log.debug("Turning gzip compression off due to filename ending in gz")
+ logger.trace("Turning gzip compression off due to filename ending in gz")
opts[:disable_gzip] = true
end
opts
diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/local_file.rb
index 613db02337..0719e5dbf7 100644
--- a/lib/chef/provider/remote_file/local_file.rb
+++ b/lib/chef/provider/remote_file/local_file.rb
@@ -48,7 +48,7 @@ class Chef
# Fetches the file at uri, returning a Tempfile-like File handle
def fetch
tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
- Chef::Log.debug("#{new_resource} staging #{source_path} to #{tempfile.path}")
+ Chef::Log.trace("#{new_resource} staging #{source_path} to #{tempfile.path}")
FileUtils.cp(source_path, tempfile.path)
tempfile.close if tempfile
tempfile
diff --git a/lib/chef/provider/remote_file/network_file.rb b/lib/chef/provider/remote_file/network_file.rb
index 44046132a9..ffd2d0bbce 100644
--- a/lib/chef/provider/remote_file/network_file.rb
+++ b/lib/chef/provider/remote_file/network_file.rb
@@ -19,14 +19,18 @@
require "uri"
require "tempfile"
require "chef/provider/remote_file"
+require "chef/mixin/user_context"
class Chef
class Provider
class RemoteFile
class NetworkFile
+ include Chef::Mixin::UserContext
attr_reader :new_resource
+ TRANSFER_CHUNK_SIZE = 1048576
+
def initialize(source, new_resource, current_resource)
@new_resource = new_resource
@source = source
@@ -35,13 +39,22 @@ class Chef
# Fetches the file on a network share, returning a Tempfile-like File handle
# windows only
def fetch
- tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
- Chef::Log.debug("#{new_resource} staging #{@source} to #{tempfile.path}")
- FileUtils.cp(@source, tempfile.path)
- tempfile.close if tempfile
+ begin
+ tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
+ Chef::Log.trace("#{new_resource} staging #{@source} to #{tempfile.path}")
+
+ with_user_context(new_resource.remote_user, new_resource.remote_password, new_resource.remote_domain, new_resource.authentication) do
+ ::File.open(@source, "rb") do |remote_file|
+ while data = remote_file.read(TRANSFER_CHUNK_SIZE)
+ tempfile.write(data)
+ end
+ end
+ end
+ ensure
+ tempfile.close if tempfile
+ end
tempfile
end
-
end
end
end
diff --git a/lib/chef/provider/remote_file/sftp.rb b/lib/chef/provider/remote_file/sftp.rb
index 530977e3c8..21c5c4ca04 100644
--- a/lib/chef/provider/remote_file/sftp.rb
+++ b/lib/chef/provider/remote_file/sftp.rb
@@ -68,9 +68,9 @@ class Chef
def validate_path!
path = uri.path.sub(%r{\A/}, "%2F") # re-encode the beginning slash because uri library decodes it.
directories = path.split(%r{/}, -1)
- directories.each {|d|
+ directories.each do |d|
d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
- }
+ end
unless filename = directories.pop
raise ArgumentError, "no filename: #{path.inspect}"
end
diff --git a/lib/chef/provider/route.rb b/lib/chef/provider/route.rb
index 64c89aac6d..b23d0307cc 100644
--- a/lib/chef/provider/route.rb
+++ b/lib/chef/provider/route.rb
@@ -17,213 +17,232 @@
#
require "chef/log"
-require "chef/mixin/command"
require "chef/provider"
require "ipaddr"
-class Chef::Provider::Route < Chef::Provider
- include Chef::Mixin::Command
-
- provides :route
-
- attr_accessor :is_running
-
- MASK = { "0.0.0.0" => "0",
- "128.0.0.0" => "1",
- "192.0.0.0" => "2",
- "224.0.0.0" => "3",
- "240.0.0.0" => "4",
- "248.0.0.0" => "5",
- "252.0.0.0" => "6",
- "254.0.0.0" => "7",
- "255.0.0.0" => "8",
- "255.128.0.0" => "9",
- "255.192.0.0" => "10",
- "255.224.0.0" => "11",
- "255.240.0.0" => "12",
- "255.248.0.0" => "13",
- "255.252.0.0" => "14",
- "255.254.0.0" => "15",
- "255.255.0.0" => "16",
- "255.255.128.0" => "17",
- "255.255.192.0" => "18",
- "255.255.224.0" => "19",
- "255.255.240.0" => "20",
- "255.255.248.0" => "21",
- "255.255.252.0" => "22",
- "255.255.254.0" => "23",
- "255.255.255.0" => "24",
- "255.255.255.128" => "25",
- "255.255.255.192" => "26",
- "255.255.255.224" => "27",
- "255.255.255.240" => "28",
- "255.255.255.248" => "29",
- "255.255.255.252" => "30",
- "255.255.255.254" => "31",
- "255.255.255.255" => "32" }
-
- def hex2ip(hex_data)
- # Cleanup hex data
- hex_ip = hex_data.to_s.downcase.gsub(/[^0-9a-f]/, "")
-
- # Check hex data format (IP is a 32bit integer, so should be 8 chars long)
- return nil if hex_ip.length != hex_data.length || hex_ip.length != 8
-
- # Extract octets from hex data
- octets = hex_ip.scan(/../).reverse.collect { |octet| [octet].pack("H2").unpack("C").first }
-
- # Validate IP
- ip = octets.join(".")
- begin
- IPAddr.new(ip, Socket::AF_INET).to_s
- rescue ArgumentError
- Chef::Log.debug("Invalid IP address data: hex=#{hex_ip}, ip=#{ip}")
- return nil
- end
- end
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- self.is_running = false
+class Chef
+ class Provider
+ class Route < Chef::Provider
+
+ provides :route
+
+ attr_accessor :is_running
+
+ MASK = { "0.0.0.0" => "0",
+ "128.0.0.0" => "1",
+ "192.0.0.0" => "2",
+ "224.0.0.0" => "3",
+ "240.0.0.0" => "4",
+ "248.0.0.0" => "5",
+ "252.0.0.0" => "6",
+ "254.0.0.0" => "7",
+ "255.0.0.0" => "8",
+ "255.128.0.0" => "9",
+ "255.192.0.0" => "10",
+ "255.224.0.0" => "11",
+ "255.240.0.0" => "12",
+ "255.248.0.0" => "13",
+ "255.252.0.0" => "14",
+ "255.254.0.0" => "15",
+ "255.255.0.0" => "16",
+ "255.255.128.0" => "17",
+ "255.255.192.0" => "18",
+ "255.255.224.0" => "19",
+ "255.255.240.0" => "20",
+ "255.255.248.0" => "21",
+ "255.255.252.0" => "22",
+ "255.255.254.0" => "23",
+ "255.255.255.0" => "24",
+ "255.255.255.128" => "25",
+ "255.255.255.192" => "26",
+ "255.255.255.224" => "27",
+ "255.255.255.240" => "28",
+ "255.255.255.248" => "29",
+ "255.255.255.252" => "30",
+ "255.255.255.254" => "31",
+ "255.255.255.255" => "32" }.freeze
+
+ def hex2ip(hex_data)
+ # Cleanup hex data
+ hex_ip = hex_data.to_s.downcase.gsub(/[^0-9a-f]/, "")
+
+ # Check hex data format (IP is a 32bit integer, so should be 8 chars long)
+ return nil if hex_ip.length != hex_data.length || hex_ip.length != 8
+
+ # Extract octets from hex data
+ octets = hex_ip.scan(/../).reverse.collect { |octet| [octet].pack("H2").unpack("C").first }
+
+ # Validate IP
+ ip = octets.join(".")
+ begin
+ IPAddr.new(ip, Socket::AF_INET).to_s
+ rescue ArgumentError
+ logger.trace("Invalid IP address data: hex=#{hex_ip}, ip=#{ip}")
+ return nil
+ end
+ end
- # cidr or quad dot mask
- if @new_resource.netmask
- new_ip = IPAddr.new("#{@new_resource.target}/#{@new_resource.netmask}")
- else
- new_ip = IPAddr.new(@new_resource.target)
- end
+ def load_current_resource
+ self.is_running = false
+
+ # cidr or quad dot mask
+ new_ip = if new_resource.target == "default"
+ IPAddr.new(new_resource.gateway)
+ elsif new_resource.netmask
+ IPAddr.new("#{new_resource.target}/#{new_resource.netmask}")
+ else
+ IPAddr.new(new_resource.target)
+ end
+
+ # For linux, we use /proc/net/route file to read proc table info
+ return if node[:os] != "linux"
+
+ route_file = ::File.open("/proc/net/route", "r")
+
+ # Read all routes
+ while (line = route_file.gets)
+ # Get all the fields for a route
+ _, destination, gateway, _, _, _, _, mask = line.split
+
+ # Convert hex-encoded values to quad-dotted notation (e.g. 0064A8C0 => 192.168.100.0)
+ destination = hex2ip(destination)
+ gateway = hex2ip(gateway)
+ mask = hex2ip(mask)
+
+ # Skip formatting lines (header, etc)
+ next unless destination && gateway && mask
+ logger.trace("#{new_resource} system has route: dest=#{destination} mask=#{mask} gw=#{gateway}")
+
+ # check if what were trying to configure is already there
+ # use an ipaddr object with ip/mask this way we can have
+ # a new resource be in cidr format (i don't feel like
+ # expanding bitmask by hand.
+ #
+ running_ip = IPAddr.new("#{destination}/#{mask}")
+ logger.trace("#{new_resource} new ip: #{new_ip.inspect} running ip: #{running_ip.inspect}")
+ self.is_running = true if running_ip == new_ip && gateway == new_resource.gateway
+ end
- # For linux, we use /proc/net/route file to read proc table info
- if node[:os] == "linux"
- route_file = ::File.open("/proc/net/route", "r")
-
- # Read all routes
- while (line = route_file.gets)
- # Get all the fields for a route
- iface, destination, gateway, flags, refcnt, use, metric, mask, mtu, window, irtt = line.split
-
- # Convert hex-encoded values to quad-dotted notation (e.g. 0064A8C0 => 192.168.100.0)
- destination = hex2ip(destination)
- gateway = hex2ip(gateway)
- mask = hex2ip(mask)
-
- # Skip formatting lines (header, etc)
- next unless destination && gateway && mask
- Chef::Log.debug("#{@new_resource} system has route: dest=#{destination} mask=#{mask} gw=#{gateway}")
-
- # check if what were trying to configure is already there
- # use an ipaddr object with ip/mask this way we can have
- # a new resource be in cidr format (i don't feel like
- # expanding bitmask by hand.
- #
- running_ip = IPAddr.new("#{destination}/#{mask}")
- Chef::Log.debug("#{@new_resource} new ip: #{new_ip.inspect} running ip: #{running_ip.inspect}")
- self.is_running = true if running_ip == new_ip && gateway == @new_resource.gateway
+ route_file.close
end
- route_file.close
- end
- end
+ def action_add
+ # check to see if load_current_resource found the route
+ if is_running
+ logger.trace("#{new_resource} route already active - nothing to do")
+ else
+ command = generate_command(:add)
+ converge_by("run #{command.join(' ')} to add route") do
+ shell_out_compact!(command)
+ logger.info("#{new_resource} added")
+ end
+ end
- def action_add
- # check to see if load_current_resource found the route
- if is_running
- Chef::Log.debug("#{@new_resource} route already active - nothing to do")
- else
- command = generate_command(:add)
- converge_by ("run #{ command } to add route") do
- run_command( :command => command )
- Chef::Log.info("#{@new_resource} added")
+ # for now we always write the file (ugly but its what it is)
+ generate_config
end
- end
- #for now we always write the file (ugly but its what it is)
- generate_config
- end
+ def action_delete
+ if is_running
+ command = generate_command(:delete)
+ converge_by("run #{command.join(' ')} to delete route ") do
+ shell_out_compact!(command)
+ logger.info("#{new_resource} removed")
+ end
+ else
+ logger.trace("#{new_resource} route does not exist - nothing to do")
+ end
- def action_delete
- if is_running
- command = generate_command(:delete)
- converge_by ("run #{ command } to delete route ") do
- run_command( :command => command )
- Chef::Log.info("#{@new_resource} removed")
+ # for now we always write the file (ugly but its what it is)
+ generate_config
end
- else
- Chef::Log.debug("#{@new_resource} route does not exist - nothing to do")
- end
-
- #for now we always write the file (ugly but its what it is)
- generate_config
- end
- def generate_config
- conf = Hash.new
- case node[:platform]
- when "centos", "redhat", "fedora"
- # walk the collection
- run_context.resource_collection.each do |resource|
- if resource.is_a? Chef::Resource::Route
- # default to eth0
- if resource.device
- dev = resource.device
- else
- dev = "eth0"
+ def generate_config
+ conf = {}
+ case node[:platform]
+ when "centos", "redhat", "fedora"
+ # walk the collection
+ run_context.resource_collection.each do |resource|
+ next unless resource.is_a? Chef::Resource::Route
+ # default to eth0
+ dev = if resource.device
+ resource.device
+ else
+ "eth0"
+ end
+
+ conf[dev] = "" if conf[dev].nil?
+ case @action
+ when :add
+ conf[dev] << config_file_contents(:add, comment: resource.comment, device: resource.device, target: resource.target, metric: resource.metric, netmask: resource.netmask, gateway: resource.gateway) if resource.action == [:add]
+ when :delete
+ # need to do this for the case when the last route on an int
+ # is removed
+ conf[dev] << config_file_contents(:delete)
+ end
end
-
- conf[dev] = String.new if conf[dev].nil?
- case @action
- when :add
- conf[dev] << config_file_contents(:add, :target => resource.target, :netmask => resource.netmask, :gateway => resource.gateway) if resource.action == [:add]
- when :delete
- # need to do this for the case when the last route on an int
- # is removed
- conf[dev] << config_file_contents(:delete)
+ conf.each_key do |k|
+ if new_resource.target == "default"
+ network_file_name = "/etc/sysconfig/network"
+ converge_by("write route default route to #{network_file_name}") do
+ logger.trace("#{new_resource} writing default route #{new_resource.gateway} to #{network_file_name}")
+ if ::File.exist?(network_file_name)
+ network_file = ::Chef::Util::FileEdit.new(network_file_name)
+ network_file.search_file_replace_line /^GATEWAY=/, "GATEWAY=#{new_resource.gateway}"
+ network_file.insert_line_if_no_match /^GATEWAY=/, "GATEWAY=#{new_resource.gateway}"
+ network_file.write_file
+ else
+ network_file = ::File.new(network_file_name, "w")
+ network_file.puts("GATEWAY=#{new_resource.gateway}")
+ network_file.close
+ end
+ end
+ else
+ network_file_name = "/etc/sysconfig/network-scripts/route-#{k}"
+ converge_by("write route route.#{k}\n#{conf[k]} to #{network_file_name}") do
+ network_file = ::File.new(network_file_name, "w")
+ network_file.puts(conf[k])
+ logger.trace("#{new_resource} writing route.#{k}\n#{conf[k]}")
+ network_file.close
+ end
+ end
end
end
end
- conf.each do |k, v|
- network_file_name = "/etc/sysconfig/network-scripts/route-#{k}"
- converge_by ("write route route.#{k}\n#{conf[k]} to #{ network_file_name }") do
- network_file = ::File.new(network_file_name, "w")
- network_file.puts(conf[k])
- Chef::Log.debug("#{@new_resource} writing route.#{k}\n#{conf[k]}")
- network_file.close
+
+ def generate_command(action)
+ target = new_resource.target
+ target = "#{target}/#{MASK[new_resource.netmask.to_s]}" if new_resource.netmask
+
+ case action
+ when :add
+ command = [ "ip", "route", "replace", target ]
+ command += [ "via", new_resource.gateway ] if new_resource.gateway
+ command += [ "dev", new_resource.device ] if new_resource.device
+ command += [ "metric", new_resource.metric ] if new_resource.metric
+ when :delete
+ command = [ "ip", "route", "delete", target ]
+ command += [ "via", new_resource.gateway ] if new_resource.gateway
end
- end
- end
- end
- def generate_command(action)
- common_route_items = ""
- common_route_items << "/#{MASK[@new_resource.netmask.to_s]}" if @new_resource.netmask
- common_route_items << " via #{@new_resource.gateway} " if @new_resource.gateway
-
- case action
- when :add
- command = "ip route replace #{@new_resource.target}"
- command << common_route_items
- command << " dev #{@new_resource.device} " if @new_resource.device
- when :delete
- command = "ip route delete #{@new_resource.target}"
- command << common_route_items
- end
+ command
+ end
- return command
- end
+ def config_file_contents(action, options = {})
+ content = ""
+ case action
+ when :add
+ content << "# #{options[:comment]}\n" if options[:comment]
+ content << (options[:target]).to_s
+ content << "/#{MASK[options[:netmask].to_s]}" if options[:netmask]
+ content << " via #{options[:gateway]}" if options[:gateway]
+ content << " dev #{options[:device]}" if options[:device]
+ content << " metric #{options[:metric]}" if options[:metric]
+ content << "\n"
+ end
- def config_file_contents(action, options = {})
- content = ""
- case action
- when :add
- content << "#{options[:target]}"
- content << "/#{options[:netmask]}" if options[:netmask]
- content << " via #{options[:gateway]}" if options[:gateway]
- content << "\n"
+ content
+ end
end
-
- return content
end
end
diff --git a/lib/chef/provider/ruby_block.rb b/lib/chef/provider/ruby_block.rb
index 0817b14044..01f041ca3b 100644
--- a/lib/chef/provider/ruby_block.rb
+++ b/lib/chef/provider/ruby_block.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2009-2016, Opscode
+# Copyright:: Copyright 2009-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,18 +22,14 @@ class Chef
class RubyBlock < Chef::Provider
provides :ruby_block
- def whyrun_supported?
- true
- end
-
def load_current_resource
true
end
def action_run
- converge_by("execute the ruby block #{@new_resource.name}") do
- @new_resource.block.call
- Chef::Log.info("#{@new_resource} called")
+ converge_by("execute the ruby block #{new_resource.name}") do
+ new_resource.block.call
+ logger.info("#{new_resource} called")
end
end
diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb
index 6ca4e9f6f3..c5966370f7 100644
--- a/lib/chef/provider/script.rb
+++ b/lib/chef/provider/script.rb
@@ -18,6 +18,7 @@
require "tempfile"
require "chef/provider/execute"
+require "chef/win32/security" if Chef::Platform.windows?
require "forwardable"
class Chef
@@ -33,7 +34,7 @@ class Chef
provides :ruby
provides :script
- def_delegators :@new_resource, :interpreter, :flags
+ def_delegators :new_resource, :interpreter, :flags
attr_accessor :code
@@ -50,7 +51,7 @@ class Chef
super
# @todo Chef-13: change this to an exception
if code.nil?
- Chef::Log.warn "#{@new_resource}: No code attribute was given, resource does nothing, this behavior is deprecated and will be removed in Chef-13"
+ logger.warn "#{new_resource}: No code attribute was given, resource does nothing, this behavior is deprecated and will be removed in Chef-13"
end
end
@@ -66,10 +67,45 @@ class Chef
end
def set_owner_and_group
- # FileUtils itself implements a no-op if +user+ or +group+ are nil
- # You can prove this by running FileUtils.chown(nil,nil,'/tmp/file')
- # as an unprivileged user.
- FileUtils.chown(new_resource.user, new_resource.group, script_file.path)
+ if Chef::Platform.windows?
+ # And on Windows also this is a no-op if there is no user specified.
+ grant_alternate_user_read_access
+ else
+ # FileUtils itself implements a no-op if +user+ or +group+ are nil
+ # You can prove this by running FileUtils.chown(nil,nil,'/tmp/file')
+ # as an unprivileged user.
+ FileUtils.chown(new_resource.user, new_resource.group, script_file.path)
+ end
+ end
+
+ def grant_alternate_user_read_access
+ # Do nothing if an alternate user isn't specified -- the file
+ # will already have the correct permissions for the user as part
+ # of the default ACL behavior on Windows.
+ return if new_resource.user.nil?
+
+ # Duplicate the script file's existing DACL
+ # so we can add an ACE later
+ securable_object = Chef::ReservedNames::Win32::Security::SecurableObject.new(script_file.path)
+ aces = securable_object.security_descriptor.dacl.reduce([]) { |result, current| result.push(current) }
+
+ username = new_resource.user
+
+ if new_resource.domain
+ username = new_resource.domain + '\\' + new_resource.user
+ end
+
+ # Create an ACE that allows the alternate user read access to the script
+ # file so it can be read and executed.
+ user_sid = Chef::ReservedNames::Win32::Security::SID.from_account(username)
+ read_ace = Chef::ReservedNames::Win32::Security::ACE.access_allowed(user_sid, Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE, 0)
+ aces.push(read_ace)
+ acl = Chef::ReservedNames::Win32::Security::ACL.create(aces)
+
+ # This actually applies the modified DACL to the file
+ # Use parentheses to bypass RuboCop / ChefStyle warning
+ # about useless setter
+ (securable_object.dacl = acl)
end
def script_file
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index e693bd2eed..c116d321f1 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
# Author:: Davide Cavalca (<dcavalca@fb.com>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,15 +17,12 @@
# limitations under the License.
#
-require "chef/mixin/command"
require "chef/provider"
class Chef
class Provider
class Service < Chef::Provider
- include Chef::Mixin::Command
-
def supports
@supports ||= new_resource.supports.dup
end
@@ -35,10 +32,6 @@ class Chef
@enabled = nil
end
- def whyrun_supported?
- true
- end
-
def load_current_resource
supports[:status] = false if supports[:status].nil?
supports[:reload] = false if supports[:reload].nil?
@@ -51,21 +44,21 @@ class Chef
# XXX?: the #nil? check below will likely fail if this is a cloned resource or if
# we just run multiple actions.
def load_new_resource_state
- if @new_resource.enabled.nil?
- @new_resource.enabled(@current_resource.enabled)
+ if new_resource.enabled.nil?
+ new_resource.enabled(current_resource.enabled)
end
- if @new_resource.running.nil?
- @new_resource.running(@current_resource.running)
+ if new_resource.running.nil?
+ new_resource.running(current_resource.running)
end
- if @new_resource.masked.nil?
- @new_resource.masked(@current_resource.masked)
+ if new_resource.masked.nil?
+ new_resource.masked(current_resource.masked)
end
end
# subclasses should override this if they do implement user services
def user_services_requirements
requirements.assert(:all_actions) do |a|
- a.assertion { @new_resource.user.nil? }
+ a.assertion { new_resource.user.nil? }
a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support user services"
end
end
@@ -76,7 +69,7 @@ class Chef
def define_resource_requirements
requirements.assert(:reload) do |a|
- a.assertion { supports[:reload] || @new_resource.reload_command }
+ a.assertion { supports[:reload] || new_resource.reload_command }
a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload"
# if a service is not declared to support reload, that won't
# typically change during the course of a run - so no whyrun
@@ -85,97 +78,97 @@ class Chef
end
def action_enable
- if @current_resource.enabled
- Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+ if current_resource.enabled
+ logger.trace("#{new_resource} already enabled - nothing to do")
else
- converge_by("enable service #{@new_resource}") do
+ converge_by("enable service #{new_resource}") do
enable_service
- Chef::Log.info("#{@new_resource} enabled")
+ logger.info("#{new_resource} enabled")
end
end
load_new_resource_state
- @new_resource.enabled(true)
+ new_resource.enabled(true)
end
def action_disable
- if @current_resource.enabled
- converge_by("disable service #{@new_resource}") do
+ if current_resource.enabled
+ converge_by("disable service #{new_resource}") do
disable_service
- Chef::Log.info("#{@new_resource} disabled")
+ logger.info("#{new_resource} disabled")
end
else
- Chef::Log.debug("#{@new_resource} already disabled - nothing to do")
+ logger.trace("#{new_resource} already disabled - nothing to do")
end
load_new_resource_state
- @new_resource.enabled(false)
+ new_resource.enabled(false)
end
def action_mask
- if @current_resource.masked
- Chef::Log.debug("#{@new_resource} already masked - nothing to do")
+ if current_resource.masked
+ logger.trace("#{new_resource} already masked - nothing to do")
else
- converge_by("mask service #{@new_resource}") do
+ converge_by("mask service #{new_resource}") do
mask_service
- Chef::Log.info("#{@new_resource} masked")
+ logger.info("#{new_resource} masked")
end
end
load_new_resource_state
- @new_resource.masked(true)
+ new_resource.masked(true)
end
def action_unmask
- if @current_resource.masked
- converge_by("unmask service #{@new_resource}") do
+ if current_resource.masked
+ converge_by("unmask service #{new_resource}") do
unmask_service
- Chef::Log.info("#{@new_resource} unmasked")
+ logger.info("#{new_resource} unmasked")
end
else
- Chef::Log.debug("#{@new_resource} already unmasked - nothing to do")
+ logger.trace("#{new_resource} already unmasked - nothing to do")
end
load_new_resource_state
- @new_resource.masked(false)
+ new_resource.masked(false)
end
def action_start
- unless @current_resource.running
- converge_by("start service #{@new_resource}") do
+ unless current_resource.running
+ converge_by("start service #{new_resource}") do
start_service
- Chef::Log.info("#{@new_resource} started")
+ logger.info("#{new_resource} started")
end
else
- Chef::Log.debug("#{@new_resource} already running - nothing to do")
+ logger.trace("#{new_resource} already running - nothing to do")
end
load_new_resource_state
- @new_resource.running(true)
+ new_resource.running(true)
end
def action_stop
- if @current_resource.running
- converge_by("stop service #{@new_resource}") do
+ if current_resource.running
+ converge_by("stop service #{new_resource}") do
stop_service
- Chef::Log.info("#{@new_resource} stopped")
+ logger.info("#{new_resource} stopped")
end
else
- Chef::Log.debug("#{@new_resource} already stopped - nothing to do")
+ logger.trace("#{new_resource} already stopped - nothing to do")
end
load_new_resource_state
- @new_resource.running(false)
+ new_resource.running(false)
end
def action_restart
- converge_by("restart service #{@new_resource}") do
+ converge_by("restart service #{new_resource}") do
restart_service
- Chef::Log.info("#{@new_resource} restarted")
+ logger.info("#{new_resource} restarted")
end
load_new_resource_state
- @new_resource.running(true)
+ new_resource.running(true)
end
def action_reload
- if @current_resource.running
- converge_by("reload service #{@new_resource}") do
+ if current_resource.running
+ converge_by("reload service #{new_resource}") do
reload_service
- Chef::Log.info("#{@new_resource} reloaded")
+ logger.info("#{new_resource} reloaded")
end
end
load_new_resource_state
@@ -216,17 +209,17 @@ class Chef
protected
def default_init_command
- if @new_resource.init_command
- @new_resource.init_command
- elsif self.instance_variable_defined?(:@init_command)
+ if new_resource.init_command
+ new_resource.init_command
+ elsif instance_variable_defined?(:@init_command)
@init_command
end
end
def custom_command_for_action?(action)
method_name = "#{action}_command".to_sym
- @new_resource.respond_to?(method_name) &&
- !!@new_resource.send(method_name)
+ new_resource.respond_to?(method_name) &&
+ !!new_resource.send(method_name)
end
module ServicePriorityInit
@@ -252,7 +245,7 @@ class Chef
Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: "arch"
Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: "gentoo"
Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: "debian"
- Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w{rhel fedora suse}
+ Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w{rhel fedora suse amazon}
end
end
end
diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb
index 201f9ff5f9..10ea06152b 100644
--- a/lib/chef/provider/service/aix.rb
+++ b/lib/chef/provider/service/aix.rb
@@ -1,6 +1,6 @@
#
# Author:: kaustubh (<kaustubh@clogeny.com>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright 2014-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -43,10 +43,6 @@ class Chef
@current_resource
end
- def whyrun_supported?
- true
- end
-
def start_service
if @is_resource_group
shell_out!("startsrc -g #{@new_resource.service_name}")
@@ -92,7 +88,7 @@ class Chef
protected
def determine_current_status!
- Chef::Log.debug "#{@new_resource} using lssrc to check the status"
+ logger.trace "#{@new_resource} using lssrc to check the status"
begin
if is_resource_group?
# Groups as a whole have no notion of whether they're running
@@ -105,7 +101,7 @@ class Chef
@current_resource.running false
end
end
- Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
+ logger.trace "#{@new_resource} running: #{@current_resource.running}"
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
# Temporarily catching different types of exceptions here until we get Shellout fixed.
# TODO: Remove the line before one we get the ShellOut fix.
@@ -119,7 +115,7 @@ class Chef
def is_resource_group?
so = shell_out("lssrc -g #{@new_resource.service_name}")
if so.exitstatus == 0
- Chef::Log.debug("#{@new_resource.service_name} is a group")
+ logger.trace("#{@new_resource.service_name} is a group")
@is_resource_group = true
end
end
diff --git a/lib/chef/provider/service/aixinit.rb b/lib/chef/provider/service/aixinit.rb
index 73c5e07715..dd8514cf20 100644
--- a/lib/chef/provider/service/aixinit.rb
+++ b/lib/chef/provider/service/aixinit.rb
@@ -45,11 +45,11 @@ class Chef
priority_ok = @current_resource.priority == @new_resource.priority
end
if @current_resource.enabled && priority_ok
- Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+ logger.trace("#{@new_resource} already enabled - nothing to do")
else
converge_by("enable service #{@new_resource}") do
enable_service
- Chef::Log.info("#{@new_resource} enabled")
+ logger.info("#{@new_resource} enabled")
end
end
load_new_resource_state
diff --git a/lib/chef/provider/service/arch.rb b/lib/chef/provider/service/arch.rb
index 2fd32e37aa..e34227036a 100644
--- a/lib/chef/provider/service/arch.rb
+++ b/lib/chef/provider/service/arch.rb
@@ -66,7 +66,7 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
end
end
- def enable_service()
+ def enable_service
new_daemons = []
entries = daemons
@@ -92,7 +92,7 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
end
end
- def disable_service()
+ def disable_service
new_daemons = []
entries = daemons
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 67b71953f7..09af9f224f 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -35,8 +35,6 @@ class Chef
def load_current_resource
super
- @priority_success = true
- @rcd_status = nil
current_resource.priority(get_priority)
current_resource.enabled(service_currently_enabled?(current_resource.priority))
current_resource
@@ -54,8 +52,8 @@ class Chef
end
requirements.assert(:all_actions) do |a|
- a.assertion { @priority_success }
- a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{current_resource.service_name} failed - #{@rcd_status.inspect}"
+ a.assertion { @got_priority == true }
+ a.failure_message Chef::Exceptions::Service, "Unable to determine priority for service"
# This can happen if the service is not yet installed,so we'll fake it.
a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.",
"Assigning temporary priorities to continue.",
@@ -72,23 +70,38 @@ class Chef
end
end
+ # returns a list of levels that the service should be stopped or started on
+ def parse_init_file(path)
+ return [] unless ::File.exist?(path)
+ in_info = false
+ ::File.readlines(path).each_with_object([]) do |line, acc|
+ if line =~ /^### BEGIN INIT INFO/
+ in_info = true
+ elsif line =~ /^### END INIT INFO/
+ in_info = false
+ elsif in_info
+ if line =~ /Default-(Start|Stop):\s+(\d.*)/
+ acc << $2.split(" ")
+ end
+ end
+ end.flatten
+ end
+
def get_priority
priority = {}
+ rc_files = []
- @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{current_resource.service_name} remove") do |pid, stdin, stdout, stderr|
-
- [stdout, stderr].each do |iop|
- iop.each_line do |line|
- if UPDATE_RC_D_PRIORITIES =~ line
- # priority[runlevel] = [ S|K, priority ]
- # S = Start, K = Kill
- # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot
- priority[$1] = [($2 == "S" ? :start : :stop), $3]
- end
- if line =~ UPDATE_RC_D_ENABLED_MATCHES
- enabled = true
- end
- end
+ levels = parse_init_file(@init_command)
+ levels.each do |level|
+ rc_files.push Dir.glob("/etc/rc#{level}.d/[SK][0-9][0-9]#{current_resource.service_name}")
+ end
+
+ rc_files.flatten.each do |line|
+ if UPDATE_RC_D_PRIORITIES =~ line
+ # priority[runlevel] = [ S|K, priority ]
+ # S = Start, K = Kill
+ # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot
+ priority[$1] = [($2 == "S" ? :start : :stop), $3]
end
end
@@ -98,21 +111,19 @@ class Chef
priority = priority[2].last
end
- unless @rcd_status.exitstatus == 0
- @priority_success = false
- end
+ @got_priority = true
priority
end
def service_currently_enabled?(priority)
enabled = false
- priority.each { |runlevel, arguments|
- Chef::Log.debug("#{new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}")
+ priority.each do |runlevel, arguments|
+ logger.trace("#{new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}")
# if we are in a update-rc.d default startup runlevel && we start in this runlevel
if %w{ 1 2 3 4 5 S }.include?(runlevel) && arguments[0] == :start
enabled = true
end
- }
+ end
enabled
end
@@ -125,11 +136,11 @@ class Chef
priority_ok = @current_resource.priority == new_resource.priority
end
if current_resource.enabled && priority_ok
- Chef::Log.debug("#{new_resource} already enabled - nothing to do")
+ logger.trace("#{new_resource} already enabled - nothing to do")
else
converge_by("enable service #{new_resource}") do
enable_service
- Chef::Log.info("#{new_resource} enabled")
+ logger.info("#{new_resource} enabled")
end
end
load_new_resource_state
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 76d8c1d17b..68de39fd0f 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -18,7 +18,6 @@
require "chef/resource/service"
require "chef/provider/service/init"
-require "chef/mixin/command"
class Chef
class Provider
@@ -48,7 +47,7 @@ class Chef
return current_resource unless init_command
- Chef::Log.debug("#{current_resource} found at #{init_command}")
+ logger.trace("#{current_resource} found at #{init_command}")
@status_load_success = true
determine_current_status! # see Chef::Provider::Service::Simple
@@ -74,7 +73,7 @@ class Chef
end
requirements.assert(:start, :enable, :reload, :restart) do |a|
- a.assertion { service_enable_variable_name != nil }
+ a.assertion { !service_enable_variable_name.nil? }
a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{init_command} and rcvar"
# No recovery in whyrun mode - the init file is present but not correct.
end
@@ -146,7 +145,7 @@ class Chef
end
# some scripts support multiple instances through symlinks such as openvpn.
# We should get the service name from rcvar.
- Chef::Log.debug("name=\"service\" not found at #{init_command}. falling back to rcvar")
+ logger.trace("name=\"service\" not found at #{init_command}. falling back to rcvar")
shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
else
# for why-run mode when the rcd_script is not there yet
@@ -172,7 +171,7 @@ class Chef
end
if current_resource.enabled.nil?
- Chef::Log.debug("#{new_resource.name} enable/disable state unknown")
+ logger.trace("#{new_resource.name} enable/disable state unknown")
current_resource.enabled false
end
end
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
index 8fb6d1f9af..69b3d20a3f 100644
--- a/lib/chef/provider/service/gentoo.rb
+++ b/lib/chef/provider/service/gentoo.rb
@@ -18,7 +18,6 @@
#
require "chef/provider/service/init"
-require "chef/mixin/command"
require "chef/util/path_helper"
class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
@@ -37,11 +36,11 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
@found_script = true
exists = ::File.exists? file
readable = ::File.readable? file
- Chef::Log.debug "#{@new_resource} exists: #{exists}, readable: #{readable}"
+ logger.trace "#{@new_resource} exists: #{exists}, readable: #{readable}"
exists && readable
end
)
- Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}"
+ logger.trace "#{@new_resource} enabled: #{@current_resource.enabled}"
@current_resource
end
@@ -61,11 +60,11 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
end
end
- def enable_service()
+ def enable_service
shell_out!("/sbin/rc-update add #{@new_resource.service_name} default")
end
- def disable_service()
+ def disable_service
shell_out!("/sbin/rc-update del #{@new_resource.service_name} default")
end
end
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
index dff627d016..c6c582f8b8 100644
--- a/lib/chef/provider/service/init.rb
+++ b/lib/chef/provider/service/init.rb
@@ -17,7 +17,6 @@
#
require "chef/provider/service/simple"
-require "chef/mixin/command"
require "chef/platform/service_helpers"
class Chef
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 76b2ee7477..a8e841f8b3 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright 2011-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,7 @@ class Chef
class Service
class Insserv < Chef::Provider::Service::Init
- provides :service, platform_family: %w{debian rhel fedora suse} do |node|
+ provides :service, platform_family: %w{debian rhel fedora suse amazon} do |node|
Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
end
@@ -45,12 +45,12 @@ class Chef
current_resource
end
- def enable_service()
+ def enable_service
shell_out!("/sbin/insserv -r -f #{new_resource.service_name}")
shell_out!("/sbin/insserv -d -f #{new_resource.service_name}")
end
- def disable_service()
+ def disable_service
shell_out!("/sbin/insserv -r -f #{new_resource.service_name}")
end
end
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 648cd9748b..40021f9ba6 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -28,7 +28,7 @@ class Chef
class Service
class Macosx < Chef::Provider::Service::Simple
- provides :macosx_service, os: "darwin"
+ provides :macosx_service
provides :service, os: "darwin"
def self.gather_plist_dirs
@@ -52,21 +52,22 @@ class Chef
@plist_size = 0
@plist = @new_resource.plist ? @new_resource.plist : find_service_plist
@service_label = find_service_label
- # LauchAgents should be loaded as the console user.
+ # LaunchAgents should be loaded as the console user.
@console_user = @plist ? @plist.include?("LaunchAgents") : false
@session_type = @new_resource.session_type
if @console_user
- @console_user = Etc.getlogin
- Chef::Log.debug("#{new_resource} console_user: '#{@console_user}'")
+ @console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
+ logger.trace("#{new_resource} console_user: '#{@console_user}'")
cmd = "su "
param = this_version_or_newer?("10.10") ? "" : "-l "
+ param = "-l " if this_version_or_newer?("10.12")
@base_user_cmd = cmd + param + "#{@console_user} -c"
- # Default LauchAgent session should be Aqua
+ # Default LaunchAgent session should be Aqua
@session_type = "Aqua" if @session_type.nil?
end
- Chef::Log.debug("#{new_resource} Plist: '#{@plist}' service_label: '#{@service_label}'")
+ logger.trace("#{new_resource} Plist: '#{@plist}' service_label: '#{@service_label}'")
set_service_status
@current_resource
@@ -107,7 +108,7 @@ class Chef
def start_service
if @current_resource.running
- Chef::Log.debug("#{@new_resource} already running, not starting")
+ logger.trace("#{@new_resource} already running, not starting")
else
if @new_resource.start_command
super
@@ -119,7 +120,7 @@ class Chef
def stop_service
unless @current_resource.running
- Chef::Log.debug("#{@new_resource} not running, not stopping")
+ logger.trace("#{@new_resource} not running, not stopping")
else
if @new_resource.stop_command
super
@@ -146,7 +147,7 @@ class Chef
# supervisor that will restart daemons that are crashing, etc.
def enable_service
if @current_resource.enabled
- Chef::Log.debug("#{@new_resource} already enabled, not enabling")
+ logger.trace("#{@new_resource} already enabled, not enabling")
else
load_service
end
@@ -154,7 +155,7 @@ class Chef
def disable_service
unless @current_resource.enabled
- Chef::Log.debug("#{@new_resource} not enabled, not disabling")
+ logger.trace("#{@new_resource} not enabled, not disabling")
else
unload_service
end
@@ -181,7 +182,7 @@ class Chef
end
def set_service_status
- return if @plist == nil || @service_label.to_s.empty?
+ return if @plist.nil? || @service_label.to_s.empty?
cmd = "launchctl list #{@service_label}"
res = shell_out_as_user(cmd)
@@ -197,8 +198,8 @@ class Chef
case line.downcase
when /\s+\"pid\"\s+=\s+(\d+).*/
pid = $1
- @current_resource.running(!pid.to_i.zero?)
- Chef::Log.debug("Current PID for #{@service_label} is #{pid}")
+ @current_resource.running(pid.to_i != 0)
+ logger.trace("Current PID for #{@service_label} is #{pid}")
end
end
else
diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb
index c60bbf170c..552173fbee 100644
--- a/lib/chef/provider/service/openbsd.rb
+++ b/lib/chef/provider/service/openbsd.rb
@@ -16,7 +16,6 @@
# limitations under the License.
#
-require "chef/mixin/command"
require "chef/mixin/shell_out"
require "chef/provider/service/init"
require "chef/resource/service"
@@ -49,7 +48,7 @@ class Chef
@current_resource = Chef::Resource::Service.new(new_resource.name)
current_resource.service_name(new_resource.service_name)
- Chef::Log.debug("#{current_resource} found at #{init_command}")
+ logger.trace("#{current_resource} found at #{init_command}")
determine_current_status!
determine_enabled_status!
@@ -72,7 +71,7 @@ class Chef
end
requirements.assert(:start, :enable, :reload, :restart) do |a|
- a.assertion { init_command && builtin_service_enable_variable_name != nil }
+ a.assertion { init_command && !builtin_service_enable_variable_name.nil? }
a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{init_command} and rcvar"
# No recovery in whyrun mode - the init file is present but not correct.
end
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 200a2d3400..1da3d7c01a 100644
--- a/lib/chef/provider/service/redhat.rb
+++ b/lib/chef/provider/service/redhat.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,7 +28,7 @@ class Chef
# @api private
attr_accessor :current_run_levels
- provides :service, platform_family: %w{rhel fedora suse} do |node|
+ provides :service, platform_family: %w{rhel fedora suse amazon} do |node|
Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
end
@@ -109,7 +109,7 @@ class Chef
(run_levels.nil? || run_levels.empty?) ? "" : "--level #{run_levels.join('')} "
end
- def enable_service()
+ def enable_service
unless run_levels.nil? || run_levels.empty?
disable_levels = current_run_levels - run_levels
shell_out! "/sbin/chkconfig --level #{disable_levels.join('')} #{new_resource.service_name} off" unless disable_levels.empty?
@@ -117,7 +117,7 @@ class Chef
shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} on"
end
- def disable_service()
+ def disable_service
shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} off"
end
end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
index fe4768b2e8..7b5f75c4b3 100644
--- a/lib/chef/provider/service/simple.rb
+++ b/lib/chef/provider/service/simple.rb
@@ -18,7 +18,6 @@
require "chef/provider/service"
require "chef/resource/service"
-require "chef/mixin/command"
class Chef
class Provider
@@ -41,10 +40,6 @@ class Chef
@current_resource
end
- def whyrun_supported?
- true
- end
-
def shared_resource_requirements
super
requirements.assert(:all_actions) do |a|
@@ -76,8 +71,9 @@ class Chef
end
requirements.assert(:all_actions) do |a|
- a.assertion { @new_resource.status_command || supports[:status] ||
- (!ps_cmd.nil? && !ps_cmd.empty?) }
+ a.assertion do
+ @new_resource.status_command || supports[:status] ||
+ (!ps_cmd.nil? && !ps_cmd.empty?) end
a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute"
end
requirements.assert(:all_actions) do |a|
@@ -108,16 +104,16 @@ class Chef
shell_out_with_systems_locale!(@new_resource.reload_command)
end
- protected
+ protected
def determine_current_status!
if @new_resource.status_command
- Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
+ logger.trace("#{@new_resource} you have specified a status command, running..")
begin
if shell_out(@new_resource.status_command).exitstatus == 0
@current_resource.running true
- Chef::Log.debug("#{@new_resource} is running")
+ logger.trace("#{@new_resource} is running")
end
rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
@@ -129,11 +125,11 @@ class Chef
end
elsif supports[:status]
- Chef::Log.debug("#{@new_resource} supports status, running")
+ logger.trace("#{@new_resource} supports status, running")
begin
if shell_out("#{default_init_command} status").exitstatus == 0
@current_resource.running true
- Chef::Log.debug("#{@new_resource} is running")
+ logger.trace("#{@new_resource} is running")
end
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
# Temporarily catching different types of exceptions here until we get Shellout fixed.
@@ -144,9 +140,9 @@ class Chef
nil
end
else
- Chef::Log.debug "#{@new_resource} falling back to process table inspection"
+ logger.trace "#{@new_resource} falling back to process table inspection"
r = Regexp.new(@new_resource.pattern)
- Chef::Log.debug "#{@new_resource} attempting to match '#{@new_resource.pattern}' (#{r.inspect}) against process list"
+ logger.trace "#{@new_resource} attempting to match '#{@new_resource.pattern}' (#{r.inspect}) against process list"
begin
shell_out!(ps_cmd).stdout.each_line do |line|
if r.match(line)
@@ -156,7 +152,7 @@ class Chef
end
@current_resource.running false unless @current_resource.running
- Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
+ logger.trace "#{@new_resource} running: #{@current_resource.running}"
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
# Temporarily catching different types of exceptions here until we get Shellout fixed.
# TODO: Remove the line before one we get the ShellOut fix.
diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb
index 1e5398eba8..f2b1ec4262 100644
--- a/lib/chef/provider/service/solaris.rb
+++ b/lib/chef/provider/service/solaris.rb
@@ -18,7 +18,6 @@
require "chef/provider/service"
require "chef/resource/service"
-require "chef/mixin/command"
class Chef
class Provider
@@ -32,7 +31,7 @@ class Chef
super
@init_command = "/usr/sbin/svcadm"
@status_command = "/bin/svcs"
- @maintenace = false
+ @maintenance = false
end
def load_current_resource
@@ -40,7 +39,7 @@ class Chef
@current_resource.service_name(@new_resource.service_name)
[@init_command, @status_command].each do |cmd|
- unless ::File.executable? cmd then
+ unless ::File.executable? cmd
raise Chef::Exceptions::Service, "#{cmd} not executable!"
end
end
@@ -55,12 +54,16 @@ class Chef
end
def enable_service
+ # Running service status to update maintenance status to invoke svcadm clear
+ service_status
shell_out!(default_init_command, "clear", @new_resource.service_name) if @maintenance
- shell_out!(default_init_command, "enable", "-s", @new_resource.service_name)
+ enable_flags = [ "-s", @new_resource.options ].flatten.compact
+ shell_out!(default_init_command, "enable", *enable_flags, @new_resource.service_name)
end
def disable_service
- shell_out!(default_init_command, "disable", "-s", @new_resource.service_name)
+ disable_flags = [ "-s", @new_resource.options ].flatten.compact
+ shell_out!(default_init_command, "disable", *disable_flags, @new_resource.service_name)
end
alias_method :stop_service, :disable_service
@@ -73,7 +76,7 @@ class Chef
def restart_service
## svcadm restart doesn't supports sync(-s) option
disable_service
- return enable_service
+ enable_service
end
def service_status
@@ -92,6 +95,9 @@ class Chef
# dependency require_all/error svc:/milestone/multi-user:default (online)
# $
+ # Set the default value for maintenance
+ @maintenance = false
+
# load output into hash
status = {}
cmd.stdout.each_line do |line|
@@ -100,7 +106,6 @@ class Chef
end
# check service state
- @maintenance = false
case status["state"]
when "online"
@current_resource.enabled(true)
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 1597d46a3d..1bcc2f3a00 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -20,6 +20,7 @@
require "chef/resource/service"
require "chef/provider/service/simple"
require "chef/mixin/which"
+require "shellwords"
class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
@@ -41,7 +42,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
@status_check_success = true
if new_resource.status_command
- Chef::Log.debug("#{new_resource} you have specified a status command, running..")
+ logger.trace("#{new_resource} you have specified a status command, running..")
unless shell_out(new_resource.status_command).error?
current_resource.running(true)
@@ -76,12 +77,12 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
def get_systemctl_options_args
if new_resource.user
- uid = node["etc"]["passwd"][new_resource.user]["uid"]
+ uid = Etc.getpwuid(new_resource.user).uid
options = {
- "environment" => {
+ :environment => {
"DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{uid}/bus",
},
- "user" => new_resource.user,
+ :user => new_resource.user,
}
args = "--user"
else
@@ -89,31 +90,31 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
args = "--system"
end
- return options, args
+ [options, args]
end
def start_service
if current_resource.running
- Chef::Log.debug("#{new_resource} already running, not starting")
+ logger.trace("#{new_resource} already running, not starting")
else
if new_resource.start_command
super
else
options, args = get_systemctl_options_args
- shell_out_with_systems_locale!("#{systemctl_path} #{args} start #{new_resource.service_name}", options)
+ shell_out_with_systems_locale!("#{systemctl_path} #{args} start #{Shellwords.escape(new_resource.service_name)}", options)
end
end
end
def stop_service
unless current_resource.running
- Chef::Log.debug("#{new_resource} not running, not stopping")
+ logger.trace("#{new_resource} not running, not stopping")
else
if new_resource.stop_command
super
else
options, args = get_systemctl_options_args
- shell_out_with_systems_locale!("#{systemctl_path} #{args} stop #{new_resource.service_name}", options)
+ shell_out_with_systems_locale!("#{systemctl_path} #{args} stop #{Shellwords.escape(new_resource.service_name)}", options)
end
end
end
@@ -123,7 +124,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
super
else
options, args = get_systemctl_options_args
- shell_out_with_systems_locale!("#{systemctl_path} #{args} restart #{new_resource.service_name}", options)
+ shell_out_with_systems_locale!("#{systemctl_path} #{args} restart #{Shellwords.escape(new_resource.service_name)}", options)
end
end
@@ -133,7 +134,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
else
if current_resource.running
options, args = get_systemctl_options_args
- shell_out_with_systems_locale!("#{systemctl_path} #{args} reload #{new_resource.service_name}", options)
+ shell_out_with_systems_locale!("#{systemctl_path} #{args} reload #{Shellwords.escape(new_resource.service_name)}", options)
else
start_service
end
@@ -142,37 +143,37 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
def enable_service
options, args = get_systemctl_options_args
- shell_out!("#{systemctl_path} #{args} enable #{new_resource.service_name}", options)
+ shell_out!("#{systemctl_path} #{args} enable #{Shellwords.escape(new_resource.service_name)}", options)
end
def disable_service
options, args = get_systemctl_options_args
- shell_out!("#{systemctl_path} #{args} disable #{new_resource.service_name}", options)
+ shell_out!("#{systemctl_path} #{args} disable #{Shellwords.escape(new_resource.service_name)}", options)
end
def mask_service
options, args = get_systemctl_options_args
- shell_out!("#{systemctl_path} #{args} mask #{new_resource.service_name}", options)
+ shell_out!("#{systemctl_path} #{args} mask #{Shellwords.escape(new_resource.service_name)}", options)
end
def unmask_service
options, args = get_systemctl_options_args
- shell_out!("#{systemctl_path} #{args} unmask #{new_resource.service_name}", options)
+ shell_out!("#{systemctl_path} #{args} unmask #{Shellwords.escape(new_resource.service_name)}", options)
end
def is_active?
options, args = get_systemctl_options_args
- shell_out("#{systemctl_path} #{args} is-active #{new_resource.service_name} --quiet", options).exitstatus == 0
+ shell_out("#{systemctl_path} #{args} is-active #{Shellwords.escape(new_resource.service_name)} --quiet", options).exitstatus == 0
end
def is_enabled?
options, args = get_systemctl_options_args
- shell_out("#{systemctl_path} #{args} is-enabled #{new_resource.service_name} --quiet", options).exitstatus == 0
+ shell_out("#{systemctl_path} #{args} is-enabled #{Shellwords.escape(new_resource.service_name)} --quiet", options).exitstatus == 0
end
def is_masked?
options, args = get_systemctl_options_args
- s = shell_out("#{systemctl_path} #{args} is-enabled #{new_resource.service_name}", options)
+ s = shell_out("#{systemctl_path} #{args} is-enabled #{Shellwords.escape(new_resource.service_name)}", options)
s.exitstatus != 0 && s.stdout.include?("masked")
end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 3ac5ff51da..810d9eabb7 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -18,7 +18,6 @@
require "chef/resource/service"
require "chef/provider/service/simple"
-require "chef/mixin/command"
require "chef/util/file_edit"
class Chef
@@ -26,12 +25,16 @@ class Chef
class Service
class Upstart < Chef::Provider::Service::Simple
+ # to maintain a local state of service across restart's internal calls
+ attr_accessor :upstart_service_running
+
provides :service, platform_family: "debian", override: true do |node|
Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
end
UPSTART_STATE_FORMAT = /\S+ \(?(start|stop)?\)? ?[\/ ](\w+)/
+ # Returns true if the configs for the service name has upstart variable
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart)
end
@@ -80,8 +83,11 @@ class Chef
shared_resource_requirements
requirements.assert(:all_actions) do |a|
if !@command_success
- whyrun_msg = @new_resource.status_command ? "Provided status command #{@new_resource.status_command} failed." :
- "Could not determine upstart state for service"
+ whyrun_msg = if @new_resource.status_command
+ "Provided status command #{@new_resource.status_command} failed."
+ else
+ "Could not determine upstart state for service"
+ end
end
a.assertion { @command_success }
# no failure here, just document the assumptions made.
@@ -103,42 +109,42 @@ class Chef
# We do not support searching for a service via ps when using upstart since status is a native
# upstart function. We will however support status_command in case someone wants to do something special.
if @new_resource.status_command
- Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
+ logger.trace("#{@new_resource} you have specified a status command, running..")
begin
if shell_out!(@new_resource.status_command).exitstatus == 0
- @current_resource.running true
+ @upstart_service_running = true
end
rescue
@command_success = false
- @current_resource.running false
+ @upstart_service_running = false
nil
end
else
begin
- if upstart_state == "running"
- @current_resource.running true
+ if upstart_goal_state == "start"
+ @upstart_service_running = true
else
- @current_resource.running false
+ @upstart_service_running = false
end
rescue Chef::Exceptions::Exec
@command_success = false
- @current_resource.running false
+ @upstart_service_running = false
nil
end
end
# Get enabled/disabled state by reading job configuration file
if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
- Chef::Log.debug("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ logger.trace("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
::File.open("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}", "r") do |file|
while line = file.gets
case line
when /^start on/
- Chef::Log.debug("#{@new_resource} enabled: #{line.chomp}")
+ logger.trace("#{@new_resource} enabled: #{line.chomp}")
@current_resource.enabled true
break
when /^#start on/
- Chef::Log.debug("#{@new_resource} disabled: #{line.chomp}")
+ logger.trace("#{@new_resource} disabled: #{line.chomp}")
@current_resource.enabled false
break
end
@@ -146,18 +152,19 @@ class Chef
end
else
@config_file_found = false
- Chef::Log.debug("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ logger.trace("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
@current_resource.enabled false
end
+ @current_resource.running @upstart_service_running
@current_resource
end
def start_service
# Calling start on a service that is already started will return 1
# Our 'goal' when we call start is to ensure the service is started
- if @current_resource.running
- Chef::Log.debug("#{@new_resource} already running, not starting")
+ if @upstart_service_running
+ logger.trace("#{@new_resource} already running, not starting")
else
if @new_resource.start_command
super
@@ -165,13 +172,15 @@ class Chef
shell_out_with_systems_locale!("/sbin/start #{@job}")
end
end
+
+ @upstart_service_running = true
end
def stop_service
# Calling stop on a service that is already stopped will return 1
# Our 'goal' when we call stop is to ensure the service is stopped
- unless @current_resource.running
- Chef::Log.debug("#{@new_resource} not running, not stopping")
+ unless @upstart_service_running
+ logger.trace("#{@new_resource} not running, not stopping")
else
if @new_resource.stop_command
super
@@ -179,6 +188,8 @@ class Chef
shell_out_with_systems_locale!("/sbin/stop #{@job}")
end
end
+
+ @upstart_service_running = false
end
def restart_service
@@ -186,13 +197,19 @@ class Chef
super
# Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start.
# Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883
+ # But for safe working of latest upstart job config being loaded, 'restart' can't be used as per link
+ # http://upstart.ubuntu.com/cookbook/#restart (it doesn't uses latest jon config from disk but retains old)
else
- if @current_resource.running
- shell_out_with_systems_locale!("/sbin/restart #{@job}")
+ if @upstart_service_running
+ stop_service
+ sleep 1
+ start_service
else
start_service
end
end
+
+ @upstart_service_running = true
end
def reload_service
@@ -202,37 +219,38 @@ class Chef
# upstart >= 0.6.3-4 supports reload (HUP)
shell_out_with_systems_locale!("/sbin/reload #{@job}")
end
+
+ @upstart_service_running = true
end
# https://bugs.launchpad.net/upstart/+bug/94065
def enable_service
- Chef::Log.debug("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file")
+ logger.trace("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file")
conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
conf.search_file_replace(/^#start on/, "start on")
conf.write_file
end
def disable_service
- Chef::Log.debug("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file")
+ logger.trace("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file")
conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
conf.search_file_replace(/^start on/, "#start on")
conf.write_file
end
- def upstart_state
+ def upstart_goal_state
command = "/sbin/status #{@job}"
- status = popen4(command) do |pid, stdin, stdout, stderr|
- stdout.each_line do |line|
- # service goal/state
- # OR
- # service (instance) goal/state
- # OR
- # service (goal) state
- line =~ UPSTART_STATE_FORMAT
- data = Regexp.last_match
- return data[2]
- end
+ so = shell_out(command)
+ so.stdout.each_line do |line|
+ # service goal/state
+ # OR
+ # service (instance) goal/state
+ # OR
+ # service (goal) state
+ line =~ UPSTART_STATE_FORMAT
+ data = Regexp.last_match
+ return data[1]
end
end
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index 9bfd9238cd..417ec03ef4 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -2,7 +2,7 @@
# Author:: Nuo Yan <nuo@chef.io>
# Author:: Bryan McLellan <btm@loftninjas.org>
# Author:: Seth Chisamore <schisamo@chef.io>
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright 2010-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +19,7 @@
#
require "chef/provider/service/simple"
+require "chef/win32_service_constants"
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
require "chef/win32/error"
require "win32/service"
@@ -26,10 +27,11 @@ end
class Chef::Provider::Service::Windows < Chef::Provider::Service
provides :service, os: "windows"
- provides :windows_service, os: "windows"
+ provides :windows_service
include Chef::Mixin::ShellOut
include Chef::ReservedNames::Win32::API::Error rescue LoadError
+ include Chef::Win32ServiceConstants
#Win32::Service.get_start_type
AUTO_START = "auto start"
@@ -49,23 +51,34 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
SERVICE_RIGHT = "SeServiceLogonRight"
- def whyrun_supported?
- false
- end
-
def load_current_resource
- @current_resource = Chef::Resource::WindowsService.new(@new_resource.name)
- @current_resource.service_name(@new_resource.service_name)
- @current_resource.running(current_state == RUNNING)
- Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
- case current_start_type
- when AUTO_START
- @current_resource.enabled(true)
- when DISABLED
- @current_resource.enabled(false)
+ @current_resource = Chef::Resource::WindowsService.new(new_resource.name)
+ current_resource.service_name(new_resource.service_name)
+
+ if Win32::Service.exists?(current_resource.service_name)
+ current_resource.running(current_state == RUNNING)
+ logger.trace "#{new_resource} running: #{current_resource.running}"
+ case current_startup_type
+ when :automatic
+ current_resource.enabled(true)
+ when :disabled
+ current_resource.enabled(false)
+ end
+ logger.trace "#{new_resource} enabled: #{current_resource.enabled}"
+
+ config_info = Win32::Service.config_info(current_resource.service_name)
+ current_resource.service_type(get_service_type(config_info.service_type)) if config_info.service_type
+ current_resource.startup_type(start_type_to_sym(config_info.start_type)) if config_info.start_type
+ current_resource.error_control(get_error_control(config_info.error_control)) if config_info.error_control
+ current_resource.binary_path_name(config_info.binary_path_name) if config_info.binary_path_name
+ current_resource.load_order_group(config_info.load_order_group) if config_info.load_order_group
+ current_resource.dependencies(config_info.dependencies) if config_info.dependencies
+ current_resource.run_as_user(config_info.service_start_name) if config_info.service_start_name
+ current_resource.display_name(config_info.display_name) if config_info.display_name
+ current_resource.delayed_start(current_delayed_start) if current_delayed_start
end
- Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}"
- @current_resource
+
+ current_resource
end
def start_service
@@ -78,9 +91,10 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
}.reject { |k, v| v.nil? || v.length == 0 }
Win32::Service.configure(new_config)
- Chef::Log.info "#{@new_resource} configured with #{new_config.inspect}"
+ logger.info "#{@new_resource} configured with #{new_config.inspect}"
- if new_config.has_key?(:service_start_name)
+ # LocalSystem is the default runas user, which is a special service account that should ultimately have the rights of BUILTIN\Administrators, but we wouldn't see that from get_account_right
+ if new_config.has_key?(:service_start_name) && new_config[:service_start_name].casecmp("localsystem") != 0
unless Chef::ReservedNames::Win32::Security.get_account_right(canonicalize_username(new_config[:service_start_name])).include?(SERVICE_RIGHT)
grant_service_logon(new_config[:service_start_name])
end
@@ -88,13 +102,13 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
state = current_state
if state == RUNNING
- Chef::Log.debug "#{@new_resource} already started - nothing to do"
+ logger.trace "#{@new_resource} already started - nothing to do"
elsif state == START_PENDING
- Chef::Log.debug "#{@new_resource} already sent start signal - waiting for start"
+ logger.trace "#{@new_resource} already sent start signal - waiting for start"
wait_for_state(RUNNING)
elsif state == STOPPED
if @new_resource.start_command
- Chef::Log.debug "#{@new_resource} starting service using the given start_command"
+ logger.trace "#{@new_resource} starting service using the given start_command"
shell_out!(@new_resource.start_command)
else
spawn_command_thread do
@@ -102,7 +116,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
Win32::Service.start(@new_resource.service_name)
rescue SystemCallError => ex
if ex.errno == ERROR_SERVICE_LOGON_FAILED
- Chef::Log.error ex.message
+ logger.error ex.message
raise Chef::Exceptions::Service,
"Service #{@new_resource} did not start due to a logon failure (error #{ERROR_SERVICE_LOGON_FAILED}): possibly the specified user '#{@new_resource.run_as_user}' does not have the 'log on as a service' privilege, or the password is incorrect."
else
@@ -117,7 +131,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
raise Chef::Exceptions::Service, "Service #{@new_resource} can't be started from state [#{state}]"
end
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
end
end
@@ -126,7 +140,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
state = current_state
if state == RUNNING
if @new_resource.stop_command
- Chef::Log.debug "#{@new_resource} stopping service using the given stop_command"
+ logger.trace "#{@new_resource} stopping service using the given stop_command"
shell_out!(@new_resource.stop_command)
else
spawn_command_thread do
@@ -136,22 +150,22 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
@new_resource.updated_by_last_action(true)
elsif state == STOPPED
- Chef::Log.debug "#{@new_resource} already stopped - nothing to do"
+ logger.trace "#{@new_resource} already stopped - nothing to do"
elsif state == STOP_PENDING
- Chef::Log.debug "#{@new_resource} already sent stop signal - waiting for stop"
+ logger.trace "#{@new_resource} already sent stop signal - waiting for stop"
wait_for_state(STOPPED)
else
raise Chef::Exceptions::Service, "Service #{@new_resource} can't be stopped from state [#{state}]"
end
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
end
end
def restart_service
if Win32::Service.exists?(@new_resource.service_name)
if @new_resource.restart_command
- Chef::Log.debug "#{@new_resource} restarting service using the given restart_command"
+ logger.trace "#{@new_resource} restarting service using the given restart_command"
shell_out!(@new_resource.restart_command)
else
stop_service
@@ -159,7 +173,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
@new_resource.updated_by_last_action(true)
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
end
end
@@ -167,7 +181,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
if Win32::Service.exists?(@new_resource.service_name)
set_startup_type(:automatic)
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
end
end
@@ -175,62 +189,88 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
if Win32::Service.exists?(@new_resource.service_name)
set_startup_type(:disabled)
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
+ end
+ end
+
+ action :create do
+ if Win32::Service.exists?(new_resource.service_name)
+ logger.trace "#{new_resource} already exists - nothing to do"
+ return
+ end
+
+ converge_by("create service #{new_resource.service_name}") do
+ Win32::Service.new(windows_service_config)
end
+
+ converge_delayed_start
+ end
+
+ action :delete do
+ unless Win32::Service.exists?(new_resource.service_name)
+ logger.trace "#{new_resource} does not exist - nothing to do"
+ return
+ end
+
+ converge_by("delete service #{new_resource.service_name}") do
+ Win32::Service.delete(new_resource.service_name)
+ end
+ end
+
+ action :configure do
+ unless Win32::Service.exists?(new_resource.service_name)
+ logger.warn "#{new_resource} does not exist. Maybe you need to prepend action :create"
+ return
+ end
+
+ # Until #6300 is solved this is required
+ if new_resource.run_as_user == new_resource.class.properties[:run_as_user].default
+ new_resource.run_as_user = new_resource.class.properties[:run_as_user].default
+ end
+
+ converge_if_changed :service_type, :startup_type, :error_control,
+ :binary_path_name, :load_order_group, :dependencies,
+ :run_as_user, :display_name, :description do
+ Win32::Service.configure(windows_service_config(:configure))
+ end
+
+ converge_delayed_start
end
def action_enable
- if current_start_type != AUTO_START
+ if current_startup_type != :automatic
converge_by("enable service #{@new_resource}") do
enable_service
- Chef::Log.info("#{@new_resource} enabled")
+ logger.info("#{@new_resource} enabled")
end
else
- Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+ logger.trace("#{@new_resource} already enabled - nothing to do")
end
load_new_resource_state
@new_resource.enabled(true)
end
def action_disable
- if current_start_type != DISABLED
+ if current_startup_type != :disabled
converge_by("disable service #{@new_resource}") do
disable_service
- Chef::Log.info("#{@new_resource} disabled")
+ logger.info("#{@new_resource} disabled")
end
else
- Chef::Log.debug("#{@new_resource} already disabled - nothing to do")
+ logger.trace("#{@new_resource} already disabled - nothing to do")
end
load_new_resource_state
@new_resource.enabled(false)
end
def action_configure_startup
- case @new_resource.startup_type
- when :automatic
- if current_start_type != AUTO_START
- converge_by("set service #{@new_resource} startup type to automatic") do
- set_startup_type(:automatic)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already automatic - nothing to do")
- end
- when :manual
- if current_start_type != MANUAL
- converge_by("set service #{@new_resource} startup type to manual") do
- set_startup_type(:manual)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already manual - nothing to do")
- end
- when :disabled
- if current_start_type != DISABLED
- converge_by("set service #{@new_resource} startup type to disabled") do
- set_startup_type(:disabled)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already disabled - nothing to do")
+ startup_type = @new_resource.startup_type
+ if current_startup_type != startup_type
+ converge_by("set service #{@new_resource} startup type to #{startup_type}") do
+ set_startup_type(startup_type)
end
+ else
+ logger.trace("#{@new_resource} startup_type already #{startup_type} - nothing to do")
end
# Avoid changing enabled from true/false for now
@@ -239,15 +279,23 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
private
+ def current_delayed_start
+ if service = Win32::Service.services.find { |x| x.service_name == new_resource.service_name }
+ service.delayed_start == 0 ? false : true
+ else
+ nil
+ end
+ end
+
def grant_service_logon(username)
begin
Chef::ReservedNames::Win32::Security.add_account_right(canonicalize_username(username), SERVICE_RIGHT)
rescue Chef::Exceptions::Win32APIError => err
- Chef::Log.fatal "Logon-as-service grant failed with output: #{err}"
+ logger.fatal "Logon-as-service grant failed with output: #{err}"
raise Chef::Exceptions::Service, "Logon-as-service grant failed for #{username}: #{err}"
end
- Chef::Log.info "Grant logon-as-service to user '#{username}' successful."
+ logger.info "Grant logon-as-service to user '#{username}' successful."
true
end
@@ -264,8 +312,9 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
Win32::Service.status(@new_resource.service_name).current_state
end
- def current_start_type
- Win32::Service.config_info(@new_resource.service_name).start_type
+ def current_startup_type
+ start_type = Win32::Service.config_info(@new_resource.service_name).start_type
+ start_type_to_sym(start_type)
end
# Helper method that waits for a status to change its state since state
@@ -293,21 +342,143 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
end
- # Takes Win32::Service start_types
- def set_startup_type(type)
- # Set-Service Startup Type => Win32::Service Constant
- allowed_types = { :automatic => Win32::Service::AUTO_START,
- :manual => Win32::Service::DEMAND_START,
- :disabled => Win32::Service::DISABLED }
- unless allowed_types.keys.include?(type)
+ # @param type [Symbol]
+ # @return [Integer]
+ # @raise [Chef::Exceptions::ConfigurationError] if the startup type is
+ # not supported.
+ # @see Chef::Resource::WindowsService::ALLOWED_START_TYPES
+ def startup_type_to_int(type)
+ Chef::Resource::WindowsService::ALLOWED_START_TYPES.fetch(type) do
raise Chef::Exceptions::ConfigurationError, "#{@new_resource.name}: Startup type '#{type}' is not supported"
end
+ end
- Chef::Log.debug "#{@new_resource.name} setting start_type to #{type}"
+ # Takes Win32::Service start_types
+ def set_startup_type(type)
+ startup_type = startup_type_to_int(type)
+
+ logger.trace "#{@new_resource.name} setting start_type to #{type}"
Win32::Service.configure(
:service_name => @new_resource.service_name,
- :start_type => allowed_types[type]
+ :start_type => startup_type
)
@new_resource.updated_by_last_action(true)
end
+
+ def windows_service_config(action = :create)
+ config = {}
+
+ config[:service_name] = new_resource.service_name
+ config[:display_name] = new_resource.display_name if new_resource.display_name
+ config[:service_type] = new_resource.service_type if new_resource.service_type
+ config[:start_type] = startup_type_to_int(new_resource.startup_type) if new_resource.startup_type
+ config[:error_control] = new_resource.error_control if new_resource.error_control
+ config[:binary_path_name] = new_resource.binary_path_name if new_resource.binary_path_name
+ config[:load_order_group] = new_resource.load_order_group if new_resource.load_order_group
+ config[:dependencies] = new_resource.dependencies if new_resource.dependencies
+ config[:service_start_name] = new_resource.run_as_user unless new_resource.run_as_user.empty?
+ config[:password] = new_resource.run_as_password unless new_resource.run_as_user.empty? || new_resource.run_as_password.empty?
+ config[:description] = new_resource.description if new_resource.description
+
+ case action
+ when :create
+ config[:desired_access] = new_resource.desired_access if new_resource.desired_access
+ end
+
+ config
+ end
+
+ def converge_delayed_start
+ config = {}
+ config[:service_name] = new_resource.service_name
+ config[:delayed_start] = new_resource.delayed_start ? 1 : 0
+
+ # Until #6300 is solved this is required
+ if new_resource.delayed_start == new_resource.class.properties[:delayed_start].default
+ new_resource.delayed_start = new_resource.class.properties[:delayed_start].default
+ end
+
+ converge_if_changed :delayed_start do
+ Win32::Service.configure(config)
+ end
+ end
+
+ # @return [Symbol]
+ def start_type_to_sym(start_type)
+ case start_type
+ when "auto start"
+ :automatic
+ when "boot start"
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ when "demand start"
+ :manual
+ when "disabled"
+ :disabled
+ when "system start"
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ else
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ end
+ end
+
+ def get_service_type(service_type)
+ case service_type
+ when "file system driver"
+ SERVICE_FILE_SYSTEM_DRIVER
+ when "kernel driver"
+ SERVICE_KERNEL_DRIVER
+ when "own process"
+ SERVICE_WIN32_OWN_PROCESS
+ when "share process"
+ SERVICE_WIN32_SHARE_PROCESS
+ when "recognizer driver"
+ SERVICE_RECOGNIZER_DRIVER
+ when "driver"
+ SERVICE_DRIVER
+ when "win32"
+ SERVICE_WIN32
+ when "all"
+ SERVICE_TYPE_ALL
+ when "own process, interactive"
+ SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
+ when "share process, interactive"
+ SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
+ else
+ raise("Unsupported service type, #{service_type}. Submit bug request to fix.")
+ end
+ end
+
+ # @return [Integer]
+ def get_start_type(start_type)
+ case start_type
+ when "auto start"
+ SERVICE_AUTO_START
+ when "boot start"
+ SERVICE_BOOT_START
+ when "demand start"
+ SERVICE_DEMAND_START
+ when "disabled"
+ SERVICE_DISABLED
+ when "system start"
+ SERVICE_SYSTEM_START
+ else
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ end
+ end
+
+ def get_error_control(error_control)
+ case error_control
+ when "critical"
+ SERVICE_ERROR_CRITICAL
+ when "ignore"
+ SERVICE_ERROR_IGNORE
+ when "normal"
+ SERVICE_ERROR_NORMAL
+ when "severe"
+ SERVICE_ERROR_SEVERE
+ else
+ nil
+ end
+ end
+
end
diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb
index ea32283bc9..abcc260a78 100644
--- a/lib/chef/provider/subversion.rb
+++ b/lib/chef/provider/subversion.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,7 +20,6 @@
require "chef/log"
require "chef/provider"
-require "chef/mixin/command"
require "chef-config/mixin/fuzzy_hostname_matcher"
require "fileutils"
@@ -32,19 +31,14 @@ class Chef
SVN_INFO_PATTERN = /^([\w\s]+): (.+)$/
- include Chef::Mixin::Command
include ChefConfig::Mixin::FuzzyHostnameMatcher
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Subversion.new(@new_resource.name)
+ @current_resource = Chef::Resource::Subversion.new(new_resource.name)
- unless [:export, :force_export].include?(Array(@new_resource.action).first)
+ unless [:export, :force_export].include?(Array(new_resource.action).first)
if current_revision = find_current_revision
- @current_resource.revision current_revision
+ current_resource.revision current_revision
end
end
end
@@ -53,21 +47,21 @@ class Chef
requirements.assert(:all_actions) do |a|
# Make sure the parent dir exists, or else fail.
# for why run, print a message explaining the potential error.
- parent_directory = ::File.dirname(@new_resource.destination)
+ parent_directory = ::File.dirname(new_resource.destination)
a.assertion { ::File.directory?(parent_directory) }
a.failure_message(Chef::Exceptions::MissingParentDirectory,
- "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{parent_directory} does not exist")
+ "Cannot clone #{new_resource} to #{new_resource.destination}, the enclosing directory #{parent_directory} does not exist")
a.whyrun("Directory #{parent_directory} does not exist, assuming it would have been created")
end
end
def action_checkout
if target_dir_non_existent_or_empty?
- converge_by("perform checkout of #{@new_resource.repository} into #{@new_resource.destination}") do
+ converge_by("perform checkout of #{new_resource.repository} into #{new_resource.destination}") do
shell_out!(checkout_command, run_options)
end
else
- Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
+ logger.trace "#{new_resource} checkout destination #{new_resource.destination} already exists or is a non-empty directory - nothing to do"
end
end
@@ -75,25 +69,25 @@ class Chef
if target_dir_non_existent_or_empty?
action_force_export
else
- Chef::Log.debug "#{@new_resource} export destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
+ logger.trace "#{new_resource} export destination #{new_resource.destination} already exists or is a non-empty directory - nothing to do"
end
end
def action_force_export
- converge_by("export #{@new_resource.repository} into #{@new_resource.destination}") do
+ converge_by("export #{new_resource.repository} into #{new_resource.destination}") do
shell_out!(export_command, run_options)
end
end
def action_sync
assert_target_directory_valid!
- if ::File.exist?(::File.join(@new_resource.destination, ".svn"))
+ if ::File.exist?(::File.join(new_resource.destination, ".svn"))
current_rev = find_current_revision
- Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{revision_int}"
+ logger.trace "#{new_resource} current revision: #{current_rev} target revision: #{revision_int}"
unless current_revision_matches_target_revision?
- converge_by("sync #{@new_resource.destination} from #{@new_resource.repository}") do
+ converge_by("sync #{new_resource.destination} from #{new_resource.repository}") do
shell_out!(sync_command, run_options)
- Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}"
+ logger.info "#{new_resource} updated to revision: #{revision_int}"
end
end
else
@@ -102,24 +96,24 @@ class Chef
end
def sync_command
- c = scm :update, @new_resource.svn_arguments, verbose, authentication, proxy, "-r#{revision_int}", @new_resource.destination
- Chef::Log.debug "#{@new_resource} updated working copy #{@new_resource.destination} to revision #{@new_resource.revision}"
+ c = scm :update, new_resource.svn_arguments, verbose, authentication, proxy, "-r#{revision_int}", new_resource.destination
+ logger.trace "#{new_resource} updated working copy #{new_resource.destination} to revision #{new_resource.revision}"
c
end
def checkout_command
- c = scm :checkout, @new_resource.svn_arguments, verbose, authentication, proxy,
- "-r#{revision_int}", @new_resource.repository, @new_resource.destination
- Chef::Log.info "#{@new_resource} checked out #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}"
+ c = scm :checkout, new_resource.svn_arguments, verbose, authentication, proxy,
+ "-r#{revision_int}", new_resource.repository, new_resource.destination
+ logger.info "#{new_resource} checked out #{new_resource.repository} at revision #{new_resource.revision} to #{new_resource.destination}"
c
end
def export_command
args = ["--force"]
- args << @new_resource.svn_arguments << verbose << authentication << proxy <<
- "-r#{revision_int}" << @new_resource.repository << @new_resource.destination
+ args << new_resource.svn_arguments << verbose << authentication << proxy <<
+ "-r#{revision_int}" << new_resource.repository << new_resource.destination
c = scm :export, *args
- Chef::Log.info "#{@new_resource} exported #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}"
+ logger.info "#{new_resource} exported #{new_resource.repository} at revision #{new_resource.revision} to #{new_resource.destination}"
c
end
@@ -128,10 +122,10 @@ class Chef
# If the specified revision is an integer, trust it.
def revision_int
@revision_int ||= begin
- if @new_resource.revision =~ /^\d+$/
- @new_resource.revision
+ if new_resource.revision =~ /^\d+$/
+ new_resource.revision
else
- command = scm(:info, @new_resource.repository, @new_resource.svn_info_args, authentication, "-r#{@new_resource.revision}")
+ command = scm(:info, new_resource.repository, new_resource.svn_info_args, authentication, "-r#{new_resource.revision}")
svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0, 1])).stdout
extract_revision_info(svn_info)
@@ -142,7 +136,7 @@ class Chef
alias :revision_slug :revision_int
def find_current_revision
- return nil unless ::File.exist?(::File.join(@new_resource.destination, ".svn"))
+ return nil unless ::File.exist?(::File.join(new_resource.destination, ".svn"))
command = scm(:info)
svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0, 1])).stdout
@@ -150,20 +144,20 @@ class Chef
end
def current_revision_matches_target_revision?
- (!@current_resource.revision.nil?) && (revision_int.strip.to_i == @current_resource.revision.strip.to_i)
+ (!current_resource.revision.nil?) && (revision_int.strip.to_i == current_resource.revision.strip.to_i)
end
def run_options(run_opts = {})
- run_opts[:user] = @new_resource.user if @new_resource.user
- run_opts[:group] = @new_resource.group if @new_resource.group
- run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout
+ run_opts[:user] = new_resource.user if new_resource.user
+ run_opts[:group] = new_resource.group if new_resource.group
+ run_opts[:timeout] = new_resource.timeout if new_resource.timeout
run_opts
end
private
def cwd
- @new_resource.destination
+ new_resource.destination
end
def verbose
@@ -181,7 +175,7 @@ class Chef
rev = (repo_attrs["Last Changed Rev"] || repo_attrs["Revision"])
rev.strip! if rev
raise "Could not parse `svn info` data: #{svn_info}" if repo_attrs.empty?
- Chef::Log.debug "#{@new_resource} resolved revision #{@new_resource.revision} to #{rev}"
+ logger.trace "#{new_resource} resolved revision #{new_resource.revision} to #{rev}"
rev
end
@@ -190,14 +184,14 @@ class Chef
# switch, since Capistrano will check for that prompt in the output
# and will respond appropriately.
def authentication
- return "" unless @new_resource.svn_username
- result = "--username #{@new_resource.svn_username} "
- result << "--password #{@new_resource.svn_password} "
+ return "" unless new_resource.svn_username
+ result = "--username #{new_resource.svn_username} "
+ result << "--password #{new_resource.svn_password} "
result
end
def proxy
- repo_uri = URI.parse(@new_resource.repository)
+ repo_uri = URI.parse(new_resource.repository)
proxy_uri = Chef::Config.proxy_uri(repo_uri.scheme, repo_uri.host, repo_uri.port)
return "" if proxy_uri.nil?
@@ -213,18 +207,18 @@ class Chef
end
def target_dir_non_existent_or_empty?
- !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == [".", ".."]
+ !::File.exist?(new_resource.destination) || Dir.entries(new_resource.destination).sort == [".", ".."]
end
def svn_binary
- @new_resource.svn_binary ||
+ new_resource.svn_binary ||
(Chef::Platform.windows? ? "svn.exe" : "svn")
end
def assert_target_directory_valid!
- target_parent_directory = ::File.dirname(@new_resource.destination)
+ target_parent_directory = ::File.dirname(new_resource.destination)
unless ::File.directory?(target_parent_directory)
- msg = "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{target_parent_directory} does not exist"
+ msg = "Cannot clone #{new_resource} to #{new_resource.destination}, the enclosing directory #{target_parent_directory} does not exist"
raise Chef::Exceptions::MissingParentDirectory, msg
end
end
diff --git a/lib/chef/provider/support/yum_repo.erb b/lib/chef/provider/support/yum_repo.erb
new file mode 100644
index 0000000000..f60d8688da
--- /dev/null
+++ b/lib/chef/provider/support/yum_repo.erb
@@ -0,0 +1,138 @@
+# This file was generated by Chef
+# Do NOT modify this file by hand.
+
+[<%= @config.repositoryid %>]
+name=<%= @config.description %>
+<% if @config.baseurl %>
+baseurl=<%= case @config.baseurl
+ when Array
+ @config.baseurl.join("\n")
+ else
+ @config.baseurl
+ end %>
+<% end -%>
+<% if @config.cost %>
+cost=<%= @config.cost %>
+<% end %>
+<% if @config.enabled %>
+enabled=1
+<% else %>
+enabled=0
+<% end %>
+<% if @config.enablegroups %>
+enablegroups=1
+<% end %>
+<% if @config.exclude %>
+exclude=<%= @config.exclude %>
+<% end %>
+<% if @config.failovermethod %>
+failovermethod=<%= @config.failovermethod %>
+<% end %>
+<% if @config.fastestmirror_enabled %>
+fastestmirror_enabled=1
+<% else %>
+fastestmirror_enabled=0
+<% end %>
+<% if @config.gpgcheck %>
+gpgcheck=1
+<% else %>
+gpgcheck=0
+<% end %>
+<% if @config.gpgkey %>
+gpgkey=<%= case @config.gpgkey
+ when Array
+ @config.gpgkey.join("\n ")
+ else
+ @config.gpgkey
+ end %>
+<% end -%>
+<% if @config.http_caching %>
+http_caching=<%= @config.http_caching %>
+<% end %>
+<% if @config.include_config %>
+include=<%= @config.include_config %>
+<% end %>
+<% if @config.includepkgs %>
+includepkgs=<%= @config.includepkgs %>
+<% end %>
+<% if @config.keepalive %>
+keepalive=1
+<% end %>
+<% if @config.metadata_expire %>
+metadata_expire=<%= @config.metadata_expire %>
+<% end %>
+<% if @config.metalink %>
+metalink=<%= @config.metalink %>
+<% end %>
+<% if @config.mirrorlist %>
+mirrorlist=<%= @config.mirrorlist %>
+<% end %>
+<% if @config.mirror_expire %>
+mirror_expire=<%= @config.mirror_expire %>
+<% end %>
+<% if @config.mirrorlist_expire %>
+mirrorlist_expire=<%= @config.mirrorlist_expire %>
+<% end %>
+<% if @config.priority %>
+priority=<%= @config.priority %>
+<% end %>
+<% if @config.proxy %>
+proxy=<%= @config.proxy %>
+<% end %>
+<% if @config.proxy_username %>
+proxy_username=<%= @config.proxy_username %>
+<% end %>
+<% if @config.proxy_password %>
+proxy_password=<%= @config.proxy_password %>
+<% end %>
+<% if @config.username %>
+username=<%= @config.username %>
+<% end %>
+<% if @config.password %>
+password=<%= @config.password %>
+<% end %>
+<% if @config.repo_gpgcheck %>
+repo_gpgcheck=1
+<% end %>
+<% if @config.max_retries %>
+retries=<%= @config.max_retries %>
+<% end %>
+<% if @config.report_instanceid %>
+report_instanceid=<%= @config.report_instanceid %>
+<% end %>
+<% if @config.skip_if_unavailable %>
+skip_if_unavailable=1
+<% end %>
+<% if @config.sslcacert %>
+sslcacert=<%= @config.sslcacert %>
+<% end %>
+<% if @config.sslclientcert %>
+sslclientcert=<%= @config.sslclientcert %>
+<% end %>
+<% if @config.sslclientkey %>
+sslclientkey=<%= @config.sslclientkey %>
+<% end %>
+<% unless @config.sslverify.nil? %>
+sslverify=<%= ( @config.sslverify ) ? 'true' : 'false' %>
+<% end %>
+<% if @config.throttle %>
+throttle=<%= @config.throttle %>
+<% end %>
+<% if @config.timeout %>
+timeout=<%= @config.timeout %>
+<% end %>
+<% if @config.options -%>
+<% @config.options.each do |key, value| -%>
+<%= key %>=<%=
+ case value
+ when Array
+ value.join("\n ")
+ when TrueClass
+ '1'
+ when FalseClass
+ '0'
+ else
+ value
+ end %>
+<% end -%>
+<% end -%>
diff --git a/lib/chef/provider/support/zypper_repo.erb b/lib/chef/provider/support/zypper_repo.erb
new file mode 100644
index 0000000000..6d508fa77f
--- /dev/null
+++ b/lib/chef/provider/support/zypper_repo.erb
@@ -0,0 +1,17 @@
+# This file was generated by Chef
+# Do NOT modify this file by hand.
+
+[<%= @config.repo_name %>]
+<% %w{ type enabled autorefresh gpgcheck gpgkey baseurl mirrorlist path priority keeppackages mode refresh_cache }.each do |prop| -%>
+<% next if @config.send(prop.to_sym).nil? -%>
+<%= prop %>=<%=
+ case @config.send(prop.to_sym)
+ when TrueClass
+ '1'
+ when FalseClass
+ '0'
+ else
+ @config.send(prop.to_sym)
+ end %>
+<% end -%>
+name=<%= @config.description || @config.repo_name %>
diff --git a/lib/chef/provider/systemd_unit.rb b/lib/chef/provider/systemd_unit.rb
index db71a6c234..d8c83d2b4b 100644
--- a/lib/chef/provider/systemd_unit.rb
+++ b/lib/chef/provider/systemd_unit.rb
@@ -1,6 +1,6 @@
#
# Author:: Nathan Williams (<nath.e.will@gmail.com>)
-# Copyright:: Copyright 2016, Nathan Williams
+# Copyright:: Copyright 2016-2018, Nathan Williams
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,6 +20,9 @@ require "chef/provider"
require "chef/mixin/which"
require "chef/mixin/shell_out"
require "chef/resource/file"
+require "chef/resource/file/verification/systemd_unit"
+require "iniparse"
+require "shellwords"
class Chef
class Provider
@@ -27,11 +30,12 @@ class Chef
include Chef::Mixin::Which
include Chef::Mixin::ShellOut
- provides :systemd_unit, os: "linux"
+ provides :systemd_unit
def load_current_resource
@current_resource = Chef::Resource::SystemdUnit.new(new_resource.name)
+ current_resource.unit_name(new_resource.unit_name)
current_resource.content(::File.read(unit_path)) if ::File.exist?(unit_path)
current_resource.user(new_resource.user)
current_resource.enabled(enabled?)
@@ -43,9 +47,18 @@ class Chef
current_resource
end
+ def define_resource_requirements
+ super
+
+ requirements.assert(:create) do |a|
+ a.assertion { IniParse.parse(new_resource.to_ini) }
+ a.failure_message "Unit content is not valid INI text"
+ end
+ end
+
def action_create
if current_resource.content != new_resource.to_ini
- converge_by("creating unit: #{new_resource.name}") do
+ converge_by("creating unit: #{new_resource.unit_name}") do
manage_unit_file(:create)
daemon_reload if new_resource.triggers_reload
end
@@ -54,108 +67,144 @@ class Chef
def action_delete
if ::File.exist?(unit_path)
- converge_by("deleting unit: #{new_resource.name}") do
+ converge_by("deleting unit: #{new_resource.unit_name}") do
manage_unit_file(:delete)
daemon_reload if new_resource.triggers_reload
end
end
end
+ def action_preset
+ converge_by("restoring enable/disable preset configuration for unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:preset, new_resource.unit_name)
+ end
+ end
+
+ def action_revert
+ converge_by("reverting to vendor version of unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:revert, new_resource.unit_name)
+ end
+ end
+
def action_enable
if current_resource.static
- Chef::Log.debug("#{new_resource.name} is a static unit, enabling is a NOP.")
+ logger.trace("#{new_resource.unit_name} is a static unit, enabling is a NOP.")
end
unless current_resource.enabled || current_resource.static
- converge_by("enabling unit: #{new_resource.name}") do
- systemctl_execute!(:enable, new_resource.name)
+ converge_by("enabling unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:enable, new_resource.unit_name)
end
end
end
def action_disable
if current_resource.static
- Chef::Log.debug("#{new_resource.name} is a static unit, disabling is a NOP.")
+ logger.trace("#{new_resource.unit_name} is a static unit, disabling is a NOP.")
end
if current_resource.enabled && !current_resource.static
- converge_by("disabling unit: #{new_resource.name}") do
- systemctl_execute!(:disable, new_resource.name)
+ converge_by("disabling unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:disable, new_resource.unit_name)
end
end
end
+ def action_reenable
+ converge_by("reenabling unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:reenable, new_resource.unit_name)
+ end
+ end
+
def action_mask
unless current_resource.masked
- converge_by("masking unit: #{new_resource.name}") do
- systemctl_execute!(:mask, new_resource.name)
+ converge_by("masking unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:mask, new_resource.unit_name)
end
end
end
def action_unmask
if current_resource.masked
- converge_by("unmasking unit: #{new_resource.name}") do
- systemctl_execute!(:unmask, new_resource.name)
+ converge_by("unmasking unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:unmask, new_resource.unit_name)
end
end
end
def action_start
unless current_resource.active
- converge_by("starting unit: #{new_resource.name}") do
- systemctl_execute!(:start, new_resource.name)
+ converge_by("starting unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:start, new_resource.unit_name)
end
end
end
def action_stop
if current_resource.active
- converge_by("stopping unit: #{new_resource.name}") do
- systemctl_execute!(:stop, new_resource.name)
+ converge_by("stopping unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:stop, new_resource.unit_name)
end
end
end
def action_restart
- converge_by("restarting unit: #{new_resource.name}") do
- systemctl_execute!(:restart, new_resource.name)
+ converge_by("restarting unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:restart, new_resource.unit_name)
end
end
def action_reload
if current_resource.active
- converge_by("reloading unit: #{new_resource.name}") do
- systemctl_execute!(:reload, new_resource.name)
+ converge_by("reloading unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:reload, new_resource.unit_name)
end
else
- Chef::Log.debug("#{new_resource.name} is not active, skipping reload.")
+ logger.trace("#{new_resource.unit_name} is not active, skipping reload.")
+ end
+ end
+
+ def action_try_restart
+ converge_by("try-restarting unit: #{new_resource.unit_name}") do
+ systemctl_execute!("try-restart", new_resource.unit_name)
+ end
+ end
+
+ def action_reload_or_restart
+ converge_by("reload-or-restarting unit: #{new_resource.unit_name}") do
+ systemctl_execute!("reload-or-restart", new_resource.unit_name)
+ end
+ end
+
+ def action_reload_or_try_restart
+ converge_by("reload-or-try-restarting unit: #{new_resource.unit_name}") do
+ systemctl_execute!("reload-or-try-restart", new_resource.unit_name)
end
end
def active?
- systemctl_execute("is-active", new_resource.name).exitstatus == 0
+ systemctl_execute("is-active", new_resource.unit_name).exitstatus == 0
end
def enabled?
- systemctl_execute("is-enabled", new_resource.name).exitstatus == 0
+ systemctl_execute("is-enabled", new_resource.unit_name).exitstatus == 0
end
def masked?
- systemctl_execute(:status, new_resource.name).stdout.include?("masked")
+ systemctl_execute(:status, new_resource.unit_name).stdout.include?("masked")
end
def static?
- systemctl_execute("is-enabled", new_resource.name).stdout.include?("static")
+ systemctl_execute("is-enabled", new_resource.unit_name).stdout.include?("static")
end
private
def unit_path
if new_resource.user
- "/etc/systemd/user/#{new_resource.name}"
+ "/etc/systemd/user/#{new_resource.unit_name}"
else
- "/etc/systemd/system/#{new_resource.name}"
+ "/etc/systemd/system/#{new_resource.unit_name}"
end
end
@@ -165,6 +214,7 @@ class Chef
f.group "root"
f.mode "0644"
f.content new_resource.to_ini
+ f.verify :systemd_unit if new_resource.verify
end.run_action(action)
end
@@ -173,11 +223,11 @@ class Chef
end
def systemctl_execute!(action, unit)
- shell_out_with_systems_locale!("#{systemctl_cmd} #{action} #{unit}", systemctl_opts)
+ shell_out_with_systems_locale!("#{systemctl_cmd} #{action} #{Shellwords.escape(unit)}", systemctl_opts)
end
def systemctl_execute(action, unit)
- shell_out("#{systemctl_cmd} #{action} #{unit}", systemctl_opts)
+ shell_out("#{systemctl_cmd} #{action} #{Shellwords.escape(unit)}", systemctl_opts)
end
def systemctl_cmd
@@ -195,10 +245,11 @@ class Chef
def systemctl_opts
@systemctl_opts ||=
if new_resource.user
+ uid = Etc.getpwuid(new_resource.user).uid
{
- "user" => new_resource.user,
- "environment" => {
- "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{node['etc']['passwd'][new_resource.user]['uid']}/bus",
+ :user => new_resource.user,
+ :environment => {
+ "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{uid}/bus",
},
}
else
diff --git a/lib/chef/provider/template.rb b/lib/chef/provider/template.rb
index 3c46a6eb7d..05cdbdbf62 100644
--- a/lib/chef/provider/template.rb
+++ b/lib/chef/provider/template.rb
@@ -19,25 +19,19 @@
require "chef/provider/template_finder"
require "chef/provider/file"
-require "chef/deprecation/provider/template"
-require "chef/deprecation/warnings"
class Chef
class Provider
class Template < Chef::Provider::File
provides :template
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::Template
- add_deprecation_warnings_for(Chef::Deprecation::Provider::Template.instance_methods)
-
def initialize(new_resource, run_context)
@content_class = Chef::Provider::Template::Content
super
end
def load_current_resource
- @current_resource = Chef::Resource::Template.new(@new_resource.name)
+ @current_resource = Chef::Resource::Template.new(new_resource.name)
super
end
@@ -55,8 +49,8 @@ class Chef
private
def managing_content?
- return true if @new_resource.checksum
- return true if !@new_resource.source.nil? && @action != :create_if_missing
+ return true if new_resource.checksum
+ return true if !new_resource.source.nil? && @action != :create_if_missing
false
end
diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb
index acf200621d..b40794564a 100644
--- a/lib/chef/provider/template/content.rb
+++ b/lib/chef/provider/template/content.rb
@@ -36,7 +36,30 @@ class Chef
private
def file_for_provider
- context = TemplateContext.new(new_resource.variables)
+ # Deal with any DelayedEvaluator values in the template variables.
+ visitor = lambda do |obj|
+ case obj
+ when Hash
+ # If this is an Attribute object, we need to change class otherwise
+ # we get the immutable behavior. This could probably be fixed by
+ # using Hash#transform_values once we only support Ruby 2.4.
+ obj_class = obj.is_a?(Chef::Node::ImmutableMash) ? Mash : obj.class
+ # Avoid mutating hashes in the resource in case we're changing anything.
+ obj.each_with_object(obj_class.new) do |(key, value), memo|
+ memo[key] = visitor.call(value)
+ end
+ when Array
+ # Avoid mutating arrays in the resource in case we're changing anything.
+ obj.map { |value| visitor.call(value) }
+ when DelayedEvaluator
+ new_resource.instance_eval(&obj)
+ else
+ obj
+ end
+ end
+ variables = visitor.call(new_resource.variables)
+
+ context = TemplateContext.new(variables)
context[:node] = run_context.node
context[:template_finder] = template_finder
diff --git a/lib/chef/provider/template_finder.rb b/lib/chef/provider/template_finder.rb
index 67342a86ea..1e8b925071 100644
--- a/lib/chef/provider/template_finder.rb
+++ b/lib/chef/provider/template_finder.rb
@@ -40,7 +40,7 @@ class Chef
cookbook.preferred_filename_on_disk_location(@node, :templates, template_name)
end
- protected
+ protected
def template_source_name(name, options)
if options[:source]
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index 85bd674d8d..18cf2d4d99 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,13 +17,11 @@
#
require "chef/provider"
-require "chef/mixin/command"
require "etc"
class Chef
class Provider
class User < Chef::Provider
- include Chef::Mixin::Command
attr_accessor :user_exists, :locked
@@ -36,74 +34,70 @@ class Chef
end
def convert_group_name
- if @new_resource.gid.is_a? String
- @new_resource.gid(Etc.getgrnam(@new_resource.gid).gid)
+ if new_resource.gid.is_a? String
+ new_resource.gid(Etc.getgrnam(new_resource.gid).gid)
end
- rescue ArgumentError => e
+ rescue ArgumentError
@group_name_resolved = false
end
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::User.new(@new_resource.name)
- @current_resource.username(@new_resource.username)
+ @current_resource = Chef::Resource::User.new(new_resource.name)
+ current_resource.username(new_resource.username)
begin
- user_info = Etc.getpwnam(@new_resource.username)
- rescue ArgumentError => e
+ user_info = Etc.getpwnam(new_resource.username)
+ rescue ArgumentError
@user_exists = false
- Chef::Log.debug("#{@new_resource} user does not exist")
+ logger.trace("#{new_resource} user does not exist")
user_info = nil
end
if user_info
- @current_resource.uid(user_info.uid)
- @current_resource.gid(user_info.gid)
- @current_resource.home(user_info.dir)
- @current_resource.shell(user_info.shell)
- @current_resource.password(user_info.passwd)
-
- if @new_resource.comment
- user_info.gecos.force_encoding(@new_resource.comment.encoding)
+ current_resource.uid(user_info.uid)
+ current_resource.gid(user_info.gid)
+ current_resource.home(user_info.dir)
+ current_resource.shell(user_info.shell)
+ current_resource.password(user_info.passwd)
+
+ if new_resource.comment
+ user_info.gecos.force_encoding(new_resource.comment.encoding)
end
- @current_resource.comment(user_info.gecos)
+ current_resource.comment(user_info.gecos)
- if @new_resource.password && @current_resource.password == "x"
+ if new_resource.password && current_resource.password == "x"
begin
require "shadow"
rescue LoadError
@shadow_lib_ok = false
else
- shadow_info = Shadow::Passwd.getspnam(@new_resource.username)
- @current_resource.password(shadow_info.sp_pwdp)
+ shadow_info = Shadow::Passwd.getspnam(new_resource.username)
+ current_resource.password(shadow_info.sp_pwdp)
end
end
- convert_group_name if @new_resource.gid
+ convert_group_name if new_resource.gid
end
- @current_resource
+ current_resource
end
def define_resource_requirements
requirements.assert(:create, :modify, :manage, :lock, :unlock) do |a|
a.assertion { @group_name_resolved }
- a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}"
- a.whyrun "group name #{@new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously."
+ a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{new_resource.gid}"
+ a.whyrun "group name #{new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously."
end
requirements.assert(:all_actions) do |a|
a.assertion { @shadow_lib_ok }
a.failure_message Chef::Exceptions::MissingLibrary, "You must have ruby-shadow installed for password support!"
- a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." +
+ a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." \
"Note that user update converge may report false-positive on the basis of mismatched password. "
end
requirements.assert(:modify, :lock, :unlock) do |a|
a.assertion { @user_exists }
- a.failure_message(Chef::Exceptions::User, "Cannot modify user #{@new_resource.username} - does not exist!")
- a.whyrun("Assuming user #{@new_resource.username} would have been created")
+ a.failure_message(Chef::Exceptions::User, "Cannot modify user #{new_resource.username} - does not exist!")
+ a.whyrun("Assuming user #{new_resource.username} would have been created")
end
end
@@ -113,99 +107,98 @@ class Chef
# <true>:: If a change is required
# <false>:: If the users are identical
def compare_user
- changed = [ :comment, :home, :shell, :password ].select do |user_attrib|
- !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
- end
+ return true if !new_resource.home.nil? && Pathname.new(new_resource.home).cleanpath != Pathname.new(current_resource.home).cleanpath
- changed += [ :uid, :gid ].select do |user_attrib|
- !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib).to_s != @current_resource.send(user_attrib).to_s
+ [ :comment, :shell, :password, :uid, :gid ].each do |user_attrib|
+ return true if !new_resource.send(user_attrib).nil? && new_resource.send(user_attrib).to_s != current_resource.send(user_attrib).to_s
end
- changed.any?
+ false
end
def action_create
if !@user_exists
- converge_by("create user #{@new_resource.username}") do
+ converge_by("create user #{new_resource.username}") do
create_user
- Chef::Log.info("#{@new_resource} created")
+ logger.info("#{new_resource} created")
end
elsif compare_user
- converge_by("alter user #{@new_resource.username}") do
+ converge_by("alter user #{new_resource.username}") do
manage_user
- Chef::Log.info("#{@new_resource} altered")
+ logger.info("#{new_resource} altered")
end
end
end
def action_remove
- if @user_exists
- converge_by("remove user #{@new_resource.username}") do
- remove_user
- Chef::Log.info("#{@new_resource} removed")
- end
+ return unless @user_exists
+ converge_by("remove user #{new_resource.username}") do
+ remove_user
+ logger.info("#{new_resource} removed")
end
end
- def remove_user
- raise NotImplementedError
- end
-
def action_manage
- if @user_exists && compare_user
- converge_by("manage user #{@new_resource.username}") do
- manage_user
- Chef::Log.info("#{@new_resource} managed")
- end
+ return unless @user_exists && compare_user
+ converge_by("manage user #{new_resource.username}") do
+ manage_user
+ logger.info("#{new_resource} managed")
end
end
- def manage_user
- raise NotImplementedError
- end
-
def action_modify
- if compare_user
- converge_by("modify user #{@new_resource.username}") do
- manage_user
- Chef::Log.info("#{@new_resource} modified")
- end
+ return unless compare_user
+ converge_by("modify user #{new_resource.username}") do
+ manage_user
+ logger.info("#{new_resource} modified")
end
end
def action_lock
- if check_lock() == false
- converge_by("lock the user #{@new_resource.username}") do
+ if check_lock == false
+ converge_by("lock the user #{new_resource.username}") do
lock_user
- Chef::Log.info("#{@new_resource} locked")
+ logger.info("#{new_resource} locked")
end
else
- Chef::Log.debug("#{@new_resource} already locked - nothing to do")
+ logger.trace("#{new_resource} already locked - nothing to do")
end
end
- def check_lock
+ def action_unlock
+ if check_lock == true
+ converge_by("unlock user #{new_resource.username}") do
+ unlock_user
+ logger.info("#{new_resource} unlocked")
+ end
+ else
+ logger.trace("#{new_resource} already unlocked - nothing to do")
+ end
+ end
+
+ def create_user
raise NotImplementedError
end
- def lock_user
+ def remove_user
raise NotImplementedError
end
- def action_unlock
- if check_lock() == true
- converge_by("unlock user #{@new_resource.username}") do
- unlock_user
- Chef::Log.info("#{@new_resource} unlocked")
- end
- else
- Chef::Log.debug("#{@new_resource} already unlocked - nothing to do")
- end
+ def manage_user
+ raise NotImplementedError
+ end
+
+ def lock_user
+ raise NotImplementedError
end
def unlock_user
raise NotImplementedError
end
+
+ def check_lock
+ raise NotImplementedError
+ end
end
end
end
diff --git a/lib/chef/provider/user/aix.rb b/lib/chef/provider/user/aix.rb
index 3f168b8da3..64a088dd5c 100644
--- a/lib/chef/provider/user/aix.rb
+++ b/lib/chef/provider/user/aix.rb
@@ -14,13 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require "chef/provider/user/useradd"
+
class Chef
class Provider
class User
class Aix < Chef::Provider::User::Useradd
- provides :user, platform: %w{aix}
+ provides :user, os: "aix"
+ provides :aix_user
- UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
+ UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]].freeze
def create_user
super
@@ -41,53 +44,52 @@ class Chef
end
def check_lock
- lock_info = shell_out!("lsuser -a account_locked #{new_resource.username}")
+ lock_info = shell_out_compact!("lsuser", "-a", "account_locked", new_resource.username)
if whyrun_mode? && passwd_s.stdout.empty? && lock_info.stderr.match(/does not exist/)
# if we're in whyrun mode and the user is not yet created we assume it would be
return false
end
- raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if lock_info.stdout.empty?
+ raise Chef::Exceptions::User, "Cannot determine if #{new_resource} is locked!" if lock_info.stdout.empty?
status = /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)
- if status && status[1] == "true"
- @locked = true
- else
- @locked = false
- end
+ @locked =
+ if status && status[1] == "true"
+ true
+ else
+ false
+ end
@locked
end
def lock_user
- shell_out!("chuser account_locked=true #{new_resource.username}")
+ shell_out_compact!("chuser", "account_locked=true", new_resource.username)
end
def unlock_user
- shell_out!("chuser account_locked=false #{new_resource.username}")
+ shell_out_compact!("chuser", "account_locked=false", new_resource.username)
end
- private
+ private
def add_password
- if @current_resource.password != @new_resource.password && @new_resource.password
- Chef::Log.debug("#{@new_resource.username} setting password to #{@new_resource.password}")
- command = "echo '#{@new_resource.username}:#{@new_resource.password}' | chpasswd -e"
- shell_out!(command)
- end
+ return unless current_resource.password != new_resource.password && new_resource.password
+ logger.trace("#{new_resource.username} setting password to #{new_resource.password}")
+ command = "echo '#{new_resource.username}:#{new_resource.password}' | chpasswd -e"
+ shell_out!(command)
end
# Aix specific handling to update users home directory.
def manage_home
+ return unless updating_home? && new_resource.manage_home
# -m option does not work on aix, so move dir.
- if updating_home? && managing_home_dir?
- universal_options.delete("-m")
- if ::File.directory?(@current_resource.home)
- Chef::Log.debug("Changing users home directory from #{@current_resource.home} to #{new_resource.home}")
- shell_out!("mv #{@current_resource.home} #{new_resource.home}")
- else
- Chef::Log.debug("Creating users home directory #{new_resource.home}")
- shell_out!("mkdir -p #{new_resource.home}")
- end
+ universal_options.delete("-m")
+ if ::File.directory?(current_resource.home)
+ logger.trace("Changing users home directory from #{current_resource.home} to #{new_resource.home}")
+ FileUtils.mv current_resource.home, new_resource.home
+ else
+ logger.trace("Creating users home directory #{new_resource.home}")
+ FileUtils.mkdir_p new_resource.home
end
end
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
index 541160bf93..a1ff30ef1c 100644
--- a/lib/chef/provider/user/dscl.rb
+++ b/lib/chef/provider/user/dscl.rb
@@ -1,6 +1,6 @@
#
# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright 2009-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -48,8 +48,14 @@ class Chef
attr_accessor :authentication_authority
attr_accessor :password_shadow_conversion_algorithm
+ provides :dscl_user
provides :user, os: "darwin"
+ # Just-in-case a recipe calls the user dscl provider without specifying
+ # a gid property. Avoids chown issues in move_home when the manage_home
+ # property is in use. #5393
+ STAFF_GROUP_ID = 20
+
def define_resource_requirements
super
@@ -59,12 +65,12 @@ class Chef
end
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/bin/dscl") }
+ a.assertion { ::File.exist?("/usr/bin/dscl") }
a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{new_resource}!")
end
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/bin/plutil") }
+ a.assertion { ::File.exist?("/usr/bin/plutil") }
a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{new_resource}!")
end
@@ -151,7 +157,7 @@ user password using shadow hash.")
convert_group_name if new_resource.gid
else
@user_exists = false
- Chef::Log.debug("#{new_resource} user does not exist")
+ logger.trace("#{new_resource} user does not exist")
end
current_resource
@@ -193,7 +199,7 @@ user password using shadow hash.")
# Create a user using dscl
#
def dscl_create_user
- run_dscl("create /Users/#{new_resource.username}")
+ run_dscl("create", "/Users/#{new_resource.username}")
end
#
@@ -202,7 +208,7 @@ user password using shadow hash.")
#
def dscl_create_comment
comment = new_resource.comment || new_resource.username
- run_dscl("create /Users/#{new_resource.username} RealName '#{comment}'")
+ run_dscl("create", "/Users/#{new_resource.username}", "RealName", comment)
end
#
@@ -218,7 +224,7 @@ user password using shadow hash.")
raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{new_resource.uid} is already in use")
end
- run_dscl("create /Users/#{new_resource.username} UniqueID #{new_resource.uid}")
+ run_dscl("create", "/Users/#{new_resource.username}", "UniqueID", new_resource.uid)
end
#
@@ -229,7 +235,7 @@ user password using shadow hash.")
uid = nil
base_uid = new_resource.system ? 200 : 501
next_uid_guess = base_uid
- users_uids = run_dscl("list /Users uid")
+ users_uids = run_dscl("list", "/Users", "uid")
while next_uid_guess < search_limit + base_uid
if users_uids =~ Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n")
next_uid_guess += 1
@@ -238,7 +244,7 @@ user password using shadow hash.")
break
end
end
- return uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
+ uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
end
#
@@ -246,39 +252,39 @@ user password using shadow hash.")
#
def uid_used?(uid)
return false unless uid
- users_uids = run_dscl("list /Users uid").split("\n")
- uid_map = users_uids.inject({}) do |tmap, tuid|
+ users_uids = run_dscl("list", "/Users", "uid").split("\n")
+ uid_map = users_uids.each_with_object({}) do |tuid, tmap|
x = tuid.split
tmap[x[1]] = x[0]
tmap
end
if uid_map[uid.to_s]
- unless uid_map[uid.to_s] == new_resource.username.to_s
+ unless uid_map[uid.to_s] == new_resource.username
return true
end
end
- return false
+ false
end
#
# Sets the group id for the user using dscl. Fails if a group doesn't
# exist on the system with given group id. If `gid` is not specified, it
- # sets a default Mac user group "staff", with id 20.
+ # sets a default Mac user group "staff", with id 20 using the CONSTANT
#
def dscl_set_gid
if new_resource.gid.nil?
# XXX: mutates the new resource
- new_resource.gid(20)
+ new_resource.gid(STAFF_GROUP_ID)
elsif !new_resource.gid.to_s.match(/^\d+$/)
begin
- possible_gid = run_dscl("read /Groups/#{new_resource.gid} PrimaryGroupID").split(" ").last
- rescue Chef::Exceptions::DsclCommandFailed => e
- raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{new_resource.gid} when creating user #{new_resource.username}")
+ possible_gid = run_dscl("read", "/Groups/#{new_resource.gid}", "PrimaryGroupID").split(" ").last
+ rescue Chef::Exceptions::DsclCommandFailed
+ raise Chef::Exceptions::GroupIDNotFound, "Group not found for #{new_resource.gid} when creating user #{new_resource.username}"
end
# XXX: mutates the new resource
new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
end
- run_dscl("create /Users/#{new_resource.username} PrimaryGroupID '#{new_resource.gid}'")
+ run_dscl("create", "/Users/#{new_resource.username}", "PrimaryGroupID", new_resource.gid)
end
#
@@ -287,13 +293,11 @@ user password using shadow hash.")
#
def dscl_set_home
if new_resource.home.nil? || new_resource.home.empty?
- run_dscl("delete /Users/#{new_resource.username} NFSHomeDirectory")
+ run_dscl("delete", "/Users/#{new_resource.username}", "NFSHomeDirectory")
return
end
- run_dscl("create /Users/#{new_resource.username} NFSHomeDirectory '#{new_resource.home}'")
-
- if new_resource.supports[:manage_home]
+ if new_resource.manage_home
validate_home_dir_specification!
if (current_resource.home == new_resource.home) && !new_home_exists?
@@ -304,6 +308,7 @@ user password using shadow hash.")
move_home
end
end
+ run_dscl("create", "/Users/#{new_resource.username}", "NFSHomeDirectory", new_resource.home)
end
def validate_home_dir_specification!
@@ -313,24 +318,24 @@ user password using shadow hash.")
end
def current_home_exists?
- ::File.exist?("#{current_resource.home}")
+ !!current_resource.home && ::File.exist?(current_resource.home)
end
def new_home_exists?
- ::File.exist?("#{new_resource.home}")
+ ::File.exist?(new_resource.home)
end
def ditto_home
- shell_out! "/usr/sbin/createhomedir -c -u #{new_resource.username}"
+ shell_out_compact!("/usr/sbin/createhomedir -c -u #{new_resource.username}")
end
def move_home
- Chef::Log.debug("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}")
-
+ logger.trace("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}")
+ new_resource.gid(STAFF_GROUP_ID) if new_resource.gid.nil?
src = current_resource.home
FileUtils.mkdir_p(new_resource.home)
files = ::Dir.glob("#{Chef::Util::PathHelper.escape_glob_dir(src)}/*", ::File::FNM_DOTMATCH) - ["#{src}/.", "#{src}/.."]
- ::FileUtils.mv(files, new_resource.home, :force => true)
+ ::FileUtils.mv(files, new_resource.home, force: true)
::FileUtils.rmdir(src)
::FileUtils.chown_R(new_resource.username, new_resource.gid.to_s, new_resource.home)
end
@@ -339,10 +344,10 @@ user password using shadow hash.")
# Sets the shell for the user using dscl.
#
def dscl_set_shell
- if new_resource.shell || ::File.exists?("#{new_resource.shell}")
- run_dscl("create /Users/#{new_resource.username} UserShell '#{new_resource.shell}'")
+ if new_resource.shell
+ run_dscl("create", "/Users/#{new_resource.username}", "UserShell", new_resource.shell)
else
- run_dscl("create /Users/#{new_resource.username} UserShell '/usr/bin/false'")
+ run_dscl("create", "/Users/#{new_resource.username}", "UserShell", "/usr/bin/false")
end
end
@@ -359,9 +364,8 @@ user password using shadow hash.")
# Shadow info is saved as binary plist. Convert the info to binary plist.
shadow_info_binary = StringIO.new
- command = Mixlib::ShellOut.new("plutil -convert binary1 -o - -",
- :input => shadow_info.to_plist, :live_stream => shadow_info_binary)
- command.run_command
+ shell_out_compact("plutil", "-convert", "binary1", "-o", "-", "-",
+ input: shadow_info.to_plist, live_stream: shadow_info_binary)
if user_info.nil?
# User is just created. read_user_info() will read the fresh information
@@ -393,7 +397,7 @@ user password using shadow hash.")
# Create a random 4 byte salt
salt = OpenSSL::Random.random_bytes(4)
encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + new_resource.password)
- hash_value = salt.unpack("H*").first + encoded_password
+ salt.unpack("H*").first + encoded_password
end
shadow_info["SALTED-SHA512"] = StringIO.new
@@ -435,27 +439,27 @@ user password using shadow hash.")
# and deleting home directory if needed.
#
def remove_user
- if new_resource.supports[:manage_home]
+ if new_resource.manage_home
# Remove home directory
FileUtils.rm_rf(current_resource.home)
end
# Remove the user from its groups
- run_dscl("list /Groups").each_line do |group|
+ run_dscl("list", "/Groups").each_line do |group|
if member_of_group?(group.chomp)
- run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{new_resource.username}'")
+ run_dscl("delete", "/Groups/#{group.chomp}", "GroupMembership", new_resource.username)
end
end
# Remove user account
- run_dscl("delete /Users/#{new_resource.username}")
+ run_dscl("delete", "/Users/#{new_resource.username}")
end
#
# Locks the user.
#
def lock_user
- run_dscl("append /Users/#{new_resource.username} AuthenticationAuthority ';DisabledUser;'")
+ run_dscl("append", "/Users/#{new_resource.username}", "AuthenticationAuthority", ";DisabledUser;")
end
#
@@ -463,7 +467,7 @@ user password using shadow hash.")
#
def unlock_user
auth_string = authentication_authority.gsub(/AuthenticationAuthority: /, "").gsub(/;DisabledUser;/, "").strip
- run_dscl("create /Users/#{new_resource.username} AuthenticationAuthority '#{auth_string}'")
+ run_dscl("create", "/Users/#{new_resource.username}", "AuthenticationAuthority", auth_string)
end
#
@@ -481,7 +485,7 @@ user password using shadow hash.")
# This is the interface base User provider requires to provide idempotency.
#
def check_lock
- return @locked = locked?
+ @locked = locked?
end
#
@@ -493,11 +497,11 @@ user password using shadow hash.")
# given attribute.
#
def diverged?(parameter)
- parameter_updated?(parameter) && (not new_resource.send(parameter).nil?)
+ parameter_updated?(parameter) && !new_resource.send(parameter).nil?
end
def parameter_updated?(parameter)
- not (new_resource.send(parameter) == current_resource.send(parameter))
+ !(new_resource.send(parameter) == current_resource.send(parameter))
end
#
@@ -543,7 +547,7 @@ user password using shadow hash.")
def member_of_group?(group_name)
membership_info = ""
begin
- membership_info = run_dscl("read /Groups/#{group_name}")
+ membership_info = run_dscl("read", "/Groups/#{group_name}")
rescue Chef::Exceptions::DsclCommandFailed
# Raised if the group doesn't contain any members
end
@@ -560,14 +564,14 @@ user password using shadow hash.")
# A simple map of Chef's terms to DSCL's terms.
DSCL_PROPERTY_MAP = {
- :uid => "uid",
- :gid => "gid",
- :home => "home",
- :shell => "shell",
- :comment => "realname",
- :password => "passwd",
- :auth_authority => "authentication_authority",
- :shadow_hash => "ShadowHashData",
+ uid: "uid",
+ gid: "gid",
+ home: "home",
+ shell: "shell",
+ comment: "realname",
+ password: "passwd",
+ auth_authority: "authentication_authority",
+ shadow_hash: "ShadowHashData",
}.freeze
# Directory where the user plist files are stored for versions 10.7 and above
@@ -582,11 +586,11 @@ user password using shadow hash.")
# We flush the cache here in order to make sure that we read fresh information
# for the user.
- shell_out("dscacheutil '-flushcache'")
+ shell_out_compact("dscacheutil", "-flushcache") # FIXME: this is MacOS version dependent
begin
user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist"
- user_plist_info = run_plutil("convert xml1 -o - #{user_plist_file}")
+ user_plist_info = run_plutil("convert", "xml1", "-o", "-", user_plist_file)
user_info = Plist.parse_xml(user_plist_info)
rescue Chef::Exceptions::PlistUtilCommandFailed
end
@@ -601,7 +605,7 @@ user password using shadow hash.")
def save_user_info(user_info)
user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist"
Plist::Emit.save_plist(user_info, user_plist_file)
- run_plutil("convert binary1 #{user_plist_file}")
+ run_plutil("convert", "binary1", user_plist_file)
end
#
@@ -650,7 +654,9 @@ user password using shadow hash.")
end
def run_dscl(*args)
- result = shell_out("dscl . -#{args.join(' ')}")
+ argdup = args.dup
+ cmd = argdup.shift
+ result = shell_out_compact("dscl", ".", "-#{cmd}", argdup)
return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") unless result.exitstatus == 0
raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
@@ -658,17 +664,19 @@ user password using shadow hash.")
end
def run_plutil(*args)
- result = shell_out("plutil -#{args.join(' ')}")
+ argdup = args.dup
+ cmd = argdup.shift
+ result = shell_out_compact("plutil", "-#{cmd}", argdup)
raise(Chef::Exceptions::PlistUtilCommandFailed, "plutil error: #{result.inspect}") unless result.exitstatus == 0
if result.stdout.encoding == Encoding::ASCII_8BIT
- result.stdout.encode("utf-8", "binary", :undef => :replace, :invalid => :replace, :replace => "?")
+ result.stdout.encode("utf-8", "binary", undef: :replace, invalid: :replace, replace: "?")
else
result.stdout
end
end
def convert_binary_plist_to_xml(binary_plist_string)
- Mixlib::ShellOut.new("plutil -convert xml1 -o - -", :input => binary_plist_string).run_command.stdout
+ shell_out_compact("plutil", "-convert", "xml1", "-o", "-", "-", input: binary_plist_string).stdout
end
def convert_to_binary(string)
diff --git a/lib/chef/provider/user/linux.rb b/lib/chef/provider/user/linux.rb
new file mode 100644
index 0000000000..2db6c218bd
--- /dev/null
+++ b/lib/chef/provider/user/linux.rb
@@ -0,0 +1,126 @@
+#
+# Copyright:: Copyright 2016-2017, 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/user"
+
+class Chef
+ class Provider
+ class User
+ class Linux < Chef::Provider::User
+ provides :linux_user
+ provides :user, os: "linux"
+
+ def create_user
+ shell_out_compact!("useradd", universal_options, useradd_options, new_resource.username)
+ end
+
+ def manage_user
+ shell_out_compact!("usermod", universal_options, usermod_options, new_resource.username)
+ end
+
+ def remove_user
+ shell_out_compact!("userdel", userdel_options, new_resource.username)
+ end
+
+ def lock_user
+ shell_out_compact!("usermod", "-L", new_resource.username)
+ end
+
+ def unlock_user
+ shell_out_compact!("usermod", "-U", new_resource.username)
+ end
+
+ # common to usermod and useradd
+ def universal_options
+ opts = []
+ opts << "-c" << new_resource.comment if should_set?(:comment)
+ opts << "-g" << new_resource.gid if should_set?(:gid)
+ opts << "-p" << new_resource.password if should_set?(:password)
+ opts << "-s" << new_resource.shell if should_set?(:shell)
+ opts << "-u" << new_resource.uid if should_set?(:uid)
+ opts << "-d" << new_resource.home if updating_home?
+ opts << "-o" if new_resource.non_unique
+ opts
+ end
+
+ def usermod_options
+ opts = []
+ opts += [ "-u", new_resource.uid ] if new_resource.non_unique
+ if updating_home?
+ if new_resource.manage_home
+ opts << "-m"
+ end
+ end
+ opts
+ end
+
+ def useradd_options
+ opts = []
+ opts << "-r" if new_resource.system
+ opts << if new_resource.manage_home
+ "-m"
+ else
+ "-M"
+ end
+ opts
+ end
+
+ def userdel_options
+ opts = []
+ opts << "-r" if new_resource.manage_home
+ opts << "-f" if new_resource.force
+ opts
+ end
+
+ def should_set?(sym)
+ current_resource.send(sym).to_s != new_resource.send(sym).to_s && new_resource.send(sym)
+ end
+
+ def updating_home?
+ return false unless new_resource.home
+ return true unless current_resource.home
+ new_resource.home && Pathname.new(current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath
+ end
+
+ def check_lock
+ # there's an old bug in rhel (https://bugzilla.redhat.com/show_bug.cgi?id=578534)
+ # which means that both 0 and 1 can be success.
+ passwd_s = shell_out_compact("passwd", "-S", new_resource.username, returns: [ 0, 1 ])
+
+ # checking "does not exist" has to come before exit code handling since centos and ubuntu differ in exit codes
+ if passwd_s.stderr =~ /does not exist/
+ return false if whyrun_mode?
+ raise Chef::Exceptions::User, "User #{new_resource.username} does not exist when checking lock status for #{new_resource}"
+ end
+
+ # now raise if we didn't get a 0 or 1 (see above)
+ passwd_s.error!
+
+ # now the actual output parsing
+ @locked = nil
+ status_line = passwd_s.stdout.split(" ")
+ @locked = false if status_line[1] =~ /^[PN]/
+ @locked = true if status_line[1] =~ /^L/
+
+ raise Chef::Exceptions::User, "Cannot determine if user #{new_resource.username} is locked for #{new_resource}" if @locked.nil?
+
+ # FIXME: should probably go on the current_resource
+ @locked
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index 949a21790b..695dbfd539 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Haynes (<sh@nomitor.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright 2009-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,53 +22,50 @@ class Chef
class Provider
class User
class Pw < Chef::Provider::User
- provides :user, platform: %w{freebsd}
+ provides :pw_user
+ provides :user, os: "freebsd"
def load_current_resource
super
- raise Chef::Exceptions::User, "Could not find binary /usr/sbin/pw for #{@new_resource}" unless ::File.exists?("/usr/sbin/pw")
+ raise Chef::Exceptions::User, "Could not find binary /usr/sbin/pw for #{new_resource}" unless ::File.exist?("/usr/sbin/pw")
end
def create_user
- command = "pw useradd"
- command << set_options
- run_command(:command => command)
+ shell_out_compact!("pw", "useradd", set_options)
modify_password
end
def manage_user
- command = "pw usermod"
- command << set_options
- run_command(:command => command)
+ shell_out_compact!("pw", "usermod", set_options)
modify_password
end
def remove_user
- command = "pw userdel #{@new_resource.username}"
- command << " -r" if @new_resource.supports[:manage_home]
- run_command(:command => command)
+ command = [ "pw", "userdel", new_resource.username ]
+ command << "-r" if new_resource.manage_home
+ shell_out_compact!(command)
end
def check_lock
- case @current_resource.password
- when /^\*LOCKED\*/
- @locked = true
- else
- @locked = false
- end
+ @locked = case current_resource.password
+ when /^\*LOCKED\*/
+ true
+ else
+ false
+ end
@locked
end
def lock_user
- run_command(:command => "pw lock #{@new_resource.username}")
+ shell_out_compact!("pw", "lock", new_resource.username)
end
def unlock_user
- run_command(:command => "pw unlock #{@new_resource.username}")
+ shell_out_compact!("pw", "unlock", new_resource.username)
end
def set_options
- opts = " #{@new_resource.username}"
+ opts = [ new_resource.username ]
field_list = {
"comment" => "-c",
@@ -77,35 +74,29 @@ class Chef
"uid" => "-u",
"shell" => "-s",
}
- field_list.sort { |a, b| a[0] <=> b[0] }.each do |field, option|
+ field_list.sort_by { |a| a[0] }.each do |field, option|
field_symbol = field.to_sym
- if @current_resource.send(field_symbol) != @new_resource.send(field_symbol)
- if @new_resource.send(field_symbol)
- Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field_symbol)}")
- opts << " #{option} '#{@new_resource.send(field_symbol)}'"
- end
+ next unless current_resource.send(field_symbol) != new_resource.send(field_symbol)
+ if new_resource.send(field_symbol)
+ logger.trace("#{new_resource} setting #{field} to #{new_resource.send(field_symbol)}")
+ opts << option
+ opts << new_resource.send(field_symbol)
end
end
- if @new_resource.supports[:manage_home]
- Chef::Log.debug("#{@new_resource} is managing the users home directory")
- opts << " -m"
+ if new_resource.manage_home
+ logger.trace("#{new_resource} is managing the users home directory")
+ opts << "-m"
end
opts
end
def modify_password
- if (not @new_resource.password.nil?) && (@current_resource.password != @new_resource.password)
- Chef::Log.debug("#{new_resource} updating password")
- command = "pw usermod #{@new_resource.username} -H 0"
- status = popen4(command, :waitlast => true) do |pid, stdin, stdout, stderr|
- stdin.puts "#{@new_resource.password}"
- end
-
- unless status.exitstatus == 0
- raise Chef::Exceptions::User, "pw failed - #{status.inspect}!"
- end
+ if !new_resource.password.nil? && (current_resource.password != new_resource.password)
+ logger.trace("#{new_resource} updating password")
+ command = "pw usermod #{new_resource.username} -H 0"
+ shell_out!(command, input: new_resource.password.to_s)
else
- Chef::Log.debug("#{new_resource} no change needed to password")
+ logger.trace("#{new_resource} no change needed to password")
end
end
end
diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb
index 1f0cbb6054..59074d5ba8 100644
--- a/lib/chef/provider/user/solaris.rb
+++ b/lib/chef/provider/user/solaris.rb
@@ -2,7 +2,7 @@
# Author:: Stephen Nelson-Smith (<sns@chef.io>)
# Author:: Jon Ramsey (<jonathon.ramsey@gmail.com>)
# Author:: Dave Eddy (<dave@daveeddy.com>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright 2012-2017, Chef Software Inc.
# Copyright:: Copyright 2015-2016, Dave Eddy
# License:: Apache License, Version 2.0
#
@@ -24,8 +24,9 @@ class Chef
class Provider
class User
class Solaris < Chef::Provider::User::Useradd
- provides :user, platform: %w{omnios solaris2}
- UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
+ provides :solaris_user
+ provides :user, os: %w{omnios solaris2}
+ UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]].freeze
attr_writer :password_file
@@ -45,38 +46,45 @@ class Chef
end
def check_lock
- shadow_line = shell_out!("getent", "shadow", new_resource.username).stdout.strip rescue nil
+ user = IO.read(@password_file).match(/^#{Regexp.escape(new_resource.username)}:([^:]*):/)
- # if the command fails we return nil, this can happen if the user
- # in question doesn't exist
- return nil if shadow_line.nil?
+ # If we're in whyrun mode, and the user is not created, we assume it will be
+ return false if whyrun_mode? && user.nil?
- # convert "dave:NP:16507::::::\n" to "NP"
- fields = shadow_line.split(":")
+ raise Chef::Exceptions::User, "Cannot determine if #{new_resource} is locked!" if user.nil?
- # '*LK*...' and 'LK' are both considered locked,
- # so look for LK at the beginning of the shadow entry
- # optionally surrounded by '*'
- @locked = !!fields[1].match(/^\*?LK\*?/)
-
- @locked
+ @locked = user[1].start_with?("*LK*")
end
def lock_user
- shell_out!("passwd", "-l", new_resource.username)
+ shell_out_compact!("passwd", "-l", new_resource.username)
end
def unlock_user
- shell_out!("passwd", "-u", new_resource.username)
+ shell_out_compact!("passwd", "-u", new_resource.username)
end
- private
+ private
+
+ # Override the version from {#Useradd} because Solaris doesn't support
+ # system users and therefore has no `-r` option. This also inverts the
+ # logic for manage_home as Solaris defaults to no-manage-home and only
+ # offers `-m`.
+ #
+ # @since 12.15
+ # @api private
+ # @see Useradd#useradd_options
+ # @return [Array<String>]
+ def useradd_options
+ opts = []
+ opts << "-m" if new_resource.manage_home
+ opts
+ end
def manage_password
- if @current_resource.password != @new_resource.password && @new_resource.password
- Chef::Log.debug("#{@new_resource} setting password to #{@new_resource.password}")
- write_shadow_file
- end
+ return unless current_resource.password != new_resource.password && new_resource.password
+ logger.trace("#{new_resource} setting password to #{new_resource.password}")
+ write_shadow_file
end
def write_shadow_file
@@ -84,7 +92,7 @@ class Chef
::File.open(@password_file) do |shadow_file|
shadow_file.each do |entry|
user = entry.split(":").first
- if user == @new_resource.username
+ if user == new_resource.username
buffer.write(updated_password(entry))
else
buffer.write(entry)
@@ -95,7 +103,7 @@ class Chef
# FIXME: mostly duplicates code with file provider deploying a file
s = ::File.stat(@password_file)
- mode = s.mode & 07777
+ mode = s.mode & 0o7777
uid = s.uid
gid = s.gid
@@ -107,7 +115,7 @@ class Chef
def updated_password(entry)
fields = entry.split(":")
- fields[1] = @new_resource.password
+ fields[1] = new_resource.password
fields[2] = days_since_epoch
fields.join(":")
end
diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb
index 3fef8d3642..47c0ece101 100644
--- a/lib/chef/provider/user/useradd.rb
+++ b/lib/chef/provider/user/useradd.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright 2008-2017, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,45 +23,45 @@ class Chef
class Provider
class User
class Useradd < Chef::Provider::User
- provides :user
+ # the linux version of this has been forked off, this is the base class now of solaris and AIX and should be abandoned
+ # and those provider should be rewritten like the linux version.
- UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]]
+ UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]].freeze
def create_user
command = compile_command("useradd") do |useradd|
useradd.concat(universal_options)
useradd.concat(useradd_options)
end
- shell_out!(*command)
+ shell_out_compact!(command)
end
def manage_user
- unless universal_options.empty?
- command = compile_command("usermod") do |u|
- u.concat(universal_options)
- end
- shell_out!(*command)
+ return if universal_options.empty?
+ command = compile_command("usermod") do |u|
+ u.concat(universal_options)
end
+ shell_out_compact!(command)
end
def remove_user
command = [ "userdel" ]
- command << "-r" if managing_home_dir?
+ command << "-r" if new_resource.manage_home
command << "-f" if new_resource.force
command << new_resource.username
- shell_out!(*command)
+ shell_out_compact!(command)
end
def check_lock
# we can get an exit code of 1 even when it's successful on
# rhel/centos (redhat bug 578534). See additional error checks below.
- passwd_s = shell_out!("passwd", "-S", new_resource.username, :returns => [0, 1])
+ passwd_s = shell_out_compact!("passwd", "-S", new_resource.username, returns: [0, 1])
if whyrun_mode? && passwd_s.stdout.empty? && passwd_s.stderr.match(/does not exist/)
# if we're in whyrun mode and the user is not yet created we assume it would be
return false
end
- raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if passwd_s.stdout.empty?
+ raise Chef::Exceptions::User, "Cannot determine if #{new_resource} is locked!" if passwd_s.stdout.empty?
status_line = passwd_s.stdout.split(" ")
case status_line[1]
@@ -76,7 +76,7 @@ class Chef
unless passwd_s.exitstatus == 0
raise_lock_error = false
if %w{redhat centos}.include?(node[:platform])
- passwd_version_check = shell_out!("rpm -q passwd")
+ passwd_version_check = shell_out_compact!("rpm", "-q", "passwd")
passwd_version = passwd_version_check.stdout.chomp
unless passwd_version == "passwd-0.73-1"
@@ -93,11 +93,11 @@ class Chef
end
def lock_user
- shell_out!("usermod", "-L", new_resource.username)
+ shell_out_compact!("usermod", "-L", new_resource.username)
end
def unlock_user
- shell_out!("usermod", "-U", new_resource.username)
+ shell_out_compact!("usermod", "-U", new_resource.username)
end
def compile_command(base_command)
@@ -116,31 +116,30 @@ class Chef
update_options(field, option, opts)
end
if updating_home?
- if managing_home_dir?
- Chef::Log.debug("#{new_resource} managing the users home directory")
- opts << "-d" << new_resource.home << "-m"
+ opts << "-d" << new_resource.home
+ if new_resource.manage_home
+ logger.trace("#{new_resource} managing the users home directory")
+ opts << "-m"
else
- Chef::Log.debug("#{new_resource} setting home to #{new_resource.home}")
- opts << "-d" << new_resource.home
+ logger.trace("#{new_resource} setting home to #{new_resource.home}")
end
end
- opts << "-o" if new_resource.non_unique || new_resource.supports[:non_unique]
+ opts << "-o" if new_resource.non_unique
opts
end
end
def update_options(field, option, opts)
- if @current_resource.send(field).to_s != new_resource.send(field).to_s
- if new_resource.send(field)
- Chef::Log.debug("#{new_resource} setting #{field} to #{new_resource.send(field)}")
- opts << option << new_resource.send(field).to_s
- end
- end
+ return unless current_resource.send(field).to_s != new_resource.send(field).to_s
+ return unless new_resource.send(field)
+ logger.trace("#{new_resource} setting #{field} to #{new_resource.send(field)}")
+ opts << option << new_resource.send(field).to_s
end
def useradd_options
opts = []
opts << "-r" if new_resource.system
+ opts << "-M" unless new_resource.manage_home
opts
end
@@ -149,12 +148,8 @@ class Chef
# Pathname#cleanpath does a better job than ::File::expand_path (on both unix and windows)
# ::File.expand_path("///tmp") == ::File.expand_path("/tmp") => false
# ::File.expand_path("\\tmp") => "C:/tmp"
- return true if @current_resource.home.nil? && new_resource.home
- new_resource.home && Pathname.new(@current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath
- end
-
- def managing_home_dir?
- new_resource.manage_home || new_resource.supports[:manage_home]
+ return true if current_resource.home.nil? && new_resource.home
+ new_resource.home && Pathname.new(current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath
end
end
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
index 9545b1fd59..994f1a6774 100644
--- a/lib/chef/provider/user/windows.rb
+++ b/lib/chef/provider/user/windows.rb
@@ -26,36 +26,35 @@ class Chef
class Provider
class User
class Windows < Chef::Provider::User
-
+ provides :windows_user
provides :user, os: "windows"
def initialize(new_resource, run_context)
super
- @net_user = Chef::Util::Windows::NetUser.new(@new_resource.username)
+ @net_user = Chef::Util::Windows::NetUser.new(new_resource.username)
end
def load_current_resource
- if @new_resource.gid
- Chef::Log.warn("The 'gid' attribute is not implemented by the Windows platform. Please use the 'group' resource to assign a user to a group.")
+ if new_resource.gid
+ logger.warn("The 'gid' (or 'group') property is not implemented on the Windows platform. Please use the `members` property of the 'group' resource to assign a user to a group.")
end
- @current_resource = Chef::Resource::User.new(@new_resource.name)
- @current_resource.username(@new_resource.username)
- user_info = nil
+ @current_resource = Chef::Resource::User.new(new_resource.name)
+ current_resource.username(new_resource.username)
begin
user_info = @net_user.get_info
- @current_resource.uid(user_info[:user_id])
- @current_resource.comment(user_info[:full_name])
- @current_resource.home(user_info[:home_dir])
- @current_resource.shell(user_info[:script_path])
+ current_resource.uid(user_info[:user_id])
+ current_resource.comment(user_info[:full_name])
+ current_resource.home(user_info[:home_dir])
+ current_resource.shell(user_info[:script_path])
rescue Chef::Exceptions::UserIDNotFound => e
# e.message should be "The user name could not be found" but checking for that could cause a localization bug
@user_exists = false
- Chef::Log.debug("#{@new_resource} does not exist (#{e.message})")
+ logger.trace("#{new_resource} does not exist (#{e.message})")
end
- @current_resource
+ current_resource
end
# Check to see if the user needs any changes
@@ -64,12 +63,12 @@ class Chef
# <true>:: If a change is required
# <false>:: If the users are identical
def compare_user
- unless @net_user.validate_credentials(@new_resource.password)
- Chef::Log.debug("#{@new_resource} password has changed")
+ unless @net_user.validate_credentials(new_resource.password)
+ logger.trace("#{new_resource} password has changed")
return true
end
[ :uid, :comment, :home, :shell ].any? do |user_attrib|
- !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
+ !new_resource.send(user_attrib).nil? && new_resource.send(user_attrib) != current_resource.send(user_attrib)
end
end
@@ -98,7 +97,7 @@ class Chef
end
def set_options
- opts = { :name => @new_resource.username }
+ opts = { name: new_resource.username }
field_list = {
"comment" => "full_name",
@@ -108,16 +107,14 @@ class Chef
"password" => "password",
}
- field_list.sort { |a, b| a[0] <=> b[0] }.each do |field, option|
+ field_list.sort_by { |a| a[0] }.each do |field, option|
field_symbol = field.to_sym
- if @current_resource.send(field_symbol) != @new_resource.send(field_symbol)
- if @new_resource.send(field_symbol)
- unless field_symbol == :password
- Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field_symbol)}")
- end
- opts[option.to_sym] = @new_resource.send(field_symbol)
- end
+ next unless current_resource.send(field_symbol) != new_resource.send(field_symbol)
+ next unless new_resource.send(field_symbol)
+ unless field_symbol == :password
+ logger.trace("#{new_resource} setting #{field} to #{new_resource.send(field_symbol)}")
end
+ opts[option.to_sym] = new_resource.send(field_symbol)
end
opts
end
diff --git a/lib/chef/provider/whyrun_safe_ruby_block.rb b/lib/chef/provider/whyrun_safe_ruby_block.rb
index 3ea48017b7..ee4a659b00 100644
--- a/lib/chef/provider/whyrun_safe_ruby_block.rb
+++ b/lib/chef/provider/whyrun_safe_ruby_block.rb
@@ -22,10 +22,10 @@ class Chef
provides :whyrun_safe_ruby_block
def action_run
- @new_resource.block.call
- @new_resource.updated_by_last_action(true)
- @run_context.events.resource_update_applied(@new_resource, :create, "execute the whyrun_safe_ruby_block #{@new_resource.name}")
- Chef::Log.info("#{@new_resource} called")
+ new_resource.block.call
+ new_resource.updated_by_last_action(true)
+ @run_context.events.resource_update_applied(new_resource, :create, "execute the whyrun_safe_ruby_block #{new_resource.name}")
+ logger.info("#{new_resource} called")
end
end
end
diff --git a/lib/chef/provider/windows_env.rb b/lib/chef/provider/windows_env.rb
new file mode 100644
index 0000000000..4e7fa34216
--- /dev/null
+++ b/lib/chef/provider/windows_env.rb
@@ -0,0 +1,207 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright 2010-2016, VMware, 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"
+require "chef/resource/windows_env"
+require "chef/mixin/windows_env_helper"
+
+class Chef
+ class Provider
+ class WindowsEnv < Chef::Provider
+ include Chef::Mixin::WindowsEnvHelper
+ attr_accessor :key_exists
+
+ provides :env
+ provides :windows_env
+
+ def whyrun_supported?
+ false
+ end
+
+ def initialize(new_resource, run_context)
+ super
+ @key_exists = true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsEnv.new(new_resource.name)
+ current_resource.key_name(new_resource.key_name)
+
+ if env_key_exists(new_resource.key_name)
+ current_resource.value(env_value(new_resource.key_name))
+ else
+ @key_exists = false
+ logger.trace("#{new_resource} key does not exist")
+ end
+
+ current_resource
+ end
+
+ def env_key_exists(key_name)
+ env_value(key_name) ? true : false
+ end
+
+ # Check to see if value needs any changes
+ #
+ # ==== Returns
+ # <true>:: If a change is required
+ # <false>:: If a change is not required
+ def requires_modify_or_create?
+ if new_resource.delim
+ #e.g. check for existing value within PATH
+ new_values.inject(0) do |index, val|
+ next_index = current_values.find_index val
+ return true if next_index.nil? || next_index < index
+ next_index
+ end
+ false
+ else
+ new_resource.value != current_resource.value
+ end
+ end
+
+ alias_method :compare_value, :requires_modify_or_create?
+
+ def action_create
+ if @key_exists
+ if requires_modify_or_create?
+ modify_env
+ logger.info("#{new_resource} altered")
+ new_resource.updated_by_last_action(true)
+ end
+ else
+ create_env
+ logger.info("#{new_resource} created")
+ new_resource.updated_by_last_action(true)
+ end
+ end
+
+ #e.g. delete a PATH element
+ #
+ # ==== Returns
+ # <true>:: If we handled the element case and caller should not delete the key
+ # <false>:: Caller should delete the key, either no :delim was specific or value was empty
+ # after we removed the element.
+ def delete_element
+ return false unless new_resource.delim #no delim: delete the key
+ needs_delete = new_values.any? { |v| current_values.include?(v) }
+ if !needs_delete
+ logger.trace("#{new_resource} element '#{new_resource.value}' does not exist")
+ return true #do not delete the key
+ else
+ new_value =
+ current_values.select do |item|
+ not new_values.include?(item)
+ end.join(new_resource.delim)
+
+ if new_value.empty?
+ return false #nothing left here, delete the key
+ else
+ old_value = new_resource.value(new_value)
+ create_env
+ logger.trace("#{new_resource} deleted #{old_value} element")
+ new_resource.updated_by_last_action(true)
+ return true #we removed the element and updated; do not delete the key
+ end
+ end
+ end
+
+ def action_delete
+ if ( ENV[new_resource.key_name] || @key_exists ) && !delete_element
+ delete_env
+ logger.info("#{new_resource} deleted")
+ new_resource.updated_by_last_action(true)
+ end
+ end
+
+ def action_modify
+ if @key_exists
+ if requires_modify_or_create?
+ modify_env
+ logger.info("#{new_resource} modified")
+ new_resource.updated_by_last_action(true)
+ end
+ else
+ raise Chef::Exceptions::WindowsEnv, "Cannot modify #{new_resource} - key does not exist!"
+ end
+ end
+
+ def create_env
+ obj = env_obj(@new_resource.key_name)
+ unless obj
+ obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_
+ obj.name = @new_resource.key_name
+ obj.username = new_resource.user
+ end
+ obj.variablevalue = @new_resource.value
+ obj.put_
+ value = @new_resource.value
+ value = expand_path(value) if @new_resource.key_name.casecmp("PATH") == 0
+ ENV[@new_resource.key_name] = value
+ broadcast_env_change
+ end
+
+ def delete_env
+ obj = env_obj(@new_resource.key_name)
+ if obj
+ obj.delete_
+ broadcast_env_change
+ end
+ if ENV[@new_resource.key_name]
+ ENV.delete(@new_resource.key_name)
+ end
+ end
+
+ def modify_env
+ if new_resource.delim
+ new_resource.value((new_values + current_values).uniq.join(new_resource.delim))
+ end
+ create_env
+ end
+
+ # Returns the current values to split by delimiter
+ def current_values
+ @current_values ||= current_resource.value.split(new_resource.delim)
+ end
+
+ # Returns the new values to split by delimiter
+ def new_values
+ @new_values ||= new_resource.value.split(new_resource.delim)
+ end
+
+ def env_value(key_name)
+ obj = env_obj(key_name)
+ obj.variablevalue if obj
+ end
+
+ def env_obj(key_name)
+ return @env_obj if @env_obj
+ wmi = WmiLite::Wmi.new
+ # Note that by design this query is case insensitive with regard to key_name
+ environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'")
+ if environment_variables && environment_variables.length > 0
+ environment_variables.each do |env|
+ @env_obj = env.wmi_ole_object
+ return @env_obj if @env_obj.username.split('\\').last.casecmp(new_resource.user) == 0
+ end
+ end
+ @env_obj = nil
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/windows_path.rb b/lib/chef/provider/windows_path.rb
new file mode 100644
index 0000000000..1c78e20606
--- /dev/null
+++ b/lib/chef/provider/windows_path.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
+# Copyright:: Copyright 2008-2017, 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/mixin/windows_env_helper" if Chef::Platform.windows?
+require "chef/mixin/wide_string"
+require "chef/exceptions"
+
+class Chef
+ class Provider
+ class WindowsPath < Chef::Provider
+ include Chef::Mixin::WindowsEnvHelper if Chef::Platform.windows?
+
+ provides :windows_path
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsPath.new(new_resource.name)
+ @current_resource.path(new_resource.path)
+ @current_resource
+ end
+
+ action :add do
+ # The windows Env provider does not correctly expand variables in
+ # the PATH environment variable. Ruby expects these to be expanded.
+ #
+ path = expand_path(new_resource.path)
+ declare_resource(:env, "path") do
+ action :modify
+ delim ::File::PATH_SEPARATOR
+ value path.tr("/", '\\')
+ end
+ end
+
+ action :remove do
+ # The windows Env provider does not correctly expand variables in
+ # the PATH environment variable. Ruby expects these to be expanded.
+ #
+ path = expand_path(new_resource.path)
+ declare_resource(:env, "path") do
+ action :delete
+ delim ::File::PATH_SEPARATOR
+ value path.tr("/", '\\')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb
index 2de127addf..3b0202790c 100644
--- a/lib/chef/provider/windows_script.rb
+++ b/lib/chef/provider/windows_script.rb
@@ -33,8 +33,11 @@ class Chef
super( new_resource, run_context )
@script_extension = script_extension
- target_architecture = new_resource.architecture.nil? ?
- node_windows_architecture(run_context.node) : new_resource.architecture
+ target_architecture = if new_resource.architecture.nil?
+ node_windows_architecture(run_context.node)
+ else
+ new_resource.architecture
+ end
@is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture)
diff --git a/lib/chef/provider/windows_task.rb b/lib/chef/provider/windows_task.rb
new file mode 100644
index 0000000000..9a6fd39582
--- /dev/null
+++ b/lib/chef/provider/windows_task.rb
@@ -0,0 +1,587 @@
+#
+# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
+# Copyright:: Copyright 2008-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/mixin/shell_out"
+require "rexml/document"
+require "iso8601"
+require "chef/mixin/powershell_out"
+require "chef/provider"
+require "win32/taskscheduler" if Chef::Platform.windows?
+
+class Chef
+ class Provider
+ class WindowsTask < Chef::Provider
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::PowershellOut
+
+ if Chef::Platform.windows?
+ include Win32
+
+ provides :windows_task
+
+ MONTHS = {
+ JAN: TaskScheduler::JANUARY,
+ FEB: TaskScheduler::FEBRUARY,
+ MAR: TaskScheduler::MARCH,
+ APR: TaskScheduler::APRIL,
+ MAY: TaskScheduler::MAY,
+ JUN: TaskScheduler::JUNE,
+ JUL: TaskScheduler::JULY,
+ AUG: TaskScheduler::AUGUST,
+ SEP: TaskScheduler::SEPTEMBER,
+ OCT: TaskScheduler::OCTOBER,
+ NOV: TaskScheduler::NOVEMBER,
+ DEC: TaskScheduler::DECEMBER
+ }
+
+ DAYS_OF_WEEK = { MON: TaskScheduler::MONDAY,
+ TUE: TaskScheduler::TUESDAY,
+ WED: TaskScheduler::WEDNESDAY,
+ THU: TaskScheduler::THURSDAY,
+ FRI: TaskScheduler::FRIDAY,
+ SAT: TaskScheduler::SATURDAY,
+ SUN: TaskScheduler::SUNDAY }
+
+ WEEKS_OF_MONTH = {
+ FIRST: TaskScheduler::FIRST_WEEK,
+ SECOND: TaskScheduler::SECOND_WEEK,
+ THIRD: TaskScheduler::THIRD_WEEK,
+ FOURTH: TaskScheduler::FOURTH_WEEK
+ }
+
+ DAYS_OF_MONTH = {
+ 1 => TaskScheduler::TASK_FIRST,
+ 2 => TaskScheduler::TASK_SECOND,
+ 3 => TaskScheduler::TASK_THIRD,
+ 4 => TaskScheduler::TASK_FOURTH,
+ 5 => TaskScheduler::TASK_FIFTH,
+ 6 => TaskScheduler::TASK_SIXTH,
+ 7 => TaskScheduler::TASK_SEVENTH,
+ 8 => TaskScheduler::TASK_EIGHTH,
+ 9 => TaskScheduler::TASK_NINETH,
+ 10 => TaskScheduler::TASK_TENTH,
+ 11 => TaskScheduler::TASK_ELEVENTH,
+ 12 => TaskScheduler::TASK_TWELFTH,
+ 13 => TaskScheduler::TASK_THIRTEENTH,
+ 14 => TaskScheduler::TASK_FOURTEENTH,
+ 15 => TaskScheduler::TASK_FIFTEENTH,
+ 16 => TaskScheduler::TASK_SIXTEENTH,
+ 17 => TaskScheduler::TASK_SEVENTEENTH,
+ 18 => TaskScheduler::TASK_EIGHTEENTH,
+ 19 => TaskScheduler::TASK_NINETEENTH,
+ 20 => TaskScheduler::TASK_TWENTIETH,
+ 21 => TaskScheduler::TASK_TWENTY_FIRST,
+ 22 => TaskScheduler::TASK_TWENTY_SECOND,
+ 23 => TaskScheduler::TASK_TWENTY_THIRD,
+ 24 => TaskScheduler::TASK_TWENTY_FOURTH,
+ 25 => TaskScheduler::TASK_TWENTY_FIFTH,
+ 26 => TaskScheduler::TASK_TWENTY_SIXTH,
+ 27 => TaskScheduler::TASK_TWENTY_SEVENTH,
+ 28 => TaskScheduler::TASK_TWENTY_EIGHTH,
+ 29 => TaskScheduler::TASK_TWENTY_NINTH,
+ 30 => TaskScheduler::TASK_THIRTYETH,
+ 31 => TaskScheduler::TASK_THIRTY_FIRST
+ }
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsTask.new(new_resource.name)
+ task = TaskScheduler.new
+ if task.exists?(new_resource.task_name)
+ @current_resource.exists = true
+ task.get_task(new_resource.task_name)
+ @current_resource.task = task
+ pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}"
+ @current_resource.task_name(pathed_task_name)
+ else
+ @current_resource.exists = false
+ end
+ @current_resource
+ end
+
+ def action_create
+ if current_resource.exists
+ logger.trace "#{new_resource} task exist."
+ unless (task_needs_update?(current_resource.task)) || (new_resource.force)
+ logger.info "#{new_resource} task does not need updating and force is not specified - nothing to do"
+ return
+ end
+
+ # if start_day and start_time is not set by user current date and time will be set while updating any property
+ set_start_day_and_time unless new_resource.frequency == :none
+ update_task(current_resource.task)
+ else
+ basic_validation
+ set_start_day_and_time
+ converge_by("#{new_resource} task created") do
+ task = TaskScheduler.new
+ if new_resource.frequency == :none
+ task.new_work_item(new_resource.task_name, {})
+ task.activate(new_resource.task_name)
+ else
+ task.new_work_item(new_resource.task_name, trigger)
+ end
+ task.application_name = new_resource.command
+ task.working_directory = new_resource.cwd if new_resource.cwd
+ task.configure_settings(config_settings)
+ task.configure_principals(principal_settings)
+ task.set_account_information(new_resource.user, new_resource.password)
+ task.creator = new_resource.user
+ task.activate(new_resource.task_name)
+ end
+ end
+ end
+
+ def action_run
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status == "running"
+ logger.info "#{new_resource} task is currently running, skipping run"
+ else
+ converge_by("run scheduled task #{new_resource}") do
+ current_resource.task.run
+ end
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ def action_delete
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ converge_by("delete scheduled task #{new_resource}") do
+ ts = TaskScheduler.new
+ ts.delete(current_resource.task_name)
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ def action_end
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status != "running"
+ logger.trace "#{new_resource} is not running - nothing to do"
+ else
+ converge_by("#{new_resource} task ended") do
+ current_resource.task.stop
+ end
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ def action_enable
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status == "not scheduled"
+ converge_by("#{new_resource} task enabled") do
+ #TODO wind32-taskscheduler currently not having any method to handle this so using schtasks.exe here
+ run_schtasks "CHANGE", "ENABLE" => ""
+ end
+ else
+ logger.trace "#{new_resource} already enabled - nothing to do"
+ end
+ else
+ logger.fatal "#{new_resource} task does not exist - nothing to do"
+ raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable"
+ end
+ end
+
+ def action_disable
+ if current_resource.exists
+ logger.info "#{new_resource} task exists"
+ if %w{ready running}.include?(current_resource.task.status)
+ converge_by("#{new_resource} task disabled") do
+ #TODO: in win32-taskscheduler there is no method whcih disbales the task so currently calling disable with schtasks.exe
+ run_schtasks "CHANGE", "DISABLE" => ""
+ end
+ else
+ logger.warn "#{new_resource} already disabled - nothing to do"
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ alias_method :action_change, :action_create
+
+ private
+
+ def set_start_day_and_time
+ new_resource.start_day = Time.now.strftime("%m/%d/%Y") unless new_resource.start_day
+ new_resource.start_time = Time.now.strftime("%H:%M") unless new_resource.start_time
+ end
+
+ def update_task(task)
+ converge_by("#{new_resource} task updated") do
+ task.set_account_information(new_resource.user, new_resource.password)
+ task.application_name = new_resource.command if new_resource.command
+ task.working_directory = new_resource.cwd if new_resource.cwd
+ task.trigger = trigger unless new_resource.frequency == :none
+ task.configure_settings(config_settings)
+ task.creator = new_resource.user
+ task.configure_principals(principal_settings)
+ end
+ end
+
+ def trigger
+ start_month, start_day, start_year = new_resource.start_day.to_s.split("/")
+ start_hour, start_minute = new_resource.start_time.to_s.split(":")
+ #TODO currently end_month, end_year and end_year needs to be set to 0. If not set win32-taskscheduler throwing nil into integer error.
+ trigger_hash = {
+ start_year: start_year.to_i,
+ start_month: start_month.to_i,
+ start_day: start_day.to_i,
+ start_hour: start_hour.to_i,
+ start_minute: start_minute.to_i,
+ end_month: 0,
+ end_day: 0,
+ end_year: 0,
+ trigger_type: trigger_type,
+ type: type,
+ random_minutes_interval: new_resource.random_delay
+ }
+
+ if new_resource.frequency == :minute
+ trigger_hash[:minutes_interval] = new_resource.frequency_modifier
+ end
+
+ if new_resource.frequency == :hourly
+ minutes = convert_hours_in_minutes(new_resource.frequency_modifier.to_i)
+ trigger_hash[:minutes_interval] = minutes
+ end
+
+ if new_resource.minutes_interval
+ trigger_hash[:minutes_interval] = new_resource.minutes_interval
+ end
+
+ if new_resource.minutes_duration
+ trigger_hash[:minutes_duration] = new_resource.minutes_duration
+ end
+
+ if trigger_type == TaskScheduler::MONTHLYDOW && frequency_modifier_contains_last_week?(new_resource.frequency_modifier)
+ trigger_hash[:run_on_last_week_of_month] = true
+ else
+ trigger_hash[:run_on_last_week_of_month] = false
+ end
+
+ if trigger_type == TaskScheduler::MONTHLYDATE && day_includes_last_or_lastday?(new_resource.day)
+ trigger_hash[:run_on_last_day_of_month] = true
+ else
+ trigger_hash[:run_on_last_day_of_month] = false
+ end
+ trigger_hash
+ end
+
+ def frequency_modifier_contains_last_week?(frequency_modifier)
+ frequency_modifier = frequency_modifier.to_s.split(",")
+ frequency_modifier.map! { |value| value.strip.upcase }
+ frequency_modifier.include?("LAST")
+ end
+
+ def day_includes_last_or_lastday?(day)
+ day = day.to_s.split(",")
+ day.map! { |value| value.strip.upcase }
+ day.include?("LAST") || day.include?("LASTDAY")
+ end
+
+ def convert_hours_in_minutes(hours)
+ hours.to_i * 60 if hours
+ end
+
+ #TODO : Try to optimize this method
+ # known issue : Since start_day and time is not mandatory while updating weekly frequency for which start_day is not mentioned by user idempotency
+ # is not gettting maintained as new_resource.start_day is nil and we fetch the day of week from start_day to set and its currently coming as nil and don't match with current_task
+ def task_needs_update?(task)
+ flag = false
+ if new_resource.frequency == :none
+ flag = (task.account_information != new_resource.user ||
+ task.application_name != new_resource.command ||
+ task.principals[:run_level] != run_level)
+ else
+ current_task_trigger = task.trigger(0)
+ new_task_trigger = trigger
+ flag = (ISO8601::Duration.new(task.idle_settings[:idle_duration])) != (ISO8601::Duration.new(new_resource.idle_time * 60)) if new_resource.frequency == :on_idle
+ flag = (ISO8601::Duration.new(task.execution_time_limit)) != (ISO8601::Duration.new(new_resource.execution_time_limit * 60)) unless new_resource.execution_time_limit.nil?
+
+ # if trigger not found updating the task to add the trigger
+ if current_task_trigger.nil?
+ flag = true
+ else
+ flag = true if start_day_updated?(current_task_trigger, new_task_trigger) == true ||
+ start_time_updated?(current_task_trigger, new_task_trigger) == true ||
+ current_task_trigger[:trigger_type] != new_task_trigger[:trigger_type] ||
+ current_task_trigger[:type] != new_task_trigger[:type] ||
+ current_task_trigger[:random_minutes_interval].to_i != new_task_trigger[:random_minutes_interval].to_i ||
+ current_task_trigger[:minutes_interval].to_i != new_task_trigger[:minutes_interval].to_i ||
+ task.account_information != new_resource.user ||
+ task.application_name != new_resource.command ||
+ task.working_directory != new_resource.cwd.to_s ||
+ task.principals[:logon_type] != logon_type ||
+ task.principals[:run_level] != run_level
+
+ if trigger_type == TaskScheduler::MONTHLYDATE
+ flag = true if current_task_trigger[:run_on_last_day_of_month] != new_task_trigger[:run_on_last_day_of_month]
+ end
+
+ if trigger_type == TaskScheduler::MONTHLYDOW
+ flag = true if current_task_trigger[:run_on_last_week_of_month] != new_task_trigger[:run_on_last_week_of_month]
+ end
+ end
+ end
+ flag
+ end
+
+ def start_day_updated?(current_task_trigger, new_task_trigger)
+ ( new_resource.start_day && (current_task_trigger[:start_year].to_i != new_task_trigger[:start_year] ||
+ current_task_trigger[:start_month].to_i != new_task_trigger[:start_month] ||
+ current_task_trigger[:start_day].to_i != new_task_trigger[:start_day]) )
+ end
+
+ def start_time_updated?(current_task_trigger, new_task_trigger)
+ ( new_resource.start_time && ( current_task_trigger[:start_hour].to_i != new_task_trigger[:start_hour] ||
+ current_task_trigger[:start_minute].to_i != new_task_trigger[:start_minute] ) )
+ end
+
+ def trigger_type
+ case new_resource.frequency
+ when :once, :minute, :hourly
+ TaskScheduler::ONCE
+ when :daily
+ TaskScheduler::DAILY
+ when :weekly
+ TaskScheduler::WEEKLY
+ when :monthly
+ # If frequency modifier is set with frequency :monthly we are setting taskscheduler as monthlydow
+ # Ref https://msdn.microsoft.com/en-us/library/windows/desktop/aa382061(v=vs.85).aspx
+ new_resource.frequency_modifier.to_i.between?(1, 12) ? TaskScheduler::MONTHLYDATE : TaskScheduler::MONTHLYDOW
+ when :on_idle
+ TaskScheduler::ON_IDLE
+ when :onstart
+ TaskScheduler::AT_SYSTEMSTART
+ when :on_logon
+ TaskScheduler::AT_LOGON
+ else
+ raise ArgumentError, "Please set frequency"
+ end
+ end
+
+ def type
+ case trigger_type
+ when TaskScheduler::ONCE
+ { once: nil }
+ when TaskScheduler::DAILY
+ { days_interval: new_resource.frequency_modifier.to_i }
+ when TaskScheduler::WEEKLY
+ { weeks_interval: new_resource.frequency_modifier.to_i, days_of_week: days_of_week.to_i }
+ when TaskScheduler::MONTHLYDATE
+ { months: months_of_year.to_i, days: days_of_month.to_i }
+ when TaskScheduler::MONTHLYDOW
+ { months: months_of_year.to_i, days_of_week: days_of_week.to_i, weeks_of_month: weeks_of_month.to_i }
+ when TaskScheduler::ON_IDLE
+ # TODO: handle option for this trigger
+ when TaskScheduler::AT_LOGON
+ # TODO: handle option for this trigger
+ when TaskScheduler::AT_SYSTEMSTART
+ # TODO: handle option for this trigger
+ end
+ end
+
+ # Deleting last from the array of weeks of month since last week is handled in :run_on_last_week_of_month parameter.
+ def weeks_of_month
+ weeks_of_month = []
+ if new_resource.frequency_modifier
+ weeks = new_resource.frequency_modifier.split(",")
+ weeks.map! { |week| week.to_s.strip.upcase }
+ weeks.delete("LAST") if weeks.include?("LAST")
+ weeks_of_month = get_binary_values_from_constants(weeks, WEEKS_OF_MONTH)
+ end
+ weeks_of_month
+ end
+
+ # Deleting the "LAST" and "LASTDAY" from days since last day is handled in :run_on_last_day_of_month parameter.
+ def days_of_month
+ days_of_month = []
+ if new_resource.day
+ days = new_resource.day.split(",")
+ days.map! { |day| day.to_s.strip.upcase }
+ days.delete("LAST") if days.include?("LAST")
+ days.delete("LASTDAY") if days.include?("LASTDAY")
+ if days - (1..31).to_a
+ days.each do |day|
+ days_of_month << DAYS_OF_MONTH[day.to_i]
+ end
+ days_of_month = days_of_month.size > 1 ? days_of_month.inject(:|) : days_of_month[0]
+ end
+ else
+ days_of_month = DAYS_OF_MONTH[1]
+ end
+ days_of_month
+ end
+
+ def days_of_week
+ if new_resource.day
+ #this line of code is just to support backward compatibility of wild card *
+ new_resource.day = "mon, tue, wed, thu, fri, sat, sun" if new_resource.day == "*" && new_resource.frequency == :weekly
+ days = new_resource.day.split(",")
+ days.map! { |day| day.to_s.strip.upcase }
+ weeks_days = get_binary_values_from_constants(days, DAYS_OF_WEEK)
+ else
+ # following condition will make the frequency :weekly idempotent if start_day is not provided by user setting day as the current_resource day
+ if (current_resource) && (current_resource.task) && (current_resource.task.trigger(0)[:type][:days_of_week]) && (new_resource.start_day.nil?)
+ weeks_days = current_resource.task.trigger(0)[:type][:days_of_week]
+ else
+ day = get_day(new_resource.start_day).to_sym if new_resource.start_day
+ DAYS_OF_WEEK[day]
+ end
+ end
+ end
+
+ def months_of_year
+ months_of_year = []
+ if new_resource.frequency_modifier.to_i.between?(1, 12) && !(new_resource.months)
+ new_resource.months = set_months(new_resource.frequency_modifier.to_i)
+ end
+
+ if new_resource.months
+ #this line of code is just to support backward compatibility of wild card *
+ new_resource.months = "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec" if new_resource.months == "*" && new_resource.frequency == :monthly
+ months = new_resource.months.split(",")
+ months.map! { |month| month.to_s.strip.upcase }
+ months_of_year = get_binary_values_from_constants(months, MONTHS)
+ else
+ MONTHS.each do |key, value|
+ months_of_year << MONTHS[key]
+ end
+ months_of_year = months_of_year.inject(:|)
+ end
+ months_of_year
+ end
+
+ # This values are set for frequency_modifier set as 1-12
+ # This is to give backward compatibility validated this values with earlier code and running schtask.exe
+ # Used this as reference https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday--
+ def set_months(frequency_modifier)
+ case frequency_modifier
+ when 1
+ "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec"
+ when 2
+ "feb, apr, jun, aug, oct, dec"
+ when 3
+ "mar, jun, sep, dec"
+ when 4
+ "apr, aug, dec"
+ when 5
+ "may, oct"
+ when 6
+ "jun, dec"
+ when 7
+ "jul"
+ when 8
+ "aug"
+ when 9
+ "sep"
+ when 10
+ "oct"
+ when 11
+ "nov"
+ when 12
+ "dec"
+ end
+ end
+
+ def get_binary_values_from_constants(array_values, constant)
+ data = []
+ array_values.each do |value|
+ value = value.to_sym
+ data << constant[value]
+ end
+ data.size > 1 ? data.inject(:|) : data[0]
+ end
+
+ def run_level
+ case new_resource.run_level
+ when :highest
+ TaskScheduler::TASK_RUNLEVEL_HIGHEST
+ when :limited
+ TaskScheduler::TASK_RUNLEVEL_LUA
+ end
+ end
+
+ #TODO: while creating the configuration settings win32-taskscheduler it accepts execution time limit values in ISO8601 formata
+ def config_settings
+ settings = {
+ execution_time_limit: new_resource.execution_time_limit,
+ enabled: true
+ }
+ settings[:idle_duration] = new_resource.idle_time if new_resource.idle_time
+ settings[:run_only_if_idle] = true if new_resource.idle_time
+ settings
+ end
+
+ def principal_settings
+ settings = {}
+ settings [:run_level] = run_level
+ settings[:logon_type] = logon_type
+ settings
+ end
+
+ def logon_type
+ # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383566(v=vs.85).aspx
+ # if nothing is passed as logon_type the TASK_LOGON_SERVICE_ACCOUNT is getting set as default so using that for comparision.
+ new_resource.password.nil? ? TaskScheduler::TASK_LOGON_SERVICE_ACCOUNT : TaskScheduler::TASK_LOGON_PASSWORD
+ end
+
+ # This method checks if task and command attributes exist since those two are mandatory attributes to create a schedules task.
+ def basic_validation
+ validate = []
+ validate << "Command" if new_resource.command.nil? || new_resource.command.empty?
+ validate << "Task Name" if new_resource.task_name.nil? || new_resource.task_name.empty?
+ return true if validate.empty?
+ raise Chef::Exceptions::ValidationFailed.new "Value for '#{validate.join(', ')}' option cannot be empty"
+ end
+
+ # rubocop:disable Style/StringLiteralsInInterpolation
+ def run_schtasks(task_action, options = {})
+ cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" "
+ options.each_key do |option|
+ unless option == "TR"
+ cmd += "/#{option} "
+ cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == ""
+ end
+ end
+ # Appending Task Run [TR] option at the end since appending causing sometimes to append other options in option["TR"] value
+ if options["TR"]
+ cmd += "/TR \"#{options["TR"]} \" " unless task_action == "DELETE"
+ end
+ logger.trace("running: ")
+ logger.trace(" #{cmd}")
+ shell_out!(cmd, returns: [0])
+ end
+ # rubocop:enable Style/StringLiteralsInInterpolation
+
+ def get_day(date)
+ Date.strptime(date, "%m/%d/%Y").strftime("%a").upcase
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/yum_repository.rb b/lib/chef/provider/yum_repository.rb
new file mode 100644
index 0000000000..20a7a8e3d2
--- /dev/null
+++ b/lib/chef/provider/yum_repository.rb
@@ -0,0 +1,130 @@
+#
+# Author:: Thom May (<thom@chef.io>)
+# Copyright:: Copyright (c) 2016-2017, 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"
+require "chef/dsl/declare_resource"
+require "chef/mixin/which"
+require "chef/provider/noop"
+
+class Chef
+ class Provider
+ class YumRepository < Chef::Provider
+ extend Chef::Mixin::Which
+
+ provides :yum_repository do
+ which "yum"
+ end
+
+ def load_current_resource
+ end
+
+ action :create do
+ declare_resource(:template, "/etc/yum.repos.d/#{new_resource.repositoryid}.repo") do
+ if template_available?(new_resource.source)
+ source new_resource.source
+ else
+ source ::File.expand_path("../support/yum_repo.erb", __FILE__)
+ local true
+ end
+ sensitive new_resource.sensitive
+ variables(config: new_resource)
+ mode new_resource.mode
+ if new_resource.make_cache
+ notifies :run, "execute[yum clean metadata #{new_resource.repositoryid}]", :immediately if new_resource.clean_metadata || new_resource.clean_headers
+ notifies :run, "execute[yum-makecache-#{new_resource.repositoryid}]", :immediately
+ notifies :create, "ruby_block[package-cache-reload-#{new_resource.repositoryid}]", :immediately
+ end
+ end
+
+ declare_resource(:execute, "yum clean metadata #{new_resource.repositoryid}") do
+ command "yum clean metadata --disablerepo=* --enablerepo=#{new_resource.repositoryid}"
+ action :nothing
+ end
+
+ # get the metadata for this repo only
+ declare_resource(:execute, "yum-makecache-#{new_resource.repositoryid}") do
+ command "yum -q -y makecache --disablerepo=* --enablerepo=#{new_resource.repositoryid}"
+ action :nothing
+ only_if { new_resource.enabled }
+ end
+
+ # reload internal Chef yum/dnf cache
+ declare_resource(:ruby_block, "package-cache-reload-#{new_resource.repositoryid}") do
+ if ( platform?("fedora") && node["platform_version"].to_i >= 22 ) ||
+ ( platform_family?("rhel") && node["platform_version"].to_i >= 8 )
+ block { Chef::Provider::Package::Dnf::PythonHelper.instance.restart }
+ else
+ block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ end
+ action :nothing
+ end
+ end
+
+ action :delete do
+ # clean the repo cache first
+ declare_resource(:execute, "yum clean all #{new_resource.repositoryid}") do
+ command "yum clean all --disablerepo=* --enablerepo=#{new_resource.repositoryid}"
+ only_if "yum repolist all | grep -P '^#{new_resource.repositoryid}([ \t]|$)'"
+ end
+
+ declare_resource(:file, "/etc/yum.repos.d/#{new_resource.repositoryid}.repo") do
+ action :delete
+ notifies :create, "ruby_block[package-cache-reload-#{new_resource.repositoryid}]", :immediately
+ end
+
+ declare_resource(:ruby_block, "package-cache-reload-#{new_resource.repositoryid}") do
+ if ( platform?("fedora") && node["platform_version"].to_i >= 22 ) ||
+ ( platform_family?("rhel") && node["platform_version"].to_i >= 8 )
+ block { Chef::Provider::Package::Dnf::PythonHelper.instance.restart }
+ else
+ block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ end
+ action :nothing
+ end
+ end
+
+ action :makecache do
+ declare_resource(:execute, "yum-makecache-#{new_resource.repositoryid}") do
+ command "yum -q -y makecache --disablerepo=* --enablerepo=#{new_resource.repositoryid}"
+ action :run
+ only_if { new_resource.enabled }
+ end
+
+ declare_resource(:ruby_block, "package-cache-reload-#{new_resource.repositoryid}") do
+ if ( platform?("fedora") && node["platform_version"].to_i >= 22 ) ||
+ ( platform_family?("rhel") && node["platform_version"].to_i >= 8 )
+ block { Chef::Provider::Package::Dnf::PythonHelper.instance.restart }
+ else
+ block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ end
+ action :run
+ end
+ end
+
+ alias_method :action_add, :action_create
+ alias_method :action_remove, :action_delete
+
+ def template_available?(path)
+ !path.nil? && run_context.has_template_in_cookbook?(new_resource.cookbook_name, path)
+ end
+
+ end
+ end
+end
+
+Chef::Provider::Noop.provides :yum_repository
diff --git a/lib/chef/provider/zypper_repository.rb b/lib/chef/provider/zypper_repository.rb
new file mode 100644
index 0000000000..369d23a396
--- /dev/null
+++ b/lib/chef/provider/zypper_repository.rb
@@ -0,0 +1,169 @@
+#
+# Author:: Tim Smith (<tsmith@chef.io>)
+# Copyright:: Copyright (c) 2017, 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"
+require "chef/dsl/declare_resource"
+require "chef/provider/noop"
+require "chef/mixin/shell_out"
+require "shellwords"
+
+class Chef
+ class Provider
+ class ZypperRepository < Chef::Provider
+ provides :zypper_repository, platform_family: "suse"
+
+ def load_current_resource
+ end
+
+ action :create do
+ if new_resource.gpgautoimportkeys
+ install_gpg_key(new_resource.gpgkey)
+ else
+ logger.trace("'gpgautoimportkeys' property is set to false. Skipping key import.")
+ end
+
+ declare_resource(:template, "/etc/zypp/repos.d/#{escaped_repo_name}.repo") do
+ if template_available?(new_resource.source)
+ source new_resource.source
+ else
+ source ::File.expand_path("../support/zypper_repo.erb", __FILE__)
+ local true
+ end
+ sensitive new_resource.sensitive
+ variables(config: new_resource)
+ mode new_resource.mode
+ notifies :refresh, new_resource, :immediately if new_resource.refresh_cache
+ end
+ end
+
+ action :delete do
+ declare_resource(:execute, "zypper --quiet --non-interactive removerepo #{escaped_repo_name}") do
+ only_if "zypper --quiet lr #{escaped_repo_name}"
+ end
+ end
+
+ action :refresh do
+ declare_resource(:execute, "zypper --quiet --non-interactive refresh --force #{escaped_repo_name}") do
+ only_if "zypper --quiet lr #{escaped_repo_name}"
+ end
+ end
+
+ alias_method :action_add, :action_create
+ alias_method :action_remove, :action_delete
+
+ # zypper repos are allowed to have spaces in the names
+ # @return [String] escaped repo string
+ def escaped_repo_name
+ Shellwords.escape(new_resource.repo_name)
+ end
+
+ # return the specified cookbook name or the cookbook containing the
+ # resource.
+ #
+ # @return [String] name of the cookbook
+ def cookbook_name
+ new_resource.cookbook || new_resource.cookbook_name
+ end
+
+ # determine if a template file is available in the current run
+ # @param [String] path the path to the template file
+ #
+ # @return [Boolean] template file exists or doesn't
+ def template_available?(path)
+ !path.nil? && run_context.has_template_in_cookbook?(cookbook_name, path)
+ end
+
+ # determine if a cookbook file is available in the run
+ # @param [String] fn the path to the template file
+ #
+ # @return [Boolean] cookbook file exists or doesn't
+ def has_cookbook_file?(fn)
+ run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn)
+ end
+
+ # Given the provided key URI determine what kind of chef resource we need
+ # to fetch the key
+ # @param [String] uri the uri of the gpg key (local path or http URL)
+ #
+ # @raise [Chef::Exceptions::FileNotFound] Key isn't remote or found in the current run
+ #
+ # @return [Symbol] :remote_file or :cookbook_file
+ def key_type(uri)
+ if uri.start_with?("http")
+ logger.trace("Will use :remote_file resource to cache the gpg key locally")
+ :remote_file
+ elsif has_cookbook_file?(uri)
+ logger.trace("Will use :cookbook_file resource to cache the gpg key locally")
+ :cookbook_file
+ else
+ raise Chef::Exceptions::FileNotFound, "Cannot determine location of gpgkey. Must start with 'http' or be a file managed by Chef."
+ end
+ end
+
+ # is the provided key already installed
+ # @param [String] key_path the path to the key on the local filesystem
+ #
+ # @return [boolean] is the key already known by rpm
+ def key_installed?(key_path)
+ so = shell_out("rpm -qa gpg-pubkey*")
+ # expected output & match: http://rubular.com/r/RdF7EcXEtb
+ status = /gpg-pubkey-#{key_fingerprint(key_path)}/.match(so.stdout)
+ logger.trace("GPG key at #{key_path} is known by rpm? #{status ? "true" : "false"}")
+ status
+ end
+
+ # extract the gpg key fingerprint from a local file
+ # @param [String] key_path the path to the key on the local filesystem
+ #
+ # @return [String] the fingerprint of the key
+ def key_fingerprint(key_path)
+ so = shell_out!("gpg --with-fingerprint #{key_path}")
+ # expected output and match: http://rubular.com/r/BpfMjxySQM
+ fingerprint = /pub\s*\S*\/(\S*)/.match(so.stdout)[1].downcase
+ logger.trace("GPG fingerprint of key at #{key_path} is #{fingerprint}")
+ fingerprint
+ end
+
+ # install the provided gpg key
+ # @param [String] uri the uri of the local or remote gpg key
+ def install_gpg_key(uri)
+ unless uri
+ logger.trace("'gpgkey' property not provided or set to nil. Skipping key import.")
+ return
+ end
+
+ cached_keyfile = ::File.join(Chef::Config[:file_cache_path], uri.split("/")[-1])
+
+ declare_resource(key_type(new_resource.gpgkey), cached_keyfile) do
+ source uri
+ mode "0644"
+ sensitive new_resource.sensitive
+ action :create
+ end
+
+ declare_resource(:execute, "import gpg key from #{new_resource.gpgkey}") do
+ command "/bin/rpm --import #{cached_keyfile}"
+ not_if { key_installed?(cached_keyfile) }
+ action :run
+ end
+ end
+ end
+ end
+end
+
+Chef::Provider::Noop.provides :zypper_repository