# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: John Keiser ( "foobar") # find(:file => [ "foobar", "baz" ]) # find("file[foobar]", "file[baz]") # find("file[foobar,baz]") # # Calls `run_context.resource_collection.find(*args)` # # @return the matching resource, or an Array of matching resources. # # @raise ArgumentError if you feed it bad lookup information # @raise RuntimeError if it can't find the resources you are looking for. # def resources(*args) run_context.resource_collection.find(*args) end # # Resource User Interface (for users) # # # Create a new Resource. # # @param name The name of this resource (corresponds to the #name attribute, # used for notifications to this resource). # @param run_context The context of the Chef run. Corresponds to #run_context. # def initialize(name, run_context = nil) name(name) unless name.nil? @run_context = run_context @noop = nil @before = nil @params = Hash.new @provider = nil @allowed_actions = self.class.allowed_actions.to_a @action = self.class.default_action @updated = false @updated_by_last_action = false @supports = {} @ignore_failure = false @retries = 0 @retry_delay = 2 @not_if = [] @only_if = [] @source_line = nil # We would like to raise an error when the user gives us a guard # interpreter and a ruby_block to the guard. In order to achieve this # we need to understand when the user overrides the default guard # interpreter. Therefore we store the default separately in a different # attribute. @guard_interpreter = nil @default_guard_interpreter = :default @elapsed_time = 0 @sensitive = false end # # The action or actions that will be taken when this resource is run. # # @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`) # @return [Array[Symbol]] the list of actions. # def action(arg = nil) if arg arg = Array(arg).map(&:to_sym) arg.each do |action| validate( { action: action }, { action: { kind_of: Symbol, equal_to: allowed_actions } } ) end @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. # # If the action does not update this resource, the notification never triggers. # # Only one resource may be specified per notification. # # `delayed` notifications will only *ever* happen once per resource, so if # multiple resources all notify a single resource to perform the same action, # the action will only happen once. However, if they ask for different # actions, each action will happen once, in the order they were updated. # # `immediate` notifications will cause the action to be triggered once per # notification, regardless of how many other resources have triggered the # notification as well. # # @param action The action to run on the other resource. # @param resource_spec [String, Hash, Chef::Resource] The resource to run. # @param timing [String, Symbol] When to notify. Has these values: # - `delayed`: Will run the action on the other resource after all other # actions have been run. This is the default. # - `immediate`, `immediately`: Will run the action on the other resource # immediately (before any other action is run). # - `before`: Will run the action on the other resource # immediately *before* the action is actually run. # # @example Resource by string # file '/foo.txt' do # content 'hi' # notifies :create, 'file[/bar.txt]' # end # file '/bar.txt' do # action :nothing # content 'hi' # end # @example Resource by hash # file '/foo.txt' do # content 'hi' # notifies :create, file: '/bar.txt' # end # file '/bar.txt' do # action :nothing # content 'hi' # end # @example Direct Resource # bar = file '/bar.txt' do # action :nothing # content 'hi' # end # file '/foo.txt' do # content 'hi' # notifies :create, bar # end # def notifies(action, resource_spec, timing = :delayed) # when using old-style resources(:template => "/foo.txt") style, you # could end up with multiple resources. validate_resource_spec!(resource_spec) resources = [ resource_spec ].flatten resources.each do |resource| case timing.to_s when "delayed" notifies_delayed(action, resource) when "immediate", "immediately" notifies_immediately(action, resource) when "before" notifies_before(action, resource) else raise ArgumentError, "invalid timing: #{timing} for notifies(#{action}, #{resources.inspect}, #{timing}) resource #{self} "\ "Valid timings are: :delayed, :immediate, :immediately, :before" end end true end # # Token class to hold an unresolved subscribes call with an associated # run context. # # @api private # @see Resource#subscribes class UnresolvedSubscribes < self # The full key ise given as the name in {Resource#subscribes} alias_method :to_s, :name alias_method :declared_key, :name end # # Subscribes to updates from other resources, causing a particular action to # run on *this* resource when the other resource is updated. # # If multiple resources are specified, this resource action will be run if # *any* of them change. # # This notification will only trigger *once*, no matter how many other # resources are updated (or how many actions are run by a particular resource). # # @param action The action to run on the other resource. # @param resources [String, Resource, Array[String, Resource]] The resources to subscribe to. # @param timing [String, Symbol] When to notify. Has these values: # - `delayed`: An update will cause the action to run after all other # actions have been run. This is the default. # - `immediate`, `immediately`: The action will run immediately following # the other resource being updated. # - `before`: The action will run immediately before the # other resource is updated. # # @example Resources by string # file '/foo.txt' do # content 'hi' # action :nothing # subscribes :create, 'file[/bar.txt]' # end # file '/bar.txt' do # content 'hi' # end # @example Direct resource # bar = file '/bar.txt' do # content 'hi' # end # file '/foo.txt' do # content 'hi' # action :nothing # subscribes :create, '/bar.txt' # end # @example Multiple resources by string # file '/foo.txt' do # content 'hi' # action :nothing # subscribes :create, [ 'file[/bar.txt]', 'file[/baz.txt]' ] # end # file '/bar.txt' do # content 'hi' # end # file '/baz.txt' do # content 'hi' # end # @example Multiple resources # bar = file '/bar.txt' do # content 'hi' # end # baz = file '/bar.txt' do # content 'hi' # end # file '/foo.txt' do # content 'hi' # action :nothing # subscribes :create, [ bar, baz ] # end # def subscribes(action, resources, timing = :delayed) resources = [resources].flatten resources.each do |resource| if resource.is_a?(String) resource = UnresolvedSubscribes.new(resource, run_context) end if resource.run_context.nil? resource.run_context = run_context end resource.notifies(action, self, timing) end true end # # A command or block that indicates whether to actually run this resource. # The command or block is run just before the action actually executes, and # the action will be skipped if the block returns false. # # If a block is specified, it must return `true` in order for the Resource # to be executed. # # If a command is specified, the resource's #guard_interpreter will run the # command and interpret the results according to `opts`. For example, the # default `execute` resource will be treated as `false` if the command # returns a non-zero exit code, and `true` if it returns 0. Thus, in the # default case: # # - `only_if "your command"` will perform the action only if `your command` # returns 0. # - `only_if "your command", valid_exit_codes: [ 1, 2, 3 ]` will perform the # action only if `your command` returns 1, 2, or 3. # # @param command [String] A string to execute. # @param opts [Hash] Options control the execution of the command # @param block [Proc] A ruby block to run. Ignored if a command is given. # def only_if(command = nil, opts = {}, &block) if command || block_given? @only_if << Conditional.only_if(self, command, opts, &block) end @only_if end # # A command or block that indicates whether to actually run this resource. # The command or block is run just before the action actually executes, and # the action will be skipped if the block returns true. # # If a block is specified, it must return `false` in order for the Resource # to be executed. # # If a command is specified, the resource's #guard_interpreter will run the # command and interpret the results according to `opts`. For example, the # default `execute` resource will be treated as `false` if the command # returns a non-zero exit code, and `true` if it returns 0. Thus, in the # default case: # # - `not_if "your command"` will perform the action only if `your command` # returns a non-zero code. # - `only_if "your command", valid_exit_codes: [ 1, 2, 3 ]` will perform the # action only if `your command` returns something other than 1, 2, or 3. # # @param command [String] A string to execute. # @param opts [Hash] Options control the execution of the command # @param block [Proc] A ruby block to run. Ignored if a command is given. # def not_if(command = nil, opts = {}, &block) if command || block_given? @not_if << Conditional.not_if(self, command, opts, &block) end @not_if end # # The number of times to retry this resource if it fails by throwing an # exception while running an action. Default: 0 # # When the retries have run out, the Resource will throw the last # exception. # # @param arg [Integer] The number of retries. # @return [Integer] The number of retries. # def retries(arg = nil) set_or_return(:retries, arg, kind_of: Integer) end attr_writer :retries # # The number of seconds to wait between retries. Default: 2. # # @param arg [Integer] The number of seconds to wait between retries. # @return [Integer] The number of seconds to wait between retries. # def retry_delay(arg = nil) set_or_return(:retry_delay, arg, kind_of: Integer) end attr_writer :retry_delay # # Whether to treat this resource's data as sensitive. If set, no resource # data will be displayed in log output. # # @param arg [Boolean] Whether this resource is sensitive or not. # @return [Boolean] Whether this resource is sensitive or not. # def sensitive(arg = nil) set_or_return(:sensitive, arg, :kind_of => [ TrueClass, FalseClass ]) end attr_writer :sensitive # ??? TODO unreferenced. Delete? attr_reader :not_if_args # ??? TODO unreferenced. Delete? attr_reader :only_if_args # # The time it took (in seconds) to run the most recently-run action. Not # cumulative across actions. This is set to 0 as soon as a new action starts # running, and set to the elapsed time at the end of the action. # # @return [Integer] The time (in seconds) it took to process the most recent # action. Not cumulative. # attr_reader :elapsed_time # # The guard interpreter that will be used to process `only_if` and `not_if` # statements. If left unset, the #default_guard_interpreter will be used. # # Must be a resource class like `Chef::Resource::Execute`, or # a corresponding to the name of a resource. The resource must descend from # `Chef::Resource::Execute`. # # TODO this needs to be coerced on input so that retrieval is consistent. # # @param arg [Class, Symbol, String] The Guard interpreter resource class/ # symbol/name. # @return [Class, Symbol, String] The Guard interpreter resource. # def guard_interpreter(arg = nil) if arg.nil? @guard_interpreter || @default_guard_interpreter else set_or_return(:guard_interpreter, arg, :kind_of => Symbol) end end # # 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 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 # # Since there are collisions with LWRP parameters named 'state' this # method is not used by the resource_reporter and is most likely unused. # It certainly cannot be relied upon and cannot be fixed. # # @deprecated # alias_method :state, :state_for_resource_reporter # # 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 [Object,Hash] The identity of this resource. # def identity 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 # # Whether to ignore failures. If set to `true`, and this resource when an # action is run, the resource will be marked as failed but no exception will # be thrown (and no error will be output). Defaults to `false`. # # TODO ignore_failure and retries seem to be mutually exclusive; I doubt # that was intended. # # @param arg [Boolean] Whether to ignore failures. # @return Whether this resource will ignore failures. # def ignore_failure(arg = nil) set_or_return(:ignore_failure, arg, kind_of: [ TrueClass, FalseClass ]) end attr_writer :ignore_failure # # Equivalent to #ignore_failure. # alias :epic_fail :ignore_failure # # Make this resource into an exact (shallow) copy of the other resource. # # @param resource [Chef::Resource] The resource to copy from. # def load_from(resource) resource.instance_variables.each do |iv| unless iv == :@source_line || iv == :@action || iv == :@not_if || iv == :@only_if self.instance_variable_set(iv, resource.instance_variable_get(iv)) end end end # # Runs the given action on this resource, immediately. # # @param action The action to run (e.g. `:create`) # @param notification_type The notification type that triggered this (if any) # @param notifying_resource The resource that triggered this notification (if any) # # @raise Any error that occurs during the actual action. # def run_action(action, notification_type = nil, notifying_resource = nil) # reset state in case of multiple actions on the same resource. @elapsed_time = 0 start_time = Time.now events.resource_action_start(self, action, notification_type, notifying_resource) # Try to resolve lazy/forward references in notifications again to handle # the case where the resource was defined lazily (ie. in a ruby_block) resolve_notification_references validate_action(action) if Chef::Config[:verbose_logging] || Chef::Log.level == :debug # This can be noisy Chef::Log.info("Processing #{self} action #{action} (#{defined_at})") end # ensure that we don't leave @updated_by_last_action set to true # on accident updated_by_last_action(false) # Don't modify @retries directly and keep it intact, so that the # recipe_snippet from ResourceFailureInspector can print the value # that was set in the resource block initially. remaining_retries = retries begin return if should_skip?(action) provider_for_action(action).run_action rescue Exception => e if ignore_failure Chef::Log.error("#{custom_exception_message(e)}; ignore_failure is set, continuing") events.resource_failed(self, action, e) elsif remaining_retries > 0 events.resource_failed_retriable(self, action, remaining_retries, e) remaining_retries -= 1 Chef::Log.info("Retrying execution of #{self}, #{remaining_retries} attempt(s) left") sleep retry_delay retry else events.resource_failed(self, action, e) raise customize_exception(e) end end ensure @elapsed_time = Time.now - start_time # Reporting endpoint doesn't accept a negative resource duration so set it to 0. # A negative value can occur when a resource changes the system time backwards @elapsed_time = 0 if @elapsed_time < 0 events.resource_completed(self) end # # If we are currently initializing the resource, this will be true. # # Do NOT use this. It may be removed. It is for internal purposes only. # @api private attr_reader :resource_initializing def resource_initializing=(value) if value @resource_initializing = true else remove_instance_variable(:@resource_initializing) end end # # Generic Ruby and Data Structure Stuff (for user) # def to_s "#{resource_name}[#{name}]" end def to_text return "suppressed sensitive resource output" if sensitive ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS text = "# Declared in #{@source_line}\n\n" text << "#{resource_name}(\"#{name}\") do\n" ivars.each do |ivar| if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?) value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect text << " #{ivar.to_s.sub(/^@/, '')} #{value_string}\n" end end [@not_if, @only_if].flatten.each do |conditional| text << " #{conditional.to_text}\n" end text << "end\n" end def inspect ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS ivars.inject("<#{self}") do |str, ivar| str << " #{ivar}: #{instance_variable_get(ivar).inspect}" end << ">" end # as_json does most of the to_json heavy lifted. It exists here in case activesupport # is loaded. activesupport will call as_json and skip over to_json. This ensure # json is encoded as expected def as_json(*a) safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS instance_vars = Hash.new safe_ivars.each do |iv| instance_vars[iv.to_s.sub(/^@/, "")] = instance_variable_get(iv) end { "json_class" => self.class.name, "instance_vars" => instance_vars, } end # Serialize this object as a hash def to_json(*a) results = as_json Chef::JSONCompat.to_json(results, *a) end def to_hash # Grab all current state, then any other ivars (backcompat) result = {} self.class.state_properties.each do |p| result[p.name] = p.get(self) end safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS safe_ivars.each do |iv| key = iv.to_s.sub(/^@/, "").to_sym next if result.has_key?(key) result[key] = instance_variable_get(iv) end result end def self.json_create(o) resource = self.new(o["instance_vars"]["@name"]) o["instance_vars"].each do |k, v| resource.instance_variable_set("@#{k}".to_sym, v) end resource end # # Resource Definition Interface (for resource developers) # include Chef::Mixin::Deprecation # # The provider class for this resource. # # If `action :x do ... end` has been declared on this resource or its # superclasses, this will return the `action_class`. # # If this is not set, `provider_for_action` will dynamically determine the # provider. # # @param arg [String, Symbol, Class] Sets the provider class for this resource. # If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the # provider based on the name. # # @return The provider class for this resource. # # @see Chef::Resource.action_class # def provider(arg = nil) klass = if arg.kind_of?(String) || arg.kind_of?(Symbol) lookup_provider_constant(arg) else arg end set_or_return(:provider, klass, kind_of: [ Class ]) || self.class.action_class end def provider=(arg) provider(arg) 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] A list of property names to set as desired # state. # # @return [Array] All property names with desired state. # def self.state_attrs(*names) state_properties(*names).map { |property| property.name } 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 # # The guard interpreter that will be used to process `only_if` and `not_if` # statements by default. If left unset, or set to `:default`, the guard # interpreter used will be Chef::GuardInterpreter::DefaultGuardInterpreter. # # Must be a resource class like `Chef::Resource::Execute`, or # a corresponding to the name of a resource. The resource must descend from # `Chef::Resource::Execute`. # # TODO this needs to be coerced on input so that retrieval is consistent. # # @return [Class, Symbol, String] the default Guard interpreter resource. # attr_reader :default_guard_interpreter # # The list of actions this Resource is allowed to have. Setting `action` # will fail unless it is in this list. Default: [ :nothing ] # # @return [Array] The list of actions this Resource is allowed to # have. # attr_accessor :allowed_actions def allowed_actions(value = NOT_PASSED) if value != NOT_PASSED self.allowed_actions = value end @allowed_actions end # # Whether or not this resource was updated during an action. If multiple # actions are set on the resource, this will be `true` if *any* action # caused an update to happen. # # @return [Boolean] Whether the resource was updated during an action. # attr_reader :updated # # Whether or not this resource was updated during an action. If multiple # actions are set on the resource, this will be `true` if *any* action # caused an update to happen. # # @return [Boolean] Whether the resource was updated during an action. # def updated? updated end # # Whether or not this resource was updated during the most recent action. # This is set to `false` at the beginning of each action. # # @param true_or_false [Boolean] Whether the resource was updated during the # current / most recent action. # @return [Boolean] Whether the resource was updated during the most recent action. # def updated_by_last_action(true_or_false) @updated ||= true_or_false @updated_by_last_action = true_or_false end # # Whether or not this resource was updated during the most recent action. # This is set to `false` at the beginning of each action. # # @return [Boolean] Whether the resource was updated during the most recent action. # def updated_by_last_action? @updated_by_last_action end # # Set whether this class was updated during an action. # # @deprecated Multiple actions are supported by resources. Please call {}#updated_by_last_action} instead. # def updated=(true_or_false) Chef::Log.warn("Chef::Resource#updated=(true|false) is deprecated. Please call #updated_by_last_action(true|false) instead.") Chef::Log.warn("Called from:") caller[0..3].each { |line| Chef::Log.warn(line) } updated_by_last_action(true_or_false) @updated = true_or_false end # # The display name of this resource type, for printing purposes. # # Will be used to print out the resource in messages, e.g. resource_name[name] # # @return [Symbol] The name of this resource type (e.g. `:execute`). # def resource_name @resource_name || self.class.resource_name end # # Sets a list of capabilities of the real resource. For example, `:remount` # (for filesystems) and `:restart` (for services). # # TODO Calling resource.supports({}) will not set this to empty; it will do # a get instead. That's wrong. # # @param args Hash{Symbol=>Boolean} If non-empty, sets the capabilities of # this resource. Default: {} # @return Hash{Symbol=>Boolean} An array of things this resource supports. # def supports(args = {}) if args.any? @supports = args else @supports end end def supports=(args) supports(args) end # # A hook called after a resource is created. Meant to be overriden by # subclasses. # def after_created nil end # # The DSL name of this resource (e.g. `package` or `yum_package`) # # @return [String] The DSL name of this resource. # # @deprecated Use resource_name instead. # def self.dsl_name Chef.log_deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead." if name name = self.name.split("::")[-1] convert_to_snake_case(name) end end # # The display name of this resource type, for printing purposes. # # This also automatically calls "provides" to provide DSL with the given # name. # # resource_name defaults to your class name. # # Call `resource_name nil` to remove the resource name (and any # corresponding DSL). # # @param value [Symbol] The desired name of this resource type (e.g. # `execute`), or `nil` if this class is abstract and has no resource_name. # # @return [Symbol] The name of this resource type (e.g. `:execute`). # def self.resource_name(name = NOT_PASSED) # Setter if name != NOT_PASSED remove_canonical_dsl # Set the resource_name and call provides if name name = name.to_sym # If our class is not already providing this name, provide it. if !Chef::ResourceResolver.includes_handler?(name, self) provides name, canonical: true end @resource_name = name else @resource_name = nil end end @resource_name end def self.resource_name=(name) resource_name(name) end # # Use the class name as the resource name. # # Munges the last part of the class name from camel case to snake case, # and sets the resource_name to that: # # A::B::BlahDBlah -> blah_d_blah # def self.use_automatic_resource_name automatic_name = convert_to_snake_case(self.name.split("::")[-1]) resource_name automatic_name end # # The module where Chef should look for providers for this resource. # The provider for `MyResource` will be looked up using # `provider_base::MyResource`. Defaults to `Chef::Provider`. # # @param arg [Module] The module containing providers for this resource # @return [Module] The module containing providers for this resource # # @example # class MyResource < Chef::Resource # provider_base Chef::Provider::Deploy # # ...other stuff # end # # @deprecated Use `provides` on the provider, or `provider` on the resource, instead. # def self.provider_base(arg = nil) if arg Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.") end @provider_base ||= arg || Chef::Provider end # # The list of allowed actions for the resource. # # @param actions [Array] The list of actions to add to allowed_actions. # # @return [Array] The list of actions, as symbols. # def self.allowed_actions(*actions) @allowed_actions ||= if superclass.respond_to?(:allowed_actions) superclass.allowed_actions.dup else [ :nothing ] end @allowed_actions |= actions.flatten end def self.allowed_actions=(value) @allowed_actions = value.uniq end # # The action that will be run if no other action is specified. # # Setting default_action will automatially add the action to # allowed_actions, if it isn't already there. # # Defaults to [:nothing]. # # @param action_name [Symbol,Array] The default action (or series # of actions) to use. # # @return [Array] The default actions for the resource. # def self.default_action(action_name = NOT_PASSED) unless action_name.equal?(NOT_PASSED) @default_action = Array(action_name).map(&:to_sym) self.allowed_actions |= @default_action end if @default_action @default_action elsif superclass.respond_to?(:default_action) superclass.default_action else [:nothing] end end def self.default_action=(action_name) default_action action_name end # # Define an action on this resource. # # The action is defined as a *recipe* block that will be compiled and then # converged when the action is taken (when Resource is converged). The recipe # has access to the resource's attributes and methods, as well as the Chef # recipe DSL. # # Resources in the action recipe may notify and subscribe to other resources # within the action recipe, but cannot notify or subscribe to resources # in the main Chef run. # # Resource actions are *inheritable*: if resource A defines `action :create` # and B is a subclass of A, B gets all of A's actions. Additionally, # resource B can define `action :create` and call `super()` to invoke A's # action code. # # The first action defined (besides `:nothing`) will become the default # action for the resource. # # @param name [Symbol] The action name to define. # @param recipe_block The recipe to run when the action is taken. This block # takes no parameters, and will be evaluated in a new context containing: # # - The resource's public and protected methods (including attributes) # - The Chef Recipe DSL (file, etc.) # - super() referring to the parent version of the action (if any) # # @return The Action class implementing the action # def self.action(action, &recipe_block) action = action.to_sym declare_action_class action_class.action(action, &recipe_block) self.allowed_actions += [ action ] default_action action if Array(default_action) == [:nothing] end # # Define a method to load up this resource's properties with the current # actual values. # # @param load_block The block to load. Will be run in the context of a newly # created resource with its identity values filled in. # def self.load_current_value(&load_block) define_method(:load_current_value!, &load_block) end # # Call this in `load_current_value` to indicate that the value does not # exist and that `current_resource` should therefore be `nil`. # # @raise Chef::Exceptions::CurrentValueDoesNotExist # def current_value_does_not_exist! raise Chef::Exceptions::CurrentValueDoesNotExist end # # Get the current actual value of this resource. # # This does not cache--a new value will be returned each time. # # @return A new copy of the resource, with values filled in from the actual # current value. # def current_value provider = provider_for_action(Array(action).first) if provider.whyrun_mode? && !provider.whyrun_supported? raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run" end provider.load_current_resource provider.current_resource end # # The action class is an automatic `Provider` created to handle # actions declared by `action :x do ... end`. # # This class will be returned by `resource.provider` if `resource.provider` # is not set. `provider_for_action` will also use this instead of calling # out to `Chef::ProviderResolver`. # # If the user has not declared actions on this class or its superclasses # using `action :x do ... end`, then there is no need for this class and # `action_class` will be `nil`. # # If a block is passed, the action_class is always created and the block is # run inside it. # # @api private # def self.action_class(&block) return @action_class if @action_class && !block # If the superclass needed one, then we need one as well. if block || (superclass.respond_to?(:action_class) && superclass.action_class) @action_class = declare_action_class(&block) end @action_class end # # Ensure the action class actually gets created. This is called # when the user does `action :x do ... end`. # # If a block is passed, it is run inside the action_class. # # @api private def self.declare_action_class(&block) @action_class ||= begin if superclass.respond_to?(:action_class) base_provider = superclass.action_class end base_provider ||= Chef::Provider resource_class = self Class.new(base_provider) do include ActionClass self.resource_class = resource_class end end @action_class.class_eval(&block) if block @action_class end # # Internal Resource Interface (for Chef) # FORBIDDEN_IVARS = [:@run_context, :@not_if, :@only_if, :@enclosing_provider] HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider] include Chef::Mixin::ConvertToClassName extend Chef::Mixin::ConvertToClassName # XXX: this is required for definition params inside of the scope of a # subresource to work correctly. attr_accessor :params # @return [Chef::RunContext] The run context for this Resource. This is # where the context for the current Chef run is stored, including the node # and the resource collection. attr_accessor :run_context # @return [String] The cookbook this resource was declared in. attr_accessor :cookbook_name # @return [String] The recipe this resource was declared in. attr_accessor :recipe_name # @return [Chef::Provider] The provider this resource was declared in (if # it was declared in an LWRP). When you call methods that do not exist # on this Resource, Chef will try to call the method on the provider # as well before giving up. attr_accessor :enclosing_provider # @return [String] The source line where this resource was declared. # Expected to come from caller() or a stack trace, it usually follows one # of these formats: # /some/path/to/file.rb:80:in `wombat_tears' # C:/some/path/to/file.rb:80 in 1`wombat_tears' attr_accessor :source_line # @return [String] The actual name that was used to create this resource. # Sometimes, when you say something like `package 'blah'`, the system will # create a different resource (i.e. `YumPackage`). When this happens, the # user will expect to see the thing they wrote, not the type that was # returned. May be `nil`, in which case callers should read #resource_name. # See #declared_key. attr_accessor :declared_type # # Iterates over all immediate and delayed notifications, calling # resolve_resource_reference on each in turn, causing them to # resolve lazy/forward references. def resolve_notification_references run_context.before_notifications(self).each { |n| n.resolve_resource_reference(run_context.resource_collection) } run_context.immediate_notifications(self).each { |n| n.resolve_resource_reference(run_context.resource_collection) } run_context.delayed_notifications(self).each {|n| n.resolve_resource_reference(run_context.resource_collection) } end # Helper for #notifies def notifies_before(action, resource_spec) run_context.notifies_before(Notification.new(resource_spec, action, self)) end # Helper for #notifies def notifies_immediately(action, resource_spec) run_context.notifies_immediately(Notification.new(resource_spec, action, self)) end # Helper for #notifies def notifies_delayed(action, resource_spec) run_context.notifies_delayed(Notification.new(resource_spec, action, self)) end class << self # back-compat # NOTE: that we do not support unregistering classes as descendants like # we used to for LWRP unloading because that was horrible and removed in # Chef-12. # @deprecated # @api private alias :resource_classes :descendants # @deprecated # @api private alias :find_subclass_by_name :find_descendants_by_name end # @deprecated # @api private # We memoize a sorted version of descendants so that resource lookups don't # have to sort all the things, all the time. # This was causing performance issues in test runs, and probably in real # life as well. @@sorted_descendants = nil def self.sorted_descendants @@sorted_descendants ||= descendants.sort_by { |x| x.to_s } end def self.inherited(child) super @@sorted_descendants = nil # set resource_name automatically if it's not set if child.name && !child.resource_name if child.name =~ /^Chef::Resource::(\w+)$/ child.resource_name(convert_to_snake_case($1)) end end end # If an unknown method is invoked, determine whether the enclosing Provider's # lexical scope can fulfill the request. E.g. This happens when the Resource's # block invokes new_resource. def method_missing(method_symbol, *args, &block) if enclosing_provider && enclosing_provider.respond_to?(method_symbol) enclosing_provider.send(method_symbol, *args, &block) else raise NoMethodError, "undefined method `#{method_symbol}' for #{self.class}" end end # # Mark this resource as providing particular DSL. # # Resources have an automatic DSL based on their resource_name, equivalent to # `provides :resource_name` (providing the resource on all OS's). If you # declare a `provides` with the given resource_name, it *replaces* that # provides (so that you can provide your resource DSL only on certain OS's). # def self.provides(name, **options, &block) name = name.to_sym # `provides :resource_name, os: 'linux'`) needs to remove the old # canonical DSL before adding the new one. if @resource_name && name == @resource_name remove_canonical_dsl end result = Chef.resource_handler_map.set(name, self, options, &block) Chef::DSL::Resources.add_resource_dsl(name) result end def self.provides?(node, resource_name) Chef::ResourceResolver.new(node, resource_name).provided_by?(self) end # Helper for #notifies def validate_resource_spec!(resource_spec) run_context.resource_collection.validate_lookup_spec!(resource_spec) end # We usually want to store and reference resources by their declared type and not the actual type that # was looked up by the Resolver (IE, "package" becomes YumPackage class). If we have not been provided # the declared key we want to fall back on the old to_s key. def declared_key return to_s if declared_type.nil? "#{declared_type}[#{@name}]" end def before_notifications run_context.before_notifications(self) end def immediate_notifications run_context.immediate_notifications(self) end def delayed_notifications run_context.delayed_notifications(self) end def source_line_file if source_line source_line.match(/(.*):(\d+):?.*$/).to_a[1] else nil end end def source_line_number if source_line source_line.match(/(.*):(\d+):?.*$/).to_a[2] else nil end end def defined_at # The following regexp should match these two sourceline formats: # /some/path/to/file.rb:80:in `wombat_tears' # C:/some/path/to/file.rb:80 in 1`wombat_tears' # extracting the path to the source file and the line number. if cookbook_name && recipe_name && source_line "#{cookbook_name}::#{recipe_name} line #{source_line_number}" elsif source_line "#{source_line_file} line #{source_line_number}" else "dynamically defined" end end # # The cookbook in which this Resource was defined (if any). # # @return Chef::CookbookVersion The cookbook in which this Resource was defined. # def cookbook_version if cookbook_name run_context.cookbook_collection[cookbook_name] end end def events run_context.events end def validate_action(action) raise ArgumentError, "nil is not a valid action for resource #{self}" if action.nil? end def provider_for_action(action) provider_class = Chef::ProviderResolver.new(node, self, action).resolve provider = provider_class.new(self, run_context) provider.action = action provider end # ??? TODO Seems unused. Delete? def noop(tf = nil) if !tf.nil? raise ArgumentError, "noop must be true or false!" unless tf == true || tf == false @noop = tf end @noop end # TODO Seems unused. Delete? def is(*args) if args.size == 1 args.first else return *args end end # # Preface an exception message with generic Resource information. # # @param e [StandardError] An exception with `e.message` # @return [String] An exception message customized with class name. # def custom_exception_message(e) "#{self} (#{defined_at}) had an error: #{e.class.name}: #{e.message}" end def customize_exception(e) new_exception = e.exception(custom_exception_message(e)) new_exception.set_backtrace(e.backtrace) new_exception end # Evaluates not_if and only_if conditionals. Returns a falsey value if any # of the conditionals indicate that this resource should be skipped, i.e., # if an only_if evaluates to false or a not_if evaluates to true. # # If this resource should be skipped, returns the first conditional that # "fails" its check. Subsequent conditionals are not evaluated, so in # general it's not a good idea to rely on side effects from not_if or # only_if commands/blocks being evaluated. # # Also skips conditional checking when the action is :nothing def should_skip?(action) conditional_action = ConditionalActionNotNothing.new(action) conditionals = [ conditional_action ] + only_if + not_if conditionals.find do |conditional| if conditional.continue? false else events.resource_skipped(self, action, conditional) Chef::Log.debug("Skipping #{self} due to #{conditional.description}") true end end end # Returns a resource based on a short_name and node # # ==== Parameters # short_name:: short_name of the resource (ie :directory) # node:: Node object to look up platform and version in # # === Returns # :: returns the proper Chef::Resource class def self.resource_for_node(short_name, node) klass = Chef::ResourceResolver.resolve(short_name, node: node) raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil? klass end # # Returns the class with the given resource_name. # # ==== Parameters # short_name:: short_name of the resource (ie :directory) # # === Returns # :: returns the proper Chef::Resource class # def self.resource_matching_short_name(short_name) Chef::ResourceResolver.resolve(short_name, canonical: true) end # @api private def lookup_provider_constant(name, action = :nothing) begin self.class.provider_base.const_get(convert_to_class_name(name.to_s)) rescue NameError => e if e.to_s =~ /#{Regexp.escape(self.class.provider_base.to_s)}/ raise ArgumentError, "No provider found to match '#{name}'" else raise e end end end module DeprecatedLWRPClass # @api private def register_deprecated_lwrp_class(resource_class, class_name) if Chef::Resource.const_defined?(class_name, false) Chef::Log.warn "#{class_name} already exists! Deprecation class overwrites #{resource_class}" Chef::Resource.send(:remove_const, class_name) end if !Chef::Config[:treat_deprecation_warnings_as_errors] Chef::Resource.const_set(class_name, resource_class) Chef::Resource.deprecated_constants[class_name.to_sym] = resource_class end end def deprecated_constants raise "Deprecated constants should be called only on Chef::Resource" unless self == Chef::Resource @deprecated_constants ||= {} end end private def self.remove_canonical_dsl if @resource_name remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self) if !remaining Chef::DSL::Resources.remove_resource_dsl(@resource_name) end end end extend DeprecatedLWRPClass end end # Requiring things at the bottom breaks cycles require "chef/chef_class"