Inputs & Outputs
Every runbook has a global context, a shared set of key-value pairs that blocks read and write. The keys are variables.
Values enter the global context in two ways. Input providers such as <Inputs> and <Template> collect values from the user through a form, and consumer blocks pull them in with the inputsId prop. Blocks that run something, such as <Command>, <Check> and <DirPicker>, publish results that later blocks read through the outputs namespace.
Variables
Section titled “Variables”Reference a variable in templates, scripts, and block props as {{ .inputs.VarName }}. For example:
<Command id="create-account" command='aws organizations create-account --email "{{ .inputs.Email }}" --account-name "{{ .inputs.AccountName }}"'/>When this block runs, Runbooks resolves {{ .inputs.Email }} and {{ .inputs.AccountName }} from the global context, producing a command like:
aws organizations create-account --email "alice@example.com" --account-name "my-account"Boilerplate variables
Section titled “Boilerplate variables”Runbooks uses Gruntwork Boilerplate as its template engine, so variables are Boilerplate variables:
- The same
{{ .inputs.VarName }}syntax - The same types (
string,int,float,bool,enum,list,map) - The same validation rules (
required,email,url,alpha,digit,alphanumeric,countrycode2,semver,regex) - The same
boilerplate.ymlformat for defining them - The Boilerplate helper functions and pipe modifiers (
upper,lower,snakeCase,camelCase,fromJson, conditionals, loops)
The Boilerplate templates page covers the syntax, variable definitions, helper functions, and testing templates locally with the Boilerplate CLI.
Template syntax
Section titled “Template syntax”Reference inputs as {{ .inputs.VarName }} and outputs as {{ .outputs.blockId.outputName }}. Runbooks also exposes every input at the root of the context, so {{ .VarName }} works in every block and in templates written for the Boilerplate CLI.
Input providers
Section titled “Input providers”An input provider is a block that collects variable values and makes them available to other blocks.
<Inputs>
Section titled “<Inputs>”The <Inputs> block renders a form from a boilerplate.yml definition. This inline definition declares one variable, ProjectName:
<Inputs id="config">```yamlvariables: - name: ProjectName type: string description: Name for your project```</Inputs><Template>
Section titled “<Template>”The <Template> block generates files from a Boilerplate template directory. It renders a form for the variables in that directory’s boilerplate.yml, so it is an input provider too.
<Template id="infra" path="templates/rds" />Here the variables come from templates/rds/boilerplate.yml.
Wiring blocks with inputsId
Section titled “Wiring blocks with inputsId”Any block that consumes variables (<Command>, <Check>, <Template>, <TemplateInline>) accepts an inputsId prop naming an input provider by its id. This Command uses the value collected by the Inputs block:
<Inputs id="config">```yamlvariables: - name: ProjectName type: string```</Inputs>
<Command inputsId="config" command='mkdir "{{ .inputs.ProjectName }}"' />Multiple sources
Section titled “Multiple sources”Pass an array of IDs to inputsId to merge variables from more than one input provider. Variables merge in order, so on a name conflict the later ID wins.
<Command inputsId={["global-config", "local-config"]} command='deploy --env "{{ .inputs.Environment }}" --name "{{ .inputs.AppName }}"'/>Embedded inputs
Section titled “Embedded inputs”An <Inputs> block nested inside a <Command> or <Check> feeds the parent without inputsId:
<Command command='echo "Hello, {{ .inputs.Name }}!"'> <Inputs id="greeting"> ```yaml variables: - name: Name type: string ``` </Inputs></Command>Other blocks can still reference the embedded Inputs with inputsId="greeting".
When variables overlap
Section titled “When variables overlap”A consuming block receives every variable from its input provider.
<Template> also has its own boilerplate.yml, which can define variables too. When a Template imports from an input provider, Runbooks compares the two sets:
- A variable only the Template defines appears as an editable field in the Template’s form. Use this to collect extra inputs, such as an environment name, that the provider doesn’t know about.
- A variable only the input provider defines is passed to the template engine and doesn’t appear in the Template’s form.
- A variable both define appears in the Template’s form read-only, synced to the input provider’s value, so the two forms can’t disagree.
Block outputs
Section titled “Block outputs”A block that has run can publish outputs for later blocks to read.
Producing outputs
Section titled “Producing outputs”<Command> and <Check> blocks produce outputs by writing key=value pairs to the file specified by the $RUNBOOK_OUTPUT environment variable:
#!/bin/bashACCOUNT_ID=$(aws organizations create-account ...)
echo "account_id=$ACCOUNT_ID" >> "$RUNBOOK_OUTPUT"echo "region=us-west-2" >> "$RUNBOOK_OUTPUT"For complex data (lists, maps), serialize as JSON:
echo 'users=["alice","bob","charlie"]' >> "$RUNBOOK_OUTPUT"Runbooks sets $RUNBOOK_OUTPUT before the script runs.
Sensitive outputs
Section titled “Sensitive outputs”When an output holds a credential, such as a secret key, token or password, write sensitive: before its key:
echo "AWS_ACCESS_KEY_ID=$ACCESS_KEY_ID" >> "$RUNBOOK_OUTPUT"echo "sensitive:AWS_SECRET_ACCESS_KEY=$SECRET_ACCESS_KEY" >> "$RUNBOOK_OUTPUT"echo "sensitive:AWS_SESSION_TOKEN=$SESSION_TOKEN" >> "$RUNBOOK_OUTPUT"The prefix isn’t part of the key. The second output above is named AWS_SECRET_ACCESS_KEY, so later blocks read it as {{ .outputs.<block_id>.AWS_SECRET_ACCESS_KEY }}. Auth blocks that take credentials from a block, such as <AwsAuth detectCredentials={[{ block: '<block-id>' }]} />, find it by its usual name too.
Runbooks hands a sensitive output’s real value only to what uses it:
- A Command or Check script that references it in a template runs with the real value.
- A
<Template>, or a<TemplateInline>withgenerateFile, writes the real value into the file it generates. - An auth block’s
detectCredentialsblock source reads the real value. - The copy button on the output’s row in View Outputs copies the real value.
- The test CLI’s
output_equalsandoutput_matchesassertions compare the real value.
Everywhere Runbooks only shows the value, it masks it:
- View Outputs shows
••••••••, and Copy JSON writes"<redacted>". - The script view of a Command or Check that references the output shows
<redacted>in its place, though the script runs with the real value. - A
<TemplateInline>withoutgenerateFileshows<redacted>in its preview. - A block prop that references the output, such as a title, a description, a clone URL or a pull request’s title and body, gets
<redacted>. - The test CLI prints
<redacted>in its--verboseoutput, in the rendered output of a<TemplateInline>and in the message of a failedoutput_equalsoroutput_matchesassertion.
An empty sensitive output reveals nothing, so wherever <redacted> would stand in for it, it stays empty. An {{ if }} on it then takes the same branch in the script view as when the script runs.
<redacted> only stands in for the value, so a template that processes the value can’t process <redacted>. For example, the script view of {{ (fromJson .outputs.<block_id>.credentials).user }} shows a render error that explains this, though the script runs with the real value. A <TemplateInline> preview of it shows the same error.
Instruction mode treats a sensitive output like one that hasn’t been produced yet, even if its step already ran. The command shows a <name> placeholder, with a field to paste the value into.
Auth blocks mark their own secrets the same way, including a credential they read from a block that marked it sensitive:. <AwsAuth> publishes AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN as sensitive outputs. <GitAuth>, <GitHubAuth> and <GitLabAuth> publish their token as one when their outputs include it, which they do for a personal access token or a { block } source. So {{ .outputs.<auth_block_id>.AWS_SECRET_ACCESS_KEY }} is masked like any other sensitive output.
Once any line marks a key sensitive:, that output stays sensitive, even if a later line writes the same key without the prefix. The prefix is lowercase and comes first on the line.
Consuming outputs
Section titled “Consuming outputs”Later blocks reference outputs through the outputs namespace:
{{ .outputs.<block-id>.<output-name> }}For example, a Command whose id is create-account that outputs account_id is referenced as:
{{ .outputs.create_account.account_id }}This syntax works in <Command> and <Check> scripts, <Template> files, and <TemplateInline> content.
Iterating over complex outputs
Section titled “Iterating over complex outputs”Use fromJson to parse JSON output values, then range to iterate:
{{- range (fromJson .outputs.list_users.users) }}- {{ . }}{{- end }}Output dependencies
Section titled “Output dependencies”When a block references outputs from a block that hasn’t run yet, a warning names the blocks that need to run first, the Run or Generate button is disabled, and the template shows the raw {{ }} syntax.
A Command or Check block’s button is also disabled while the upstream block is running again. See Run order.
After a block runs, View Outputs below the log viewer lists its outputs in a table. Sensitive outputs are masked there.
Outputs a block emits only sometimes
Section titled “Outputs a block emits only sometimes”Some outputs exist only some of the time. <GitClone> publishes org_id and repo_id only when it can look the repository up on GitHub, and a script can write an output only when it has something to report. Templates treat a missing key as an error, so a block that reads such an output waits until the output exists, which may be never.
To read an output that may be missing, check for it with hasKey and read it only inside that branch:
{{ if hasKey .outputs.clone_repo "org_id" }}org_id = "{{ .outputs.clone_repo.org_id }}"{{ end }}When every reference to an output sits inside such a branch, the block waits for the upstream block to run, but not for that output. The branch is skipped when the output is missing. If the upstream block later withdraws its outputs, for example when a <GitClone> is reset or a <Command> fails, the block waits again.
A guard covers only the branch it opens. A reference anywhere else makes the block wait for the output again, whether it comes after {{ end }}, in an {{ else }} branch, or in another file of the same template. These forms count as a guard:
{{ if hasKey .outputs.<block_id> "<output>" }}, or$.outputsin place of.outputsinsiderangeorwith{{ if and (hasKey .outputs.<block_id> "<output>") ... }}, which also guards the arguments ofandthat follow it- The
{{ else }}branch of{{ if not (hasKey .outputs.<block_id> "<output>") }}
Other forms aren’t recognized, such as a guard on a variable ({{ if hasKey $repo "org_id" }}), so the block waits for the output.
Combining inputs and outputs
Section titled “Combining inputs and outputs”Inputs and block outputs can appear in the same template:
# From an Inputs block (via inputsId)environment = "{{ .inputs.Environment }}"
# From a Command block's outputsaccount_id = "{{ .outputs.create_account.account_id }}"Example
Section titled “Example”The block-outputs feature demo passes inputs and outputs between Command, Check, Template, and TemplateInline blocks.
To run it directly:
runbooks open https://github.com/gruntwork-io/runbooks/tree/main/testdata/feature-demos/block-outputs