RFC | Author | Status | Type |
---|---|---|---|
unassigned |
Daniel DeLeo <dan@chef.io> |
Draft |
Standards Track |
This RFC proposes a new API for setting and accessing attributes in cookbooks. The goals of this new API are:
- Separate desired and observed state.
- Structure the syntax in a way that "trainwreck" errors
(
NoMethodError
for[]
onnil
) can be avoided. - Structure the syntax in a way that attribute tracing can be implemented simply.
- Separate node-specific persistent storage from cookbook/role/environment attributes.
- Structure the syntax in a way that can be implemented efficiently.
The new API is added alongside the existing API, so existing code will work without modification.
There are several motivations for this change.
In the current attributes system, system "facts" determined by Ohai are
intermixed with user attributes, which are generally used to define the
desired state of the system. For example, node[:fqdn]
gives the
system's FQDN, as determined by Ohai at the beginning of the Chef run,
and identical syntax is used to reference a user-defined attribute, such
as node[:mysql][:version]
. As a result, it's not possible to know
which attributes are "facts" except by committing to memory the list of
attributes provided by Ohai. Defining a new syntax where Ohai and
user-defined attributes are accessed by different methods makes cookbook
code clearer.
Additionally, separating Ohai data from user-defined attributes
eliminates the possibility of attribute name collisions. For example, a
user might wish to set the FQDN of a host with Chef and use the fqdn
attribute to define the desired FQDN, or create an Ohai plugin to detect
the currently installed version of MySQL, storing this as
node[:mysql][:version]
. With the current attributes system, the user
can't use these attribute names because they conflict with existing Ohai
or cookbook attributes.
The current attribute system presents node data as a nested collection of Hash-like objects. There are some unfortunate limitations of this model that lead to frustrating behavior for users. In particular, code like this:
node[:foo][:bar][:baz]
May raise a NoMethodError
with the message:
undefined method `[]' for nil:NilClass
Note that in this example, it's ambiguous as to which part of the
attribute structure is missing, as this error will occur in an identical
way if either node[:foo]
or node[:foo][:bar]
are nil
.
The UX of this error case cannot be significantly improved without
abandoning the nested Hash model. If an object other than nil
was
returned for accessing a missing key, then code like
do_something if node[:some_attribute_present]
would break (Ruby does
not allow custom false-y types), and monkey patching nil
is
error-prone because nil
is an immediate object (there is only one
instance).
A common problem when developing cookbook code is attributes containing unexpected values, which can be frustrating to debug. The current implementation of attributes makes implementation of a tracing feature difficult; in particular, it requires that attribute objects have circular references to their parent objects so that the full attribute path is known when setting a new attribute value. It is preferable to avoid circular references because code that blindly walks the object graph (such as a string inspection method) may enter an infinite loop if it does not correctly guard against this case. Additionally, edge cases such as "reparenting" a part of the node tree may be difficult to implement correctly.
Providing an attributes API where the full attribute path is known when new attributes are set would greatly ease the implementation of the tracing feature.
So-called "normal attributes" in the current attributes API have a unique behavior compared to other attributes in that they are persisted between Chef Client runs. While this behavior is useful and necessary for some use cases, it causes some frustrations for users:
- learning chef attributes is more complicated, since documentation and recommendations are more complicated to understand.
- "Phantom" attributes. Users remove an attribute, or modify a complicated data structure, but their changes are mysteriously not applied because the normal attributes from a previous run are not cleared.
- Recipes that generate node data that needs to be persisted must call
node.save
manually, to ensure that persisted data is not lost in the event that the chef-client run subsequently fails. This causes issues with search data consistency, increased server load, and other problems.
The current implementation has poor performance when attributes are mutated and then read frequently. Chef 12 introduces a partial cache to mitigate the problem, but this doesn't completely solve the performance issues in certain degenerate cases. Due to the limitations of the existing API, it is difficult to improve the performance further. The proposed API should allow for straightforward implementation of a granular merged attribute cache.
The current attributes system allows attributes to be accessed via a
method_missing
API, e.g., node["fqdn"]
can also be accessed as
node.fqdn
. While this makes the code appear more readable,
implementing it directly on the node
object leads to method name
collisions which can be confusing for users. For example:
node.foo # returns node['foo']
node.chef_environment # returns the environment, which is an ivar on the
# node object
node.class # returns the Chef::Node symbol
Providing a new API where attribute access occurs via arguments to statically defined methods eliminates this source of ambiguity.
TBD
TBD
Syntax is TBD.
Node-specific storage will initially be implemented as a facade on top of the existing "normal" attributes structure. In the future we may propose additional enhancements to this API to provide pluggability, orchestration support, and/or improved authorization behaviors.
This section will be updated once the specification is more complete.
This work is in the public domain. In jurisdictions that do not allow for this, this work is available under CC0. To the extent possible under law, the person who associated CC0 with this work has waived all copyright and related or neighboring rights to this work.