Skip to content

Workflow Tool»

The Workflow Tool stack setting allows you to choose between:

  • OpenTofu — out-of-the-box support for OpenTofu.
  • Custom — allows you to customize the commands that are executed as part of the workflow.

OpenTofu»

The OpenTofu option gives you out-of-the-box support for OpenTofu. All you need to do is choose the version you want to use and you're good to go.

If you need extra flags, you do not need the Custom option. Have Spacelift manage the binary and use TF_CLI_ARGS_<command> for the extra flags. There are only two limits to this feature:

  1. The values are static strings and cannot reference the workspace name.
  2. Flags cannot be added to the apply stage.

Custom»

This option allows you to customize the commands that are executed as part of Spacelift's OpenTofu workflow. This can be useful if you want to run a custom binary instead of one of the OpenTofu versions supported out of the box by Spacelift.

Info

Note that any custom binary is considered third-party software and you need to make sure you have the necessary rights (e.g. license) to use it.

How does it work?»

Each stage of the OpenTofu workflow uses certain commands to perform tasks such as generating a plan, or getting the current state. We provide a built-in set of commands to use for OpenTofu, but you can also specify your own custom commands. You do this via a file called .spacelift/workflow.yml.

The following is an example of what the default workflow commands look like for OpenTofu:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# Used to initialize your root module.
init: tofu init -input=false

# Used to select the correct workspace. Only used for Stacks that are using a custom state
# backend, and which have the Workspace setting configured.
#
# Available template parameters:
# - .WorkspaceName - contains the name of the workspace to select.
workspaceSelect: tofu workspace select "{{ .WorkspaceName }}"

# Used to create a new workspace if none with the required name exists. Only used for Stacks
# that are using a custom state backend, and which have the Workspace setting configured.
#
# Available template parameters:
# - .WorkspaceName - contains the name of the workspace to select.
workspaceNew: tofu workspace new "{{ .WorkspaceName }}"

# Used to generate a plan of the infrastructure changes that will be applied.
#
# Available template parameters:
# - .Lock         - whether the state should be locked.
# - .Refresh      - whether state resources should be refreshed. Currently always
#                   true for OpenTofu stacks, so -refresh=false is never emitted.
# - .Concise      - whether concise output is enabled (adds -concise flag). Only set
#                   by the built-in OpenTofu workflow; always false for custom workflows.
# - .JsonIntoPath - path OpenTofu should write its JSON output to. Only populated
#                   by the built-in OpenTofu workflow on OpenTofu 1.12+. Always empty
#                   for custom workflows, so keep the surrounding 'if' guard.
# - .PlanFileName - the name of the file to store the plan in.
# - .Targets      - the list of targets to plan. Used during targeted replans.
plan: >-
  tofu plan -input=false -lock={{ .Lock }}
  {{ if not .Refresh }}-refresh=false {{ end }}
  {{ if .Concise }}-concise {{ end }}
  {{ if .JsonIntoPath }}-json-into="{{ .JsonIntoPath }}" {{ end }}
  -out={{ .PlanFileName }}
  {{ range .Targets }}-target='{{ . }}' {{ end }}

# Outputs the current state information as JSON.
showState: tofu show -json

# Used to convert a plan file to its JSON representation.
#
# Available template parameters:
# - .PlanFileName - the name of the file containing the plan.
showPlan: tofu show -json "{{ .PlanFileName }}"

# Used to get the current outputs from the state.
getOutputs: tofu output -json

# Used to apply any changes contained in the specified plan.
#
# Available template parameters:
# - .Concise      - whether concise output is enabled (adds -concise flag). Only set
#                   by the built-in OpenTofu workflow; always false for custom workflows.
# - .JsonIntoPath - path OpenTofu should write its JSON output to. Only populated
#                   by the built-in OpenTofu workflow on OpenTofu 1.12+. Always empty
#                   for custom workflows, so keep the surrounding 'if' guard.
# - .PlanFileName - the name of the file containing the plan.
apply: >-
  tofu apply -auto-approve -input=false
  {{ if .Concise }}-concise {{ end }}
  {{ if .JsonIntoPath }}-json-into="{{ .JsonIntoPath }}" {{ end }}
  "{{ .PlanFileName }}"

# Used to tear down any resources as part of deleting a stack.
#
# Available template parameters:
# - .Concise - whether concise output is enabled (adds -concise flag). Only set by
#              the built-in OpenTofu workflow; always false for custom workflows.
destroy: >-
  tofu destroy -auto-approve -input=false
  {{ if .Concise }}-concise {{ end }}

OpenTofu-specific features need the built-in workflow

