summaryrefslogtreecommitdiff
path: root/lib/chef
diff options
context:
space:
mode:
authorRanjib Dey <ranjib@pagerduty.com>2015-07-06 08:47:09 -0700
committerRanjib Dey <ranjib@pagerduty.com>2015-07-06 08:47:09 -0700
commitfce939cbafe91eaade275d44c4e1cc5bfa724b2c (patch)
tree86efad67c8afbf5b84147b60c760d59e75000929 /lib/chef
parent720f3331f794a2ad31bee2b1113ac99fada85389 (diff)
parentcfd2b1fa1b26e8b0aa92a5ba8bd294b96379b2fa (diff)
downloadchef-fce939cbafe91eaade275d44c4e1cc5bfa724b2c.tar.gz
Merge remote-tracking branch 'origin/master' into chef_handler
Conflicts: lib/chef/exceptions.rb
Diffstat (limited to 'lib/chef')
-rw-r--r--lib/chef/application/solo.rb2
-rw-r--r--lib/chef/chef_fs/config.rb46
-rw-r--r--lib/chef/chef_fs/file_pattern.rb19
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_dir.rb1
-rw-r--r--lib/chef/chef_fs/knife.rb42
-rw-r--r--lib/chef/chef_fs/path_utils.rb99
-rw-r--r--lib/chef/constants.rb27
-rw-r--r--lib/chef/delayed_evaluator.rb21
-rw-r--r--lib/chef/exceptions.rb10
-rw-r--r--lib/chef/formatters/doc.rb4
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb5
-rw-r--r--lib/chef/knife/bootstrap/templates/chef-full.erb10
-rw-r--r--lib/chef/knife/user_edit.rb3
-rw-r--r--lib/chef/mixin/params_validate.rb194
-rw-r--r--lib/chef/node_map.rb32
-rw-r--r--lib/chef/platform/priority_map.rb8
-rw-r--r--lib/chef/platform/provider_mapping.rb5
-rw-r--r--lib/chef/property.rb539
-rw-r--r--lib/chef/provider/dsc_resource.rb4
-rw-r--r--lib/chef/provider/dsc_script.rb2
-rw-r--r--lib/chef/provider/mount/aix.rb2
-rw-r--r--lib/chef/provider/package.rb2
-rw-r--r--lib/chef/provider/package/openbsd.rb1
-rw-r--r--lib/chef/provider/powershell_script.rb51
-rw-r--r--lib/chef/provider/registry_key.rb10
-rw-r--r--lib/chef/provider_resolver.rb100
-rw-r--r--lib/chef/resource.rb463
-rw-r--r--lib/chef/resource/dsc_script.rb2
-rw-r--r--lib/chef/resource/ips_package.rb1
-rw-r--r--lib/chef/resource/mount.rb8
-rw-r--r--lib/chef/resource/openbsd_package.rb11
-rw-r--r--lib/chef/resource/solaris_package.rb5
-rw-r--r--lib/chef/resource_resolver.rb69
-rw-r--r--lib/chef/run_context.rb7
-rw-r--r--lib/chef/run_list/versioned_recipe_list.rb11
-rw-r--r--lib/chef/win32/registry.rb12
-rw-r--r--lib/chef/workstation_config_loader.rb161
37 files changed, 1330 insertions, 659 deletions
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index dd09d65b42..5bb2a1ceb0 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -214,7 +214,7 @@ class Chef::Application::Solo < Chef::Application
FileUtils.mkdir_p(recipes_path)
tarball_path = File.join(recipes_path, 'recipes.tgz')
fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path)
- Chef::Mixin::Command.run_command(:command => "tar zxvf #{tarball_path} -C #{recipes_path}")
+ Mixlib::ShellOut.new("tar zxvf #{tarball_path} -C #{recipes_path}").run_command
end
# json_attribs shuld be fetched after recipe_url tarball is unpacked.
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index 6666a3deee..40cbb36530 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -111,7 +111,7 @@ class Chef
#
def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil)
@chef_config = chef_config
- @cwd = cwd
+ @cwd = File.expand_path(cwd)
@cookbook_version = options[:cookbook_version]
if @chef_config[:repo_mode] == 'everything' && is_hosted? && !ui.nil?
@@ -166,34 +166,37 @@ class Chef
# server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
# server_path('/home/*/chef_repo/cookbooks/blah') == nil
#
- # If there are multiple paths (cookbooks, roles, data bags, etc. can all
- # have separate paths), and cwd+the path reaches into one of them, we will
- # return a path relative to that. Otherwise we will return a path to
- # chef_repo.
+ # If there are multiple different, manually specified paths to object locations
+ # (cookbooks, roles, data bags, etc. can all have separate paths), and cwd+the
+ # path reaches into one of them, we will return a path relative to the first
+ # one to match it. Otherwise we expect the path provided to be to the chef
+ # repo path itself. Paths that are not available on the server are not supported.
#
# Globs are allowed as well, but globs outside server paths are NOT
# (presently) supported. See above examples. TODO support that.
#
# If the path does not reach into ANY specified directory, nil is returned.
def server_path(file_path)
- pwd = File.expand_path(Dir.pwd)
- absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
+ target_path = Chef::ChefFS::PathUtils.realest_path(file_path, @cwd)
# Check all object paths (cookbooks_dir, data_bags_dir, etc.)
+ # These are either manually specified by the user or autogenerated relative
+ # to chef_repo_path.
object_paths.each_pair do |name, paths|
paths.each do |path|
- realest_path = Chef::ChefFS::PathUtils.realest_path(path)
- if PathUtils.descendant_of?(absolute_pwd, realest_path)
- relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path)
- return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
+ object_abs_path = Chef::ChefFS::PathUtils.realest_path(path, @cwd)
+ if relative_path = PathUtils.descendant_path(target_path, object_abs_path)
+ return Chef::ChefFS::PathUtils.join("/#{name}", relative_path)
end
end
end
# Check chef_repo_path
Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path|
- realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path)
- if absolute_pwd == realest_chef_repo_path
+ # We're using realest_path here but we really don't need to - we can just expand the
+ # path and use realpath because a repo_path if provided *must* exist.
+ realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path, @cwd)
+ if Chef::ChefFS::PathUtils.os_path_eq?(target_path, realest_chef_repo_path)
return '/'
end
end
@@ -201,15 +204,10 @@ class Chef
nil
end
- # The current directory, relative to server root
+ # The current directory, relative to server root. This is a case-sensitive server path.
+ # It only exists if the current directory is a child of one of the recognized object_paths below.
def base_path
- @base_path ||= begin
- if @chef_config[:chef_repo_path]
- server_path(File.expand_path(@cwd))
- else
- nil
- end
- end
+ @base_path ||= server_path(@cwd)
end
# Print the given server path, relative to the current directory
@@ -217,10 +215,10 @@ class Chef
server_path = entry.path
if base_path && server_path[0,base_path.length] == base_path
if server_path == base_path
- return "."
- elsif server_path[base_path.length,1] == "/"
+ return '.'
+ elsif server_path[base_path.length,1] == '/'
return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
- elsif base_path == "/" && server_path[0,1] == "/"
+ elsif base_path == '/' && server_path[0,1] == '/'
return server_path[1, server_path.length - 1]
end
end
diff --git a/lib/chef/chef_fs/file_pattern.rb b/lib/chef/chef_fs/file_pattern.rb
index 134d22cbd5..b2351dac68 100644
--- a/lib/chef/chef_fs/file_pattern.rb
+++ b/lib/chef/chef_fs/file_pattern.rb
@@ -72,7 +72,7 @@ class Chef
def could_match_children?(path)
return false if path == '' # Empty string is not a path
- argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
return false if is_absolute != argument_is_absolute
path = path[1,path.length-1] if argument_is_absolute
@@ -111,7 +111,7 @@ class Chef
#
# This method assumes +could_match_children?(path)+ is +true+.
def exact_child_name_under(path)
- path = path[1,path.length-1] if !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ path = path[1,path.length-1] if Chef::ChefFS::PathUtils::is_absolute?(path)
dirs_in_path = Chef::ChefFS::PathUtils::split(path).length
return nil if exact_parts.length <= dirs_in_path
return exact_parts[dirs_in_path]
@@ -149,7 +149,7 @@ class Chef
# abc/*/def.match?('abc/foo/def') == true
# abc/*/def.match?('abc/foo') == false
def match?(path)
- argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
return false if is_absolute != argument_is_absolute
path = path[1,path.length-1] if argument_is_absolute
!!regexp.match(path)
@@ -160,17 +160,6 @@ class Chef
pattern
end
- # Given a relative file pattern and a directory, makes a new file pattern
- # starting with the directory.
- #
- # FilePattern.relative_to('/usr/local', 'bin/*grok') == FilePattern.new('/usr/local/bin/*grok')
- #
- # BUG: this does not support patterns starting with <tt>..</tt>
- def self.relative_to(dir, pattern)
- return FilePattern.new(pattern) if pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/
- FilePattern.new(Chef::ChefFS::PathUtils::join(dir, pattern))
- end
-
private
def regexp
@@ -195,7 +184,7 @@ class Chef
def calculate
if !@regexp
- @is_absolute = !!(@pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ @is_absolute = Chef::ChefFS::PathUtils::is_absolute?(@pattern)
full_regexp_parts = []
normalized_parts = []
diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb
index 03652dc376..555f9aef0a 100644
--- a/lib/chef/chef_fs/file_system/cookbook_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb
@@ -16,6 +16,7 @@
# limitations under the License.
#
+require 'chef/chef_fs/command_line'
require 'chef/chef_fs/file_system/rest_list_dir'
require 'chef/chef_fs/file_system/cookbook_subdir'
require 'chef/chef_fs/file_system/cookbook_file'
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
index 86872dab71..9101e455f8 100644
--- a/lib/chef/chef_fs/knife.rb
+++ b/lib/chef/chef_fs/knife.rb
@@ -17,6 +17,7 @@
#
require 'chef/knife'
+require 'pathname'
class Chef
module ChefFS
@@ -63,7 +64,7 @@ class Chef
# --chef-repo-path forcibly overrides all other paths
if config[:chef_repo_path]
Chef::Config[:chef_repo_path] = config[:chef_repo_path]
- %w(acl client cookbook container data_bag environment group node role user).each do |variable_name|
+ Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name|
Chef::Config.delete("#{variable_name}_path".to_sym)
end
end
@@ -98,14 +99,41 @@ class Chef
end
def pattern_arg_from(arg)
- # TODO support absolute file paths and not just patterns? Too much?
- # Could be super useful in a world with multiple repo paths
- if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg)
- # Check if chef repo path is specified to give a better error message
- ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path")
+ inferred_path = nil
+ if Chef::ChefFS::PathUtils.is_absolute?(arg)
+ # We should be able to use this as-is - but the user might have incorrectly provided
+ # us with a path that is based off of the OS root path instead of the Chef-FS root.
+ # Do a quick and dirty sanity check.
+ if possible_server_path = @chef_fs_config.server_path(arg)
+ ui.warn("The absolute path provided is suspicious: #{arg}")
+ ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.")
+ ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'")
+ end
+ # Use the original path because we can't be sure.
+ inferred_path = arg
+ elsif arg[0,1] == '~'
+ # Let's be nice and fix it if possible - but warn the user.
+ ui.warn("A path relative to a user home directory has been provided: #{arg}")
+ ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+ inferred_path = @chef_fs_config.server_path(arg)
+ ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+ elsif Pathname.new(arg).absolute?
+ # It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be
+ # interpreted as a Chef-FS absolute path. Again attempt to be nice but warn the user.
+ ui.warn("An absolute file system path that isn't a server path was provided: #{arg}")
+ ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+ inferred_path = @chef_fs_config.server_path(arg)
+ ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+ elsif @chef_fs_config.base_path.nil?
+ # These are all relative paths. We can't resolve and root paths unless we are in the
+ # chef repo.
+ ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.")
+ ui.error("Current working directory is '#{@chef_fs_config.cwd}'.")
exit(1)
+ else
+ inferred_path = Chef::ChefFS::PathUtils::join(@chef_fs_config.base_path, arg)
end
- Chef::ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg)
+ Chef::ChefFS::FilePattern.new(inferred_path)
end
def format_path(entry)
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb
index 9ef75ce2e5..595f966378 100644
--- a/lib/chef/chef_fs/path_utils.rb
+++ b/lib/chef/chef_fs/path_utils.rb
@@ -23,31 +23,31 @@ class Chef
module ChefFS
class PathUtils
- # If you are in 'source', this is what you would have to type to reach 'dest'
- # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
- # relative_to('/a/b', '/a/b') == '.'
- def self.relative_to(dest, source)
- # Skip past the common parts
- source_parts = Chef::ChefFS::PathUtils.split(source)
- dest_parts = Chef::ChefFS::PathUtils.split(dest)
- i = 0
- until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i]
- i+=1
- end
- # dot-dot up from 'source' to the common ancestor, then
- # descend to 'dest' from the common ancestor
- result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i]))
- result == '' ? '.' : result
- end
+ # A Chef-FS path is a path in a chef-repository that can be used to address
+ # both files on a local file-system as well as objects on a chef server.
+ # These paths are stricter than file-system paths allowed on various OSes.
+ # Absolute Chef-FS paths begin with "/" (on windows, "\" is acceptable as well).
+ # "/" is used as the path element separator (on windows, "\" is acceptable as well).
+ # No directory/path element may contain a literal "\" character. Any such characters
+ # encountered are either dealt with as separators (on windows) or as escape
+ # characters (on POSIX systems). Relative Chef-FS paths may use ".." or "." but
+ # may never use these to back-out of the root of a Chef-FS path. Any such extraneous
+ # ".."s are ignored.
+ # Chef-FS paths are case sensitive (since the paths on the server are).
+ # On OSes with case insensitive paths, you may be unable to locally deal with two
+ # objects whose server paths only differ by case. OTOH, the case of path segments
+ # that are outside the Chef-FS root (such as when looking at a file-system absolute
+ # path to discover the Chef-FS root path) are handled in accordance to the rules
+ # of the local file-system and OS.
def self.join(*parts)
return "" if parts.length == 0
# Determine if it started with a slash
absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/
# Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away)
- parts = parts.map { |part| part.gsub(/^\/|\/$/, "") }
+ parts = parts.map { |part| part.gsub(/^#{regexp_path_separator}+|#{regexp_path_separator}+$/, '') }
# Don't join empty bits
- result = parts.select { |part| part != "" }.join("/")
+ result = parts.select { |part| part != '' }.join('/')
# Put the / back on
absolute ? "/#{result}" : result
end
@@ -60,36 +60,67 @@ class Chef
Chef::ChefFS::windows? ? '[\/\\\\]' : '/'
end
+ # Given a server path, determines if it is absolute.
+ def self.is_absolute?(path)
+ !!(path =~ /^#{regexp_path_separator}/)
+ end
# Given a path which may only be partly real (i.e. /x/y/z when only /x exists,
# or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest
- # part that actually exists.
+ # part that actually exists. The paths operated on here are not Chef-FS paths.
+ # These are OS paths that may contain symlinks but may not also fully exist.
#
# If /x is a symlink to /blarghle, and has no subdirectories, then:
# PathUtils.realest_path('/x/y/z') == '/blarghle/y/z'
# PathUtils.realest_path('/x/*/z') == '/blarghle/*/z'
# PathUtils.realest_path('/*/y/z') == '/*/y/z'
- def self.realest_path(path)
- path = Pathname.new(path)
- begin
- path.realpath.to_s
- rescue Errno::ENOENT
- dirname = path.dirname
- if dirname
- PathUtils.join(realest_path(dirname), path.basename.to_s)
- else
- path.to_s
+ #
+ # TODO: Move this to wherever util/path_helper is these days.
+ def self.realest_path(path, cwd = Dir.pwd)
+ path = File.expand_path(path, cwd)
+ parent_path = File.dirname(path)
+ suffix = []
+
+ # File.dirname happens to return the path as its own dirname if you're
+ # at the root (such as at \\foo\bar, C:\ or /)
+ until parent_path == path do
+ # This can occur if a path such as "C:" is given. Ruby gives the parent as "C:."
+ # for reasons only it knows.
+ raise ArgumentError "Invalid path segment #{path}" if parent_path.length > path.length
+ begin
+ path = File.realpath(path)
+ break
+ rescue Errno::ENOENT
+ suffix << File.basename(path)
+ path = parent_path
+ parent_path = File.dirname(path)
end
end
+ File.join(path, *suffix.reverse)
end
- def self.descendant_of?(path, ancestor)
- path[0,ancestor.length] == ancestor &&
- (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/)
+ # Compares two path fragments according to the case-sentitivity of the host platform.
+ def self.os_path_eq?(left, right)
+ Chef::ChefFS::windows? ? left.casecmp(right) == 0 : left == right
end
- def self.is_absolute?(path)
- path =~ /^#{regexp_path_separator}/
+ # Given two general OS-dependent file paths, determines the relative path of the
+ # child with respect to the ancestor. Both child and ancestor must exist and be
+ # fully resolved - this is strictly a lexical comparison. No trailing slashes
+ # and other shenanigans are allowed.
+ #
+ # TODO: Move this to util/path_helper.
+ def self.descendant_path(path, ancestor)
+ candidate_fragment = path[0, ancestor.length]
+ return nil unless PathUtils.os_path_eq?(candidate_fragment, ancestor)
+ if ancestor.length == path.length
+ ''
+ elsif path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/
+ path[ancestor.length+1..-1]
+ else
+ nil
+ end
end
+
end
end
end
diff --git a/lib/chef/constants.rb b/lib/chef/constants.rb
new file mode 100644
index 0000000000..d39ce4c68d
--- /dev/null
+++ b/lib/chef/constants.rb
@@ -0,0 +1,27 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ NOT_PASSED = Object.new
+ def NOT_PASSED.to_s
+ "NOT_PASSED"
+ end
+ def NOT_PASSED.inspect
+ to_s
+ end
+ NOT_PASSED.freeze
+end
diff --git a/lib/chef/delayed_evaluator.rb b/lib/chef/delayed_evaluator.rb
new file mode 100644
index 0000000000..9f18a53445
--- /dev/null
+++ b/lib/chef/delayed_evaluator.rb
@@ -0,0 +1,21 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ class DelayedEvaluator < Proc
+ end
+end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index 0857a9e75e..e2e36e8162 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -17,12 +17,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'chef-config/exceptions'
+
class Chef
# == Chef::Exceptions
# Chef's custom exceptions are all contained within the Chef::Exceptions
# namespace.
class Exceptions
+ ConfigurationError = ChefConfig::ConfigurationError
+
# Backcompat with Chef::ShellOut code:
require 'mixlib/shellout/exceptions'
@@ -68,7 +72,6 @@ class Chef
class DuplicateRole < RuntimeError; end
class ValidationFailed < ArgumentError; end
class InvalidPrivateKey < ArgumentError; end
- class ConfigurationError < ArgumentError; end
class MissingKeyAttribute < ArgumentError; end
class KeyCommandInputError < ArgumentError; end
class InvalidKeyArgument < ArgumentError; end
@@ -97,8 +100,11 @@ class Chef
class ConflictingMembersInGroup < ArgumentError; end
class InvalidResourceReference < RuntimeError; end
class ResourceNotFound < RuntimeError; end
+ class ProviderNotFound < RuntimeError; end
+ NoProviderAvailable = ProviderNotFound
class VerificationNotFound < RuntimeError; end
class InvalidEventType < ArgumentError; end
+ class MultipleIdentityError < RuntimeError; end
# Can't find a Resource of this type that is valid on this platform.
class NoSuchResourceType < NameError
@@ -219,8 +225,6 @@ class Chef
class ChildConvergeError < RuntimeError; end
- class NoProviderAvailable < RuntimeError; end
-
class DeprecatedFeatureError < RuntimeError;
def initalize(message)
super("#{message} (raising error due to treat_deprecation_warnings_as_errors being set)")
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index e76a940c38..5ea9823d78 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -22,6 +22,7 @@ class Chef
@failed_audits = 0
@start_time = Time.now
@end_time = @start_time
+ @skipped_resources = 0
end
def elapsed_time
@@ -33,7 +34,7 @@ class Chef
end
def total_resources
- @up_to_date_resources + @updated_resources
+ @up_to_date_resources + @updated_resources + @skipped_resources
end
def total_audits
@@ -236,6 +237,7 @@ class Chef
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
+ @skipped_resources += 1
# TODO: more info about conditional
puts " (skipped due to #{conditional.short_description})", :stream => resource
unindent
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
index d4b386a15a..8cff3bc032 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -68,7 +68,10 @@ class Chef
run_action = action || @resource.action
begin
- @resource.run_action(run_action)
+ # Coerce to an array to be safe. This could happen with a legacy
+ # resource or something overriding the default_action code in a
+ # subclass.
+ Array(run_action).each {|action_to_run| @resource.run_action(action_to_run) }
resource_updated = @resource.updated
rescue Mixlib::ShellOut::ShellCommandFailed
resource_updated = nil
diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb
index 335b1f181c..575aec0f50 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/lib/chef/knife/bootstrap/templates/chef-full.erb
@@ -12,7 +12,7 @@ tmp_dir="$tmp/install.sh.$$"
(umask 077 && mkdir $tmp_dir) || exit 1
exists() {
- if command -v $1 &>/dev/null
+ if command -v $1 >/dev/null 2>&1
then
return 0
else
@@ -166,12 +166,12 @@ do_download() {
<%= knife_config[:bootstrap_install_command] %>
<% else %>
install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>"
- if ! exists /usr/bin/chef-client; then
+ if test -f /usr/bin/chef-client; then
+ echo "-----> Existing Chef installation detected"
+ else
echo "-----> Installing Chef Omnibus (<%= latest_current_chef_version_string %>)"
do_download ${install_sh} $tmp_dir/install.sh
sh $tmp_dir/install.sh -P chef <%= latest_current_chef_version_string %>
- else
- echo "-----> Existing Chef installation detected"
fi
<% end %>
@@ -226,6 +226,6 @@ cat > /etc/chef/first-boot.json <<EOP
<%= Chef::JSONCompat.to_json(first_boot) %>
EOP
-echo "Starting first Chef Client run..."
+echo "Starting the first Chef Client run..."
<%= start_chef %>'
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
index dd2fc02743..d194f6697b 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -57,7 +57,6 @@ EOF
end
original_user = Chef::User.load(@user_name).to_hash
-
# DEPRECATION NOTE
# Remove this if statement and corrosponding code post OSC 11 support.
#
@@ -73,7 +72,7 @@ EOF
user.update
ui.msg("Saved #{user}.")
else
- ui.msg("User unchaged, not saving.")
+ ui.msg("User unchanged, not saving.")
end
end
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index f7d52a19cf..322caea474 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -15,11 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-class Chef
- NOT_PASSED = Object.new
+require 'chef/constants'
+require 'chef/property'
+require 'chef/delayed_evaluator'
- class DelayedEvaluator < Proc
- end
+class Chef
module Mixin
module ParamsValidate
@@ -34,20 +34,55 @@ class Chef
# Would raise an exception if the value of :one above is not a kind_of? string. Valid
# map options are:
#
- # :default:: Sets the default value for this parameter.
- # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
- # The key will be inserted into the error message if the Proc does not return true:
- # "Option #{key}'s value #{value} #{message}!"
- # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure
- # that the value is one of those types.
- # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of
- # method names.
- # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
- # by default, options are not required.
- # :regex:: Match the value of the parameter against a regular expression.
- # :equal_to:: Match the value of the parameter with ==. An array means it can be equal to any
- # of the values.
+ # @param opts [Hash<Symbol,Object>] Validation opts.
+ # @option opts [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`opts[:is].any? { |v| v === value }`). (See #_pv_is.)
+ # @option opts [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`opts[:is].any? { |v| v == value }`) (See #_pv_equal_to.)
+ # @option opts [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # (See #_pv_regex)
+ # @option opts [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of. (See
+ # #_pv_kind_of.)
+ # @option opts [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches). (See
+ # #_pv_callbacks.)
+ # @option opts [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to. (See
+ # #_pv_respond_to.)
+ # @option opts [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty). (See #_pv_cannot_be.)
+ # @option opts [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # (See #_pv_coerce.) (See #_pv_coerce.)
+ # @option opts [Boolean] :required `true` if this property
+ # must be present and not `nil`; `false` otherwise. This is checked
+ # after the resource is fully initialized. (See #_pv_required.)
+ # @option opts [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set. (See
+ # #_pv_name_property.)
+ # @option opts [Boolean] :name_attribute Same as `name_property`.
+ # @option opts [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties). (See #_pv_default.)
+ #
def validate(opts, map)
+ map = map.validation_options if map.is_a?(Property)
+
#--
# validate works by taking the keys in the validation map, assuming it's a hash, and
# looking for _pv_:symbol as methods. Assuming it find them, it calls the right
@@ -84,91 +119,8 @@ class Chef
end
def set_or_return(symbol, value, validation)
- symbol = symbol.to_sym
- iv_symbol = :"@#{symbol}"
-
- # Steal default, coerce, name_property and required from validation
- # so that we can handle the order in which they are applied
- validation = validation.dup
- if validation.has_key?(:default)
- default = validation.delete(:default)
- elsif validation.has_key?('default')
- default = validation.delete('default')
- else
- default = NOT_PASSED
- end
- coerce = validation.delete(:coerce)
- coerce ||= validation.delete('coerce')
- name_property = validation.delete(:name_property)
- name_property ||= validation.delete('name_property')
- name_property ||= validation.delete(:name_attribute)
- name_property ||= validation.delete('name_attribute')
- required = validation.delete(:required)
- required ||= validation.delete('required')
-
- opts = {}
- # If the user passed NOT_PASSED, or passed nil, then this is a get.
- if value == NOT_PASSED || (value.nil? && !explicitly_allows_nil?(symbol, validation))
-
- # Get the value if there is one
- if self.instance_variable_defined?(iv_symbol)
- opts[symbol] = self.instance_variable_get(iv_symbol)
-
- # Handle lazy values
- if opts[symbol].is_a?(DelayedEvaluator)
- if opts[symbol].arity >= 1
- opts[symbol] = opts[symbol].call(self)
- else
- opts[symbol] = opts[symbol].call
- end
-
- # Coerce and validate the default value
- _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
- _pv_coerce(opts, symbol, coerce) if coerce
- validate(opts, { symbol => validation })
- end
-
- # Get the default value
- else
- _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
- _pv_default(opts, symbol, default) unless default == NOT_PASSED
- _pv_name_property(opts, symbol, name_property)
-
- if opts.has_key?(symbol)
- # Handle lazy defaults.
- if opts[symbol].is_a?(DelayedEvaluator)
- if opts[symbol].arity >= 1
- opts[symbol] = opts[symbol].call(self)
- else
- opts[symbol] = instance_eval(&opts[symbol])
- end
- end
-
- # Coerce and validate the default value
- _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
- _pv_coerce(opts, symbol, coerce) if coerce
- # We presently do not validate defaults, for backwards compatibility.
-# validate(opts, { symbol => validation })
-
- # Defaults are presently "stickily" set on the instance
- self.instance_variable_set(iv_symbol, opts[symbol])
- end
- end
-
- # Set the value
- else
- opts[symbol] = value
- unless opts[symbol].is_a?(DelayedEvaluator)
- # Coerce and validate the value
- _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
- _pv_coerce(opts, symbol, coerce) if coerce
- validate(opts, { symbol => validation })
- end
-
- self.instance_variable_set(iv_symbol, opts[symbol])
- end
-
- opts[symbol]
+ property = SetOrReturnProperty.new(name: symbol, **validation)
+ property.call(self, value)
end
private
@@ -193,8 +145,9 @@ class Chef
if is_required
return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
- raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
+ raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!"
end
+ true
end
#
@@ -380,7 +333,7 @@ class Chef
def _pv_name_property(opts, key, is_name_property=true)
if is_name_property
if opts[key].nil?
- opts[key] = self.instance_variable_get("@name")
+ opts[key] = self.instance_variable_get(:"@name")
end
end
end
@@ -425,9 +378,9 @@ class Chef
# x 1 #=> invalid
# ```
#
- # @example PropertyType
+ # @example Property
# ```ruby
- # type = PropertyType.new(is: String)
+ # type = Property.new(is: String)
# property :x, type
# x 'foo' #=> valid
# x 1 #=> invalid
@@ -448,8 +401,12 @@ class Chef
value = _pv_opts_lookup(opts, key)
to_be = [ to_be ].flatten(1)
to_be.each do |tb|
- if tb.is_a?(Proc)
+ case tb
+ when Proc
return true if instance_exec(value, &tb)
+ when Property
+ validate(opts, { key => tb.validation_options })
+ return true
else
return true if tb === value
end
@@ -484,6 +441,29 @@ class Chef
opts[key.to_sym] = instance_exec(opts[key], &coercer)
end
end
+
+ # Used by #set_or_return to avoid emitting a deprecation warning for
+ # "value nil" and to keep default stickiness working exactly the same
+ # @api private
+ class SetOrReturnProperty < Chef::Property
+ def get(resource)
+ value = super
+ # All values are sticky, frozen or not
+ if !is_set?(resource)
+ set_value(resource, value)
+ end
+ value
+ end
+
+ def call(resource, value=NOT_PASSED)
+ # setting to nil does a get
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ get(resource)
+ else
+ super
+ end
+ end
+ end
end
end
end
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index d5eed7c215..d905c8779e 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -20,13 +20,6 @@ class Chef
class NodeMap
#
- # Create a new NodeMap
- #
- def initialize
- @map = {}
- end
-
- #
# Set a key/value pair on the map with a filter. The filter must be true
# when applied to the node in order to retrieve the value.
#
@@ -55,18 +48,17 @@ class Chef
# The map is sorted in order of preference already; we just need to find
# our place in it (just before the first value with the same preference level).
insert_at = nil
- @map[key] ||= []
- @map[key].each_with_index do |matcher,index|
+ map[key] ||= []
+ map[key].each_with_index do |matcher,index|
cmp = compare_matchers(key, new_matcher, matcher)
insert_at ||= index if cmp && cmp <= 0
end
if insert_at
- @map[key].insert(insert_at, new_matcher)
+ map[key].insert(insert_at, new_matcher)
else
- @map[key] << new_matcher
+ map[key] << new_matcher
end
- insert_at ||= @map[key].size - 1
- @map
+ map
end
#
@@ -100,8 +92,8 @@ class Chef
#
def list(node, key, canonical: nil)
raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil?
- return [] unless @map.has_key?(key)
- @map[key].select do |matcher|
+ return [] unless map.has_key?(key)
+ map[key].select do |matcher|
node_matches?(node, matcher) && canonical_matches?(canonical, matcher)
end.map { |matcher| matcher[:value] }
end
@@ -110,11 +102,11 @@ class Chef
# @return remaining
# @api private
def delete_canonical(key, value)
- remaining = @map[key]
+ remaining = map[key]
if remaining
remaining.delete_if { |matcher| matcher[:canonical] && Array(matcher[:value]) == Array(value) }
if remaining.empty?
- @map.delete(key)
+ map.delete(key)
remaining = nil
end
end
@@ -181,7 +173,7 @@ class Chef
end
def compare_matchers(key, new_matcher, matcher)
- cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:block] }
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:block] }
return cmp if cmp != 0
cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
return cmp if cmp != 0
@@ -222,5 +214,9 @@ class Chef
end
cmp
end
+
+ def map
+ @map ||= {}
+ end
end
end
diff --git a/lib/chef/platform/priority_map.rb b/lib/chef/platform/priority_map.rb
index d559eece78..73554eafe1 100644
--- a/lib/chef/platform/priority_map.rb
+++ b/lib/chef/platform/priority_map.rb
@@ -6,7 +6,7 @@ class Chef
def priority(resource_name, priority_array, *filter)
set_priority_array(resource_name.to_sym, priority_array, *filter)
end
-
+
# @api private
def get_priority_array(node, key)
get(node, key)
@@ -24,6 +24,12 @@ class Chef
list(node, key, **filters).flatten(1).uniq
end
+ # @api private
+ def includes_handler?(key, handler)
+ return false if !map.has_key?(key)
+ map[key].any? { |m| h = m[:value]; h.is_a?(Array) ? h.include?(handler) : h == handler }
+ end
+
#
# Priority maps have one extra precedence: priority arrays override "provides,"
# and "provides" lines with identical filters sort by class name (ascending).
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index af17d8e1b4..38dd0e38af 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -176,7 +176,7 @@ class Chef
platform_provider(platform, version, resource_type) ||
resource_matching_provider(platform, version, resource_type)
- raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
+ raise Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
provider_klass
end
@@ -197,7 +197,8 @@ class Chef
def resource_matching_provider(platform, version, resource_type)
if resource_type.kind_of?(Chef::Resource)
- class_name = resource_type.class.to_s.split('::').last
+ class_name = resource_type.class.name ? resource_type.class.name.split('::').last :
+ convert_to_class_name(resource_type.resource_name.to_s)
begin
result = Chef::Provider.const_get(class_name)
diff --git a/lib/chef/property.rb b/lib/chef/property.rb
new file mode 100644
index 0000000000..408090d37b
--- /dev/null
+++ b/lib/chef/property.rb
@@ -0,0 +1,539 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 John Keiser.
+# 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/exceptions'
+require 'chef/delayed_evaluator'
+
+class Chef
+ #
+ # Type and validation information for a property on a resource.
+ #
+ # A property named "x" manipulates the "@x" instance variable on a
+ # resource. The *presence* of the variable (`instance_variable_defined?(@x)`)
+ # tells whether the variable is defined; it may have any actual value,
+ # constrained only by validation.
+ #
+ # Properties may have validation, defaults, and coercion, and have full
+ # support for lazy values.
+ #
+ # @see Chef::Resource.property
+ # @see Chef::DelayedEvaluator
+ #
+ class Property
+ #
+ # Create a reusable property type that can be used in multiple properties
+ # in different resources.
+ #
+ # @param options [Hash<Symbol,Object>] Validation options. See Chef::Resource.property for
+ # the list of options.
+ #
+ # @example
+ # Property.derive(default: 'hi')
+ #
+ def self.derive(**options)
+ new(**options)
+ end
+
+ #
+ # Create a new property.
+ #
+ # @param options [Hash<Symbol,Object>] Property options, including
+ # control options here, as well as validation options (see
+ # Chef::Mixin::ParamsValidate#validate for a description of validation
+ # options).
+ # @option options [Symbol] :name The name of this property.
+ # @option options [Class] :declared_in The class this property comes from.
+ # @option options [Symbol] :instance_variable_name The instance variable
+ # tied to this property. Must include a leading `@`. Defaults to `@<name>`.
+ # `nil` means the property is opaque and not tied to a specific instance
+ # variable.
+ # @option options [Boolean] :desired_state `true` if this property is part of desired
+ # state. Defaults to `true`.
+ # @option options [Boolean] :identity `true` if this property is part of object
+ # identity. Defaults to `false`.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties) and cached. If not, the value will be frozen with Object#freeze
+ # to prevent users from modifying it in an instance.
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ #
+ def initialize(**options)
+ options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) }
+ options[:name_property] = options.delete(:name_attribute) unless options.has_key?(:name_property)
+ @options = options
+
+ if options.has_key?(:default)
+ options[:default] = options[:default].freeze
+ end
+ options[:name] = options[:name].to_sym if options[:name]
+ options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name]
+ end
+
+ #
+ # The name of this property.
+ #
+ # @return [String]
+ #
+ def name
+ options[:name]
+ end
+
+ #
+ # The class this property was defined in.
+ #
+ # @return [Class]
+ #
+ def declared_in
+ options[:declared_in]
+ end
+
+ #
+ # The instance variable associated with this property.
+ #
+ # Defaults to `@<name>`
+ #
+ # @return [Symbol]
+ #
+ def instance_variable_name
+ if options.has_key?(:instance_variable_name)
+ options[:instance_variable_name]
+ elsif name
+ :"@#{name}"
+ end
+ end
+
+ #
+ # The raw default value for this resource.
+ #
+ # Does not coerce or validate the default. Does not evaluate lazy values.
+ #
+ # Defaults to `lazy { name }` if name_property is true; otherwise defaults to
+ # `nil`
+ #
+ def default
+ return options[:default] if options.has_key?(:default)
+ return Chef::DelayedEvaluator.new { name } if name_property?
+ nil
+ end
+
+ #
+ # Whether this is part of the resource's natural identity or not.
+ #
+ # @return [Boolean]
+ #
+ def identity?
+ options[:identity]
+ end
+
+ #
+ # Whether this is part of desired state or not.
+ #
+ # Defaults to true.
+ #
+ # @return [Boolean]
+ #
+ def desired_state?
+ return true if !options.has_key?(:desired_state)
+ options[:desired_state]
+ end
+
+ #
+ # Whether this is name_property or not.
+ #
+ # @return [Boolean]
+ #
+ def name_property?
+ options[:name_property]
+ end
+
+ #
+ # Whether this property has a default value.
+ #
+ # @return [Boolean]
+ #
+ def has_default?
+ options.has_key?(:default) || name_property?
+ end
+
+ #
+ # Whether this property is required or not.
+ #
+ # @return [Boolean]
+ #
+ def required?
+ options[:required]
+ end
+
+ #
+ # Validation options. (See Chef::Mixin::ParamsValidate#validate.)
+ #
+ # @return [Hash<Symbol,Object>]
+ #
+ def validation_options
+ @validation_options ||= options.reject { |k,v|
+ [:declared_in,:name,:instance_variable_name,:desired_state,:identity,:default,:name_property,:coerce,:required].include?(k)
+ }
+ end
+
+ #
+ # Handle the property being called.
+ #
+ # The base implementation does the property get-or-set:
+ #
+ # ```ruby
+ # resource.myprop # get
+ # resource.myprop value # set
+ # ```
+ #
+ # Subclasses may implement this with any arguments they want, as long as
+ # the corresponding DSL calls it correctly.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ # @param value The value to set (or NOT_PASSED if it is a get).
+ #
+ # @return The current value of the property. If it is a `set`, lazy values
+ # will be returned without running, validating or coercing. If it is a
+ # `get`, the non-lazy, coerced, validated value will always be returned.
+ #
+ def call(resource, value=NOT_PASSED)
+ if value == NOT_PASSED
+ return get(resource)
+ end
+
+ # myprop nil is sometimes a get (backcompat)
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ # If you say "my_property nil" and the property explicitly accepts
+ # nil values, we consider this a get.
+ Chef::Log.deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.")
+ return get(resource)
+ end
+
+ # Anything else (myprop value) is a set
+ set(resource, value)
+ end
+
+ #
+ # Get the property value from the resource, handling lazy values,
+ # defaults, and validation.
+ #
+ # - If the property's value is lazy, it is evaluated, coerced and validated.
+ # - If the property has no value, and is required, raises ValidationFailed.
+ # - If the property has no value, but has a lazy default, it is evaluated,
+ # coerced and validated. If the evaluated value is frozen, the resulting
+ # - If the property has no value, but has a default, the default value
+ # will be returned and frozen. If the default value is lazy, it will be
+ # evaluated, coerced and validated, and the result stored in the property.
+ # - If the property has no value, but is name_property, `resource.name`
+ # is retrieved, coerced, validated and stored in the property.
+ # - Otherwise, `nil` is returned.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ # @return The value of the property.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property, or if the value is required and not set.
+ #
+ def get(resource)
+ if is_set?(resource)
+ value = get_value(resource)
+ if value.is_a?(DelayedEvaluator)
+ value = exec_in_resource(resource, value)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ value
+
+ else
+ if has_default?
+ value = default
+ if value.is_a?(DelayedEvaluator)
+ value = exec_in_resource(resource, value)
+ end
+
+ value = coerce(resource, value)
+
+ # We don't validate defaults
+
+ # If the value is mutable (non-frozen), we set it on the instance
+ # so that people can mutate it. (All constant default values are
+ # frozen.)
+ if !value.frozen?
+ set_value(resource, value)
+ end
+
+ value
+
+ elsif required?
+ raise Chef::Exceptions::ValidationFailed, "#{name} is required"
+ end
+ end
+ end
+
+ #
+ # Set the value of this property in the given resource.
+ #
+ # Non-lazy values are coerced and validated before being set. Coercion
+ # and validation of lazy values is delayed until they are first retrieved.
+ #
+ # @param resource [Chef::Resource] The resource to set this property in.
+ # @param value The value to set.
+ #
+ # @return The value that was set, after coercion (if lazy, still returns
+ # the lazy value)
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def set(resource, value)
+ unless value.is_a?(DelayedEvaluator)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ set_value(resource, value)
+ end
+
+ #
+ # Find out whether this property has been set.
+ #
+ # This will be true if:
+ # - The user explicitly set the value
+ # - The property has a default, and the value was retrieved.
+ #
+ # From this point of view, it is worth looking at this as "what does the
+ # user think this value should be." In order words, if the user grabbed
+ # the value, even if it was a default, they probably based calculations on
+ # it. If they based calculations on it and the value changes, the rest of
+ # the world gets inconsistent.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ # @return [Boolean]
+ #
+ def is_set?(resource)
+ value_is_set?(resource)
+ end
+
+ #
+ # Reset the value of this property so that is_set? will return false and the
+ # default will be returned in the future.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ def reset(resource)
+ reset_value(resource)
+ end
+
+ #
+ # Coerce an input value into canonical form for the property.
+ #
+ # After coercion, the value is suitable for storage in the resource.
+ # You must validate values after coercion, however.
+ #
+ # Does no special handling for lazy values.
+ #
+ # @param resource [Chef::Resource] The resource we're coercing against
+ # (to provide context for the coerce).
+ # @param value The value to coerce.
+ #
+ # @return The coerced value.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def coerce(resource, value)
+ if options.has_key?(:coerce)
+ value = exec_in_resource(resource, options[:coerce], value)
+ end
+ value
+ end
+
+ #
+ # Validate a value.
+ #
+ # Calls Chef::Mixin::ParamsValidate#validate with #validation_options as
+ # options.
+ #
+ # @param resource [Chef::Resource] The resource we're validating against
+ # (to provide context for the validate).
+ # @param value The value to validate.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def validate(resource, value)
+ resource.validate({ name => value }, { name => validation_options })
+ end
+
+ #
+ # Derive a new Property that is just like this one, except with some added or
+ # changed options.
+ #
+ # @param options [Hash<Symbol,Object>] List of options that would be passed
+ # to #initialize.
+ #
+ # @return [Property] The new property type.
+ #
+ def derive(**modified_options)
+ Property.new(**options.merge(**modified_options))
+ end
+
+ #
+ # Emit the DSL for this property into the resource class (`declared_in`).
+ #
+ # Creates a getter and setter for the property.
+ #
+ def emit_dsl
+ # We don't create the getter/setter if it's a custom property; we will
+ # be using the existing getter/setter to manipulate it instead.
+ return if !instance_variable_name
+
+ # We prefer this form because the property name won't show up in the
+ # stack trace if you use `define_method`.
+ declared_in.class_eval <<-EOM, __FILE__, __LINE__+1
+ def #{name}(value=NOT_PASSED)
+ self.class.properties[#{name.inspect}].call(self, value)
+ end
+ def #{name}=(value)
+ self.class.properties[#{name.inspect}].set(self, value)
+ end
+ EOM
+ rescue SyntaxError
+ # If the name is not a valid ruby name, we use define_method.
+ resource_class.define_method(name) do |value=NOT_PASSED|
+ self.class.properties[name].call(self, value)
+ end
+ resource_class.define_method("#{name}=") do |value|
+ self.class.properties[name].set(self, value)
+ end
+ end
+
+ protected
+
+ #
+ # The options this Property will use for get/set behavior and validation.
+ #
+ # @see #initialize for a list of valid options.
+ #
+ attr_reader :options
+
+ #
+ # Find out whether this type accepts nil explicitly.
+ #
+ # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply
+ # an empty type.
+ #
+ # These examples accept nil explicitly:
+ # ```ruby
+ # property :a, [ String, nil ]
+ # property :a, [ String, NilClass ]
+ # property :a, [ String, proc { |v| v.nil? } ]
+ # ```
+ #
+ # This does not (because the "is" doesn't exist or doesn't have nil):
+ #
+ # ```ruby
+ # property :x, String
+ # ```
+ #
+ # These do not, even though nil would validate fine (because they do not
+ # have "is"):
+ #
+ # ```ruby
+ # property :a
+ # property :a, equal_to: [ 1, 2, 3, nil ]
+ # property :a, kind_of: [ String, NilClass ]
+ # property :a, respond_to: [ ]
+ # property :a, callbacks: { "a" => proc { |v| v.nil? } }
+ # ```
+ #
+ # @param resource [Chef::Resource] The resource we're coercing against
+ # (to provide context for the coerce).
+ #
+ # @return [Boolean] Whether this value explicitly accepts nil.
+ #
+ # @api private
+ def explicitly_accepts_nil?(resource)
+ options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false)
+ end
+
+ def get_value(resource)
+ if instance_variable_name
+ resource.instance_variable_get(instance_variable_name)
+ else
+ resource.send(name)
+ end
+ end
+
+ def set_value(resource, value)
+ if instance_variable_name
+ resource.instance_variable_set(instance_variable_name, value)
+ else
+ resource.send(name, value)
+ end
+ end
+
+ def value_is_set?(resource)
+ if instance_variable_name
+ resource.instance_variable_defined?(instance_variable_name)
+ else
+ true
+ end
+ end
+
+ def reset_value(resource)
+ if instance_variable_name
+ if value_is_set?(resource)
+ resource.remove_instance_variable(instance_variable_name)
+ end
+ else
+ raise ArgumentError, "Property #{name} has no instance variable defined and cannot be reset"
+ end
+ end
+
+ def exec_in_resource(resource, proc, *args)
+ if resource
+ if proc.arity > args.size
+ value = proc.call(resource, *args)
+ else
+ value = resource.instance_exec(*args, &proc)
+ end
+ else
+ value = proc.call
+ end
+
+ if value.is_a?(DelayedEvaluator)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ value
+ end
+ end
+end
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 5fa84a21e9..379369ba6e 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -53,7 +53,7 @@ class Chef
requirements.assert(:run) do |a|
a.assertion { supports_dsc_invoke_resource? }
err = ["You must have Powershell version >= 5.0.10018.0 to use dsc_resource."]
- a.failure_message Chef::Exceptions::NoProviderAvailable,
+ a.failure_message Chef::Exceptions::ProviderNotFound,
err
a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."]
a.block_action!
@@ -63,7 +63,7 @@ class Chef
meta_configuration['RefreshMode'] == 'Disabled'
}
err = ["The LCM must have its RefreshMode set to Disabled. "]
- a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+ a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
a.block_action!
end
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb
index a75e68a475..b2432132b7 100644
--- a/lib/chef/provider/dsc_script.rb
+++ b/lib/chef/provider/dsc_script.rb
@@ -70,7 +70,7 @@ class Chef
"Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.",
]
a.assertion { supports_dsc? }
- a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+ a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."]
a.block_action!
end
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 4ad7b24c15..510dfde46d 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -32,7 +32,7 @@ class Chef
@new_resource.options.clear
end
if @new_resource.fstype == "auto"
- @new_resource.fstype = nil
+ @new_resource.send(:clear_fstype)
end
end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 9d534ec414..aca8d0dc3b 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -142,7 +142,7 @@ class Chef
def action_remove
if removing_package?
description = @new_resource.version ? "version #{@new_resource.version} of " : ""
- converge_by("remove #{description} package #{@current_resource.package_name}") do
+ converge_by("remove #{description}package #{@current_resource.package_name}") do
remove_package(@current_resource.package_name, @new_resource.version)
Chef::Log.info("#{@new_resource} removed")
end
diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb
index f231101390..83fc09c8ae 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -31,6 +31,7 @@ class Chef
class Openbsd < Chef::Provider::Package
provides :package, os: "openbsd"
+ provides :openbsd_package
include Chef::Mixin::ShellOut
include Chef::Mixin::GetSourceFromPackage
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index ed44dee6ae..3c52db1f33 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -30,8 +30,8 @@ class Chef
end
def action_run
- valid_syntax = validate_script_syntax!
- super if valid_syntax
+ validate_script_syntax!
+ super
end
def flags
@@ -62,30 +62,43 @@ class Chef
def validate_script_syntax!
interpreter_arguments = default_interpreter_flags.join(' ')
Tempfile.open(['chef_powershell_script-user-code', '.ps1']) do | user_script_file |
- user_script_file.puts("{#{@new_resource.code}}")
- user_script_file.close
+ # Wrap the user's code in a PowerShell script block so that
+ # it isn't executed. However, syntactically invalid script
+ # in that block will still trigger a syntax error which is
+ # exactly what we want here -- verify the syntax without
+ # actually running the script.
+ user_code_wrapped_in_powershell_script_block = <<-EOH
+{
+ #{@new_resource.code}
+}
+EOH
+ user_script_file.puts user_code_wrapped_in_powershell_script_block
+ # A .close or explicit .flush required to ensure the file is
+ # written to the file system at this point, which is required since
+ # the intent is to execute the code just written to it.
+ user_script_file.close
validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command #{user_script_file.path}"
- # For consistency with other script resources, allow even syntax errors
- # to be suppressed if the returns attribute would have suppressed it
- # at converge.
- valid_returns = [0]
- specified_returns = @new_resource.returns.is_a?(Integer) ?
- [@new_resource.returns] :
- @new_resource.returns
- valid_returns.concat([1]) if specified_returns.include?(1)
-
- result = shell_out!(validation_command, {returns: valid_returns})
- result.exitstatus == 0
+ # Note that other script providers like bash allow syntax errors
+ # to be suppressed by setting 'returns' to a value that the
+ # interpreter would return as a status code in the syntax
+ # error case. We explicitly don't do this here -- syntax
+ # errors will not be suppressed, since doing so could make
+ # it harder for users to detect / debug invalid scripts.
+
+ # Therefore, the only return value for a syntactically valid
+ # script is 0. If an exception is raised by shellout, this
+ # means a non-zero return and thus a syntactically invalid script.
+ shell_out!(validation_command, {returns: [0]})
end
end
def default_interpreter_flags
- # 'Bypass' is preferable since it doesn't require user input confirmation
- # for files such as PowerShell modules downloaded from the
- # Internet. However, 'Bypass' is not supported prior to
- # PowerShell 3.0, so the fallback is 'Unrestricted'
+ # Execution policy 'Bypass' is preferable since it doesn't require
+ # user input confirmation for files such as PowerShell modules
+ # downloaded from the Internet. However, 'Bypass' is not supported
+ # prior to PowerShell 3.0, so the fallback is 'Unrestricted'
execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted'
[
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index cd62f7c56f..948fa6c63f 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -64,7 +64,7 @@ class Chef
def values_to_hash(values)
if values
- @name_hash = Hash[values.map { |val| [val[:name], val] }]
+ @name_hash = Hash[values.map { |val| [val[:name].downcase, val] }]
else
@name_hash = {}
end
@@ -100,8 +100,8 @@ class Chef
end
end
@new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name])
- current_value = @name_hash[value[:name]]
+ if @name_hash.has_key?(value[:name].downcase)
+ current_value = @name_hash[value[:name].downcase]
unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
converge_by("set value #{value}") do
registry.set_value(@new_resource.key, value)
@@ -122,7 +122,7 @@ class Chef
end
end
@new_resource.unscrubbed_values.each do |value|
- unless @name_hash.has_key?(value[:name])
+ unless @name_hash.has_key?(value[:name].downcase)
converge_by("create value #{value}") do
registry.set_value(@new_resource.key, value)
end
@@ -133,7 +133,7 @@ class Chef
def action_delete
if registry.key_exists?(@new_resource.key)
@new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name])
+ if @name_hash.has_key?(value[:name].downcase)
converge_by("delete value #{value}") do
registry.delete_value(@new_resource.key, value)
end
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
index 5bfee343d1..8e731ff96a 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -17,7 +17,7 @@
#
require 'chef/exceptions'
-require 'chef/platform/provider_priority_map'
+require 'chef/platform/priority_map'
class Chef
#
@@ -62,12 +62,35 @@ class Chef
maybe_chef_platform_lookup(resource)
end
+ # Does NOT call provides? on the resource (it is assumed this is being
+ # called *from* provides?).
def provided_by?(provider_class)
- prioritized_handlers.include?(provider_class)
+ potential_handlers.include?(provider_class)
+ end
+
+ def self.includes_handler?(resource_name, provider_class)
+ priority_map.includes_handler?(resource_name, provider_class)
+ end
+
+ def enabled_handlers
+ @enabled_handlers ||=
+ potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) }
+ end
+
+ # TODO deprecate this and allow actions to be passed as a filter to
+ # `provides` so we don't have to have two separate things.
+ # @api private
+ def supported_handlers
+ @supported_handlers ||=
+ enabled_handlers.select { |handler| handler.supports?(resource, action) }
end
private
+ def potential_handlers
+ priority_map.list_handlers(node, resource.resource_name)
+ end
+
# if resource.provider is set, just return one of those objects
def maybe_explicit_provider(resource)
return nil unless resource.provider
@@ -78,28 +101,16 @@ class Chef
def maybe_dynamic_provider_resolution(resource, action)
Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
- # Get all the handlers in the priority bucket
- handlers = prioritized_handlers
-
- # Narrow it down to handlers that return `true` to `provides?`
- # TODO deprecate this and don't bother calling--the fact that they said
- # `provides` should be enough. But we need to do it right now because
- # some classes implement additional handling.
- enabled_handlers = prioritized_handlers.select { |handler| handler.provides?(node, resource) }
-
- # Narrow it down to handlers that return `true` to `supports?`
- # TODO deprecate this and allow actions to be passed as a filter to
- # `provides` so we don't have to have two separate things.
- supported_handlers = enabled_handlers.select { |handler| handler.supports?(resource, action) }
- if supported_handlers.empty?
+ handlers = supported_handlers
+ if handlers.empty?
# if none of the providers specifically support the resource, we still need to pick one of the providers that are
# enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then.
Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway."
- handler = enabled_handlers.first
- else
- handler = supported_handlers.first
+ handlers = enabled_handlers
end
+ handler = handlers.first
+
if handler
Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}"
else
@@ -114,13 +125,16 @@ class Chef
Chef::Platform.find_provider_for_node(node, resource)
end
- def provider_priority_map
- Chef::Platform::ProviderPriorityMap.instance
+ def self.priority_map
+ Chef.provider_priority_map
end
- def prioritized_handlers
- @prioritized_handlers ||=
- provider_priority_map.list_handlers(node, resource.resource_name).flatten(1).uniq
+ def priority_map
+ Chef.provider_priority_map
+ end
+
+ def overrode_provides?(handler)
+ handler.method(:provides?).owner != Chef::Provider.method(:provides?).owner
end
module Deprecated
@@ -129,33 +143,21 @@ class Chef
@providers ||= Chef::Provider.descendants
end
- # this cut looks at if the provider can handle the resource type on the node
def enabled_handlers
- @enabled_handlers ||=
- providers.select do |klass|
- # NB: this is different from resource_resolver which must pass a resource_name
- # FIXME: deprecate this and normalize on passing resource_name here
- klass.provides?(node, resource)
- end.sort {|a,b| a.to_s <=> b.to_s }
- end
-
- # this cut looks at if the provider can handle the specific resource and action
- def supported_handlers
- @supported_handlers ||=
- enabled_handlers.select do |klass|
- klass.supports?(resource, action)
- end
- end
-
- # If there are no providers for a DSL, we search through the
- def prioritized_handlers
- @prioritized_handlers ||= super || begin
- result = providers.select { |handler| handler.provides?(node, resource) }.sort_by(:name)
- if !result.empty?
- Chef::Log.deprecation("#{resource.resource_name.to_sym} is marked as providing DSL #{method_symbol}, but provides #{resource.resource_name.to_sym.inspect} was never called!")
- Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+ @enabled_handlers ||= begin
+ handlers = super
+ if handlers.empty?
+ # Look through all providers, and find ones that return true to provides.
+ # Don't bother with ones that don't override provides?, since they
+ # would have been in enabled_handlers already if that were so. (It's a
+ # perf concern otherwise.)
+ handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) }
+ handlers.each do |handler|
+ Chef::Log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!")
+ Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+ end
end
- result
+ handlers
end
end
end
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 8d2532dac4..5e9ba42703 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -101,7 +101,7 @@ class Chef
# @param run_context The context of the Chef run. Corresponds to #run_context.
#
def initialize(name, run_context=nil)
- name(name)
+ name(name) unless name.nil?
@run_context = run_context
@noop = nil
@before = nil
@@ -130,37 +130,27 @@ class Chef
end
#
- # The name of this particular resource.
+ # The list of properties defined on this resource.
#
- # This special resource attribute is set automatically from the declaration
- # of the resource, e.g.
- #
- # execute 'Vitruvius' do
- # command 'ls'
- # end
+ # Everything defined with `property` is in this list.
#
- # Will set the name to "Vitruvius".
+ # @param include_superclass [Boolean] `true` to include properties defined
+ # on superclasses; `false` or `nil` to return the list of properties
+ # directly on this class.
#
- # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
+ # @return [Hash<Symbol,Property>] The list of property names and types.
#
- # This is also used for resource notifications and subscribes in the same manner.
- #
- # This will coerce any object into a string via #to_s. Arrays are a special case
- # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
- # awkward `package[["foo", "bar"]]` that #to_s would produce.
- #
- # @param name [Object] The name to set, typically a String or Array
- # @return [String] The name of this Resource.
- #
- def name(name=nil)
- if !name.nil?
- if name.is_a?(Array)
- @name = name.join(', ')
+ def self.properties(include_superclass=true)
+ @properties ||= {}
+ if include_superclass
+ if superclass.respond_to?(:properties)
+ superclass.properties.merge(@properties)
else
- @name = name.to_s
+ @properties.dup
end
+ else
+ @properties
end
- @name
end
#
@@ -169,26 +159,24 @@ class Chef
# @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`)
# @return [Array[Symbol]] the list of actions.
#
- attr_accessor :action
def action(arg=nil)
if arg
- if arg.is_a?(Array)
- arg = arg.map { |a| a.to_sym }
- else
- arg = arg.to_sym
- end
- Array(arg).each do |action|
+ arg = Array(arg).map(&:to_sym)
+ arg.each do |action|
validate(
{ action: action },
{ action: { kind_of: Symbol, equal_to: allowed_actions } }
)
end
- self.action = arg
+ @action = arg
else
@action
end
end
+ # Alias for normal assigment syntax.
+ alias_method :action=, :action
+
#
# Sets up a notification that will run a particular action on another resource
# if and when *this* resource is updated by an action.
@@ -477,13 +465,21 @@ class Chef
#
# Get the value of the state attributes in this resource as a hash.
#
+ # Does not include properties that are not set (unless they are identity
+ # properties).
+ #
# @return [Hash{Symbol => Object}] A Hash of attribute => value for the
# Resource class's `state_attrs`.
+ #
def state_for_resource_reporter
- self.class.state_attrs.inject({}) do |state_attrs, attr_name|
- state_attrs[attr_name] = send(attr_name)
- state_attrs
+ state = {}
+ state_properties = self.class.state_properties
+ state_properties.each do |property|
+ if property.identity? || property.is_set?(self)
+ state[property.name] = send(property.name)
+ end
end
+ state
end
#
@@ -496,17 +492,22 @@ class Chef
alias_method :state, :state_for_resource_reporter
#
- # The value of the identity attribute, if declared. Falls back to #name if
- # no identity attribute is declared.
+ # The value of the identity of this resource.
+ #
+ # - If there are no identity properties on the resource, `name` is returned.
+ # - If there is exactly one identity property on the resource, it is returned.
+ # - If there are more than one, they are returned in a hash.
#
- # @return The value of the identity attribute.
+ # @return [Object,Hash<Symbol,Object>] The identity of this resource.
#
def identity
- if identity_attr = self.class.identity_attr
- send(identity_attr)
- else
- name
+ result = {}
+ identity_properties = self.class.identity_properties
+ identity_properties.each do |property|
+ result[property.name] = send(property.name)
end
+ return result.values.first if identity_properties.size == 1
+ result
end
#
@@ -758,6 +759,10 @@ class Chef
# will return if the user does not set one. If this is `lazy`, it will
# be run in the context of the instance (and able to access other
# properties).
+ # @option options [Boolean] :desired_state `true` if this property is
+ # part of desired state. Defaults to `true`.
+ # @option options [Boolean] :identity `true` if this property
+ # is part of object identity. Defaults to `false`.
#
# @example Bare property
# property :x
@@ -774,29 +779,100 @@ class Chef
def self.property(name, type=NOT_PASSED, **options)
name = name.to_sym
- if type != NOT_PASSED
+ options.merge!(name: name, declared_in: self)
+
+ if type == NOT_PASSED
+ # If a type is not passed, the property derives from the
+ # superclass property (if any)
+ if properties.has_key?(name)
+ property = properties[name].derive(**options)
+ else
+ property = property_type(**options)
+ end
+
+ # If a Property is specified, derive a new one from that.
+ elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property)
+ property = type.derive(**options)
+
+ # If a primitive type was passed, combine it with "is"
+ else
if options[:is]
options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
else
options[:is] = type
end
+ property = property_type(**options)
end
- define_method(name) do |value=NOT_PASSED|
- set_or_return(name, value, options)
- end
- define_method("#{name}=") do |value|
- set_or_return(name, value, options)
- end
+ local_properties = properties(false)
+ local_properties[name] = property
+
+ property.emit_dsl
+ end
+
+ #
+ # Create a reusable property type that can be used in multiple properties
+ # in different resources.
+ #
+ # @param options [Hash<Symbol,Object>] Validation options. see #property for
+ # the list of options.
+ #
+ # @example
+ # property_type(default: 'hi')
+ #
+ def self.property_type(**options)
+ Property.derive(**options)
end
#
+ # The name of this particular resource.
+ #
+ # This special resource attribute is set automatically from the declaration
+ # of the resource, e.g.
+ #
+ # execute 'Vitruvius' do
+ # command 'ls'
+ # end
+ #
+ # Will set the name to "Vitruvius".
+ #
+ # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
+ #
+ # This is also used for resource notifications and subscribes in the same manner.
+ #
+ # This will coerce any object into a string via #to_s. Arrays are a special case
+ # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
+ # awkward `package[["foo", "bar"]]` that #to_s would produce.
+ #
+ # @param name [Object] The name to set, typically a String or Array
+ # @return [String] The name of this Resource.
+ #
+ property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(', ') : v.to_s }, desired_state: false
+
+ #
# Whether this property has been set (or whether it has a default that has
# been retrieved).
#
+ # @param name [Symbol] The name of the property.
+ # @return [Boolean] `true` if the property has been set.
+ #
def property_is_set?(name)
- name = name.to_sym
- instance_variable_defined?("@#{name}")
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ property.is_set?(self)
+ end
+
+ #
+ # Clear this property as if it had never been set. It will thereafter return
+ # the default.
+ # been retrieved).
+ #
+ # @param name [Symbol] The name of the property.
+ #
+ def reset_property(name)
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ property.reset(self)
end
#
@@ -810,46 +886,186 @@ class Chef
DelayedEvaluator.new(&block)
end
- # Set or return the list of "state attributes" implemented by the Resource
- # subclass. State attributes are attributes that describe the desired state
- # of the system, such as file permissions or ownership. In general, state
- # attributes are attributes that could be populated by examining the state
- # of the system (e.g., File.stat can tell you the permissions on an
- # existing file). Contrarily, attributes that are not "state attributes"
- # usually modify the way Chef itself behaves, for example by providing
- # additional options for a package manager to use when installing a
- # package.
+ #
+ # Get or set the list of desired state properties for this resource.
+ #
+ # State properties are properties that describe the desired state
+ # of the system, such as file permissions or ownership.
+ # In general, state properties are properties that could be populated by
+ # examining the state of the system (e.g., File.stat can tell you the
+ # permissions on an existing file). Contrarily, properties that are not
+ # "state properties" usually modify the way Chef itself behaves, for example
+ # by providing additional options for a package manager to use when
+ # installing a package.
#
# This list is used by the Chef client auditing system to extract
# information from resources to describe changes made to the system.
- def self.state_attrs(*attr_names)
- @state_attrs ||= []
- @state_attrs = attr_names unless attr_names.empty?
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties are added to state_properties by default, and can be turned off
+ # with `desired_state: false`.
+ #
+ # ```ruby
+ # property :x # part of desired state
+ # property :y, desired_state: false # not part of desired state
+ # ```
+ #
+ # @param names [Array<Symbol>] A list of property names to set as desired
+ # state.
+ #
+ # @return [Array<Property>] All properties in desired state.
+ #
+ def self.state_properties(*names)
+ if !names.empty?
+ names = names.map { |name| name.to_sym }.uniq
- # Return *all* state_attrs that this class has, including inherited ones
- if superclass.respond_to?(:state_attrs)
- superclass.state_attrs + @state_attrs
- else
- @state_attrs
+ local_properties = properties(false)
+ # Add new properties to the list.
+ names.each do |name|
+ property = properties[name]
+ if !property
+ self.property name, instance_variable_name: false, desired_state: true
+ elsif !property.desired_state?
+ self.property name, desired_state: true
+ end
+ end
+
+ # If state_attrs *excludes* something which is currently desired state,
+ # mark it as desired_state: false.
+ local_properties.each do |name,property|
+ if property.desired_state? && !names.include?(name)
+ self.property name, desired_state: false
+ end
+ end
end
+
+ properties.values.select { |property| property.desired_state? }
+ end
+
+ #
+ # Set or return the list of "state properties" implemented by the Resource
+ # subclass.
+ #
+ # Equivalent to calling #state_properties and getting `state_properties.keys`.
+ #
+ # @deprecated Use state_properties.keys instead. Note that when you declare
+ # properties with `property`: properties are added to state_properties by
+ # default, and can be turned off with `desired_state: false`
+ #
+ # ```ruby
+ # property :x # part of desired state
+ # property :y, desired_state: false # not part of desired state
+ # ```
+ #
+ # @param names [Array<Symbol>] A list of property names to set as desired
+ # state.
+ #
+ # @return [Array<Symbol>] All property names with desired state.
+ #
+ def self.state_attrs(*names)
+ state_properties(*names).map { |property| property.name }
end
- # Set or return the "identity attribute" for this resource class. This is
- # generally going to be the "name attribute" for this resource. In other
- # words, the resource type plus this attribute uniquely identify a given
- # bit of state that chef manages. For a File resource, this would be the
- # path, for a package resource, it will be the package name. This will show
- # up in chef-client's audit records as a searchable field.
- def self.identity_attr(attr_name=nil)
- @identity_attr ||= nil
- @identity_attr = attr_name if attr_name
+ #
+ # Set the identity of this resource to a particular set of properties.
+ #
+ # This drives #identity, which returns data that uniquely refers to a given
+ # resource on the given node (in such a way that it can be correlated
+ # across Chef runs).
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties can be added to identity during declaration with
+ # `identity: true`.
+ #
+ # ```ruby
+ # property :x, identity: true # part of identity
+ # property :y # not part of identity
+ # ```
+ #
+ # If no properties are marked as identity, "name" is considered the identity.
+ #
+ # @param names [Array<Symbol>] A list of property names to set as the identity.
+ #
+ # @return [Array<Property>] All identity properties.
+ #
+ def self.identity_properties(*names)
+ if !names.empty?
+ names = names.map { |name| name.to_sym }
- # If this class doesn't have an identity attr, we'll defer to the superclass:
- if @identity_attr || !superclass.respond_to?(:identity_attr)
- @identity_attr
- else
- superclass.identity_attr
+ # Add or change properties that are not part of the identity.
+ names.each do |name|
+ property = properties[name]
+ if !property
+ self.property name, instance_variable_name: false, identity: true
+ elsif !property.identity?
+ self.property name, identity: true
+ end
+ end
+
+ # If state_attrs *excludes* something which is currently part of the
+ # identity, mark it as identity: false.
+ properties.each do |name,property|
+ if property.identity? && !names.include?(name)
+ self.property name, identity: false
+ end
+ end
end
+
+ result = properties.values.select { |property| property.identity? }
+ result = [ properties[:name] ] if result.empty?
+ result
+ end
+
+ #
+ # Set the identity of this resource to a particular property.
+ #
+ # This drives #identity, which returns data that uniquely refers to a given
+ # resource on the given node (in such a way that it can be correlated
+ # across Chef runs).
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties can be added to identity during declaration with
+ # `identity: true`.
+ #
+ # ```ruby
+ # property :x, identity: true # part of identity
+ # property :y # not part of identity
+ # ```
+ #
+ # @param name [Symbol] A list of property names to set as the identity.
+ #
+ # @return [Symbol] The identity property if there is only one; or `nil` if
+ # there are more than one.
+ #
+ # @raise [ArgumentError] If no arguments are passed and the resource has
+ # more than one identity property.
+ #
+ def self.identity_property(name=nil)
+ result = identity_properties(*Array(name))
+ if result.size > 1
+ raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map { |r| r.name }.join(", ")})."
+ end
+ result.first
+ end
+
+ #
+ # Set a property as the "identity attribute" for this resource.
+ #
+ # Identical to calling #identity_property.first.key.
+ #
+ # @param name [Symbol] The name of the property to set.
+ #
+ # @return [Symbol]
+ #
+ # @deprecated `identity_property` should be used instead.
+ #
+ # @raise [ArgumentError] If no arguments are passed and the resource has
+ # more than one identity property.
+ #
+ def self.identity_attr(name=nil)
+ property = identity_property(name)
+ return nil if !property
+ property.name
end
#
@@ -1019,7 +1235,7 @@ class Chef
if name
name = name.to_sym
# If our class is not already providing this name, provide it.
- if !Chef::ResourceResolver.list(name).include?(self)
+ if !Chef::ResourceResolver.includes_handler?(name, self)
provides name, canonical: true
end
@resource_name = name
@@ -1095,22 +1311,17 @@ class Chef
# Setting default_action will automatially add the action to
# allowed_actions, if it isn't already there.
#
- # Defaults to :nothing.
+ # Defaults to [:nothing].
#
# @param action_name [Symbol,Array<Symbol>] The default action (or series
# of actions) to use.
#
- # @return [Symbol,Array<Symbol>] The default actions for the resource.
+ # @return [Array<Symbol>] The default actions for the resource.
#
def self.default_action(action_name=NOT_PASSED)
unless action_name.equal?(NOT_PASSED)
- if action_name.is_a?(Array)
- @default_action = action_name.map { |arg| arg.to_sym }
- else
- @default_action = action_name.to_sym
- end
-
- self.allowed_actions |= Array(@default_action)
+ @default_action = Array(action_name).map(&:to_sym)
+ self.allowed_actions |= @default_action
end
if @default_action
@@ -1118,7 +1329,7 @@ class Chef
elsif superclass.respond_to?(:default_action)
superclass.default_action
else
- :nothing
+ [:nothing]
end
end
def self.default_action=(action_name)
@@ -1159,7 +1370,7 @@ class Chef
action = action.to_sym
new_action_provider_class.action(action, &recipe_block)
self.allowed_actions += [ action ]
- default_action action if default_action == :nothing
+ default_action action if Array(default_action) == [:nothing]
end
#
@@ -1206,9 +1417,6 @@ class Chef
define_method(:load_current_resource) {}
end
end
- def self.default_action=(action_name)
- default_action(action_name)
- end
#
# Internal Resource Interface (for Chef)
@@ -1304,7 +1512,7 @@ class Chef
end
def self.inherited(child)
super
- @sorted_descendants = nil
+ @@sorted_descendants = nil
# set resource_name automatically if it's not set
if child.name && !child.resource_name
if child.name =~ /^Chef::Resource::(\w+)$/
@@ -1347,8 +1555,8 @@ class Chef
result
end
- def self.provides?(node, resource)
- Chef::ResourceResolver.resolve(resource, node: node).provided_by?(self)
+ def self.provides?(node, resource_name)
+ Chef::ResourceResolver.new(node, resource_name).provided_by?(self)
end
# Helper for #notifies
@@ -1506,56 +1714,11 @@ class Chef
Chef::Resource.send(:remove_const, class_name)
end
- # In order to generate deprecation warnings when you use Chef::Resource::MyLwrp,
- # we make a special subclass (identical in nearly all respects) of the
- # actual LWRP. When you say any of these, a deprecation warning will be
- # generated:
- #
- # - Chef::Resource::MyLwrp.new(...)
- # - resource.is_a?(Chef::Resource::MyLwrp)
- # - resource.kind_of?(Chef::Resource::MyLwrp)
- # - case resource
- # when Chef::Resource::MyLwrp
- # end
- #
- resource_subclass = Class.new(resource_class) do
- resource_name nil # we do not actually provide anything
- def initialize(*args, &block)
- Chef::Log.deprecation("Using an LWRP by its name (#{self.class.name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
- super
- end
- def self.resource_name(*args)
- if args.empty?
- @resource_name ||= superclass.resource_name
- else
- super
- end
- end
- self
- end
- eval("Chef::Resource::#{class_name} = resource_subclass")
- # Make case, is_a and kind_of work with the new subclass, for backcompat.
- # Any subclass of Chef::Resource::ResourceClass is already a subclass of resource_class
- # Any subclass of resource_class is considered a subclass of Chef::Resource::ResourceClass
- resource_class.class_eval do
- define_method(:is_a?) do |other|
- other.is_a?(Module) && other === self
- end
- define_method(:kind_of?) do |other|
- other.is_a?(Module) && other === self
- end
+ if !Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Resource.const_set(class_name, resource_class)
+ deprecated_constants[class_name.to_sym] = resource_class
end
- resource_subclass.class_eval do
- define_singleton_method(:===) do |other|
- Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
- # resource_subclass is a superclass of all resource_class descendants.
- if self == resource_subclass && other.class <= resource_class
- return true
- end
- super(other)
- end
- end
- deprecated_constants[class_name.to_sym] = resource_subclass
+
end
def self.deprecated_constants
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index 2fcf183375..2877f61eb4 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -22,7 +22,7 @@ class Chef
class Resource
class DscScript < Chef::Resource
- provides :dsc_script, platform: "windows"
+ provides :dsc_script, os: "windows"
default_action :run
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
index 8d720dd411..2bf8e1dba8 100644
--- a/lib/chef/resource/ips_package.rb
+++ b/lib/chef/resource/ips_package.rb
@@ -23,6 +23,7 @@ class Chef
class Resource
class IpsPackage < ::Chef::Resource::Package
+ provides :package, os: "solaris2"
provides :ips_package, os: "solaris2"
allowed_actions :install, :remove, :upgrade
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 79986d127f..a5da0ba329 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -174,6 +174,14 @@ class Chef
)
end
+ private
+
+ # Used by the AIX provider to set fstype to nil.
+ # TODO use property to make nil a valid value for fstype
+ def clear_fstype
+ @fstype = nil
+ end
+
end
end
end
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index f91fdb37e0..9ae8813d69 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -29,17 +29,6 @@ class Chef
include Chef::Mixin::ShellOut
provides :package, os: "openbsd"
-
- def after_created
- assign_provider
- end
-
- private
-
- def assign_provider
- @provider = Chef::Provider::Package::Openbsd
- end
-
end
end
end
diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb
index 2dc72d5c47..a98fb8b4fa 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -24,10 +24,7 @@ class Chef
class Resource
class SolarisPackage < Chef::Resource::Package
provides :package, os: "solaris2", platform_family: "nexentacore"
- provides :package, os: "solaris2", platform_family: "solaris2" do |node|
- # on >= Solaris 11 we default to IPS packages instead
- node[:platform_version].to_f <= 5.10
- end
+ provides :package, os: "solaris2", platform_family: "solaris2", platform_version: "<= 5.10"
end
end
end
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index 9df627beb2..2f27e93a3b 100644
--- a/lib/chef/resource_resolver.rb
+++ b/lib/chef/resource_resolver.rb
@@ -83,9 +83,9 @@ class Chef
# @api private use Chef::ResourceResolver.resolve instead.
def resolve
# log this so we know what resources will work for the generic resource on the node (early cut)
- Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
+ Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{enabled_handlers}"
- handler = prioritized_handlers.first
+ handler = enabled_handlers.first
if handler
Chef::Log.debug "Resource for #{resource_name} is #{handler}"
@@ -98,59 +98,70 @@ class Chef
# @api private
def list
- Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
- prioritized_handlers
+ Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{enabled_handlers}"
+ enabled_handlers
end
#
# Whether this DSL is provided by the given resource_class.
#
+ # Does NOT call provides? on the resource (it is assumed this is being
+ # called *from* provides?).
+ #
# @api private
def provided_by?(resource_class)
- !prioritized_handlers.include?(resource_class)
+ potential_handlers.include?(resource_class)
+ end
+
+ #
+ # Whether the given handler attempts to provide the resource class at all.
+ #
+ # @api private
+ def self.includes_handler?(resource_name, resource_class)
+ priority_map.includes_handler?(resource_name, resource_class)
end
protected
+ def self.priority_map
+ Chef.resource_priority_map
+ end
+
def priority_map
- Chef::Platform::ResourcePriorityMap.instance
+ Chef.resource_priority_map
end
- def prioritized_handlers
- @prioritized_handlers ||=
- priority_map.list_handlers(node, resource_name, canonical: canonical)
+ # @api private
+ def potential_handlers
+ priority_map.list_handlers(node, resource_name, canonical: canonical)
+ end
+
+ def enabled_handlers
+ potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource_name) }
+ end
+
+ def overrode_provides?(handler)
+ handler.method(:provides?).owner != Chef::Resource.method(:provides?).owner
end
module Deprecated
# return a deterministically sorted list of Chef::Resource subclasses
- # @deprecated Now prioritized_handlers does its own work (more efficiently)
def resources
Chef::Resource.sorted_descendants
end
- # A list of all handlers
- # @deprecated Now prioritized_handlers does its own work
def enabled_handlers
- Chef::Log.deprecation("enabled_handlers is deprecated. If you are implementing a ResourceResolver, use provided_handlers. If you are not, use Chef::ResourceResolver.list(#{resource_name.inspect}, node: <node>)")
- resources.select { |klass| klass.provides?(node, resource_name) }
- end
-
- protected
-
- # A list of all handlers for the given DSL. If there are no handlers in
- # the map, we still check all descendants of Chef::Resource for backwards
- # compatibility purposes.
- def prioritized_handlers
- @prioritized_handlers ||= super ||
- resources.select do |klass|
- # Don't bother calling provides? unless it's overridden. We already
- # know prioritized_handlers
- if klass.method(:provides?).owner != Chef::Resource && klass.provides?(node, resource_name)
- Chef::Log.deprecation("Resources #{provided.join(", ")} are marked as providing DSL #{resource_name}, but provides #{resource_name.inspect} was never called!")
+ @enabled_handlers ||= begin
+ handlers = super
+ if handlers.empty?
+ handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) }
+ handlers.each do |handler|
+ Chef::Log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!")
Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
- true
end
end
+ handlers
+ end
end
end
prepend Deprecated
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index f55d1740b1..b1113f594e 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -532,17 +532,24 @@ ERROR_MESSAGE
###
# These need to be settable so deploy can run a resource_collection
# independent of any cookbooks via +recipe_eval+
+ def resource_collection=(value)
+ Chef::Log.deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @resource_collection = value
+ end
def audits=(value)
Chef::Log.deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @audits = value
end
def immediate_notification_collection=(value)
Chef::Log.deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @immediate_notification_collection = value
end
def delayed_notification_collection=(value)
Chef::Log.deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @delayed_notification_collection = value
end
end
prepend Deprecated
diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb
index 7cce6fa48c..2824f08f31 100644
--- a/lib/chef/run_list/versioned_recipe_list.rb
+++ b/lib/chef/run_list/versioned_recipe_list.rb
@@ -70,15 +70,16 @@ class Chef
# @return [Array] Array of strings with fully-qualified recipe names
def with_fully_qualified_names_and_version_constraints
self.map do |recipe_name|
- ret = if recipe_name.include?('::')
+ qualified_recipe = if recipe_name.include?('::')
recipe_name
else
"#{recipe_name}::default"
end
- if @versions[recipe_name]
- ret << "@#{@versions[recipe_name]}"
- end
- ret
+
+ version = @versions[recipe_name]
+ qualified_recipe = "#{qualified_recipe}@#{version}" if version
+
+ qualified_recipe
end
end
end
diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb
index 18f12d26b8..1a1aa12fad 100644
--- a/lib/chef/win32/registry.rb
+++ b/lib/chef/win32/registry.rb
@@ -203,7 +203,7 @@ class Chef
key_exists!(key_path)
hive, key = get_hive_and_key(key_path)
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
- return true if reg.any? {|val| val == value[:name] }
+ return true if reg.any? {|val| safely_downcase(val) == safely_downcase(value[:name]) }
end
return false
end
@@ -213,7 +213,7 @@ class Chef
hive, key = get_hive_and_key(key_path)
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
reg.each do |val_name, val_type, val_data|
- if val_name == value[:name] &&
+ if safely_downcase(val_name) == safely_downcase(value[:name]) &&
val_type == get_type_from_name(value[:type]) &&
val_data == value[:data]
return true
@@ -289,6 +289,14 @@ class Chef
private
+
+ def safely_downcase(val)
+ if val.is_a? String
+ return val.downcase
+ end
+ return val
+ end
+
def node
run_context && run_context.node
end
diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb
index 2454c9cccf..8398c5d616 100644
--- a/lib/chef/workstation_config_loader.rb
+++ b/lib/chef/workstation_config_loader.rb
@@ -1,5 +1,5 @@
#
-# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Author:: Claire McQuin (<claire@chef.io>)
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
@@ -16,163 +16,8 @@
# limitations under the License.
#
-require 'chef/config_fetcher'
-require 'chef/config'
-require 'chef/null_logger'
-require 'chef/util/path_helper'
+require 'chef-config/workstation_config_loader'
class Chef
-
- class WorkstationConfigLoader
-
- # Path to a config file requested by user, (e.g., via command line option). Can be nil
- attr_accessor :explicit_config_file
-
- # TODO: initialize this with a logger for Chef and Knife
- def initialize(explicit_config_file, logger=nil)
- @explicit_config_file = explicit_config_file
- @config_location = nil
- @logger = logger || NullLogger.new
- end
-
- def no_config_found?
- config_location.nil?
- end
-
- def config_location
- @config_location ||= (explicit_config_file || locate_local_config)
- end
-
- def chef_config_dir
- if @chef_config_dir.nil?
- @chef_config_dir = false
- full_path = working_directory.split(File::SEPARATOR)
- (full_path.length - 1).downto(0) do |i|
- candidate_directory = File.join(full_path[0..i] + [".chef" ])
- if File.exist?(candidate_directory) && File.directory?(candidate_directory)
- @chef_config_dir = candidate_directory
- break
- end
- end
- end
- @chef_config_dir
- end
-
- def load
- # Ignore it if there's no explicit_config_file and can't find one at a
- # default path.
- return false if config_location.nil?
-
- if explicit_config_file && !path_exists?(config_location)
- raise Exceptions::ConfigurationError, "Specified config file #{config_location} does not exist"
- end
-
- # Have to set Chef::Config.config_file b/c other config is derived from it.
- Chef::Config.config_file = config_location
- read_config(IO.read(config_location), config_location)
- end
-
- # (Private API, public for test purposes)
- def env
- ENV
- end
-
- # (Private API, public for test purposes)
- def path_exists?(path)
- Pathname.new(path).expand_path.exist?
- end
-
- private
-
- def have_config?(path)
- if path_exists?(path)
- logger.info("Using config at #{path}")
- true
- else
- logger.debug("Config not found at #{path}, trying next option")
- false
- end
- end
-
- def locate_local_config
- candidate_configs = []
-
- # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
- if env['KNIFE_HOME']
- candidate_configs << File.join(env['KNIFE_HOME'], 'config.rb')
- candidate_configs << File.join(env['KNIFE_HOME'], 'knife.rb')
- end
- # Look for $PWD/knife.rb
- if Dir.pwd
- candidate_configs << File.join(Dir.pwd, 'config.rb')
- candidate_configs << File.join(Dir.pwd, 'knife.rb')
- end
- # Look for $UPWARD/.chef/knife.rb
- if chef_config_dir
- candidate_configs << File.join(chef_config_dir, 'config.rb')
- candidate_configs << File.join(chef_config_dir, 'knife.rb')
- end
- # Look for $HOME/.chef/knife.rb
- Chef::Util::PathHelper.home('.chef') do |dot_chef_dir|
- candidate_configs << File.join(dot_chef_dir, 'config.rb')
- candidate_configs << File.join(dot_chef_dir, 'knife.rb')
- end
-
- candidate_configs.find do | candidate_config |
- have_config?(candidate_config)
- end
- end
-
- def working_directory
- a = if Chef::Platform.windows?
- env['CD']
- else
- env['PWD']
- end || Dir.pwd
-
- a
- end
-
- def read_config(config_content, config_file_path)
- Chef::Config.from_string(config_content, config_file_path)
- rescue SignalException
- raise
- rescue SyntaxError => e
- message = ""
- message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n"
- message << "#{e.class.name}: #{e.message}\n"
- if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/]
- line = file_line[/:([\d]+)$/, 1].to_i
- message << highlight_config_error(config_file_path, line)
- end
- raise Exceptions::ConfigurationError, message
- rescue Exception => e
- message = "You have an error in your config file #{config_file_path}\n\n"
- message << "#{e.class.name}: #{e.message}\n"
- filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
- filtered_trace.each {|bt_line| message << " " << bt_line << "\n" }
- if !filtered_trace.empty?
- line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1]
- message << highlight_config_error(config_file_path, line_nr.to_i)
- end
- raise Exceptions::ConfigurationError, message
- end
-
-
- def highlight_config_error(file, line)
- config_file_lines = []
- IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"}
- if line == 1
- lines = config_file_lines[0..3]
- else
- lines = config_file_lines[Range.new(line - 2, line)]
- end
- "Relevant file content:\n" + lines.join("\n") + "\n"
- end
-
- def logger
- @logger
- end
-
- end
+ WorkstationConfigLoader = ChefConfig::WorkstationConfigLoader
end