Skip to content

Parameter Rendering

The core of template rendering revolves around Parameter rendering. When a template is rendered, the parameter look up functions provide a value.

  • .Param
  • .ParamExpand
  • .ParamCompose
  • .ParamComposeExpand

Additionally, there is a test for existence function to provide the template the ability to handle missing or unset parameters.

  • .ParamExists

.Param

.Param is the basic lookup function. It will use Parameter Precedence to lookup a value.

.ParamExpand

.ParamExpand uses .Param to look up a value and then runs that value through the render engine for the object again. This will be applied recursively. More information is at Understanding .Param vs .ParamExpand.

.ParamExpand also handles expansion in list and map objects. For lists, each element of the list is run through the template engine. This allows for list elements to be expandable as well. For maps, the keys are not expandable; they must be strings. The values within the maps will be run through the template engine as well. This allows for the elements with the map to be indirect references as well.

Example

Here is an example parameters on a machine.

Params:
  p1: "this is a string"
  p2: "{{.ParamExpand "p1"}}"

The both parameters are strings, but the parameter p2 contains a template. If p2 is rended with .ParamExpand "p2", the value will be the value of p1, "this is a string".

.ParamExpand Concerns

  • Parameters referenced must either:

  • Define their own defaults, or

  • The template must handle missing defaults during the render expansion.

  • Avoid using .ParamExpand for template content parameters, as these are particularly prone to rendering issues.

  • Be aware that type mismatches will not be caught early, making debugging more complex.

.ParamCompose

.ParamCompose provides a path for aggregate list or map date into a single value. In some cases, a parameter's value should be the aggregate of all the values of that parameter in the precedence chain.

For lists, the higher precedence references are prepended to the returned list. The system creates an empty list and works up the list prepending results onto the list.

For maps, the higher precedence references are merged into the return maps. The systems creats an empty map and works up the precedence list apply a deep merge function against the map. This allows higher precedence references have sparse maps that inherit from the lower precedence base maps.

Example

In the bios plugin, can be configured to use composition on bios-target-configuration and bios-target-configurations.

Example

Here is a set of yaml representation for the global profile and a machine parameters.

global:
  Params:
    p1-list: [ "a", "b", "c" ]
    p2-map: { "a": 1, "b", 2 }

machine:
  Params:
    p1-list: [ "d", "e", "f" ]
    p2-map: [ "b": 3, "c": 3 ]

.ParamCompose "p1-list" | toJson renders:

[ "d", "e", "f", "a", "b", "c"]

.ParamCompose "p2-map" | toJson renders:

{ "a": 1, "b": 3, "c": 3 }

.ParamComposeExpand

.ParamComposeExpand does composition upon a parameter name. Once the list or map is constructed, the map or list will have the .ParamExpand function applied to it. Since .ParamExpand handles expasion within the list or map object, the gathered expansions will also be expanded.

Example

Here is a set of yaml representation for the global profile and a machine parameters.

global:
  Params:
    p0-value: 12
    p1-list: [ "a", "{{.ParamExpand \"p0-value\"}}", "c" ]
    p2-map: { "a": 1, "b", 2 }

machine:
  Params:
    p1-list: [ "d", "e", "f" ]
    p2-map: [ "b": 3, "c": "{{.ParamExpand \"p0-value\"}}" ]

.ParamComposeExpand "p1-list" | toJson renders:

[ "d", "e", "f", "a", 12, "c"]

.ParamCompose "p2-map" | toJson renders:

{ "a": 1, "b": 3, "c": 12 }

.ParamExists

.ParamExists checks to see if the parameter has non-nil value. The function returns true if the parameter as default value in the Schema definition or the object has the parameter specified within the Parameter Precedence scope. Otherwise, the function returns false.

Some parameters are reasonable defined where presence implies state as well. This is not the recommended path, but is some cases generate a simpler path. Instead of requiring two boolean variables to define a value and if the value should be used, a single boolean that is unset could be used instead.

Parameter Precedence

A parameter is looked up from the top to bottom of this list depending upon its type.

Machine, Cluster, or ResourceBroker Order

This is used when rendering tasks and templates for a machine-like object in workflow mode.

  • Machine Parameter
  • Machine Profiles
  • Stage Parameter
  • Stage Profiles
  • Lease Parameter
  • Lease Profiles
  • Reservation Parameter
  • Reservation Profiles
  • Subnet Parameter
  • Subnet Profiles
  • Global Profile
  • Parameter default.

WorkOrder Order

This is used when rendering tasks and templates for a machine-like object in workorder mode.

  • WorkOrder Parameter
  • WorkOrder Profiles
  • Machine Parameter
  • Machine Profiles
  • Stage Parameter
  • Stage Profiles
  • BluePrint Parameter
  • BluePrint Profiles
  • Lease Parameter
  • Lease Profiles
  • Reservation Parameter
  • Reservation Profiles
  • Subnet Parameter
  • Subnet Profiles
  • Global Profile
  • Parameter default.

Validating Parameter Data

When checking a profile for a parameter, the Params are checked first and then each profile in the Profiles list is processed. A profile is only processed once.

This is handled by the template render engine. Values at the top of the list will replace values lower in the list.

You can see the state on a machine by doing:

# All parameters - No parameter defaults
drpcli machines params <Machine ID> --aggregate

