diff options
Diffstat (limited to 'lib/chef')
26 files changed, 443 insertions, 132 deletions
diff --git a/lib/chef/api_client/registration.rb b/lib/chef/api_client/registration.rb index 213d0b7f49..8a5885eff3 100644 --- a/lib/chef/api_client/registration.rb +++ b/lib/chef/api_client/registration.rb @@ -153,7 +153,9 @@ class Chef def file_flags base_flags = File::CREAT|File::TRUNC|File::RDWR # Windows doesn't have symlinks, so it doesn't have NOFOLLOW - base_flags |= File::NOFOLLOW if defined?(File::NOFOLLOW) + if defined?(File::NOFOLLOW) && !Chef::Config[:follow_client_key_symlink] + base_flags |= File::NOFOLLOW + end base_flags end end diff --git a/lib/chef/config.rb b/lib/chef/config.rb index dc04026acb..d3871c38e8 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -396,6 +396,12 @@ class Chef # If chef-zero is enabled, this defaults to nil (no authentication). default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") } + # When registering the client, should we allow the client key location to + # be a symlink? eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem + # If the path of the key goes through a directory like /tmp this should + # never be set to true or its possibly an easily exploitable security hole. + default :follow_client_key_symlink, false + # This secret is used to decrypt encrypted data bag items. default(:encrypted_data_bag_secret) do if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret")) @@ -491,7 +497,7 @@ class Chef default :ssh_gateway, nil default :bootstrap_version, nil default :bootstrap_proxy, nil - default :bootstrap_template, "chef-full" + default :bootstrap_template, nil default :secret, nil default :secret_file, nil default :identity_file, nil diff --git a/lib/chef/event_loggers/windows_eventlog.rb b/lib/chef/event_loggers/windows_eventlog.rb index b8d279594b..6d45d4fab4 100644 --- a/lib/chef/event_loggers/windows_eventlog.rb +++ b/lib/chef/event_loggers/windows_eventlog.rb @@ -26,7 +26,6 @@ if Chef::Platform::windows? and not Chef::Platform::windows_server_2003? end require 'win32/eventlog' - include Win32 end class Chef @@ -51,12 +50,12 @@ class Chef end def initialize - @eventlog = EventLog::open('Application') + @eventlog = ::Win32::EventLog::open('Application') end def run_start(version) @eventlog.report_event( - :event_type => EventLog::INFO_TYPE, + :event_type => ::Win32::EventLog::INFO_TYPE, :source => SOURCE, :event_id => RUN_START_EVENT_ID, :data => [version] @@ -66,7 +65,7 @@ class Chef def run_started(run_status) @run_status = run_status @eventlog.report_event( - :event_type => EventLog::INFO_TYPE, + :event_type => ::Win32::EventLog::INFO_TYPE, :source => SOURCE, :event_id => RUN_STARTED_EVENT_ID, :data => [run_status.run_id] @@ -75,7 +74,7 @@ class Chef def run_completed(node) @eventlog.report_event( - :event_type => EventLog::INFO_TYPE, + :event_type => ::Win32::EventLog::INFO_TYPE, :source => SOURCE, :event_id => RUN_COMPLETED_EVENT_ID, :data => [@run_status.run_id, @run_status.elapsed_time.to_s] @@ -88,7 +87,7 @@ class Chef #Exception backtrace: %5 def run_failed(e) @eventlog.report_event( - :event_type => EventLog::ERROR_TYPE, + :event_type => ::Win32::EventLog::ERROR_TYPE, :source => SOURCE, :event_id => RUN_FAILED_EVENT_ID, :data => [@run_status.run_id, diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index f159c9105b..19a329199d 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -194,13 +194,15 @@ class Chef :description => "Verify the SSL cert for HTTPS requests to the Chef server API.", :boolean => true + def default_bootstrap_template + "chef-full" + end + def bootstrap_template - # For some reason knife.merge_configs doesn't pick up the default values from - # Chef::Config[:knife][:bootstrap_template] unless Chef::Config[:knife][:bootstrap_template] - # is forced to pick up the values before calling merge_configs. - # We therefore have Chef::Config[:knife][:bootstrap_template] to pick up the defaults - # if no option is specified. - config[:bootstrap_template] || config[:distro] || config[:template_file] || Chef::Config[:knife][:bootstrap_template] + # The order here is important. We want to check if we have the new Chef 12 option is set first. + # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at + # the end. + config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template end def find_template @@ -210,7 +212,6 @@ class Chef if File.exists?(template) Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}") return template - end # Otherwise search the template directories until we find the right one diff --git a/lib/chef/mixin/deep_merge.rb b/lib/chef/mixin/deep_merge.rb index 5e3327a526..a8a4737758 100644 --- a/lib/chef/mixin/deep_merge.rb +++ b/lib/chef/mixin/deep_merge.rb @@ -29,6 +29,7 @@ class Chef class InvalidSubtractiveMerge < ArgumentError; end + OLD_KNOCKOUT_PREFIX = "!merge:".freeze # Regex to match the "knockout prefix" that was used to indicate @@ -85,12 +86,8 @@ class Chef when Hash if dest.kind_of?(Hash) source.each do |src_key, src_value| - if dest.has_key? src_key - if dest[src_key].nil? - dest[src_key] = nil - else - dest[src_key] = deep_merge!(src_value, dest[src_key]) - end + if dest[src_key] + dest[src_key] = deep_merge!(src_value, dest[src_key]) else # dest[src_key] doesn't exist so we take whatever source has raise_if_knockout_used!(src_value) dest[src_key] = src_value diff --git a/lib/chef/mixin/which.rb b/lib/chef/mixin/which.rb new file mode 100644 index 0000000000..4179c97b62 --- /dev/null +++ b/lib/chef/mixin/which.rb @@ -0,0 +1,37 @@ +#-- +# Author:: Lamont Granquist <lamont@getchef.io> +# Copyright:: Copyright (c) 2010 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 + module Mixin + module Which + def which(cmd, opts = {}) + extra_path = + if opts[:extra_path].nil? + [ '/bin', '/usr/bin', '/sbin', '/usr/sbin' ] + else + [ opts[:extra_path] ].flatten + end + paths = ENV['PATH'].split(File::PATH_SEPARATOR) + extra_path + paths.each do |path| + filename = File.join(path, cmd) + return filename if File.executable?(filename) + end + false + end + end + end +end diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 5f788af4d4..dbb7852586 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -42,6 +42,8 @@ class Chef extend Forwardable def_delegators :attributes, :keys, :each_key, :each_value, :key?, :has_key? + def_delegators :attributes, :rm, :rm_default, :rm_normal, :rm_override + def_delegators :attributes, :default!, :normal!, :override!, :force_default!, :force_override! attr_accessor :recipe_list, :run_state, :override_runlist @@ -146,13 +148,6 @@ class Chef attributes.default end - # Set a force default attribute. Intermediate mashes will be created by - # auto-vivify if necessary. - def default! - attributes.set_unless_value_present = false - attributes.default! - end - # Set a default attribute of this node, auto-vivifying any mashes that are # missing, but if the final value already exists, don't set it def default_unless @@ -167,13 +162,6 @@ class Chef attributes.override end - # Set a force override attribute. Intermediate mashes will be created by - # auto-vivify if needed. - def override! - attributes.set_unless_value_present = false - attributes.override! - end - # Set an override attribute of this node, auto-vivifying any mashes that # are missing, but if the final value already exists, don't set it def override_unless diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb index 66569cf0e1..3eb6449046 100644 --- a/lib/chef/node/attribute.rb +++ b/lib/chef/node/attribute.rb @@ -58,7 +58,6 @@ class Chef :@force_default ] - OVERRIDE_COMPONENTS = [ :@override, :@role_override, @@ -146,7 +145,6 @@ class Chef METHOD_DEFN end - # return the cookbook level default attribute component attr_reader :default @@ -159,11 +157,6 @@ class Chef # return the force_default level attribute component attr_reader :force_default - # default! is the "advertised" method for force_default, but is - # implemented as an alias because instance variables can't (easily) have - # +!+ characters. - alias :default! :force_default - # return the "normal" level attribute component attr_reader :normal @@ -179,11 +172,6 @@ class Chef # return the force override level attribute component attr_reader :force_override - # +override!+ is the "advertised" method for +force_override+ but is - # implemented as an alias because instance variables can't easily have - # +!+ characters. - alias :override! :force_override - # return the automatic level attribute component attr_reader :automatic @@ -311,6 +299,100 @@ class Chef @automatic = VividMash.new(self, new_data) end + # + # Deleting attributes + # + + # clears attributes from all precedence levels + def rm(*args) + # just easier to compute our retval, rather than collect+merge sub-retvals + ret = args.inject(merged_attributes) do |attr, arg| + if attr.nil? || !attr.respond_to?(:[]) + nil + else + begin + attr[arg] + rescue TypeError + raise TypeError, "Wrong type in index of attribute (did you use a Hash index on an Array?)" + end + end + end + rm_default(*args) + rm_normal(*args) + rm_override(*args) + ret + end + + # does <level>['foo']['bar'].delete('baz') + def remove_from_precedence_level(level, *args, key) + multimash = level.element(*args) + multimash.nil? ? nil : multimash.delete(key) + end + + private :remove_from_precedence_level + + # clears attributes from all default precedence levels + # + # equivalent to: force_default!['foo']['bar'].delete('baz') + def rm_default(*args) + reset + remove_from_precedence_level(force_default!(autovivify: false), *args) + end + + # clears attributes from normal precedence + # + # equivalent to: normal!['foo']['bar'].delete('baz') + def rm_normal(*args) + reset + remove_from_precedence_level(normal!(autovivify: false), *args) + end + + # clears attributes from all override precedence levels + # + # equivalent to: force_override!['foo']['bar'].delete('baz') + def rm_override(*args) + reset + remove_from_precedence_level(force_override!(autovivify: false), *args) + end + + # + # Replacing attributes without merging + # + + # sets default attributes without merging + def default!(opts={}) + reset + MultiMash.new(self, @default, [], opts) + end + + # sets normal attributes without merging + def normal!(opts={}) + reset + MultiMash.new(self, @normal, [], opts) + end + + # sets override attributes without merging + def override!(opts={}) + reset + MultiMash.new(self, @override, [], opts) + end + + # clears from all default precedence levels and then sets force_default + def force_default!(opts={}) + reset + MultiMash.new(self, @force_default, [@default, @env_default, @role_default], opts) + end + + # clears from all override precedence levels and then sets force_override + def force_override!(opts={}) + reset + MultiMash.new(self, @force_override, [@override, @env_override, @role_override], opts) + end + + # + # Accessing merged attributes + # + def merged_attributes @merged_attributes ||= begin components = [merge_defaults, @normal, merge_overrides, @automatic] @@ -391,7 +473,6 @@ class Chef end end - end end diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb index f09b02b106..c8bc618762 100644 --- a/lib/chef/node/attribute_collections.rb +++ b/lib/chef/node/attribute_collections.rb @@ -209,5 +209,117 @@ class Chef end + # == MultiMash + # This is a Hash-like object that contains multiple VividMashes in it. Its + # purpose is so that the user can descend into the mash and delete a subtree + # from all of the Mash objects (used to delete all values in a subtree from + # default, force_default, role_default and env_default at the same time). The + # assignment operator strictly does assignment (does no merging) and works + # by deleting the subtree and then assigning to the last mash which passed in + # the initializer. + # + # A lot of the complexity of this class comes from the fact that at any key + # value some or all of the mashes may walk off their ends and become nil or + # true or something. The schema may change so that one precidence leve may + # be 'true' object and another may be a VividMash. It is also possible that + # one or many of them may transition from VividMashes to Hashes or Arrays. + # + # It also supports the case where you may be deleting a key using node.rm + # in which case if intermediate keys all walk off into nil then you don't want + # to be autovivifying keys as you go. On the other hand you may be using + # node.force_default! in which case you'll wind up with a []= operator at the + # end and you want autovivification, so we conditionally have to support either + # operation. + # + # @todo: can we have an autovivify class that decorates a class that doesn't + # autovivify or something so that the code is less awful? + # + class MultiMash + attr_reader :root + attr_reader :mashes + attr_reader :opts + attr_reader :primary_mash + + # Initialize with an array of mashes. For the delete return value to work + # properly the mashes must come from the same attribute level (i.e. all + # override or all default, but not a mix of both). + def initialize(root, primary_mash, mashes, opts={}) + @root = root + @primary_mash = primary_mash + @mashes = mashes + @opts = opts + @opts[:autovivify] = true if @opts[:autovivify].nil? + end + + def [](key) + # handle the secondary mashes + new_mashes = [] + mashes.each do |mash| + new_mash = safe_evalute_key(mash, key) + # secondary mashes never autovivify so once they fall into nil, we just stop tracking them + new_mashes.push(new_mash) unless new_mash.nil? + end + + new_primary_mash = safe_evalute_key(primary_mash, key) + + if new_primary_mash.nil? && @opts[:autovivify] + primary_mash[key] = VividMash.new(root) + new_primary_mash = primary_mash[key] + end + + MultiMash.new(root, new_primary_mash, new_mashes, opts) + end + + def []=(key, value) + if primary_mash.nil? + # This theoretically should never happen since node#force_default! setter methods will autovivify and + # node#rm methods do not end in #[]= operators. + raise TypeError, "No autovivification was specified initially on a method chain ending in assignment" + end + ret = delete(key) + primary_mash[key] = value + ret + end + + # mash.element('foo', 'bar') is the same as mash['foo']['bar'] + def element(key = nil, *subkeys) + return self if key.nil? + submash = self[key] + subkeys.empty? ? submash : submash.element(*subkeys) + end + + def delete(key) + # the return value is a deep merge which is correct semantics when + # merging between attributes on the same level (this would be incorrect + # if passed both override and default attributes which would need hash_only + # merging). + ret = mashes.inject(Mash.new) do |merged, mash| + Chef::Mixin::DeepMerge.merge(merged, mash) + end + ret = Chef::Mixin::DeepMerge.merge(ret, primary_mash) + mashes.each do |mash| + mash.delete(key) if mash.respond_to?(:delete) + end + primary_mash.delete(key) if primary_mash.respond_to?(:delete) + ret[key] + end + + private + + def safe_evalute_key(mash, key) + if mash.respond_to?(:[]) + if mash.respond_to?(:has_key?) + if mash.has_key?(key) + return mash[key] if mash[key].respond_to?(:[]) + end + elsif !mash[key].nil? + return mash[key] if mash[key].respond_to?(:[]) + end + end + return nil + end + + end + end end diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb index ccf6ef0bbe..765dd74859 100644 --- a/lib/chef/platform/provider_priority_map.rb +++ b/lib/chef/platform/provider_priority_map.rb @@ -34,9 +34,10 @@ class Chef ], platform_family: "gentoo" priority :service, [ - # on debian-ish system if an upstart script exists that always wins - Chef::Provider::Service::Upstart, + # we can determine what systemd supports accurately Chef::Provider::Service::Systemd, + # on debian-ish system if an upstart script exists that must win over sysv types + Chef::Provider::Service::Upstart, Chef::Provider::Service::Insserv, Chef::Provider::Service::Debian, Chef::Provider::Service::Invokercd, diff --git a/lib/chef/platform/service_helpers.rb b/lib/chef/platform/service_helpers.rb index 440391843e..dc0a808c06 100644 --- a/lib/chef/platform/service_helpers.rb +++ b/lib/chef/platform/service_helpers.rb @@ -18,6 +18,7 @@ # XXX: mixing shellout into a mixin into classes has to be code smell require 'chef/mixin/shell_out' +require 'chef/mixin/which' class Chef class Platform @@ -25,12 +26,21 @@ class Chef class << self include Chef::Mixin::ShellOut + include Chef::Mixin::Which # This helper is mostly used to sort out the mess of different # linux mechanisms that can be used to start services. It does # not necessarily need to linux-specific, but currently all our # other service providers are narrowly platform-specific with no # alternatives. + # + # NOTE: if a system has (for example) chkconfig installed then we + # should report that chkconfig is installed. The fact that a system + # may also have systemd installed does not mean that we do not + # report that systemd is also installed. This module is purely for + # discovery of all the alternatives, handling the priority of the + # different services is NOT a design concern of this module. + # def service_resource_providers service_resource_providers = [] @@ -55,8 +65,7 @@ class Chef service_resource_providers << :redhat end - if ::File.exist?("/bin/systemctl") - # FIXME: look for systemd as init provider + if systemd_sanity_check? service_resource_providers << :systemd end @@ -86,7 +95,7 @@ class Chef configs << :usr_local_etc_rcd end - if ::File.exist?("/bin/systemctl") && platform_has_systemd_unit?(service_name) + if systemd_sanity_check? && platform_has_systemd_unit?(service_name) configs << :systemd end @@ -95,17 +104,37 @@ class Chef private - def extract_systemd_services(output) + def systemctl_path + if @systemctl_path.nil? + @systemctl_path = which("systemctl") + end + @systemctl_path + end + + def systemd_sanity_check? + systemctl_path && File.exist?("/proc/1/comm") && File.open("/proc/1/comm").gets.chomp == "systemd" + end + + def extract_systemd_services(command) + output = shell_out!(command).stdout # first line finds e.g. "sshd.service" - services = output.lines.split.map { |l| l.split[0] } + services = [] + output.each_line do |line| + fields = line.split + services << fields[0] if fields[1] == "loaded" || fields[1] == "not-found" + end # this splits off the suffix after the last dot to return "sshd" - services += services.map { |s| s.sub(/(.*)\..*/, '\1') } + services += services.select {|s| s.match(/\.service$/) }.map { |s| s.sub(/(.*)\.service$/, '\1') } + rescue Mixlib::ShellOut::ShellCommandFailed + false end def platform_has_systemd_unit?(service_name) - services = extract_systemd_services(shell_out!("systemctl --all").stdout) + - extract_systemd_services(shell_out!("systemctl --list-unit-files").stdout) + services = extract_systemd_services("#{systemctl_path} --all") + + extract_systemd_services("#{systemctl_path} list-unit-files") services.include?(service_name) + rescue Mixlib::ShellOut::ShellCommandFailed + false end end end diff --git a/lib/chef/provider/service/aixinit.rb b/lib/chef/provider/service/aixinit.rb index ab4b8e5406..19beac79f0 100644 --- a/lib/chef/provider/service/aixinit.rb +++ b/lib/chef/provider/service/aixinit.rb @@ -114,4 +114,4 @@ class Chef end end end -end
\ No newline at end of file +end diff --git a/lib/chef/provider/service/arch.rb b/lib/chef/provider/service/arch.rb index 888fb3fdf5..e7fbcc820c 100644 --- a/lib/chef/provider/service/arch.rb +++ b/lib/chef/provider/service/arch.rb @@ -23,7 +23,7 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init provides :service, platform_family: "arch" def self.supports?(resource, action) - ::File.exist?("/etc/rc.d/#{resource.service_name}") + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:etc_rcd) end def initialize(new_resource, run_context) diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb index 25b1960b26..01505924cb 100644 --- a/lib/chef/provider/service/debian.rb +++ b/lib/chef/provider/service/debian.rb @@ -27,8 +27,12 @@ class Chef provides :service, platform_family: "debian" + def self.provides?(node, resource) + super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian) + end + def self.supports?(resource, action) - Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian) + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def load_current_resource diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb index ab40a720f6..0a219a69e1 100644 --- a/lib/chef/provider/service/init.rb +++ b/lib/chef/provider/service/init.rb @@ -28,6 +28,10 @@ class Chef provides :service, os: "!windows" + def self.supports?(resource, action) + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) + end + def initialize(new_resource, run_context) super @init_command = "/etc/init.d/#{@new_resource.service_name}" diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb index df5a162a45..31965a4bc6 100644 --- a/lib/chef/provider/service/insserv.rb +++ b/lib/chef/provider/service/insserv.rb @@ -26,8 +26,12 @@ class Chef provides :service, os: "linux" + def self.provides?(node, resource) + super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv) + end + def self.supports?(resource, action) - Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv) + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def load_current_resource diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb index c7472211bc..5ff24e0dbb 100644 --- a/lib/chef/provider/service/invokercd.rb +++ b/lib/chef/provider/service/invokercd.rb @@ -25,8 +25,12 @@ class Chef provides :service, platform_family: "debian" + def self.provides?(node, resource) + super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd) + end + def self.supports?(resource, action) - Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokerc) + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def initialize(new_resource, run_context) diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb index 90744ae268..850953125e 100644 --- a/lib/chef/provider/service/redhat.rb +++ b/lib/chef/provider/service/redhat.rb @@ -28,8 +28,12 @@ class Chef provides :service, platform_family: [ "rhel", "fedora", "suse" ] + def self.provides?(node, resource) + super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat) + end + def self.supports?(resource, action) - Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat) + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def initialize(new_resource, run_context) diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb index 311751ab9a..9085ffde2e 100644 --- a/lib/chef/provider/service/systemd.rb +++ b/lib/chef/provider/service/systemd.rb @@ -18,87 +18,95 @@ require 'chef/resource/service' require 'chef/provider/service/simple' +require 'chef/mixin/which' class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple + include Chef::Mixin::Which + provides :service, os: "linux" + attr_accessor :status_check_success + + def self.provides?(node, resource) + super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd) + end + def self.supports?(resource, action) - Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd) + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:systemd) end def load_current_resource - @current_resource = Chef::Resource::Service.new(@new_resource.name) - @current_resource.service_name(@new_resource.service_name) + @current_resource = Chef::Resource::Service.new(new_resource.name) + current_resource.service_name(new_resource.service_name) @status_check_success = true - if @new_resource.status_command - Chef::Log.debug("#{@new_resource} you have specified a status command, running..") + if new_resource.status_command + Chef::Log.debug("#{new_resource} you have specified a status command, running..") - unless shell_out(@new_resource.status_command).error? - @current_resource.running(true) + unless shell_out(new_resource.status_command).error? + current_resource.running(true) else @status_check_success = false - @current_resource.running(false) - @current_resource.enabled(false) - nil + current_resource.running(false) + current_resource.enabled(false) end else - @current_resource.running(is_active?) + current_resource.running(is_active?) end - @current_resource.enabled(is_enabled?) - @current_resource + current_resource.enabled(is_enabled?) + current_resource end def define_resource_requirements shared_resource_requirements requirements.assert(:all_actions) do |a| - a.assertion { @status_check_success } + a.assertion { status_check_success } # We won't stop in any case, but in whyrun warn and tell what we're doing. - a.whyrun ["Failed to determine status of #{@new_resource}, using command #{@new_resource.status_command}.", + a.whyrun ["Failed to determine status of #{new_resource}, using command #{new_resource.status_command}.", "Assuming service would have been installed and is disabled"] end end def start_service - if @current_resource.running - Chef::Log.debug("#{@new_resource} already running, not starting") + if current_resource.running + Chef::Log.debug("#{new_resource} already running, not starting") else - if @new_resource.start_command + if new_resource.start_command super else - shell_out_with_systems_locale!("/bin/systemctl start #{@new_resource.service_name}") + shell_out_with_systems_locale!("#{systemctl_path} start #{new_resource.service_name}") end end end def stop_service - unless @current_resource.running - Chef::Log.debug("#{@new_resource} not running, not stopping") + unless current_resource.running + Chef::Log.debug("#{new_resource} not running, not stopping") else - if @new_resource.stop_command + if new_resource.stop_command super else - shell_out_with_systems_locale!("/bin/systemctl stop #{@new_resource.service_name}") + shell_out_with_systems_locale!("#{systemctl_path} stop #{new_resource.service_name}") end end end def restart_service - if @new_resource.restart_command + if new_resource.restart_command super else - shell_out_with_systems_locale!("/bin/systemctl restart #{@new_resource.service_name}") + shell_out_with_systems_locale!("#{systemctl_path} restart #{new_resource.service_name}") end end def reload_service - if @new_resource.reload_command + if new_resource.reload_command super else - if @current_resource.running - shell_out_with_systems_locale!("/bin/systemctl reload #{@new_resource.service_name}") + if current_resource.running + shell_out_with_systems_locale!("#{systemctl_path} reload #{new_resource.service_name}") else start_service end @@ -106,18 +114,28 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple end def enable_service - shell_out!("/bin/systemctl enable #{@new_resource.service_name}") + shell_out!("#{systemctl_path} enable #{new_resource.service_name}") end def disable_service - shell_out!("/bin/systemctl disable #{@new_resource.service_name}") + shell_out!("#{systemctl_path} disable #{new_resource.service_name}") end def is_active? - shell_out("/bin/systemctl is-active #{@new_resource.service_name} --quiet").exitstatus == 0 + shell_out("#{systemctl_path} is-active #{new_resource.service_name} --quiet").exitstatus == 0 end def is_enabled? - shell_out("/bin/systemctl is-enabled #{@new_resource.service_name} --quiet").exitstatus == 0 + shell_out("#{systemctl_path} is-enabled #{new_resource.service_name} --quiet").exitstatus == 0 end + + private + + def systemctl_path + if @systemctl_path.nil? + @systemctl_path = which("systemctl") + end + @systemctl_path + end + end diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb index 41bd850d6a..3a3ddb2385 100644 --- a/lib/chef/provider/service/upstart.rb +++ b/lib/chef/provider/service/upstart.rb @@ -29,9 +29,12 @@ class Chef provides :service, os: "linux" + def self.provides?(node, resource) + super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart) + end + def self.supports?(resource, action) - Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart) && - Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart) + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart) end # Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in. diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb index c819b0c87f..247102f191 100644 --- a/lib/chef/provider_resolver.rb +++ b/lib/chef/provider_resolver.rb @@ -23,22 +23,42 @@ class Chef class ProviderResolver attr_reader :node + attr_reader :resource + attr_reader :action - def initialize(node) + def initialize(node, resource, action) @node = node + @resource = resource + @action = action end # return a deterministically sorted list of Chef::Provider subclasses def providers - Chef::Provider.descendants.sort {|a,b| a.to_s <=> b.to_s } + @providers ||= Chef::Provider.descendants.sort {|a,b| a.to_s <=> b.to_s } end - def resolve(resource, action) + def resolve maybe_explicit_provider(resource) || maybe_dynamic_provider_resolution(resource, action) || maybe_chef_platform_lookup(resource) 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| + klass.provides?(node, resource) + end + 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 + private # if resource.provider is set, just return one of those objects @@ -49,31 +69,23 @@ class Chef # try dynamically finding a provider based on querying the providers to see what they support def maybe_dynamic_provider_resolution(resource, action) - # this cut only depends on the node value and is going to be static for all nodes - # will contain all providers that could possibly support a resource on a node - enabled_handlers = providers.select do |klass| - klass.provides?(node, resource) - end - # log this so we know what providers will work for the generic resource on the node (early cut) Chef::Log.debug "providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}" - # ask all the enabled providers if they can actually support the resource - supported_handlers = enabled_handlers.select do |klass| - klass.supports?(resource, action) - end - # what providers were excluded by machine state (late cut) Chef::Log.debug "providers that refused resource #{resource} were: #{enabled_handlers - supported_handlers}" Chef::Log.debug "providers that support resource #{resource} include: #{supported_handlers}" + # 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. handlers = supported_handlers.empty? ? enabled_handlers : supported_handlers + Chef::Log.debug "no providers supported the resource, falling back to enabled handlers" if supported_handlers.empty? 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 - handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i } - handlers = [ handlers.first ] end @@ -81,6 +93,8 @@ class Chef raise Chef::Exceptions::AmbiguousProviderResolution.new(resource, handlers) if handlers.count >= 2 + Chef::Log.debug "dynamic provider resolver FAILED to resolve a provider" if handlers.empty? + return nil if handlers.empty? handlers[0] diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index c38f36aa72..8d964da66d 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -29,6 +29,7 @@ require 'chef/resource/conditional_action_not_nothing' require 'chef/resource_collection' require 'chef/node_map' require 'chef/node' +require 'chef/provider_resolver' require 'chef/platform' require 'chef/mixin/deprecation' @@ -679,7 +680,7 @@ F end def provider_for_action(action) - provider = run_context.provider_resolver.resolve(self, action).new(self, run_context) + provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context) provider.action = action provider end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 5b938095c6..696dbbdc55 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -78,3 +78,10 @@ require 'chef/resource/windows_package' require 'chef/resource/yum_package' require 'chef/resource/lwrp_base' require 'chef/resource/bff_package' + +begin + # Optional resources chef_node, chef_client, machine, machine_image, etc. + require 'cheffish' + require 'chef/provisioning' +rescue LoadError +end diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index 41fd11e6eb..8f7296822c 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -18,7 +18,6 @@ # limitations under the License. require 'chef/resource_collection' -require 'chef/provider_resolver' require 'chef/cookbook_version' require 'chef/node' require 'chef/role' @@ -54,9 +53,6 @@ class Chef # The list of control groups to execute during the audit phase attr_accessor :controls_groups - # Chef::ProviderResolver for this run - attr_accessor :provider_resolver - # A Hash containing the immediate notifications triggered by resources # during the converge phase of the chef run. attr_accessor :immediate_notification_collection @@ -91,7 +87,6 @@ class Chef @node.run_context = self @cookbook_compiler = nil - @provider_resolver = Chef::ProviderResolver.new(@node) end # Triggers the compile phase of the chef run. Implemented by diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb index 33c10e22a6..c5b8296c63 100644 --- a/lib/chef/shell.rb +++ b/lib/chef/shell.rb @@ -308,9 +308,17 @@ FOOTER elsif ENV['HOME'] && ::File.exist?(File.join(ENV['HOME'], '.chef', 'chef_shell.rb')) File.join(ENV['HOME'], '.chef', 'chef_shell.rb') elsif config[:solo] - "/etc/chef/solo.rb" + if Chef::Platform.windows? + "C:\\chef\\solo.rb" + else + "/etc/chef/solo.rb" + end elsif config[:client] - "/etc/chef/client.rb" + if Chef::Platform.windows? + "C:\\chef\\client.rb" + else + "/etc/chef/client.rb" + end else nil end diff --git a/lib/chef/util/selinux.rb b/lib/chef/util/selinux.rb index 92d5756552..778da042e3 100644 --- a/lib/chef/util/selinux.rb +++ b/lib/chef/util/selinux.rb @@ -21,6 +21,7 @@ # limitations under the License. require 'chef/mixin/shell_out' +require 'chef/mixin/which' class Chef class Util @@ -32,6 +33,7 @@ class Chef module Selinux include Chef::Mixin::ShellOut + include Chef::Mixin::Which # We want to initialize below variables once during a # chef-client run therefore they are class variables. @@ -67,15 +69,6 @@ class Chef @@selinuxenabled_path end - def which(cmd) - paths = ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/bin', '/usr/bin', '/sbin', '/usr/sbin' ] - paths.each do |path| - filename = File.join(path, cmd) - return filename if File.executable?(filename) - end - false - end - def check_selinux_enabled? if selinuxenabled_path cmd = shell_out!(selinuxenabled_path, :returns => [0,1]) @@ -97,4 +90,3 @@ class Chef end end end - |