diff options
Diffstat (limited to 'lib/chef')
66 files changed, 1255 insertions, 261 deletions
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb index 5efadd6f62..b23c01e622 100644 --- a/lib/chef/cookbook/metadata.rb +++ b/lib/chef/cookbook/metadata.rb @@ -100,8 +100,8 @@ class Chef @long_description = "" @license = "All rights reserved" - @maintainer = nil - @maintainer_email = nil + @maintainer = "" + @maintainer_email = "" @platforms = Mash.new @dependencies = Mash.new diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb index f5feb91063..c227739a8d 100644 --- a/lib/chef/deprecation/warnings.rb +++ b/lib/chef/deprecation/warnings.rb @@ -20,10 +20,12 @@ class Chef module Deprecation module Warnings + require "chef/version" + def add_deprecation_warnings_for(method_names) method_names.each do |name| define_method(name) do |*args| - message = "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13." + message = "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef #{Chef::VERSION.to_i.next}." message << " Please update your cookbooks accordingly." Chef.deprecated(:internal_api, message) super(*args) diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 91402bc6c2..a73062b676 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -515,7 +515,7 @@ This error is most often caused by network issues (proxies, etc) outside of chef "Resource #{r['Name']} is a binary resource" end end - super "Found multiple matching resources. #{matches_info.join("\n")}" + super "Found multiple resources matching #{matches_info[0]["Module"]["Name"]}:\n#{(matches_info.map { |f| f["Module"]["Version"] }).uniq.join("\n")}" end end diff --git a/lib/chef/http.rb b/lib/chef/http.rb index c741dcca97..14dd8b93a5 100644 --- a/lib/chef/http.rb +++ b/lib/chef/http.rb @@ -5,7 +5,7 @@ # Author:: Christopher Brown (<cb@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) # Author:: Daniel DeLeo (<dan@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"); @@ -144,9 +144,9 @@ class Chef def request(method, path, headers = {}, data = false) http_attempts ||= 0 url = create_url(path) - method, url, headers, data = apply_request_middleware(method, url, headers, data) + processed_method, url, processed_headers, processed_data = apply_request_middleware(method, url, headers, data) - response, rest_request, return_value = send_http_request(method, url, headers, data) + response, rest_request, return_value = send_http_request(processed_method, url, processed_headers, processed_data) response, rest_request, return_value = apply_response_middleware(response, rest_request, return_value) response.error! unless success_response?(response) @@ -157,7 +157,6 @@ class Chef response = e.response if response.kind_of?(Net::HTTPNotAcceptable) && version_retries - http_attempts > 0 Chef::Log.debug("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}") - sleep(http_retry_delay) retry else raise @@ -176,11 +175,12 @@ class Chef url = create_url(path) response, rest_request, return_value = nil, nil, nil tempfile = nil + data = nil method = :GET - method, url, headers, data = apply_request_middleware(method, url, headers, data) + method, url, processed_headers, data = apply_request_middleware(method, url, headers, data) - response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response| + response, rest_request, return_value = send_http_request(method, url, processed_headers, data) do |http_response| if http_response.kind_of?(Net::HTTPSuccess) tempfile = stream_to_tempfile(url, http_response, &progress_block) end @@ -196,7 +196,6 @@ class Chef response = e.response if response.kind_of?(Net::HTTPNotAcceptable) && version_retries - http_attempts > 0 Chef::Log.debug("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}") - sleep(http_retry_delay) retry else raise @@ -223,11 +222,12 @@ class Chef url = create_url(path) response, rest_request, return_value = nil, nil, nil tempfile = nil + data = nil method = :GET - method, url, headers, data = apply_request_middleware(method, url, headers, data) + method, url, processed_headers, data = apply_request_middleware(method, url, headers, data) - response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response| + response, rest_request, return_value = send_http_request(method, url, processed_headers, data) do |http_response| if http_response.kind_of?(Net::HTTPSuccess) tempfile = stream_to_tempfile(url, http_response) end @@ -252,7 +252,6 @@ class Chef response = e.response if response.kind_of?(Net::HTTPNotAcceptable) && version_retries - http_attempts > 0 Chef::Log.debug("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}") - sleep(http_retry_delay) retry else raise diff --git a/lib/chef/http/api_versions.rb b/lib/chef/http/api_versions.rb index 674d8f85a7..6c5ede40aa 100644 --- a/lib/chef/http/api_versions.rb +++ b/lib/chef/http/api_versions.rb @@ -37,6 +37,8 @@ class Chef end if http_response.key?("x-ops-server-api-version") ServerAPIVersions.instance.set_versions(JSONCompat.parse(http_response["x-ops-server-api-version"])) + else + ServerAPIVersions.instance.unversioned! end [http_response, rest_request, return_value] end diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb index 20c46aaa8d..7fc1acb889 100644 --- a/lib/chef/http/http_request.rb +++ b/lib/chef/http/http_request.rb @@ -49,6 +49,7 @@ class Chef ENCODING_GZIP_DEFLATE = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3".freeze GET = "get".freeze + PATCH = "patch".freeze PUT = "put".freeze POST = "post".freeze DELETE = "delete".freeze @@ -161,6 +162,8 @@ class Chef Net::HTTP::Post.new(req_path, headers) when PUT Net::HTTP::Put.new(req_path, headers) + when PATCH + Net::HTTP::Patch.new(req_path, headers) when DELETE Net::HTTP::Delete.new(req_path, headers) when HEAD diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index c395ebcfa0..9b16b94910 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -223,6 +223,7 @@ validation_client_name "#{@chef_config[:validation_client_name]}" attributes[:run_list] = @run_list end + attributes.delete(:run_list) if attributes[:policy_name] && !attributes[:policy_name].empty? attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty? end end diff --git a/lib/chef/knife/core/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb index df6c2fe942..f55f9abcbb 100644 --- a/lib/chef/knife/core/status_presenter.rb +++ b/lib/chef/knife/core/status_presenter.rb @@ -123,7 +123,7 @@ class Chef line_parts << run_list if run_list if node["platform"] - platform = node["platform"] + platform = node["platform"].dup if node["platform_version"] platform << " #{node['platform_version']}" end diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb index d809a8fc45..484c3ab3de 100644 --- a/lib/chef/knife/core/ui.rb +++ b/lib/chef/knife/core/ui.rb @@ -172,7 +172,7 @@ class Chef tf.sync = true tf.puts output tf.close - raise "Please set EDITOR environment variable" unless system("#{config[:editor]} #{tf.path}") + raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_using.html for details." unless system("#{config[:editor]} #{tf.path}") output = IO.read(tf.path) end diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb index c4c3380734..2a68ec5108 100644 --- a/lib/chef/knife/search.rb +++ b/lib/chef/knife/search.rb @@ -91,6 +91,8 @@ class Chef search_args[:filter_result] = create_result_filter(config[:filter_result]) elsif (not ui.config[:attribute].nil?) && (not ui.config[:attribute].empty?) search_args[:filter_result] = create_result_filter_from_attributes(ui.config[:attribute]) + elsif config[:id_only] + search_args[:filter_result] = create_result_filter_from_attributes([]) end begin diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index 380a60fdd6..ae14ce9954 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -273,6 +273,10 @@ class Chef opts[:paranoid] = false opts[:user_known_hosts_file] = "/dev/null" end + if ssh_config[:keepalive] + opts[:keepalive] = true + opts[:keepalive_interval] = ssh_config[:keepalive_interval] + end end end diff --git a/lib/chef/mash.rb b/lib/chef/mash.rb index 4e4f06634d..8b9e115dd1 100644 --- a/lib/chef/mash.rb +++ b/lib/chef/mash.rb @@ -105,6 +105,12 @@ class Mash < Hash regular_writer(convert_key(key), convert_value(value)) end + # internal API for use by Chef's deep merge cache + # @api private + def internal_set(key, value) + regular_writer(key, convert_value(value)) + end + # @param other_hash<Hash> # A hash to update values in the mash with. The keys and the values will be # converted to Mash format. diff --git a/lib/chef/mixin/deep_merge.rb b/lib/chef/mixin/deep_merge.rb index 7016b08ff7..9ec2b397d4 100644 --- a/lib/chef/mixin/deep_merge.rb +++ b/lib/chef/mixin/deep_merge.rb @@ -64,7 +64,7 @@ class Chef when Hash if dest.kind_of?(Hash) source.each do |src_key, src_value| - if dest[src_key] + if dest.key?(src_key) dest[src_key] = deep_merge!(src_value, dest[src_key]) else # dest[src_key] doesn't exist so we take whatever source has dest[src_key] = src_value diff --git a/lib/chef/mixin/user_context.rb b/lib/chef/mixin/user_context.rb new file mode 100644 index 0000000000..40a72912a3 --- /dev/null +++ b/lib/chef/mixin/user_context.rb @@ -0,0 +1,52 @@ +# +# Author:: Adam Edwards (<adamed@chef.io>) +# Copyright:: Copyright (c) 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/util/windows/logon_session" if Chef::Platform.windows? + +class Chef + module Mixin + module UserContext + + def with_user_context(user, password, domain = nil, &block) + if node["platform_family"] != "windows" + raise Exceptions::UnsupportedPlatform, "User context impersonation is supported only on the Windows platform" + end + + if ! block_given? + raise ArgumentError, "You must supply a block to `with_user_context`" + end + + login_session = nil + + begin + if user + logon_session = Chef::Util::Windows::LogonSession.new(user, password, domain) + logon_session.open + logon_session.set_user_context + end + yield + ensure + logon_session.close if logon_session + end + end + + protected(:with_user_context) + + end + end +end diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb index 57d1b0a4d3..2998866bb2 100644 --- a/lib/chef/node/attribute.rb +++ b/lib/chef/node/attribute.rb @@ -399,7 +399,7 @@ class Chef # def merged_attributes(*path) - immutablize(merge_all(path)) + merge_all(path) end def combined_override(*path) @@ -536,15 +536,10 @@ class Chef apply_path(@automatic, path), ] - components.map! do |component| - safe_dup(component) - end - - return nil if components.compact.empty? - - components.inject(ImmutableMash.new({}, self, __node__, :merged)) do |merged, component| - Chef::Mixin::DeepMerge.hash_only_merge!(merged, component) + ret = components.inject(NIL) do |merged, component| + hash_only_merge!(merged, component) end + ret == NIL ? nil : ret end # Deep merge the default attribute levels with array merging. @@ -554,10 +549,11 @@ class Chef # @param path [Array] Array of args to method chain to descend into the node object # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object def merge_defaults(path) - DEFAULT_COMPONENTS.inject(nil) do |merged, component_ivar| + ret = DEFAULT_COMPONENTS.inject(NIL) do |merged, component_ivar| component_value = apply_path(instance_variable_get(component_ivar), path) - Chef::Mixin::DeepMerge.deep_merge(component_value, merged) + deep_merge!(merged, component_value) end + ret == NIL ? nil : ret end # Deep merge the override attribute levels with array merging. @@ -567,10 +563,11 @@ class Chef # @param path [Array] Array of args to method chain to descend into the node object # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object def merge_overrides(path) - OVERRIDE_COMPONENTS.inject(nil) do |merged, component_ivar| + ret = OVERRIDE_COMPONENTS.inject(NIL) do |merged, component_ivar| component_value = apply_path(instance_variable_get(component_ivar), path) - Chef::Mixin::DeepMerge.deep_merge(component_value, merged) + deep_merge!(merged, component_value) end + ret == NIL ? nil : ret end # needed for __path__ @@ -578,7 +575,76 @@ class Chef key.kind_of?(Symbol) ? key.to_s : key end - end + NIL = Object.new + + # @api private + def deep_merge!(merge_onto, merge_with) + # If there are two Hashes, recursively merge. + if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash) + merge_with.each do |key, merge_with_value| + value = + if merge_onto.has_key?(key) + deep_merge!(safe_dup(merge_onto[key]), merge_with_value) + else + merge_with_value + end + + # internal_set bypasses converting keys, does convert values and allows writing to immutable mashes + merge_onto.internal_set(key, value) + end + merge_onto + + elsif merge_onto.kind_of?(Array) && merge_with.kind_of?(Array) + merge_onto |= merge_with + + # If merge_with is nil, don't replace merge_onto + elsif merge_with.nil? + merge_onto + + # In all other cases, replace merge_onto with merge_with + else + if merge_with.kind_of?(Hash) + Chef::Node::VividMash.new(merge_with) + elsif merge_with.kind_of?(Array) + Chef::Node::AttrArray.new(merge_with) + else + merge_with + end + end + end + # @api private + def hash_only_merge!(merge_onto, merge_with) + # If there are two Hashes, recursively merge. + if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash) + merge_with.each do |key, merge_with_value| + value = + if merge_onto.has_key?(key) + hash_only_merge!(safe_dup(merge_onto[key]), merge_with_value) + else + merge_with_value + end + + # internal_set bypasses converting keys, does convert values and allows writing to immutable mashes + merge_onto.internal_set(key, value) + end + merge_onto + + # If merge_with is nil, don't replace merge_onto + elsif merge_with.nil? + merge_onto + + # In all other cases, replace merge_onto with merge_with + else + if merge_with.kind_of?(Hash) + Chef::Node::ImmutableMash.new(merge_with) + elsif merge_with.kind_of?(Array) + Chef::Node::ImmutableArray.new(merge_with) + else + merge_with + end + end + end + end end end diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb index be9285a755..848e12d2df 100644 --- a/lib/chef/node/immutable_collections.rb +++ b/lib/chef/node/immutable_collections.rb @@ -30,16 +30,22 @@ class Chef e end - def immutablize(value) + def convert_value(value) case value when Hash ImmutableMash.new(value, __root__, __node__, __precedence__) when Array ImmutableArray.new(value, __root__, __node__, __precedence__) + when ImmutableMash, ImmutableArray + value else safe_dup(value).freeze end end + + def immutablize(value) + convert_value(value) + end end # == ImmutableArray @@ -90,7 +96,9 @@ class Chef alias_method :to_array, :to_a - # for consistency's sake -- integers 'converted' to integers + private + + # needed for __path__ def convert_key(key) key end @@ -115,31 +123,20 @@ class Chef include Immutablize include CommonAPI - alias :internal_set :[]= - private :internal_set + # this is for deep_merge usage, chef users must never touch this API + # @api private + def internal_set(key, value) + regular_writer(key, convert_value(value)) + end def initialize(mash_data = {}) mash_data.each do |key, value| - internal_set(key, immutablize(value)) + internal_set(key, value) end end - def public_method_that_only_deep_merge_should_use(key, value) - internal_set(key, immutablize(value)) - end - alias :attribute? :has_key? - # Mash uses #convert_value to mashify values on input. - # Since we're handling this ourselves, override it to be a no-op - # - # FIXME? this seems wrong to do and i think is responsible for - # #dup needing to be more complicated than Mash.new(self)? - # - def convert_value(value) - value - end - # NOTE: #default and #default= are likely to be pretty confusing. For a # regular ruby Hash, they control what value is returned for, e.g., # hash[:no_such_key] #=> hash.default diff --git a/lib/chef/provider/apt_preference.rb b/lib/chef/provider/apt_preference.rb new file mode 100644 index 0000000000..ff4ea02ff0 --- /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, os: "linux", 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") + Chef::Log.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}") + Chef::Log.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") + Chef::Log.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 05172c9f98..2d86a6fce4 100644 --- a/lib/chef/provider/apt_repository.rb +++ b/lib/chef/provider/apt_repository.rb @@ -19,7 +19,6 @@ require "chef/resource" require "chef/dsl/declare_resource" require "chef/mixin/shell_out" -require "chef/mixin/which" require "chef/http/simple" require "chef/provider/noop" @@ -27,11 +26,8 @@ class Chef class Provider class AptRepository < Chef::Provider include Chef::Mixin::ShellOut - extend Chef::Mixin::Which - provides :apt_repository do - which("apt-get") - end + provides :apt_repository, os: "linux", platform_family: "debian" LIST_APT_KEYS = "apt-key list".freeze LIST_APT_KEY_FINGERPRINTS = "apt-key adv --list-public-keys --with-fingerprint --with-colons".freeze @@ -41,10 +37,12 @@ class Chef 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 + 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 @@ -151,19 +149,19 @@ class Chef (installed_keys & proposed_keys).sort == proposed_keys.sort end - def install_key_from_uri - key_name = new_resource.key.gsub(/[^0-9A-Za-z\-]/, "_") + 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") + type = if key.start_with?("http") :remote_file - elsif has_cookbook_file?(new_resource.key) + elsif has_cookbook_file?(key) :cookbook_file else raise Chef::Exceptions::FileNotFound, "Cannot locate key file" end declare_resource(type, cached_keyfile) do - source new_resource.key + source key mode "0644" sensitive new_resource.sensitive action :create @@ -181,7 +179,7 @@ class Chef end end - def install_key_from_keyserver(key = new_resource.key, keyserver = new_resource.keyserver) + def install_key_from_keyserver(key, keyserver = new_resource.keyserver) cmd = "apt-key adv --recv" cmd << " --keyserver-options http-proxy=#{new_resource.key_proxy}" if new_resource.key_proxy cmd << " --keyserver " diff --git a/lib/chef/provider/apt_update.rb b/lib/chef/provider/apt_update.rb index 670f3ad7f6..bfd9603e4e 100644 --- a/lib/chef/provider/apt_update.rb +++ b/lib/chef/provider/apt_update.rb @@ -18,16 +18,12 @@ require "chef/provider" require "chef/provider/noop" -require "chef/mixin/which" +require "chef/dsl/declare_resource" class Chef class Provider class AptUpdate < Chef::Provider - extend Chef::Mixin::Which - - provides :apt_update do - which("apt-get") - end + provides :apt_update, os: "linux", platform_family: "debian" APT_CONF_DIR = "/etc/apt/apt.conf.d" STAMP_DIR = "/var/lib/apt/periodic" diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb index d19a95b4ae..d5a0bdfa11 100644 --- a/lib/chef/provider/execute.rb +++ b/lib/chef/provider/execute.rb @@ -27,7 +27,7 @@ class Chef provides :execute - def_delegators :new_resource, :command, :returns, :environment, :user, :domain, :password, :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) @@ -102,6 +102,7 @@ class Chef opts[:live_stream] = STDOUT end end + opts[:elevated] = elevated if elevated opts end diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb index db83e0aea6..bdbeca5941 100644 --- a/lib/chef/provider/git.rb +++ b/lib/chef/provider/git.rb @@ -28,6 +28,8 @@ class Chef extend Forwardable provides :git + GIT_VERSION_PATTERN = Regexp.compile('git version (\d+\.\d+.\d+)') + def_delegator :new_resource, :destination, :cwd def load_current_resource @@ -103,8 +105,21 @@ class Chef end end - def git_minor_version - @git_minor_version ||= Gem::Version.new( git("--version").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 + Chef::Log.warn "Unable to parse git version from '#{output}'" + @git_gem_version = nil + end + @git_gem_version end def existing_git_clone? @@ -142,7 +157,7 @@ class Chef 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_minor_version >= Gem::Version.new("1.7.10") + clone_cmd << "--no-single-branch" if new_resource.depth && git_has_single_branch_option? clone_cmd << "\"#{new_resource.repository}\"" clone_cmd << "\"#{cwd}\"" @@ -285,6 +300,8 @@ class Chef refs.find_all { |m| m[1] == pattern } end + alias git_minor_version git_gem_version + private def run_options(run_opts = {}) diff --git a/lib/chef/provider/http_request.rb b/lib/chef/provider/http_request.rb index 8370c2375c..cafdc1e007 100644 --- a/lib/chef/provider/http_request.rb +++ b/lib/chef/provider/http_request.rb @@ -62,6 +62,20 @@ class Chef 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} PATCH to #{new_resource.url} successful") + Chef::Log.debug("#{new_resource} PATCH request response: #{body}") + end + end + # 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 diff --git a/lib/chef/provider/ifconfig/redhat.rb b/lib/chef/provider/ifconfig/redhat.rb index 8af9f10f67..bf3d979e86 100644 --- a/lib/chef/provider/ifconfig/redhat.rb +++ b/lib/chef/provider/ifconfig/redhat.rb @@ -38,6 +38,10 @@ class Chef <% 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}" end diff --git a/lib/chef/provider/launchd.rb b/lib/chef/provider/launchd.rb index 9c368c2b48..8281410d42 100644 --- a/lib/chef/provider/launchd.rb +++ b/lib/chef/provider/launchd.rb @@ -90,6 +90,7 @@ class Chef end def manage_plist(action) + return unless manage_agent?(action) if source res = cookbook_file_resource else @@ -101,11 +102,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 + Chef::Log.debug("#{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 diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index 4b73f47ed3..4810728524 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -311,18 +311,19 @@ class Chef # # This MUST have 'equality' semantics -- the exact thing matches the exact thing. # - # The current_version should probably be dropped out of the method signature, it should - # always be the first argument. - # # 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 + # 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. # - # `current_version_equals?(version)` would be a better name + # `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`. + # def target_version_already_installed?(current_version, target_version) return false unless current_version && target_version current_version == target_version @@ -333,10 +334,8 @@ class Chef # # Subclasses MAY override this to provide fuzzy matching on the resource ('>=' and '~>' stuff) # - # This should only ever be offered the same arguments (so they should most likely be - # removed from the method signature). + # `version_satisfied_by?(version, constraint)` might be a better name to make this generic. # - # `new_version_satisfied?()` might be a better name def version_requirement_satisfied?(current_version, new_version) target_version_already_installed?(current_version, new_version) end diff --git a/lib/chef/provider/package/dnf.rb b/lib/chef/provider/package/dnf.rb index 42d679c940..31279e8312 100644 --- a/lib/chef/provider/package/dnf.rb +++ b/lib/chef/provider/package/dnf.rb @@ -35,10 +35,12 @@ class Chef use_multipackage_api use_package_name_for_source - provides :package, platform_family: %w{rhel fedora amazon} do + provides :package, platform_family: %w{fedora amazon} do which("dnf") && shell_out("rpm -q dnf").stdout =~ /^dnf-[1-9]/ end + provides :package, platform_family: %w{rhel}, platform_version: ">= 8" + provides :dnf_package, os: "linux" # diff --git a/lib/chef/provider/package/dnf/dnf_helper.py b/lib/chef/provider/package/dnf/dnf_helper.py index ef08bb54c2..eb4d238f65 100644 --- a/lib/chef/provider/package/dnf/dnf_helper.py +++ b/lib/chef/provider/package/dnf/dnf_helper.py @@ -72,7 +72,6 @@ def exit_handler(signal, frame): signal.signal(signal.SIGINT, exit_handler) signal.signal(signal.SIGHUP, exit_handler) signal.signal(signal.SIGPIPE, exit_handler) -signal.signal(signal.SIGCHLD, exit_handler) while 1: # kill self if we get orphaned (tragic) diff --git a/lib/chef/provider/package/windows/registry_uninstall_entry.rb b/lib/chef/provider/package/windows/registry_uninstall_entry.rb index 03f6ff318e..64889e1649 100644 --- a/lib/chef/provider/package/windows/registry_uninstall_entry.rb +++ b/lib/chef/provider/package/windows/registry_uninstall_entry.rb @@ -40,7 +40,7 @@ class Chef begin entry = reg.open(key, desired) display_name = read_registry_property(entry, "DisplayName") - if display_name == package_name + 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 diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb index 7b8fd6c426..41e8433046 100644 --- a/lib/chef/provider/package/zypper.rb +++ b/lib/chef/provider/package/zypper.rb @@ -103,7 +103,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", name, version) end def upgrade_package(name, version) @@ -112,19 +112,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", name, version) + zypper_package("addlock", *options, name, version) end def unlock_package(name, version) - zypper_package("removelock", name, version) + zypper_package("removelock", *options, name, version) end private diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb index c0a0f9433c..d2de3d0b5f 100644 --- a/lib/chef/provider/remote_file.rb +++ b/lib/chef/provider/remote_file.rb @@ -29,6 +29,25 @@ class Chef 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) super 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/network_file.rb b/lib/chef/provider/remote_file/network_file.rb index 44046132a9..a08bfd2453 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.debug("#{new_resource} staging #{@source} to #{tempfile.path}") + + with_user_context(new_resource.remote_user, new_resource.remote_password, new_resource.remote_domain) 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/route.rb b/lib/chef/provider/route.rb index 59d516be6a..2439f45eda 100644 --- a/lib/chef/provider/route.rb +++ b/lib/chef/provider/route.rb @@ -86,7 +86,9 @@ class Chef self.is_running = false # cidr or quad dot mask - new_ip = if new_resource.netmask + 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) @@ -180,12 +182,29 @@ class Chef 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 + if new_resource.target == "default" + network_file_name = "/etc/sysconfig/network" + converge_by("write route default route to #{network_file_name}") do + Chef::Log.debug("#{new_resource} writing default route #{new_resource.gateway} to #{network_file_name}") + if ::File.exists?(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]) + Chef::Log.debug("#{new_resource} writing route.#{k}\n#{conf[k]}") + network_file.close + end end end end diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb index 4056b72649..9dc7b81a29 100644 --- a/lib/chef/provider/service/macosx.rb +++ b/lib/chef/provider/service/macosx.rb @@ -52,17 +52,18 @@ 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 + @console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name Chef::Log.debug("#{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 diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb index deec25b187..2da35b3b9e 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 @@ -100,7 +101,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} 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 @@ -113,7 +114,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} 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/support/yum_repo.erb b/lib/chef/provider/support/yum_repo.erb index 6f1325573d..f60d8688da 100644 --- a/lib/chef/provider/support/yum_repo.erb +++ b/lib/chef/provider/support/yum_repo.erb @@ -61,6 +61,9 @@ keepalive=1 <% if @config.metadata_expire %> metadata_expire=<%= @config.metadata_expire %> <% end %> +<% if @config.metalink %> +metalink=<%= @config.metalink %> +<% end %> <% if @config.mirrorlist %> mirrorlist=<%= @config.mirrorlist %> <% end %> @@ -112,6 +115,9 @@ sslclientkey=<%= @config.sslclientkey %> <% unless @config.sslverify.nil? %> sslverify=<%= ( @config.sslverify ) ? 'true' : 'false' %> <% end %> +<% if @config.throttle %> +throttle=<%= @config.throttle %> +<% end %> <% if @config.timeout %> timeout=<%= @config.timeout %> <% 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 143efe7b91..a2ef64044b 100644 --- a/lib/chef/provider/systemd_unit.rb +++ b/lib/chef/provider/systemd_unit.rb @@ -22,6 +22,7 @@ require "chef/mixin/shell_out" require "chef/resource/file" require "chef/resource/file/verification/systemd_unit" require "iniparse" +require "shellwords" class Chef class Provider @@ -203,11 +204,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 diff --git a/lib/chef/provider/windows_path.rb b/lib/chef/provider/windows_path.rb new file mode 100644 index 0000000000..b31789b4b9 --- /dev/null +++ b/lib/chef/provider/windows_path.rb @@ -0,0 +1,62 @@ +# +# 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? + + 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) + converge_by "Adding #{new_resource.path} to path environment variable" do + declare_resource(:env, "path") do + action :modify + delim ::File::PATH_SEPARATOR + value path.tr("/", '\\') + end + 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_task.rb b/lib/chef/provider/windows_task.rb index a96d4b2b7e..8703c30da9 100644 --- a/lib/chef/provider/windows_task.rb +++ b/lib/chef/provider/windows_task.rb @@ -7,7 +7,7 @@ # 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 +# 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, @@ -30,129 +30,153 @@ class Chef provides :windows_task, os: "windows" def load_current_resource - @current_resource = Chef::Resource::WindowsTask.new(new_resource.name) + self.current_resource = Chef::Resource::WindowsTask.new(new_resource.name) pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}" - @current_resource.task_name(pathed_task_name) + current_resource.task_name(pathed_task_name) task_hash = load_task_hash(pathed_task_name) set_current_resource(task_hash) if task_hash.respond_to?(:[]) && task_hash[:TaskName] == pathed_task_name - @current_resource + current_resource end def set_current_resource(task_hash) - @current_resource.exists = true - @current_resource.command(task_hash[:TaskToRun]) - @current_resource.cwd(task_hash[:StartIn]) unless task_hash[:StartIn] == "N/A" - @current_resource.user(task_hash[:RunAsUser]) + current_resource.exists = true + current_resource.command(task_hash[:TaskToRun]) + current_resource.cwd(task_hash[:StartIn]) unless task_hash[:StartIn] == "N/A" + current_resource.user(task_hash[:RunAsUser]) set_current_run_level task_hash[:run_level] set_current_frequency task_hash - @current_resource.day(task_hash[:day]) if task_hash[:day] - @current_resource.months(task_hash[:months]) if task_hash[:months] + current_resource.day(task_hash[:day]) if task_hash[:day] + current_resource.months(task_hash[:months]) if task_hash[:months] set_current_idle_time(task_hash[:idle_time]) if task_hash[:idle_time] - @current_resource.random_delay(task_hash[:random_delay]) if task_hash[:random_delay] - @current_resource.execution_time_limit(task_hash[:execution_time_limit]) if task_hash[:execution_time_limit] + current_resource.random_delay(task_hash[:random_delay]) if task_hash[:random_delay] + # schtask sets execution_time_limit as PT72H by default + current_resource.execution_time_limit(task_hash[:execution_time_limit] || "PT72H") + current_resource.status = :running if task_hash[:Status] == "Running" + current_resource.enabled = true if task_hash[:ScheduledTaskState] == "Enabled" + current_resource.start_time = task_hash[:StartTime] if task_hash[:StartTime] + current_resource.start_day = task_hash[:StartDate] if task_hash[:StartDate] + end - @current_resource.status = :running if task_hash[:Status] == "Running" - @current_resource.enabled = true if task_hash[:ScheduledTaskState] == "Enabled" + # 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 - def action_create - if @current_resource.exists && !(task_need_update? || @new_resource.force) - Chef::Log.info "#{@new_resource} task already exists - nothing to do" - else - options = {} - options["F"] = "" if @new_resource.force || task_need_update? - options["SC"] = schedule - options["MO"] = @new_resource.frequency_modifier if frequency_modifier_allowed - options["I"] = @new_resource.idle_time unless @new_resource.idle_time.nil? - options["SD"] = @new_resource.start_day unless @new_resource.start_day.nil? - options["ST"] = @new_resource.start_time unless @new_resource.start_time.nil? - options["TR"] = @new_resource.command - options["RU"] = @new_resource.user - options["RP"] = @new_resource.password if use_password? - options["RL"] = "HIGHEST" if @new_resource.run_level == :highest - options["IT"] = "" if @new_resource.interactive_enabled - options["D"] = @new_resource.day if @new_resource.day - options["M"] = @new_resource.months unless @new_resource.months.nil? - - run_schtasks "CREATE", options - xml_options = [] - xml_options << "cwd" if new_resource.cwd - xml_options << "random_delay" if new_resource.random_delay - xml_options << "execution_time_limit" if new_resource.execution_time_limit - update_task_xml(xml_options) unless xml_options.empty? + # get array of windows task resource attributes + def resource_attributes + %w{ command user run_level cwd frequency_modifier frequency idle_time random_delay execution_time_limit start_day start_time } + end - new_resource.updated_by_last_action true - Chef::Log.info "#{@new_resource} task created" + def action_create + if current_resource.exists + if !(task_need_update? || new_resource.force) + Chef::Log.info "#{new_resource} task already exists - nothing to do" + return + end + # To merge current resource and new resource attributes + resource_attributes.each do |attribute| + new_resource_attribute = new_resource.send(attribute) + current_resource_attribute = current_resource.send(attribute) + new_resource.send("#{attribute}=", current_resource_attribute ) if current_resource_attribute && new_resource_attribute.nil? + end end + basic_validation + options = {} + options["F"] = "" if new_resource.force || task_need_update? + options["SC"] = schedule + options["MO"] = new_resource.frequency_modifier if frequency_modifier_allowed + options["I"] = new_resource.idle_time unless new_resource.idle_time.nil? + options["SD"] = convert_user_date_to_system_date new_resource.start_day unless new_resource.start_day.nil? + options["ST"] = new_resource.start_time unless new_resource.start_time.nil? + options["TR"] = new_resource.command + options["RU"] = new_resource.user + options["RP"] = new_resource.password if use_password? + options["RL"] = "HIGHEST" if new_resource.run_level == :highest + options["IT"] = "" if new_resource.interactive_enabled + options["D"] = new_resource.day if new_resource.day + options["M"] = new_resource.months unless new_resource.months.nil? + run_schtasks "CREATE", options + xml_options = [] + xml_options << "cwd" if new_resource.cwd + xml_options << "random_delay" if new_resource.random_delay + xml_options << "execution_time_limit" if new_resource.execution_time_limit + update_task_xml(xml_options) unless xml_options.empty? + + new_resource.updated_by_last_action true + Chef::Log.info "#{new_resource} task created" end def action_run - if @current_resource.exists - if @current_resource.status == :running - Chef::Log.info "#{@new_resource} task is currently running, skipping run" + if current_resource.exists + if current_resource.status == :running + Chef::Log.info "#{new_resource} task is currently running, skipping run" else run_schtasks "RUN" new_resource.updated_by_last_action true - Chef::Log.info "#{@new_resource} task ran" + Chef::Log.info "#{new_resource} task ran" end else - Chef::Log.warn "#{@new_resource} task doesn't exists - nothing to do" + Chef::Log.warn "#{new_resource} task doesn't exists - nothing to do" end end def action_delete - if @current_resource.exists + if current_resource.exists # always need to force deletion run_schtasks "DELETE", "F" => "" new_resource.updated_by_last_action true - Chef::Log.info "#{@new_resource} task deleted" + Chef::Log.info "#{new_resource} task deleted" else - Chef::Log.warn "#{@new_resource} task doesn't exists - nothing to do" + Chef::Log.warn "#{new_resource} task doesn't exists - nothing to do" end end def action_end - if @current_resource.exists - if @current_resource.status != :running - Chef::Log.debug "#{@new_resource} is not running - nothing to do" + if current_resource.exists + if current_resource.status != :running + Chef::Log.debug "#{new_resource} is not running - nothing to do" else run_schtasks "END" - @new_resource.updated_by_last_action true - Chef::Log.info "#{@new_resource} task ended" + new_resource.updated_by_last_action true + Chef::Log.info "#{new_resource} task ended" end else - Chef::Log.warn "#{@new_resource} task doesn't exist - nothing to do" + Chef::Log.warn "#{new_resource} task doesn't exist - nothing to do" end end def action_enable - if @current_resource.exists - if @current_resource.enabled - Chef::Log.debug "#{@new_resource} already enabled - nothing to do" + if current_resource.exists + if current_resource.enabled + Chef::Log.debug "#{new_resource} already enabled - nothing to do" else run_schtasks "CHANGE", "ENABLE" => "" - @new_resource.updated_by_last_action true - Chef::Log.info "#{@new_resource} task enabled" + new_resource.updated_by_last_action true + Chef::Log.info "#{new_resource} task enabled" end else - Chef::Log.fatal "#{@new_resource} task doesn't exist - nothing to do" - raise Errno::ENOENT, "#{@new_resource}: task does not exist, cannot enable" + Chef::Log.fatal "#{new_resource} task doesn't exist - nothing to do" + raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable" end end def action_disable - if @current_resource.exists - if @current_resource.enabled + if current_resource.exists + if current_resource.enabled run_schtasks "CHANGE", "DISABLE" => "" - @new_resource.updated_by_last_action true - Chef::Log.info "#{@new_resource} task disabled" + new_resource.updated_by_last_action true + Chef::Log.info "#{new_resource} task disabled" else - Chef::Log.warn "#{@new_resource} already disabled - nothing to do" + Chef::Log.warn "#{new_resource} already disabled - nothing to do" end else - Chef::Log.warn "#{@new_resource} task doesn't exist - nothing to do" + Chef::Log.warn "#{new_resource} task doesn't exist - nothing to do" end end @@ -160,7 +184,7 @@ class Chef # rubocop:disable Style/StringLiteralsInInterpolation def run_schtasks(task_action, options = {}) - cmd = "schtasks /#{task_action} /TN \"#{@new_resource.task_name}\" " + cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" " options.keys.each do |option| cmd += "/#{option} " cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == "" @@ -172,20 +196,21 @@ class Chef # rubocop:enable Style/StringLiteralsInInterpolation def task_need_update? - return true if @current_resource.command != @new_resource.command.tr("'", '"') || - @current_resource.user != @new_resource.user || - @current_resource.run_level != @new_resource.run_level || - @current_resource.cwd != @new_resource.cwd || - @current_resource.frequency_modifier != @new_resource.frequency_modifier || - @current_resource.frequency != @new_resource.frequency || - @current_resource.idle_time != @new_resource.idle_time || - @current_resource.random_delay != @new_resource.random_delay || - @current_resource.execution_time_limit != @new_resource.execution_time_limit || - !@new_resource.start_day.nil? || !@new_resource.start_time.nil? - + return true if (new_resource.command && + current_resource.command != new_resource.command.tr("'", '"')) || + current_resource.user != new_resource.user || + current_resource.run_level != new_resource.run_level || + current_resource.cwd != new_resource.cwd || + current_resource.frequency_modifier != new_resource.frequency_modifier || + current_resource.frequency != new_resource.frequency || + current_resource.idle_time != new_resource.idle_time || + current_resource.random_delay != new_resource.random_delay || + !new_resource.execution_time_limit.include?(current_resource.execution_time_limit) || + (new_resource.start_day && start_day_updated?) || + (new_resource.start_time && start_time_updated?) begin - return true if @new_resource.day.to_s.casecmp(@current_resource.day.to_s) != 0 || - @new_resource.months.to_s.casecmp(@current_resource.months.to_s) != 0 + return true if new_resource.day.to_s.casecmp(current_resource.day.to_s) != 0 || + new_resource.months.to_s.casecmp(current_resource.months.to_s) != 0 rescue Chef::Log.debug "caught a raise in task_needs_update?" end @@ -193,6 +218,56 @@ class Chef false end + def start_day_updated? + current_day = DateTime.strptime(current_resource.start_day, convert_system_date_format_to_ruby_date_format) + new_day = DateTime.parse(new_resource.start_day) + current_day != new_day + end + + def start_time_updated? + time = DateTime.parse(current_resource.start_time).strftime("%H:%M") + time != new_resource.start_time + end + + def convert_user_date_to_system_date(date_in_string) + DateTime.parse(date_in_string).strftime(convert_system_date_format_to_ruby_long_date) + end + + def convert_system_date_format_to_ruby_long_date + date_format = get_system_short_date_format.dup + date_format.sub!("MMM", "%m") + common_date_format_conversion(date_format) + date_format.sub!("yy", "%Y") + date_format + end + + def convert_system_date_format_to_ruby_date_format + date_format = get_system_short_date_format.dup + date_format.sub!("MMM", "%b") + common_date_format_conversion(date_format) + date_format.sub!("yy", "%y") + date_format + end + + def common_date_format_conversion(date_format) + date_format.sub!("dd", "d") + date_format.sub!("d", "%d") + date_format.sub!("MM", "%m") + date_format.sub!("M", "%m") + date_format.sub!("yyyy", "%Y") + end + + def get_system_short_date_format + return @system_short_date_format if @system_short_date_format + Chef::Log.debug "Finding system date format" + task_script = <<-EOH + [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 + [Globalization.Cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern + EOH + @system_short_date_format = powershell_out(task_script).stdout.force_encoding("UTF-8").gsub(/[\s+\uFEFF]/, "") + @system_short_date_format + end + def update_task_xml(options = []) # random_delay xml element is different for different frequencies random_delay_xml_element = { @@ -206,7 +281,7 @@ class Chef xml_element_mapping = { "cwd" => "Actions/Exec/WorkingDirectory", - "random_delay" => random_delay_xml_element[@new_resource.frequency], + "random_delay" => random_delay_xml_element[new_resource.frequency], "execution_time_limit" => "Settings/ExecutionTimeLimit", } @@ -214,7 +289,7 @@ class Chef task_script = <<-EOH [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 - schtasks /Query /TN \"#{@new_resource.task_name}\" /XML + schtasks /Query /TN \"#{new_resource.task_name}\" /XML EOH xml_cmd = powershell_out(task_script) @@ -225,7 +300,7 @@ class Chef options.each do |option| Chef::Log.debug 'Removing former #{option} if any' doc.root.elements.delete(xml_element_mapping[option]) - option_value = @new_resource.send("#{option}") + option_value = new_resource.send("#{option}") if option_value Chef::Log.debug "Setting #option as #option_value" @@ -246,9 +321,9 @@ class Chef end options = {} - options["RU"] = @new_resource.user if @new_resource.user - options["RP"] = @new_resource.password if @new_resource.password - options["IT"] = "" if @new_resource.interactive_enabled + options["RU"] = new_resource.user if new_resource.user + options["RP"] = new_resource.password if new_resource.password + options["IT"] = "" if new_resource.interactive_enabled options["XML"] = temp_task_file run_schtasks("DELETE", "F" => "") @@ -345,26 +420,26 @@ class Chef SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', "SYSTEM", 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', "USERS"].freeze def use_password? - @use_password ||= !SYSTEM_USERS.include?(@new_resource.user.upcase) + @use_password ||= !SYSTEM_USERS.include?(new_resource.user.upcase) end def schedule - case @new_resource.frequency + case new_resource.frequency when :on_logon "ONLOGON" when :on_idle "ONIDLE" else - @new_resource.frequency + new_resource.frequency end end def frequency_modifier_allowed - case @new_resource.frequency + case new_resource.frequency when :minute, :hourly, :daily, :weekly true when :monthly - @new_resource.months.nil? || %w{ FIRST SECOND THIRD FOURTH LAST LASTDAY }.include?(@new_resource.frequency_modifier) + new_resource.months.nil? || %w{ FIRST SECOND THIRD FOURTH LAST LASTDAY }.include?(new_resource.frequency_modifier) else false end @@ -373,9 +448,9 @@ class Chef def set_current_run_level(run_level) case run_level when "HighestAvailable" - @current_resource.run_level(:highest) + current_resource.run_level(:highest) when "LeastPrivilege" - @current_resource.run_level(:limited) + current_resource.run_level(:limited) end end @@ -383,34 +458,34 @@ class Chef if task_hash[:repetition_interval] duration = ISO8601::Duration.new(task_hash[:repetition_interval]) if task_hash[:repetition_interval].include?("M") - @current_resource.frequency(:minute) - @current_resource.frequency_modifier(duration.minutes.atom.to_i) + current_resource.frequency(:minute) + current_resource.frequency_modifier(duration.minutes.atom.to_i) elsif task_hash[:repetition_interval].include?("H") - @current_resource.frequency(:hourly) - @current_resource.frequency_modifier(duration.hours.atom.to_i) + current_resource.frequency(:hourly) + current_resource.frequency_modifier(duration.hours.atom.to_i) end end if task_hash[:schedule_by_day] - @current_resource.frequency(:daily) - @current_resource.frequency_modifier(task_hash[:schedule_by_day].to_i) + current_resource.frequency(:daily) + current_resource.frequency_modifier(task_hash[:schedule_by_day].to_i) end if task_hash[:schedule_by_week] - @current_resource.frequency(:weekly) - @current_resource.frequency_modifier(task_hash[:schedule_by_week].to_i) + current_resource.frequency(:weekly) + current_resource.frequency_modifier(task_hash[:schedule_by_week].to_i) end - @current_resource.frequency(:monthly) if task_hash[:schedule_by_month] - @current_resource.frequency(:on_logon) if task_hash[:on_logon] - @current_resource.frequency(:onstart) if task_hash[:onstart] - @current_resource.frequency(:on_idle) if task_hash[:on_idle] - @current_resource.frequency(:once) if task_hash[:once] + current_resource.frequency(:monthly) if task_hash[:schedule_by_month] + current_resource.frequency(:on_logon) if task_hash[:on_logon] + current_resource.frequency(:onstart) if task_hash[:onstart] + current_resource.frequency(:on_idle) if task_hash[:on_idle] + current_resource.frequency(:once) if task_hash[:once] end def set_current_idle_time(idle_time) duration = ISO8601::Duration.new(idle_time) - @current_resource.idle_time(duration.minutes.atom.to_i) + current_resource.idle_time(duration.minutes.atom.to_i) end end diff --git a/lib/chef/provider/yum_repository.rb b/lib/chef/provider/yum_repository.rb index a5d3c2bc39..957ee9f0f3 100644 --- a/lib/chef/provider/yum_repository.rb +++ b/lib/chef/provider/yum_repository.rb @@ -18,9 +18,7 @@ require "chef/resource" require "chef/dsl/declare_resource" -require "chef/mixin/shell_out" require "chef/mixin/which" -require "chef/http/simple" require "chef/provider/noop" class Chef diff --git a/lib/chef/provider/zypper_repository.rb b/lib/chef/provider/zypper_repository.rb new file mode 100644 index 0000000000..e6fd917d77 --- /dev/null +++ b/lib/chef/provider/zypper_repository.rb @@ -0,0 +1,81 @@ +# +# 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/mixin/which" +require "chef/provider/noop" +require "shellwords" + +class Chef + class Provider + class ZypperRepository < Chef::Provider + + extend Chef::Mixin::Which + + provides :zypper_repository do + which "zypper" + end + + def load_current_resource + end + + action :create do + 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 removerepo #{escaped_repo_name}") do + only_if "zypper lr #{escaped_repo_name}" + end + end + + action :refresh do + declare_resource(:execute, "zypper#{' --gpg-auto-import-keys' if new_resource.gpgautoimportkeys} --quiet --no-confirm refresh --force #{escaped_repo_name}") do + only_if "zypper 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 + def escaped_repo_name + Shellwords.escape(new_resource.repo_name) + end + + 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 :zypper_repository diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index 0ea1786594..a3332477e7 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -17,6 +17,7 @@ # require "chef/provider/apt_update" +require "chef/provider/apt_preference" require "chef/provider/apt_repository" require "chef/provider/batch" require "chef/provider/cookbook_file" @@ -59,6 +60,8 @@ require "chef/provider/user" require "chef/provider/whyrun_safe_ruby_block" require "chef/provider/yum_repository" require "chef/provider/windows_task" +require "chef/provider/zypper_repository" +require "chef/provider/windows_path" require "chef/provider/env/windows" diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index ca6603c06a..5436e3ceb3 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -641,7 +641,11 @@ class Chef all_props = {} self.class.state_properties.map do |p| - all_props[p.name.to_s] = p.sensitive? ? '"*sensitive value suppressed*"' : value_to_text(p.get(self)) + begin + all_props[p.name.to_s] = p.sensitive? ? '"*sensitive value suppressed*"' : value_to_text(p.get(self)) + rescue Chef::Exceptions::ValidationFailed + # This space left intentionally blank, the property was probably required or had an invalid default. + end end ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb index 069fefcb2b..8397f84c71 100644 --- a/lib/chef/resource/apt_package.rb +++ b/lib/chef/resource/apt_package.rb @@ -23,7 +23,7 @@ class Chef class Resource class AptPackage < Chef::Resource::Package resource_name :apt_package - provides :package, os: "linux", platform_family: [ "debian" ] + provides :package, os: "linux", platform_family: "debian" property :default_release, String, desired_state: false diff --git a/lib/chef/resource/apt_preference.rb b/lib/chef/resource/apt_preference.rb new file mode 100644 index 0000000000..603766d76b --- /dev/null +++ b/lib/chef/resource/apt_preference.rb @@ -0,0 +1,36 @@ +# +# 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/resource" + +class Chef + class Resource + class AptPreference < Chef::Resource + resource_name :apt_preference + provides :apt_preference + + property :package_name, String, name_property: true, regex: [/^([a-z]|[A-Z]|[0-9]|_|-|\.|\*|\+)+$/] + property :glob, String + property :pin, String, required: true + property :pin_priority, [String, Integer], required: true + + default_action :add + allowed_actions :add, :remove + end + end +end diff --git a/lib/chef/resource/apt_repository.rb b/lib/chef/resource/apt_repository.rb index b38bd1c8ec..8b69435246 100644 --- a/lib/chef/resource/apt_repository.rb +++ b/lib/chef/resource/apt_repository.rb @@ -1,6 +1,6 @@ # # Author:: Thom May (<thom@chef.io>) -# Copyright:: Copyright (c) 2016 Chef Software, Inc. +# Copyright:: 2016-2017, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ class Chef # whether or not to add the repository as a source repo, too property :deb_src, [TrueClass, FalseClass], default: false property :keyserver, [String, nil, false], default: "keyserver.ubuntu.com", nillable: true, coerce: proc { |x| x ? x : nil } - property :key, [String, nil, false], default: nil, nillable: true, coerce: proc { |x| x ? x : nil } + property :key, [String, Array, nil, false], default: [], coerce: proc { |x| x ? Array(x) : nil } property :key_proxy, [String, nil, false], default: nil, nillable: true, coerce: proc { |x| x ? x : nil } property :cookbook, [String, nil, false], default: nil, desired_state: false, nillable: true, coerce: proc { |x| x ? x : nil } diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb index dee497e74f..ba1b8ae6e3 100644 --- a/lib/chef/resource/execute.rb +++ b/lib/chef/resource/execute.rb @@ -132,6 +132,8 @@ class Chef property :sensitive, [ TrueClass, FalseClass ], default: false, coerce: proc { |x| password ? true : x } + property :elevated, [ TrueClass, FalseClass ], default: false + def self.set_guard_inherited_attributes(*inherited_attributes) @class_inherited_attributes = inherited_attributes end @@ -149,21 +151,29 @@ class Chef end def after_created - validate_identity_platform(user, password, domain) + validate_identity_platform(user, password, domain, elevated) identity = qualify_user(user, password, domain) domain(identity[:domain]) user(identity[:user]) end - def validate_identity_platform(specified_user, password = nil, specified_domain = nil) + def validate_identity_platform(specified_user, password = nil, specified_domain = nil, elevated = false) if node[:platform_family] == "windows" if specified_user && password.nil? raise ArgumentError, "A value for `password` must be specified when a value for `user` is specified on the Windows platform" end + + if elevated && !specified_user && !password + raise ArgumentError, "`elevated` option should be passed only with `username` and `password`." + end else if password || specified_domain raise Exceptions::UnsupportedPlatform, "Values for `domain` and `password` are only supported on the Windows platform" end + + if elevated + raise Exceptions::UnsupportedPlatform, "Value for `elevated` is only supported on the Windows platform" + end end end diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb index fcc48470bc..9fac3562f3 100644 --- a/lib/chef/resource/http_request.rb +++ b/lib/chef/resource/http_request.rb @@ -27,7 +27,7 @@ class Chef identity_attr :url default_action :get - allowed_actions :get, :put, :post, :delete, :head, :options + allowed_actions :get, :patch, :put, :post, :delete, :head, :options def initialize(name, run_context = nil) super diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb index fd523d9580..3673311348 100644 --- a/lib/chef/resource/ifconfig.rb +++ b/lib/chef/resource/ifconfig.rb @@ -44,6 +44,10 @@ class Chef @network = nil @bootproto = nil @onparent = nil + @ethtool_opts = nil + @bonding_opts = nil + @master = nil + @slave = nil end def target(arg = nil) @@ -141,6 +145,38 @@ class Chef :kind_of => String ) end + + def ethtool_opts(arg = nil) + set_or_return( + :ethtool_opts, + arg, + :kind_of => String + ) + end + + def bonding_opts(arg = nil) + set_or_return( + :bonding_opts, + arg, + :kind_of => String + ) + end + + def master(arg = nil) + set_or_return( + :master, + arg, + :kind_of => String + ) + end + + def slave(arg = nil) + set_or_return( + :slave, + arg, + :kind_of => String + ) + end end end diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb index 4a1d1c6cff..25586af702 100644 --- a/lib/chef/resource/remote_file.rb +++ b/lib/chef/resource/remote_file.rb @@ -131,6 +131,66 @@ class Chef ) end + property :remote_user, String + + property :remote_domain, String + + property :remote_password, String, sensitive: true + + def after_created + validate_identity_platform(remote_user, remote_password, remote_domain) + identity = qualify_user(remote_user, remote_password, remote_domain) + remote_domain(identity[:domain]) + remote_user(identity[:user]) + end + + def validate_identity_platform(specified_user, password = nil, specified_domain = nil) + if node[:platform_family] == "windows" + if specified_user && password.nil? + raise ArgumentError, "A value for `remote_password` must be specified when a value for `user` is specified on the Windows platform" + end + end + end + + def qualify_user(specified_user, password = nil, specified_domain = nil) + domain = specified_domain + user = specified_user + + if specified_user.nil? && ! specified_domain.nil? + raise ArgumentError, "The domain `#{specified_domain}` was specified, but no user name was given" + end + + # if domain is provided in both username and domain + if specified_user && ((specified_user.include? '\\') || (specified_user.include? "@")) && specified_domain + raise ArgumentError, "The domain is provided twice. Username: `#{specified_user}`, Domain: `#{specified_domain}`. Please specify domain only once." + end + + if ! specified_user.nil? && specified_domain.nil? + # Splitting username of format: Domain\Username + domain_and_user = user.split('\\') + + if domain_and_user.length == 2 + domain = domain_and_user[0] + user = domain_and_user[1] + elsif domain_and_user.length == 1 + # Splitting username of format: Username@Domain + domain_and_user = user.split("@") + if domain_and_user.length == 2 + domain = domain_and_user[1] + user = domain_and_user[0] + elsif domain_and_user.length != 1 + raise ArgumentError, "The specified user name `#{user}` is not a syntactically valid user name" + end + end + end + + if ( password || domain ) && user.nil? + raise ArgumentError, "A value for `password` or `domain` was specified without specification of a value for `user`" + end + + { domain: domain, user: user } + end + private include Chef::Mixin::Uris diff --git a/lib/chef/resource/windows_path.rb b/lib/chef/resource/windows_path.rb new file mode 100644 index 0000000000..5472a7e4fd --- /dev/null +++ b/lib/chef/resource/windows_path.rb @@ -0,0 +1,41 @@ +# +# 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/resource" + +class Chef + class Resource + class WindowsPath < Chef::Resource + + provides :windows_path, os: "windows" + + allowed_actions :add, :remove + default_action :add + + def initialize(name, run_context = nil) + super + @resource_name = :windows_path + @path = name + @provider = Chef::Provider::WindowsPath + @action = :add + end + + property :path, String, name_property: true + end + end +end diff --git a/lib/chef/resource/yum_repository.rb b/lib/chef/resource/yum_repository.rb index f59ad56d16..012a74908a 100644 --- a/lib/chef/resource/yum_repository.rb +++ b/lib/chef/resource/yum_repository.rb @@ -24,11 +24,12 @@ class Chef resource_name :yum_repository provides :yum_repository - # http://linux.die.net/man/5/yum.conf + # http://linux.die.net/man/5/yum.conf as well as + # http://dnf.readthedocs.io/en/latest/conf_ref.html property :baseurl, [String, Array], regex: /.*/ - property :cost, String, regex: /^\d+$/ property :clean_headers, [TrueClass, FalseClass], default: false # deprecated property :clean_metadata, [TrueClass, FalseClass], default: true + property :cost, String, regex: /^\d+$/ property :description, String, regex: /.*/, default: "Yum Repository" property :enabled, [TrueClass, FalseClass], default: true property :enablegroups, [TrueClass, FalseClass] @@ -44,17 +45,18 @@ class Chef property :make_cache, [TrueClass, FalseClass], default: true property :max_retries, [String, Integer] property :metadata_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/, /never/] - property :mirrorexpire, String, regex: /.*/ - property :mirrorlist, String, regex: /.*/ + property :metalink, String, regex: /.*/ property :mirror_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/] + property :mirrorexpire, String, regex: /.*/ property :mirrorlist_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/] + property :mirrorlist, String, regex: /.*/ property :mode, default: "0644" + property :options, Hash + property :password, String, regex: /.*/ property :priority, String, regex: /^(\d?[0-9]|[0-9][0-9])$/ - property :proxy, String, regex: /.*/ - property :proxy_username, String, regex: /.*/ property :proxy_password, String, regex: /.*/ - property :username, String, regex: /.*/ - property :password, String, regex: /.*/ + property :proxy_username, String, regex: /.*/ + property :proxy, String, regex: /.*/ property :repo_gpgcheck, [TrueClass, FalseClass] property :report_instanceid, [TrueClass, FalseClass] property :repositoryid, String, regex: /.*/, name_property: true @@ -65,7 +67,8 @@ class Chef property :sslclientkey, String, regex: /.*/ property :sslverify, [TrueClass, FalseClass] property :timeout, String, regex: /^\d+$/ - property :options, Hash + property :throttle, [String, Integer] + property :username, String, regex: /.*/ default_action :create allowed_actions :create, :remove, :makecache, :add, :delete diff --git a/lib/chef/resource/zypper_repository.rb b/lib/chef/resource/zypper_repository.rb new file mode 100644 index 0000000000..69a96b42cf --- /dev/null +++ b/lib/chef/resource/zypper_repository.rb @@ -0,0 +1,52 @@ +# +# 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" + +class Chef + class Resource + class ZypperRepository < Chef::Resource + resource_name :zypper_repository + provides :zypper_repo + + property :repo_name, String, name_property: true + property :description, String + property :type, String, default: "NONE" + property :enabled, [true, false], default: true + property :autorefresh, [true, false], default: true + property :gpgcheck, [true, false], default: true + property :gpgkey, String + property :baseurl, String + property :mirrorlist, String + property :path, String + property :priority, Integer, default: 99 + property :keeppackages, [true, false], default: false + property :mode, default: "0644" + property :refresh_cache, [true, false], default: true + property :source, String, regex: /.*/ + property :gpgautoimportkeys, [true, false], default: true + + default_action :create + allowed_actions :create, :remove, :add, :refresh + + # provide compatibility with the zypper cookbook + alias_method :key, :gpgkey + alias_method :uri, :baseurl + end + end +end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 9f87cb2454..54d21fd53c 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -17,6 +17,7 @@ # require "chef/resource/apt_package" +require "chef/resource/apt_preference" require "chef/resource/apt_repository" require "chef/resource/apt_update" require "chef/resource/bash" @@ -95,7 +96,9 @@ require "chef/resource/yum_repository" require "chef/resource/lwrp_base" require "chef/resource/bff_package" require "chef/resource/zypper_package" +require "chef/resource/zypper_repository" require "chef/resource/cab_package" require "chef/resource/powershell_package" require "chef/resource/msu_package" require "chef/resource/windows_task" +require "chef/resource/windows_path" diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb index 6f494819ba..a2663d111d 100644 --- a/lib/chef/search/query.rb +++ b/lib/chef/search/query.rb @@ -71,6 +71,11 @@ class Chef args_h = args_h.reject { |k, v| k == :fuzz } end + # Set default rows parameter to 1000. This is the default in + # Chef Server, but we set it explicitly here so that we can + # confidently advance our start parameter. + args_h[:rows] ||= 1000 + response = call_rest_service(type, query: query, **args_h) if block @@ -87,7 +92,7 @@ class Chef # args_h[:rows] to avoid asking the search backend for # overlapping pages (which could result in duplicates). # - next_start = response["start"] + (args_h[:rows] || response["rows"].length) + next_start = response["start"] + args_h[:rows] unless next_start >= response["total"] args_h[:start] = next_start search(type, query, args_h, &block) diff --git a/lib/chef/server_api_versions.rb b/lib/chef/server_api_versions.rb index 2a4d0e6a5b..40fb6385e1 100644 --- a/lib/chef/server_api_versions.rb +++ b/lib/chef/server_api_versions.rb @@ -26,15 +26,34 @@ class Chef end def min_server_version - !@versions.nil? ? Integer(@versions["min_version"]) : nil + # If we're working with a pre-api-versioning server, always claim to be zero + if @versions.nil? + unversioned? ? 0 : nil + else + Integer(@versions["min_version"]) + end end def max_server_version - !@versions.nil? ? Integer(@versions["max_version"]) : nil + # If we're working with a pre-api-versioning server, always claim to be zero + if @versions.nil? + unversioned? ? 0 : nil + else + Integer(@versions["max_version"]) + end + end + + def unversioned! + @unversioned = true + end + + def unversioned? + @unversioned end def reset! @versions = nil + @unversioned = false end end end diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb index b4c0277c57..40bdf01762 100644 --- a/lib/chef/shell.rb +++ b/lib/chef/shell.rb @@ -138,6 +138,7 @@ module Shell def self.session unless client_type.instance.node_built? puts "Session type: #{client_type.session_type}" + client_type.instance.json_configuration = @json_attribs client_type.instance.reset! end client_type.instance diff --git a/lib/chef/shell/shell_session.rb b/lib/chef/shell/shell_session.rb index 41d5bd64a0..dfed5372c1 100644 --- a/lib/chef/shell/shell_session.rb +++ b/lib/chef/shell/shell_session.rb @@ -38,7 +38,7 @@ module Shell @session_type end - attr_accessor :node, :compile, :recipe, :run_context + attr_accessor :node, :compile, :recipe, :run_context, :json_configuration attr_reader :node_attributes, :client def initialize @node_built = false @@ -151,7 +151,7 @@ module Shell def rebuild_node Chef::Config[:solo_legacy_mode] = true - @client = Chef::Client.new(nil, Chef::Config[:shell_config]) + @client = Chef::Client.new(json_configuration, Chef::Config[:shell_config]) @client.run_ohai @client.load_node @client.build_node @@ -183,7 +183,7 @@ module Shell def rebuild_node # Tell the client we're chef solo so it won't try to contact the server Chef::Config[:solo_legacy_mode] = true - @client = Chef::Client.new(nil, Chef::Config[:shell_config]) + @client = Chef::Client.new(json_configuration, Chef::Config[:shell_config]) @client.run_ohai @client.load_node @client.build_node @@ -218,7 +218,7 @@ module Shell def rebuild_node # Make sure the client knows this is not chef solo Chef::Config[:solo_legacy_mode] = false - @client = Chef::Client.new(nil, Chef::Config[:shell_config]) + @client = Chef::Client.new(json_configuration, Chef::Config[:shell_config]) @client.run_ohai @client.register @client.load_node diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb index d837a16185..1f154b1c71 100644 --- a/lib/chef/util/dsc/local_configuration_manager.rb +++ b/lib/chef/util/dsc/local_configuration_manager.rb @@ -47,15 +47,13 @@ class Chef::Util::DSC def run_configuration_cmdlet(configuration_document, apply_configuration, shellout_flags) Chef::Log.debug("DSC: Calling DSC Local Config Manager to #{apply_configuration ? "set" : "test"} configuration document.") - test_only_parameters = ! apply_configuration ? "-whatif; if (! $?) { exit 1 }" : "" start_operation_timing - command_code = lcm_command_code(@configuration_path, test_only_parameters) status = nil begin save_configuration_document(configuration_document) - cmdlet = ::Chef::Util::Powershell::Cmdlet.new(@node, "#{command_code}") + cmdlet = ::Chef::Util::Powershell::Cmdlet.new(@node, lcm_command(apply_configuration)) if apply_configuration status = cmdlet.run!({}, shellout_flags) else @@ -72,10 +70,22 @@ class Chef::Util::DSC status end - def lcm_command_code(configuration_path, test_only_parameters) - <<-EOH -$ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configuration_path} -wait -erroraction 'stop' -force #{test_only_parameters} -EOH + def lcm_command(apply_configuration) + common_command_prefix = "$ProgressPreference = 'SilentlyContinue';" + ps4_base_command = "#{common_command_prefix} Start-DscConfiguration -path #{@configuration_path} -wait -erroraction 'stop' -force" + if apply_configuration + ps4_base_command + else + if ps_version_gte_5? + "#{common_command_prefix} Test-DscConfiguration -path #{@configuration_path}" + else + ps4_base_command + " -whatif; if (! $?) { exit 1 }" + end + end + end + + def ps_version_gte_5? + Chef::Platform.supported_powershell_version?(@node, 5) end def log_what_if_exception(what_if_exception_output) diff --git a/lib/chef/util/windows/logon_session.rb b/lib/chef/util/windows/logon_session.rb new file mode 100644 index 0000000000..ef80b113b1 --- /dev/null +++ b/lib/chef/util/windows/logon_session.rb @@ -0,0 +1,126 @@ +# +# Author:: Adam Edwards (<adamed@chef.io>) +# +# Copyright:: Copyright (c) 2015 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. +# 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/win32/api/security" if Chef::Platform.windows? +require "chef/mixin/wide_string" + +class Chef + class Util + class Windows + class LogonSession + include Chef::Mixin::WideString + + def initialize(username, password, domain = nil) + if username.nil? || password.nil? + raise ArgumentError, "The logon session must be initialize with non-nil user name and password parameters" + end + + @original_username = username + @original_password = password + @original_domain = domain + @token = FFI::Buffer.new(:pointer) + @session_opened = false + @impersonating = false + end + + def open + if session_opened + raise "Attempted to open a logon session that was already open." + end + + username = wstring(original_username) + password = wstring(original_password) + domain = wstring(original_domain) + + status = Chef::ReservedNames::Win32::API::Security.LogonUserW(username, domain, password, Chef::ReservedNames::Win32::API::Security::LOGON32_LOGON_NEW_CREDENTIALS, Chef::ReservedNames::Win32::API::Security::LOGON32_PROVIDER_DEFAULT, token) + + if !status + last_error = FFI::LastError.error + raise Chef::Exceptions::Win32APIError, "Logon for user `#{original_username}` failed with Win32 status #{last_error}." + end + + @session_opened = true + end + + def close + validate_session_open! + + if impersonating + restore_user_context + end + + Chef::ReservedNames::Win32::API::System.CloseHandle(token.read_ulong) + @token = nil + @session_opened = false + end + + def set_user_context + validate_session_open! + + if ! session_opened + raise "Attempted to set the user context before opening a session." + end + + if impersonating + raise "Attempt to set the user context when the user context is already set." + end + + status = Chef::ReservedNames::Win32::API::Security.ImpersonateLoggedOnUser(token.read_ulong) + + if !status + last_error = FFI::LastError.error + raise Chef::Exceptions::Win32APIError, "Attempt to impersonate user `#{original_username}` failed with Win32 status #{last_error}." + end + + @impersonating = true + end + + def restore_user_context + validate_session_open! + + if impersonating + status = Chef::ReservedNames::Win32::API::Security.RevertToSelf + + if !status + last_error = FFI::LastError.error + raise Chef::Exceptions::Win32APIError, "Unable to restore user context with Win32 status #{last_error}." + end + end + + @impersonating = false + end + + protected + + attr_reader :original_username + attr_reader :original_password + attr_reader :original_domain + + attr_reader :token + attr_reader :session_opened + attr_reader :impersonating + + def validate_session_open! + if ! session_opened + raise "Attempted to set the user context before opening a session." + end + end + end + end + end +end diff --git a/lib/chef/version.rb b/lib/chef/version.rb index b969e726a5..e30285d620 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -23,7 +23,7 @@ require "chef/version_string" class Chef CHEF_ROOT = File.expand_path("../..", __FILE__) - VERSION = Chef::VersionString.new("13.3.1") + VERSION = Chef::VersionString.new("13.4.33") end # diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb index 355cc81378..6aa2927e1f 100644 --- a/lib/chef/win32/api/file.rb +++ b/lib/chef/win32/api/file.rb @@ -67,6 +67,7 @@ class Chef MAX_PATH = 260 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 + SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 FILE_NAME_NORMALIZED = 0x0 FILE_NAME_OPENED = 0x8 diff --git a/lib/chef/win32/api/security.rb b/lib/chef/win32/api/security.rb index a2cfe35dad..a6f79f5d7d 100644 --- a/lib/chef/win32/api/security.rb +++ b/lib/chef/win32/api/security.rb @@ -453,6 +453,8 @@ class Chef safe_attach_function :SetSecurityDescriptorSacl, [ :pointer, :BOOL, :pointer, :BOOL ], :BOOL safe_attach_function :GetTokenInformation, [ :HANDLE, :TOKEN_INFORMATION_CLASS, :pointer, :DWORD, :PDWORD ], :BOOL safe_attach_function :LogonUserW, [:LPTSTR, :LPTSTR, :LPTSTR, :DWORD, :DWORD, :PHANDLE], :BOOL + safe_attach_function :ImpersonateLoggedOnUser, [:HANDLE], :BOOL + safe_attach_function :RevertToSelf, [], :BOOL end end diff --git a/lib/chef/win32/file.rb b/lib/chef/win32/file.rb index fa3d0f7a9d..03d4496fa8 100644 --- a/lib/chef/win32/file.rb +++ b/lib/chef/win32/file.rb @@ -22,6 +22,7 @@ require "chef/win32/api/file" require "chef/win32/api/security" require "chef/win32/error" require "chef/win32/unicode" +require "chef/win32/version" class Chef module ReservedNames::Win32 @@ -60,6 +61,7 @@ class Chef # TODO do a check for CreateSymbolicLinkW and # raise NotImplemented exception on older Windows flags = ::File.directory?(old_name) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0 + flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE if Chef::ReservedNames::Win32::Version.new.win_10_creators_or_higher? old_name = encode_path(old_name) new_name = encode_path(new_name) unless CreateSymbolicLinkW(new_name, old_name, flags) diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb index 3e2d6bc1fe..f8228d40b3 100644 --- a/lib/chef/win32/version.rb +++ b/lib/chef/win32/version.rb @@ -30,6 +30,8 @@ class Chef include Chef::ReservedNames::Win32::API::Macros include Chef::ReservedNames::Win32::API::System + attr_reader :major_version, :minor_version, :build_number + # Ruby implementation of # http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx # http://msdn.microsoft.com/en-us/library/ms724358(v=vs.85).aspx @@ -114,6 +116,10 @@ class Chef end end + def win_10_creators_or_higher? + windows_10? && build_number >= 15063 + end + private def get_version |