# Single parameter - includes parameter default if the parameter is not another location
drpcli machines get <Machine ID> param ipmi/configure/network --aggregate

Note

Additionally, --expand and --compose can be added to apply those functions as well.

Note

Composition works in reverse. See .ParamCompose or .ParamComposeExpand.

Discovering Parameter Defaults Directly

To view what the default value of a param is you can find it in the UX by clicking on the Param navigation link on the left side of the screen, then searching for the param, then clicking on it. You can also use a simple drpcli command.

$ drpcli params show burnin-skip|jq .Schema
{
  "default": false,
  "type": "boolean"
}

Best Practices

  • Prefer .Param over .ParamExpand for most use cases.

  • Use .ParamExpand only when you need template evaluation within a parameter value.

  • Ensure that all expanded parameters have safe defaults and predictable rendering behavior.

  • Validate carefully to avoid runtime failures in critical automation flows.

Understanding .Param vs .ParamExpand

When working with the RackN Digital Rebar Platform (DRP) template system, it’s important to understand the nuanced differences between the .Param and .ParamExpand functions. While both are used to resolve parameter values, .ParamExpand introduces specific behaviors and pitfalls that can impact template rendering, error handling, and type validation.

Key Differences and Considerations

Rendering Failures

If the expansion references malformed templates, rendering will fail with a recoverable error.

In contrast, .Param will typically return the raw parameter value.

Default Propagation

  • With .Param, defaults propagate as expected.
  • With .ParamExpand, default handling less determinant.

If a parameter has a default value defined as another parameter expansion, but that referenced parameter does not have a default, the template fails as if the parameter is missing.

Parameter Existence Checks

.ParamExists behavior can be misleading when combined with .ParamExpand.

The presence of a template definition will cause .ParamExists to return true. However, this does not guarantee that rendering will succeed, since template evaluation may still fail later.

Type Checking Limitations

.Param does not alter type validation based on the parameter definition.

.ParamExpand defers or ignores type validation until rendering. This can lead to unexpected results.

Example

The dns-servers parameter is a list of strings that are IP addresses to DNS servers.

dns-servers: '{{ .ParamExpand "my-cool-list" }}'

This parameter expansion is allowed by the type-checker. It notices that an expansion is in play and will skip validation assuming that expansion will turn into a list of strings at render time.

YAML Example: .Param vs .ParamExpand

The yaml file below describe the parameters section of a machine and the templates section of a task as a simple example of rendering differences.

Params:
  message: "Hello, world!"
  wrapped-message: '{{ .Param "message" }}'
  expanded-message: '{{ .ParamExpand "message-template" }}'
  message-template: 'Hello, {{ if .ParamExists "username" }}{{.Param "username"}}{{else}}guest{{end}}!'

Template:
  # Using `.Param` (raw value lookup)
  - Content: |
      Raw lookup with .Param:
      Message = {{ .Param "wrapped-message" }}
    Description: "Shows stored string value only (no template expansion)."

  # Using `.ParamExpand` (evaluates template inside parameter value)
  - Content: |
      Expanded lookup with `.ParamExpand`:
      Message = {{ .ParamExpand "expanded-message" }}
    Description: "Evaluates the embedded template from parameter content."

How It Renders With .Param

Raw lookup with .Param:
Message = {{ .Param "message" }}

Note

(just shows the literal string stored in the parameter — no expansion happens).

How It Renders With .ParamExpand

Expanded lookup with `.ParamExpand`:
Message = Hello, guest!

Note

(the embedded template inside the parameter is evaluated and rendered).

Failure Example: .ParamExpand with Missing Defaults

The yaml file below describe the parameters section of a machine and the templates section of a task as a simple example of rendering differences.

Params:
  username: # no default defined
  greeting-template: 'Hello, {{ .Param "username" }}!'

Template:
  # Using `.ParamExpand`
  - Content: |
      Expanded Greeting:
      {{ .ParamExpand "greeting-template" }}
    Description: "Fails if `username` is not defined."

What Happens at Render Time

Case 1: username is defined
Expanded Greeting:
Hello, Alice!
Case 2: username is missing (no default)
Error: : template: tmp:1:10: executing "tmp" at <.Param>: error calling Param: Parameter username not in scope

Note

The actual error will vary depending upon the templates usage. See the troubleshooting sections of the Tasks, Stages, Bootenvs, or subnet automation sections.

The .ParamExists check would have passed because the greeting-template exists, but rendering still fails.

Safer Version with .ParamExists

The yaml file below describe the parameters section of a machine and the templates section of a task as a simple example of rendering differences.

Params:
  username: # optional
  greeting-template: 'Hello, {{ if .ParamExists "username" }}{{.Param "username"}}{{else}}guest{{end}}!'

Template:
  - Content: |
      Expanded Greeting:
      {{ .ParamExpand "greeting-template" }}

Now, if username is missing, it safely renders:

Expanded Greeting:
Hello, guest!

Note

Even better would be creating a Param with a default value.

✅ This illustrates the hidden risk of .ParamExpand:

Even when .ParamExists passes, the render can still fail if defaults aren’t carefully defined.

Best practice is to always add defaults when using .ParamExpand.

Summary

✅ This highlights the core difference:

  • .Param gives you the raw value.

  • .ParamExpand executes any template logic stored in the parameter.