Deferred functions overview
Defer a function to retrieve a value on the agent.
The Deferred
type instructs agents to execute a function locally to
retrieve a data value at the time of catalog application. When compiling catalogs,
functions are normally executed on the primary server, with results entered into the
catalog directly. The complete and fully resolved catalog is then sent to the agent for
application. You can choose to defer the function call until the agent applies the
catalog, meaning the agent calls the function on the agent instead of on the primary
server. This way, agents can use a function to fetch data directly, rather than having
the primary server act as an intermediary.
- To retrieve a value on the agent that the primary server doesn’t have access to, including sensitive information like passwords from a secrets store.
- To use as a placeholder to describe intent and make your desired state as descriptive as possible.
Deferred function example
The following example shows a file with a template on the agent that defers
two functions — the Vault password lookup and the epp
template compilation.
epp()
function is
deferred to render the template after the password value is
available.$variables = {
'password' => Deferred('vault_lookup::lookup',
["secret/test", 'https://vault.docker:8200']),
}
# compile the template source into the catalog
file { '/etc/secrets.conf':
ensure => file,
content => Deferred('inline_epp',
['PASSWORD=<%= $password.unwrap %>', $variables]),
}
Deferred( <name of function to invoke>, [ array, of, arguments] )
This object compiles directly into the catalog and its function is invoked as the first part of enforcing a catalog. It will be replaced by whatever it returns, similar to string interpolation. The catalog looks like the JSON hash below.
vault_lookup::lookup
invocation, and then the content key is replaced
with the results of the inline_epp invocation
.
Puppet can then manage the contents of the file without the primary server ever
knowing the secret.$ jq '.resources[] | select(.type == "File" and .title == "/etc/secrets.conf")' catalog.json
{
"type": "File",
"title": "/etc/secrets.conf",
"parameters": {
"ensure": "file",
"owner": "root",
"group": "root",
"mode": "0600",
"content": {
"__ptype": "Deferred",
"name": "inline_epp",
"arguments": [
"PASSWORD=$password\n",
{
"password": {
"__ptype": "Deferred",
"name": "vault_lookup::lookup",
"arguments": ["secret/test", "https://vault.docker:8200"]
}
}
]
},
"backup": false
}
}
When using deferred functions, take note of the following:
- If an agent is applying a cached catalog, the
Deferred
function is still called at application time, and the value returned at that time is the value that is used. - It is the responsibility of the function to handle edge cases such as providing default or cached values in cases where a remote store is unavailable.
-
Deferred
supports only the Puppet function API for Ruby. - If a function called on the agent side does not return
Sensitive
, you can wrap the value returned byDeferred
in aSensitive
type if a sensitive value is desired. For example:$d = Sensitive(Deferred("myupcase", ["example value"]))
- Deferred functions can only use types that are built into Puppet
(for example
String
). They cannot use types from modules likestdlib
because Puppet does not plugin-sync these types to the agent. - Do not use the Deferred object as a variable in a string. When compiled, these variables are interpolated, so the stringified version of the object would be passed to the agent, instead of the object itself.