Runtime environment
The primary runtime environment for the provider is the Puppet Agent, a long-running daemon process. The provider can also
be used in the puppet apply
command, a self contained version of the agent, or the puppet resource
command, a short-lived command line interface (CLI)
process for listing or managing a single resource type. Other callers that want to access the
provider must imitate these environments.
The primary life cycle of resource management in each of these tools is
the transaction, a single set of changes, for example a catalog or a CLI invocation. The
provider's class is instantiated one time for each transaction. Within that class the provider
defines any number of helper methods to support itself. To allow for a transaction to set up
the prerequisites for a provider and be used immediately, the provider is instantiated as late
as possible. A transaction usually calls get
one time, and may call set
any number of times to make changes.
The object instance that hosts the get
and set
methods can be used to cache ephemeral state
during execution. The provider should not try to cache state outside of its instances. In many
cases, such caching won't help as the hosting process only manages a single transaction. In
long-running runtime environments like the agent, the benefit of the caching needs to be
balanced with the cost of the cache at rest, and the lifetime of cache entries, which are only
useful when they are longer than the regular runinterval
.
The runtime environment has the following utilities to provide a uniform experience for its users.
Logging and reporting utilities
The provider needs to signal changes, successes, and failures to the runtime environment.
The context
is the primary way to do this. It
provides a structured logging interface for all provider actions. Using this information,
the runtime environments can do automatic processing, emit human readable progress
information, and provide status messages for operators.
To provide feedback about the overall operation of the provider, the context
has the usual set of log level methods that take a string, and pass that up to
the runtime environments logging infrastructure. For
example:
context.warning("Unexpected state detected, continuing in degraded mode.")Results in the following message:
Warning: apt_key: Unexpected state detected, continuing in degraded mode.Other common messages include:
-
debug: Detailed messages to understand everything that is happening at runtime, shown on request.
-
info: Regular progress and status messages, especially useful before long-running operations, or before operations that can fail, to provide context for interactive users.
-
notice: Indicates state changes and other events of notice from the regular operations of the provider.
-
warning: Signals error conditions that do not (yet) prohibit execution of the main part of the provider; for example, deprecation warnings, temporary errors.
-
err: Signals error conditions that have caused normal operations to fail.
-
critical, alert, emerg: Should not be used by resource providers.
In simple cases, a provider passes off work to an external tool, logs the details there, and then reports back to Puppet acknowledging these changes. This is called resource status signaling, and looks like this:
@apt_key_cmd.run(context, action, key_id) context.processed(key_id, is, should)It reports all changes from
is
to should
, using
default messages.
Providers that want to have more control over the logging throughout the processing can use
the more specific created(title)
, updated(title)
, deleted(title)
, unchanged(title)
methods. To report the change of an attribute,
the context
provides a attribute_changed(title, attribute, old_value, new_value,
message)
method.
Most of those messages are expected to be relative to a specific resource instance, and a
specific operation on that instance. To enable detailed logging without repeating key
arguments, and to provide consistent error logging, the context
provides logging context methods to capture the current action
and resource
instance:
context.updating(title) do if apt_key_not_found(title) context.warning('Original key not found') end # Update the key by calling CLI tool apt_key(...) context.attribute_changed('content', nil, content_hash, message: "Replaced with content hash #{content_hash}") endThis results in the following messages:
Debug: Apt_key[F1D2D2F9]: Started updating Warning: Apt_key[F1D2D2F9]: Updating: Original key not found Debug: Apt_key[F1D2D2F9]: Executing 'apt-key ...' Debug: Apt_key[F1D2D2F9]: Successfully executed 'apt-key ...' Notice: Apt_key[F1D2D2F9]: Updating content: Replaced with content hash E242ED3B Notice: Apt_key[F1D2D2F9]: Successfully updatedIn the case of an exception escaping the block, the error is logged appropriately:
Debug: Apt_keyF1D2D2F9]: Started updating Warning: Apt_key[F1D2D2F9]: Updating: Original key not found Error: Apt_key[F1D2D2F9]: Updating failed: Something went wrong
Logging contexts process all exceptions. A StandardError is assumed to be regular failures in handling
resources, and are consumed after logging. Everything else is assumed to be a fatal
application-level issue, and is passed up the stack, ending execution. See the Ruby documentation for details
on which exceptions are not a StandardError
.
The equivalent long-hand form of manual error handling:
context.updating(title) begin unless title_got_passed_to_set(title) raise Puppet::DevError, 'Managing resource outside of requested set: %{title}') end if apt_key_not_found(title) context.warning('Original key not found') end # Update the key by calling CLI tool result = @apt_key_cmd.run(...) if result.exitstatus != 0 context.error(title, "Failed executing apt-key #{...}") else context.attribute_changed(title, 'content', nil, content_hash, message: "Replaced with content hash #{content_hash}") end context.changed(title) rescue Exception => e context.error(title, e, message: 'Updating failed') raise unless e.is_a? StandardError endThis example is only for demonstration purposes. In the normal course of operations, providers should always use the utility functions.
The following methods are available:
-
Block functions: these functions provide logging and timing around a provider's core actions. If the the passed
&block
returns, the action is recorded as successful. To signal a failure, the block should raise an exception explaining the problem:-
creating(titles, message: 'Creating', &block)
-
updating(titles, message: 'Updating', &block)
-
deleting(titles, message: 'Deleting', &block)
-
processing(title, is, should, message: 'Processing', &block): generic processing of a resource, produces default change messages for the difference between
is:
andshould:
. -
failing(titles, message: 'Failing', &block): unlikely to be used often, but provided for completeness. It always records a failure.
-
-
Action functions:
-
created(titles, message: 'Created')
-
updated(titles, message: 'Updated')
-
deleted(titles, message: 'Deleted')
-
processed(title, is, should): the resource has been processed. It produces default logging for the resource and each attribute
-
failed(titles, message:): the resource has not been updated successfully
-
-
Attribute Change notifications:
-
attribute_changed(title, attribute, is, should, message: nil): notify the runtime environment that a specific attribute for a specific resource has changed.
is
andshould
are the original and the new value of the attribute. Either can benil
.
-
-
Plain messages:
-
debug(message)
-
debug(titles, message:)
-
info(message)
-
info(titles, message:)
-
notice(message)
-
notice(titles, message:)
-
warning(message)
-
warning(titles, message:)
-
err(message)
-
err(titles, message:)
-
titles
can be a single identifier for a resource or an
array of values, if the following block batch processes multiple resources in one pass. If
that processing is not atomic, providers should instead use the non-block forms of logging,
and provide accurate status reporting on the individual parts of update operations.
A single set()
execution may only log messages
for instances that have been passed, as part of the changes
to process. Logging for instances not requested to be changed
causes an exception - the runtime environment is not prepared for other resources to
change.
The provider is free to call different logging methods for different resources in any order
it needs to. The only ordering restriction is for all calls specifying the same title
. For example, the attribute_changed
needs logged before that resource's action logging, and the
context
needs to be opened before any other logging for
this resource.
Type definition
The provider can gain insight into the type definition through these context.type
utility methods:
attributes
: returns a hash containing the type attributes and its properties.ensurable?
: returnstrue
if the type contains the ensure attribute.feature?(feature)
: returnstrue
if the type supports a given provider feature.
For example:
# example from simple_provider.rb def set(context, changes) changes.each do |name, change| is = if context.type.feature?('simple_get_filter') change.key?(:is) ? change[:is] : (get(context, [name]) || []).find { |r| r[:name] == name } else change.key?(:is) ? change[:is] : (get(context) || []).find { |r| r[:name] == name } end ... end