-
Notifications
You must be signed in to change notification settings - Fork 31
Value Passing
- Value Passing as a Template Language Construct
- Value-Passing Semantics
- Value Passing Internals:
Value passing will be handled at Preparer time during template
execution through the use of the lookup
template function that,
which allows access into module-specific structure fields.
Depending on the specific fields that are being references and when they are being generated by the origin module they may not be available during different processing phases. The specific phases are outlined below.
Cross-referencing functions similar to parameters in that it allows
access to fields of a node that are defined within the module. For
example in xref example we access the destination of
file.content.config
which is a statically accessible value that will
be available at prepare
time.
file.content "config" {
destination = "/etc/config/config.conf"
contents = "config file data"
}
task {
check = "[[ -f output]]"
apply = "echo -n 'filename: {{lookup `file.content.config.destination`}}' | tee -a output"
}
Read-effect references are values that can only be computed at runtime
but whose computation does not have an affect on the running system.
These are values that can be checked during planning and a referenced
with the lookup
function as show below.
param "iface" {
default = "en0"
}
task "ipaddr" {
query = "ifconfig {{param `iface`}} | grep 'inet\W' | awk '{print $2}'"
}
file.content "address" {
destination="myaddress"
content="{{lookup `task.once.ipaddr.stdout`}}"
}
Effect references are values that are only available after an
effectful operation has been completed. They are accessible with the
lookup
function as shown below.
task "effectful" {
check = "[[ -f random.md5 ]]"
apply = "dd if=/dev/random bs=512 count=1 2>/dev/null | md5 > random.md5"
}
file.content "checksum" {
destination = "/etc/checksums/{{results `task.effectful.stdout`}}"
content = "{{lookup `task.effectful.stdout`}}"
}
Any call to `lookup` will be memoized for the duration of the execution. Calls to `lookup` will always reference the most recent call to `Check` or `Apply` first and will traverse the call order until no more `TaskStatus` results are found. If the end of the call list is encountered before a struct field match is found then the node is unresolvable.
All nodes are considered unresolvable at load time. A node will be resolvable if all of it's value dependency calls are resolvable at the current operation level. The table below outlines which converge operations support value reference types.
Defined during `Prepare` | Defined during `Check` | Defined during `Apply` | |
---|---|---|---|
`healthcheck` | defined | defined | undefined |
`plan` | defined | defined | undefined |
`apply` | defined | defined | defined |
`graph`1 | defined1 | defined1 | defined1 |
The system behavior with relation to undefined nodes are shown as follows:
-
healthcheck
: Healthcheck will evaluatexref
andload
calls. Any unresolvable nodes will be put in a warning state. -
plan
: Plan will evaluatexref
andload
calls. Effectful dependencies fromresults
will be displayed as unresolvable as show in Ex 1. -
apply
: Because apply will perform effectful operations all value passing functions are supported. -
graph
: Nodes are displayed irrespective of references, but edges are shown for value passing dependencies.
root/file.content.checksum output: Unresolvable
Summary: 0 errors, 1 changes
1 node(s) depend on effectful results and may change
This section describes the internal implementation of value passing with an emphasis on understanding the requirements to support value passing at the per-module level.
Package render
will now export an error ErrUnresolvable
that will
be returned by Renderer.Render()
if the template attempts to
reference an unresolvable element of a node.
The resource.Task
interface will have minor modifications to support
value passing. The interface will now be defined as shown in 3.1.2.
Of particular note is that both Check
and Apply
will now be
supplied with a Renderer
to facilitate deferred rendering.
// Task will now pass a renderer to Check and Apply, Apply now returns a
// TaskStatus
type Task interface {
Check(Renderer) (TaskStatus, error)
Apply(Renderer) (TaskStatus, error)
}
// Resource adds metadata about the executed tasks
type Resource interface {
Prepare(Renderer) (Task, error)
}
// Renderer is passed to resources
type Renderer interface {
Value() (value string, present bool)
Render(name, content string) (string, error)
}
Module specific implementations of Task
and TaskStatus
are
used for exposing fields to other modules at runtime. Structure
fields that are present in the modules' preparer and task status
structs will be made available automatically. nil
values are used
to indicate unresolvable values.
The resource.Task
implementation returned by Prepare
is the base
value that should contain any any statically resolvable data such data
specified in the HCL document or constants defined by the module.
Note that the TaskStatus
returned by Check()
and Apply()
should
expose the same values in order to ensure that valid but unresolvable
paths are differentiated from erroneous paths at template execution
time. These values should be initialized by Prepare
, which will
continue to be initialized by raw parsed hcl data during loading.
Each of the load, plan or health check, and apply phases are
considered during field reference resolution. Fields are considered
in order with priority given to the most recent exceution, except
during application where the results of Apply()
are considered
before the results of the subsequent secondary Check()
call.
+------------------------------------+----------------------+--------------+
| Apply Phase | Plan / Healthcheck | Load Phase |
+------------------------------------+----------------------+--------------+
| Task Status ----> Task Status --+-----> Task Status ---+--> Task |
| (Apply) (Check) | (Check) | (Prepare) |
+------------------------------------+----------------------+--------------+
Modules may continue to use the existing resource.Status
structure
as a default implementation of TaskStatus
, however the exposed
values from resource.Status
are insufficient for useful value
passing. As such modules should embed the resource.Status
structure
in a richer structure that contains the exposed fields.
Modules may also choose to use intermediary structures to assist in differentiating between values availble during different phases of operation, as shown in the example below:
type CheckStatus struct {
Value1 string
Value2 string
}
type ApplyStatus struct {
Value1 string
Value3 string
}
type Example struct {
*CheckStatus
*ApplyStatus
*resource.Status
}
this approach allows a user to treat execution phases are namespaces as in 3.2.2. This approach also may reduce errors related to unresolvable values.
\#+beginsrc hcl pre = "{{example.CheckStatus.Value1}}" post = "{{example.ApplyStatus.Value3}}" \#+endsrc hcl
Unresolvable references occur when a field referenced in a node is not defined until an execution phase greater than the maximum specified current execution phase. E.g. during planning any references to values defined by apply are unresolvable.
Unresolvability can be minmized by deferring template rendering until
the last possible minute. Specifically by rendering strings only
using during a modules Apply()
phase during the call to Apply
instead of during Prepare
templates may remain resolvable during a
larger set of run conditions.
To facilitate this the interface to Check()
and Apply()
has been
expanded to support a Renderer
as a parameter.