summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/chef.rb1
-rw-r--r--lib/chef/chef_class.rb130
-rw-r--r--lib/chef/client.rb19
-rw-r--r--lib/chef/event_loggers/windows_eventlog.rb16
-rw-r--r--lib/chef/exceptions.rb3
-rw-r--r--lib/chef/key.rb251
-rw-r--r--lib/chef/knife/bootstrap/templates/chef-full.erb189
-rw-r--r--lib/chef/mixin/provides.rb32
-rw-r--r--lib/chef/platform/provider_priority_map.rb23
-rw-r--r--lib/chef/platform/resource_priority_map.rb37
-rw-r--r--lib/chef/policy_builder/expand_node_object.rb14
-rw-r--r--lib/chef/policy_builder/policyfile.rb1
-rw-r--r--lib/chef/provider.rb25
-rw-r--r--lib/chef/provider_resolver.rb15
-rw-r--r--lib/chef/resource.rb44
-rw-r--r--lib/chef/resource_resolver.rb101
-rw-r--r--lib/chef/search/query.rb2
17 files changed, 796 insertions, 107 deletions
diff --git a/lib/chef.rb b/lib/chef.rb
index 7f54b91f14..6bce976439 100644
--- a/lib/chef.rb
+++ b/lib/chef.rb
@@ -32,3 +32,4 @@ require 'chef/run_status'
require 'chef/handler'
require 'chef/handler/json_file'
+require 'chef/chef_class'
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
new file mode 100644
index 0000000000..d3f7ee55c7
--- /dev/null
+++ b/lib/chef/chef_class.rb
@@ -0,0 +1,130 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# NOTE: This class is not intended for internal use by the chef-client code. Classes in
+# chef-client should still have objects like the node and run_context injected into them
+# via their initializers. This class is still global state and will complicate writing
+# unit tests for internal Chef objects. It is intended to be used only by recipe code.
+
+# NOTE: When adding require lines here you are creating tight coupling on a global that may be
+# included in many different situations and ultimately that ends in tears with circular requires.
+# Note the way that the run_context, provider_priority_map and resource_priority_map are "dependency
+# injected" into this class by other objects and do not reference the class symbols in those files
+# directly and we do not need to require those files here.
+
+class Chef
+ class << self
+
+ #
+ # Public API
+ #
+
+ # Get the node object
+ #
+ # @return [Chef::Node] node object of the chef-client run
+ attr_reader :node
+
+ # Get the run context
+ #
+ # @return [Chef::RunContext] run_context of the chef-client run
+ attr_reader :run_context
+
+ # Get the array of providers associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
+ def get_provider_priority_array(resource_name)
+ @provider_priority_map.get_priority_array(node, resource_name).dup
+ end
+
+ # Get the array of resources associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
+ def get_resource_priority_array(resource_name)
+ @resource_priority_map.get_priority_array(node, resource_name).dup
+ end
+
+ # Set the array of providers associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
+ # @param filter [Hash] Chef::Nodearray-style filter
+ # @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
+ def set_provider_priority_array(resource_name, priority_array, *filter)
+ @provider_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
+ end
+
+ # Get the array of resources associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
+ # @param filter [Hash] Chef::Nodearray-style filter
+ # @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
+ def set_resource_priority_array(resource_name, priority_array, *filter)
+ @resource_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
+ end
+
+ #
+ # Dependency Injection API (Private not Public)
+ # [ in the ruby sense these have to be public methods, but they are
+ # *NOT* for public consumption ]
+ #
+
+ # Sets the resource_priority_map
+ #
+ # @api private
+ # @param resource_priority_map [Chef::Platform::ResourcePriorityMap]
+ def set_resource_priority_map(resource_priority_map)
+ @resource_priority_map = resource_priority_map
+ end
+
+ # Sets the provider_priority_map
+ #
+ # @api private
+ # @param provider_priority_map [Chef::Platform::providerPriorityMap]
+ def set_provider_priority_map(provider_priority_map)
+ @provider_priority_map = provider_priority_map
+ end
+
+ # Sets the node object
+ #
+ # @api private
+ # @param node [Chef::Node]
+ def set_node(node)
+ @node = node
+ end
+
+ # Sets the run_context object
+ #
+ # @api private
+ # @param run_context [Chef::RunContext]
+ def set_run_context(run_context)
+ @run_context = run_context
+ end
+
+ # Resets the internal state
+ #
+ # @api private
+ def reset!
+ @run_context = nil
+ @node = nil
+ @provider_priority_map = nil
+ @resource_priority_map = nil
+ end
+ end
+end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index a4f15c271f..d04a3dbbd5 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -166,6 +166,13 @@ class Chef
if new_runlist = args.delete(:runlist)
@json_attribs["run_list"] = new_runlist
end
+
+ # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
+ require 'chef/platform/provider_priority_map' unless defined? Chef::Platform::ProviderPriorityMap
+ require 'chef/platform/resource_priority_map' unless defined? Chef::Platform::ResourcePriorityMap
+
+ Chef.set_provider_priority_map(Chef::Platform::ProviderPriorityMap.instance)
+ Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance)
end
def configure_formatters
@@ -224,21 +231,21 @@ class Chef
end
# Instantiates a Chef::Node object, possibly loading the node's prior state
- # when using chef-client. Delegates to policy_builder
- #
+ # when using chef-client. Delegates to policy_builder. Injects the built node
+ # into the Chef class.
#
- # === Returns
- # Chef::Node:: The node object for this chef run
+ # @return [Chef::Node] The node object for this Chef run
def load_node
policy_builder.load_node
@node = policy_builder.node
+ Chef.set_node(@node)
+ node
end
# Mutates the `node` object to prepare it for the chef run. Delegates to
# policy_builder
#
- # === Returns
- # Chef::Node:: The updated node object
+ # @return [Chef::Node] The updated node object
def build_node
policy_builder.build_node
@run_status = Chef::RunStatus.new(node, events)
diff --git a/lib/chef/event_loggers/windows_eventlog.rb b/lib/chef/event_loggers/windows_eventlog.rb
index 6f5ef627fb..37dcdc8693 100644
--- a/lib/chef/event_loggers/windows_eventlog.rb
+++ b/lib/chef/event_loggers/windows_eventlog.rb
@@ -88,15 +88,21 @@ class Chef
#Exception message: %4
#Exception backtrace: %5
def run_failed(e)
+ data =
+ if @run_status
+ [@run_status.run_id,
+ @run_status.elapsed_time.to_s]
+ else
+ ["UNKNOWN", "UNKNOWN"]
+ end
+
@eventlog.report_event(
:event_type => ::Win32::EventLog::ERROR_TYPE,
:source => SOURCE,
:event_id => RUN_FAILED_EVENT_ID,
- :data => [@run_status.run_id,
- @run_status.elapsed_time.to_s,
- e.class.name,
- e.message,
- e.backtrace.join("\n")]
+ :data => data + [e.class.name,
+ e.message,
+ e.backtrace.join("\n")]
)
end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index eea6a2f239..cfedbfd0d9 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -69,6 +69,9 @@ class Chef
class ValidationFailed < ArgumentError; end
class InvalidPrivateKey < ArgumentError; end
class ConfigurationError < ArgumentError; end
+ class MissingKeyAttribute < ArgumentError; end
+ class InvalidKeyArgument < ArgumentError; end
+ class InvalidKeyAttribute < ArgumentError; end
class RedirectLimitExceeded < RuntimeError; end
class AmbiguousRunlistSpecification < ArgumentError; end
class CookbookFrozen < ArgumentError; end
diff --git a/lib/chef/key.rb b/lib/chef/key.rb
new file mode 100644
index 0000000000..1828713386
--- /dev/null
+++ b/lib/chef/key.rb
@@ -0,0 +1,251 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/json_compat'
+require 'chef/mixin/params_validate'
+require 'chef/exceptions'
+
+class Chef
+ # Class for interacting with a chef key object. Can be used to create new keys,
+ # save to server, load keys from server, list keys, delete keys, etc.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr [String] actor the name of the client or user that this key is for
+ # @attr [String] name the name of the key
+ # @attr [String] public_key the RSA string of this key
+ # @attr [String] private_key the RSA string of the private key if returned via a POST or PUT
+ # @attr [String] expiration_date the ISO formatted string YYYY-MM-DDTHH:MM:SSZ, i.e. 2020-12-24T21:00:00Z
+ # @attr [String] rest Chef::REST object, initialized and cached via chef_rest method
+ # @attr [string] api_base either "users" or "clients", initialized and cached via api_base method
+ #
+ # @attr_reader [String] actor_field_name must be either 'client' or 'user'
+ class Key
+
+ include Chef::Mixin::ParamsValidate
+
+ attr_reader :actor_field_name
+
+ def initialize(actor, actor_field_name)
+ # Actor that the key is for, either a client or a user.
+ @actor = actor
+
+ unless actor_field_name == "user" || actor_field_name == "client"
+ raise Chef::Exceptions::InvalidKeyArgument, "the second argument to initialize must be either 'user' or 'client'"
+ end
+
+ @actor_field_name = actor_field_name
+
+ @name = nil
+ @public_key = nil
+ @private_key = nil
+ @expiration_date = nil
+ @create_key = nil
+ end
+
+ def chef_rest
+ @rest ||= if @actor_field_name == "user"
+ Chef::REST.new(Chef::Config[:chef_server_root])
+ else
+ Chef::REST.new(Chef::Config[:chef_server_url])
+ end
+ end
+
+ def api_base
+ @api_base ||= if @actor_field_name == "user"
+ "users"
+ else
+ "clients"
+ end
+ end
+
+ def actor(arg=nil)
+ set_or_return(:actor, arg,
+ :regex => /^[a-z0-9\-_]+$/)
+ end
+
+ def name(arg=nil)
+ set_or_return(:name, arg,
+ :kind_of => String)
+ end
+
+ def public_key(arg=nil)
+ raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set the public_key if create_key is true" if !arg.nil? && @create_key
+ set_or_return(:public_key, arg,
+ :kind_of => String)
+ end
+
+ def private_key(arg=nil)
+ set_or_return(:private_key, arg,
+ :kind_of => String)
+ end
+
+ def delete_public_key
+ @public_key = nil
+ end
+
+ def create_key(arg=nil)
+ raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set create_key to true if the public_key field exists" if arg == true && !@public_key.nil?
+ set_or_return(:create_key, arg,
+ :kind_of => [TrueClass, FalseClass])
+ end
+
+ def expiration_date(arg=nil)
+ set_or_return(:expiration_date, arg,
+ :regex => /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|infinity)$/)
+ end
+
+ def to_hash
+ result = {
+ @actor_field_name => @actor
+ }
+ result["name"] = @name if @name
+ result["public_key"] = @public_key if @public_key
+ result["private_key"] = @private_key if @private_key
+ result["expiration_date"] = @expiration_date if @expiration_date
+ result["create_key"] = @create_key if @create_key
+ result
+ end
+
+ def to_json(*a)
+ Chef::JSONCompat.to_json(to_hash, *a)
+ end
+
+ def create
+ # if public_key is undefined and create_key is false, we cannot create
+ if @public_key.nil? && !@create_key
+ raise Chef::Exceptions::MissingKeyAttribute, "either public_key must be defined or create_key must be true"
+ end
+
+ # defaults the key name to the fingerprint of the key
+ if @name.nil?
+ # if they didn't pass a public_key,
+ #then they must supply a name because we can't generate a fingerprint
+ unless @public_key.nil?
+ @name = fingerprint
+ else
+ raise Chef::Exceptions::MissingKeyAttribute, "a name cannot be auto-generated if no public key passed, either pass a public key or supply a name"
+ end
+ end
+
+ payload = {"name" => @name}
+ payload['public_key'] = @public_key unless @public_key.nil?
+ payload['create_key'] = @create_key if @create_key
+ payload['expiration_date'] = @expiration_date unless @expiration_date.nil?
+ new_key = chef_rest.post_rest("#{api_base}/#{@actor}/keys", payload)
+ Chef::Key.from_hash(new_key)
+ end
+
+ def fingerprint
+ self.class.generate_fingerprint(@public_key)
+ end
+
+ def update
+ if @name.nil?
+ raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated when update is called"
+ end
+
+ new_key = chef_rest.put_rest("#{api_base}/#{@actor}/keys/#{@name}", to_hash)
+ Chef::Key.from_hash(self.to_hash.merge(new_key))
+ end
+
+ def save
+ create
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ update
+ else
+ raise e
+ end
+ end
+
+ def destroy
+ if @name.nil?
+ raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated when delete is called"
+ end
+
+ chef_rest.delete_rest("#{api_base}/#{@actor}/keys/#{@name}")
+ end
+
+ # Class methods
+ def self.from_hash(key_hash)
+ if key_hash.has_key?("user")
+ key = Chef::Key.new(key_hash["user"], "user")
+ else
+ key = Chef::Key.new(key_hash["client"], "client")
+ end
+ key.name key_hash['name'] if key_hash.key?('name')
+ key.public_key key_hash['public_key'] if key_hash.key?('public_key')
+ key.private_key key_hash['private_key'] if key_hash.key?('private_key')
+ key.create_key key_hash['create_key'] if key_hash.key?('create_key')
+ key.expiration_date key_hash['expiration_date'] if key_hash.key?('expiration_date')
+ key
+ end
+
+ def self.from_json(json)
+ Chef::Key.from_hash(Chef::JSONCompat.from_json(json))
+ end
+
+ class << self
+ alias_method :json_create, :from_json
+ end
+
+ def self.list_by_user(actor, inflate=false)
+ keys = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys")
+ self.list(keys, actor, :load_by_user, inflate)
+ end
+
+ def self.list_by_client(actor, inflate=false)
+ keys = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys")
+ self.list(keys, actor, :load_by_client, inflate)
+ end
+
+ def self.load_by_user(actor, key_name)
+ response = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys/#{key_name}")
+ Chef::Key.from_hash(response)
+ end
+
+ def self.load_by_client(actor, key_name)
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys/#{key_name}")
+ Chef::Key.from_hash(response)
+ end
+
+ def self.generate_fingerprint(public_key)
+ openssl_key_object = OpenSSL::PKey::RSA.new(public_key)
+ data_string = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.n),
+ OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.e)
+ ])
+ OpenSSL::Digest::SHA1.hexdigest(data_string.to_der).scan(/../).join(':')
+ end
+
+ private
+
+ def self.list(keys, actor, load_method_symbol, inflate)
+ if inflate
+ keys.inject({}) do |key_map, result|
+ name = result["name"]
+ key_map[name] = Chef::Key.send(load_method_symbol, actor, name)
+ key_map
+ end
+ else
+ keys
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb
index a87ab8e544..335b1f181c 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/lib/chef/knife/bootstrap/templates/chef-full.erb
@@ -1,15 +1,16 @@
-bash -c '
+sh -c '
<%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-distro=`uname -s`
-
-if test "x$distro" = "xSunOS"; then
- if test -d "/usr/sfw/bin"; then
- PATH=/usr/sfw/bin:$PATH
- export PATH
- fi
+if test "x$TMPDIR" = "x"; then
+ tmp="/tmp"
+else
+ tmp=$TMPDIR
fi
+# secure-ish temp dir creation without having mktemp available (DDoS-able but not exploitable)
+tmp_dir="$tmp/install.sh.$$"
+(umask 077 && mkdir $tmp_dir) || exit 1
+
exists() {
if command -v $1 &>/dev/null
then
@@ -19,41 +20,183 @@ exists() {
fi
}
+http_404_error() {
+ echo "ERROR 404: Could not retrieve a valid install.sh!"
+ exit 1
+}
+
+capture_tmp_stderr() {
+ # spool up /tmp/stderr from all the commands we called
+ if test -f "$tmp_dir/stderr"; then
+ output=`cat $tmp_dir/stderr`
+ stderr_results="${stderr_results}\nSTDERR from $1:\n\n$output\n"
+ rm $tmp_dir/stderr
+ fi
+}
+
+# do_wget URL FILENAME
+do_wget() {
+ echo "trying wget..."
+ wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> -O "$2" "$1" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "ERROR 404" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "wget"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_curl URL FILENAME
+do_curl() {
+ echo "trying curl..."
+ curl -sL <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> -D $tmp_dir/stderr -o "$2" "$1" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "curl"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_fetch URL FILENAME
+do_fetch() {
+ echo "trying fetch..."
+ fetch -o "$2" "$1" 2>$tmp_dir/stderr
+ # check for bad return status
+ test $? -ne 0 && return 1
+ return 0
+}
+
+# do_perl URL FILENAME
+do_perl() {
+ echo "trying perl..."
+ perl -e "use LWP::Simple; getprint(shift @ARGV);" "$1" > "$2" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "perl"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_python URL FILENAME
+do_python() {
+ echo "trying python..."
+ python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "HTTP Error 404" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "python"
+ return 1
+ fi
+ return 0
+}
+
+# do_download URL FILENAME
+do_download() {
+ PATH=/opt/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sfw/bin:/sbin:/bin:/usr/sbin:/usr/bin
+ export PATH
+
+ echo "downloading $1"
+ echo " to file $2"
+
+ # we try all of these until we get success.
+ # perl, in particular may be present but LWP::Simple may not be installed
+
+ if exists wget; then
+ do_wget $1 $2 && return 0
+ fi
+
+ if exists curl; then
+ do_curl $1 $2 && return 0
+ fi
+
+ if exists fetch; then
+ do_fetch $1 $2 && return 0
+ fi
+
+ if exists perl; then
+ do_perl $1 $2 && return 0
+ fi
+
+ if exists python; then
+ do_python $1 $2 && return 0
+ fi
+
+ echo ">>>>>> wget, curl, fetch, perl, or python not found on this instance."
+
+ if test "x$stderr_results" != "x"; then
+ echo "\nDEBUG OUTPUT FOLLOWS:\n$stderr_results"
+ fi
+
+ return 16
+}
+
<% if knife_config[:bootstrap_install_command] %>
<%= 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
- echo "Installing Chef Client..."
- if exists wget; then
- bash <(wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> ${install_sh} -O -) <%= latest_current_chef_version_string %>
- elif exists curl; then
- bash <(curl -L <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> ${install_sh}) <%= latest_current_chef_version_string %>
- else
- echo "Neither wget nor curl found. Please install one and try again." >&2
- exit 1
- fi
+ 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 %>
+if test "x$tmp_dir" != "x"; then
+ rm -r "$tmp_dir"
+fi
+
mkdir -p /etc/chef
<% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
+cat > /etc/chef/client.pem <<EOP
<%= ::File.read(::File.expand_path(client_pem)) %>
EOP
chmod 0600 /etc/chef/client.pem
<% end -%>
<% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
+cat > /etc/chef/validation.pem <<EOP
<%= validation_key %>
EOP
chmod 0600 /etc/chef/validation.pem
<% end -%>
<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
+cat > /etc/chef/encrypted_data_bag_secret <<EOP
<%= encrypted_data_bag_secret %>
EOP
chmod 0600 /etc/chef/encrypted_data_bag_secret
@@ -69,17 +212,17 @@ mkdir -p /etc/chef/trusted_certs
mkdir -p /etc/chef/ohai/hints
<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+cat > /etc/chef/ohai/hints/<%= name %>.json <<EOP
<%= Chef::JSONCompat.to_json(hash) %>
EOP
<% end -%>
<% end -%>
-cat > /etc/chef/client.rb <<'EOP'
+cat > /etc/chef/client.rb <<EOP
<%= config_content %>
EOP
-cat > /etc/chef/first-boot.json <<'EOP'
+cat > /etc/chef/first-boot.json <<EOP
<%= Chef::JSONCompat.to_json(first_boot) %>
EOP
diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb
new file mode 100644
index 0000000000..e5bb2c2005
--- /dev/null
+++ b/lib/chef/mixin/provides.rb
@@ -0,0 +1,32 @@
+
+require 'chef/mixin/descendants_tracker'
+
+class Chef
+ module Mixin
+ module Provides
+ include Chef::Mixin::DescendantsTracker
+
+ def node_map
+ @node_map ||= Chef::NodeMap.new
+ end
+
+ def provides(short_name, opts={}, &block)
+ if !short_name.kind_of?(Symbol)
+ # YAGNI: this is probably completely unnecessary and can be removed?
+ Chef::Log.deprecation "Passing a non-Symbol to Chef::Resource#provides will be removed"
+ if short_name.kind_of?(String)
+ short_name.downcase!
+ short_name.gsub!(/\s/, "_")
+ end
+ short_name = short_name.to_sym
+ end
+ node_map.set(short_name, true, opts, &block)
+ end
+
+ # provides a node on the resource (early binding)
+ def provides?(node, resource_name)
+ node_map.get(node, resource_name)
+ end
+ end
+ end
+end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 2517f46dfd..1539f61900 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,6 +1,4 @@
-require 'chef/providers'
-
class Chef
class Platform
class ProviderPriorityMap
@@ -10,7 +8,22 @@ class Chef
load_default_map
end
+ def get_priority_array(node, resource_name)
+ priority_map.get(node, resource_name.to_sym)
+ end
+
+ def set_priority_array(resource_name, priority_array, *filter)
+ priority(resource_name.to_sym, priority_array.to_a, *filter)
+ end
+
+ def priority(*args)
+ priority_map.set(*args)
+ end
+
+ private
+
def load_default_map
+ require 'chef/providers'
#
# Linux
@@ -71,13 +84,9 @@ class Chef
end
def priority_map
+ require 'chef/node_map'
@priority_map ||= Chef::NodeMap.new
end
-
- def priority(*args)
- priority_map.set(*args)
- end
-
end
end
end
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
new file mode 100644
index 0000000000..fc43b3e7db
--- /dev/null
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -0,0 +1,37 @@
+class Chef
+ class Platform
+ class ResourcePriorityMap
+ include Singleton
+
+ def initialize
+ load_default_map
+ end
+
+ def get_priority_array(node, resource_name)
+ priority_map.get(node, resource_name.to_sym)
+ end
+
+ def set_priority_array(resource_name, priority_array, *filter)
+ priority resource_name.to_sym, priority_array.to_a, *filter
+ end
+
+ def priority(*args)
+ priority_map.set(*args)
+ end
+
+ private
+
+ def load_default_map
+ require 'chef/resources'
+
+ # MacOSX
+ priority :package, Chef::Resource::HomebrewPackage, os: "darwin"
+ end
+
+ def priority_map
+ require 'chef/node_map'
+ @priority_map ||= Chef::NodeMap.new
+ end
+ end
+ end
+end
diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb
index 2ee8a23258..524bdd95b1 100644
--- a/lib/chef/policy_builder/expand_node_object.rb
+++ b/lib/chef/policy_builder/expand_node_object.rb
@@ -24,6 +24,7 @@ require 'chef/rest'
require 'chef/run_context'
require 'chef/config'
require 'chef/node'
+require 'chef/chef_class'
class Chef
module PolicyBuilder
@@ -54,6 +55,15 @@ class Chef
@run_list_expansion = nil
end
+ # This method injects the run_context and provider and resource priority
+ # maps into the Chef class. The run_context has to be injected here, the provider and
+ # resource maps could be moved if a better place can be found to do this work.
+ #
+ # @param run_context [Chef::RunContext] the run_context to inject
+ def setup_chef_class(run_context)
+ Chef.set_run_context(run_context)
+ end
+
def setup_run_context(specific_recipes=nil)
if Chef::Config[:solo]
Chef::Cookbook::FileVendor.fetch_from_disk(Chef::Config[:cookbook_path])
@@ -68,6 +78,10 @@ class Chef
run_context = Chef::RunContext.new(node, cookbook_collection, @events)
end
+ # TODO: this is really obviously not the place for this
+ # FIXME: need same edits
+ setup_chef_class(run_context)
+
# TODO: this is not the place for this. It should be in Runner or
# CookbookCompiler or something.
run_context.load(@run_list_expansion)
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index 0c7c07f9f3..ac25b549be 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -385,4 +385,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 680fe9782f..65a56cf726 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -22,7 +22,7 @@ require 'chef/mixin/convert_to_class_name'
require 'chef/mixin/enforce_ownership_and_permissions'
require 'chef/mixin/why_run'
require 'chef/mixin/shell_out'
-require 'chef/mixin/descendants_tracker'
+require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
@@ -30,26 +30,11 @@ class Chef
class Provider
include Chef::Mixin::WhyRun
include Chef::Mixin::ShellOut
- extend Chef::Mixin::DescendantsTracker
+ extend Chef::Mixin::Provides
- class << self
- def node_map
- @node_map ||= Chef::NodeMap.new
- end
-
- def provides(resource_name, opts={}, &block)
- node_map.set(resource_name.to_sym, true, opts, &block)
- end
-
- # provides a node on the resource (early binding)
- def provides?(node, resource)
- node_map.get(node, resource.resource_name)
- end
-
- # supports the given resource and action (late binding)
- def supports?(resource, action)
- true
- end
+ # supports the given resource and action (late binding)
+ def self.supports?(resource, action)
+ true
end
attr_accessor :new_resource
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
index d83a3e0468..867c3deca8 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -47,7 +47,7 @@ class Chef
def enabled_handlers
@enabled_handlers ||=
providers.select do |klass|
- klass.provides?(node, resource)
+ klass.provides?(node, resource.resource_name)
end.sort {|a,b| a.to_s <=> b.to_s }
end
@@ -84,8 +84,13 @@ class Chef
if handlers.count >= 2
# this magic stack ranks the providers by where they appear in the provider_priority_map, it is mostly used
# to pick amongst N different ways to start init scripts on different debian/ubuntu systems.
- priority_list = [ get_provider_priority_map(resource.resource_name, node) ].flatten.compact
+ priority_list = [ get_priority_array(node, resource.resource_name) ].flatten.compact
handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
+ if priority_list.index(handlers.first).nil?
+ # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map
+ # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity.
+ Chef::Log.warn "Ambiguous provider precedence: #{handlers}, please use Chef.set_provider_priority_array to provide determinism"
+ end
handlers = [ handlers.first ]
end
@@ -106,12 +111,12 @@ class Chef
end
# dep injection hooks
- def get_provider_priority_map(resource_name, node)
- provider_priority_map.get(node, resource_name)
+ def get_priority_array(node, resource_name)
+ provider_priority_map.get_priority_array(node, resource_name)
end
def provider_priority_map
- Chef::Platform::ProviderPriorityMap.instance.priority_map
+ Chef::Platform::ProviderPriorityMap.instance
end
end
end
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index ea220b6c70..d934ec8c47 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -33,7 +33,7 @@ require 'chef/platform'
require 'chef/resource/resource_notification'
require 'chef/mixin/deprecation'
-require 'chef/mixin/descendants_tracker'
+require 'chef/mixin/provides'
class Chef
class Resource
@@ -46,6 +46,7 @@ class Chef
include Chef::DSL::PlatformIntrospection
include Chef::DSL::RegistryHelper
include Chef::DSL::RebootPending
+ extend Chef::Mixin::Provides
#
# The node the current Chef run is using.
@@ -879,7 +880,6 @@ class Chef
include Chef::Mixin::ConvertToClassName
extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::DescendantsTracker
# XXX: this is required for definition params inside of the scope of a
# subresource to work correctly.
@@ -1016,6 +1016,7 @@ class Chef
end
def provider_for_action(action)
+ require 'chef/provider_resolver'
provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
provider.action = action
provider
@@ -1080,33 +1081,6 @@ class Chef
end
end
- # Maps a short_name (and optionally a platform and version) to a
- # Chef::Resource. This allows finer grained per platform resource
- # attributes and the end of overloaded resource definitions
- # (I'm looking at you Chef::Resource::Package)
- # Ex:
- # class WindowsFile < Chef::Resource
- # provides :file, os: "linux", platform_family: "rhel", platform: "redhat"
- # provides :file, os: "!windows
- # provides :file, os: [ "linux", "aix" ]
- # provides :file, os: "solaris2" do |node|
- # node['platform_version'].to_f <= 5.11
- # end
- # # ...other stuff
- # end
- #
- def self.provides(short_name, opts={}, &block)
- short_name_sym = short_name
- if short_name.kind_of?(String)
- # YAGNI: this is probably completely unnecessary and can be removed?
- Chef::Log.warn "[DEPRECATION] Passing a String to Chef::Resource#provides will be removed"
- short_name.downcase!
- short_name.gsub!(/\s/, "_")
- short_name_sym = short_name.to_sym
- end
- node_map.set(short_name_sym, constantize(self.name), opts, &block)
- end
-
# Returns a resource based on a short_name and node
#
# ==== Parameters
@@ -1116,16 +1090,12 @@ class Chef
# === Returns
# <Chef::Resource>:: returns the proper Chef::Resource class
def self.resource_for_node(short_name, node)
- klass = node_map.get(node, short_name) ||
- resource_matching_short_name(short_name)
+ require 'chef/resource_resolver'
+ klass = Chef::ResourceResolver.new(node, short_name).resolve
raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil?
klass
end
- def self.node_map
- @@node_map ||= NodeMap.new
- end
-
# Returns the class of a Chef::Resource based on the short name
# ==== Parameters
# short_name<Symbol>:: short_name of the resource (ie :directory)
@@ -1156,7 +1126,3 @@ class Chef
end
end
end
-
-# We require this at the BOTTOM of this file to avoid circular requires (it is used
-# at runtime but not load time)
-require 'chef/provider_resolver'
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
new file mode 100644
index 0000000000..ff9d7aeb9f
--- /dev/null
+++ b/lib/chef/resource_resolver.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/exceptions'
+require 'chef/platform/resource_priority_map'
+
+class Chef
+ class ResourceResolver
+
+ attr_reader :node
+ attr_reader :resource
+ attr_reader :action
+
+ def initialize(node, resource)
+ @node = node
+ @resource = resource
+ end
+
+ # return a deterministically sorted list of Chef::Resource subclasses
+ def resources
+ @resources ||= Chef::Resource.descendants
+ end
+
+ def resolve
+ maybe_dynamic_resource_resolution(resource) ||
+ maybe_chef_platform_lookup(resource)
+ end
+
+ # this cut looks at if the resource can handle the resource type on the node
+ def enabled_handlers
+ @enabled_handlers ||=
+ resources.select do |klass|
+ klass.provides?(node, resource)
+ end.sort {|a,b| a.to_s <=> b.to_s }
+ @enabled_handlers
+ end
+
+ private
+
+ # try dynamically finding a resource based on querying the resources to see what they support
+ def maybe_dynamic_resource_resolution(resource)
+ # 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} resource enabled on node include: #{enabled_handlers}"
+
+ # if none of the resources specifically support the resource, we still need to pick one of the resources that are
+ # enabled on the node to handle the why-run use case.
+ handlers = enabled_handlers
+
+ if handlers.count >= 2
+ # this magic stack ranks the resources by where they appear in the resource_priority_map
+ priority_list = [ get_priority_array(node, resource) ].flatten.compact
+ handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
+ if priority_list.index(handlers.first).nil?
+ # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map
+ # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity.
+ Chef::Log.warn "Ambiguous resource precedence: #{handlers}, please use Chef.set_resource_priority_array to provide determinism"
+ end
+ handlers = [ handlers.first ]
+ end
+
+ Chef::Log.debug "resources that survived replacement include: #{handlers}"
+
+ raise Chef::Exceptions::AmbiguousResourceResolution.new(resource, handlers) if handlers.count >= 2
+
+ Chef::Log.debug "dynamic resource resolver FAILED to resolve a resouce" if handlers.empty?
+
+ return nil if handlers.empty?
+
+ handlers[0]
+ end
+
+ # try the old static lookup of resources by mangling name to resource klass
+ def maybe_chef_platform_lookup(resource)
+ Chef::Resource.resource_matching_short_name(resource)
+ end
+
+ # dep injection hooks
+ def get_priority_array(node, resource_name)
+ resource_priority_map.get_priority_array(node, resource_name)
+ end
+
+ def resource_priority_map
+ Chef::Platform::ResourcePriorityMap.instance
+ end
+ end
+end
diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb
index 8656e810db..6469a18c49 100644
--- a/lib/chef/search/query.rb
+++ b/lib/chef/search/query.rb
@@ -89,7 +89,7 @@ WARNDEP
if block
response["rows"].each { |row| block.call(row) if row }
unless (response["start"] + response["rows"].length) >= response["total"]
- args_h[:start] = response["start"] + (args_h[:rows] || 0)
+ args_h[:start] = response["start"] + response["rows"].length
search(type, query, args_h, &block)
end
true