Concise output, CPU profiling and structured -json-into logs are only applied by the built-in OpenTofu workflow. With a custom workflow, .Concise is always false, .JsonIntoPath is always empty, no CPU profile is captured, and Spacelift does not read any -json-into file you write yourself. The stack-level toggles for these features have no effect while the workflow tool is set to Custom.

Warning

Spacelift treats any non-zero exit code from a workflow command as an error. Do not use flags like -detailed-exitcode with tofu, since they cause the command to exit with a non-zero status even on success.

How to configure a custom tool»

To use a custom tool, three things are required:

  1. A way of providing the tool to your runs.
  2. A way of specifying the commands that should be executed.
  3. Indicating that you want to use a custom tool on your stack.

Providing a tool»

There are two main ways of providing a custom tool:

Specifying the commands»

Your custom workflow commands need to be provided in a workflow.yml file stored in the .spacelift folder at the root of your workspace ({reporoot}/.spacelift/workflow.yml). Only the .yml extension is read. There are three main ways of providing this:

  1. Via a mounted file in the stack's environment.
  2. Via a mounted file stored in a context.
  3. Directly via your Git repo.

The option you choose will depend on your exact use-case, for example using the stack's environment allows you to quickly test out a new custom tool, using a context allows you to easily share the same configuration across multiple stacks, and storing the configuration in your Git repo allows you to track your settings along with the rest of your code.

Here is an example configuration to use a fictional tool called super-iac:

1
2
3
4
5
6
7
8
9
init: super-iac init -input=false
workspaceSelect: super-iac workspace select "{{ .WorkspaceName }}"
workspaceNew: super-iac workspace new "{{ .WorkspaceName }}"
plan: super-iac plan -input=false -lock={{ .Lock }} {{ if not .Refresh }}-refresh=false {{ end }}-out={{ .PlanFileName }} {{ range .Targets }}-target='{{ . }}' {{ end }}
showState: super-iac show -json
showPlan: super-iac show -json "{{ .PlanFileName }}"
getOutputs: super-iac output -json
apply: super-iac apply -auto-approve -input=false "{{ .PlanFileName }}"
destroy: super-iac destroy -auto-approve -input=false

Using a custom tool on a stack»

To update your stack to use a custom workflow tool, edit the Workflow tool setting in the Vendor settings:

Workflow tool Stack setting

When you choose the Custom option, the version selector will disappear:

Workflow tool Stack setting - no version displayed

This is because you are responsible for ensuring that the tool is available to the run, and Spacelift will not automatically download the tool for you. Setting a version together with the Custom tool is rejected with specifying version for custom tool workflow is not possible.

Runtime Configuration»

You can also use runtime configuration to configure the workflow tool for a stack. This allows you to try out a different configuration as part of a PR without adjusting your stack settings.

The following example shows how to configure a particular stack to use a custom tool:

1
2
3
4
5
6
version: "2"

stacks:
  custom-workflow-stack:
    opentofu:
      workflow_tool: "CUSTOM"

Spacelift ignores version in the opentofu block when workflow_tool is CUSTOM.

Troubleshooting»

Workflow file not found»

If no workflow.yml file has been created but your stack has been configured to use a custom tool, you may get an error message like the following:

Workflow file not found

The message reads Loading workflow failed: could not read custom workflow file source/.spacelift/workflow.yml. If this happens, please ensure you have added a .spacelift/workflow.yml file to your Git repository, or attached it to your stack's environment via a mounted file.

Invalid source: project root during runs»

If your workflow command file has been mounted at the wrong location, you may receive an error with the failed run like Invalid source: project root <root listed in workflow file> does not exist. Your custom workflow tool will not work. Make sure the folder is at the actual root of your workspace.

For example, if you mounted the file at /mnt/workspace/.spacelift/workflow.yml, try /mnt/workspace/source/.spacelift/workflow.yml.

Commands missing»

If your .spacelift/workflow.yml does not contain all the required command definitions, or if any commands are empty, you will get an error message like the following:

Workflow commands missing

The message reads validation failed for custom workflow commands: commands init, workspaceSelect cannot be empty. This check is designed as a protection mechanism in case new commands are added but your workflow hasn't been updated. In this case, please provide an implementation for the specified commands (in the example the init and workspaceSelect commands). Every template, including destroy, is also parsed at this point, so a template syntax error fails the run during initialization rather than when the command is first used.

Tool not found»

If your custom tool binary cannot be found, the shell reports it and the phase fails with exit code 127, like the following:

Workflow tool not found

In this situation, please ensure that you are providing a custom workflow tool via a custom runner image or workflow hook.