Skip to content

Flows TF Provider

The Flows TF Provider can be used with OpenTofu and Terraform to manage Flows (yes, Flows-as-Code!), app installations, secrets, and more.

You can find installation and basic usage instructions in the GitHub repository and resource documentation on the OpenTofu registry.

When using with Terraform (as opposed to OpenTofu), you will have to provide the full registry path:

1
2
3
4
5
6
7
terraform {
  required_providers {
    flows = {
      source = "registry.opentofu.org/spacelift-io/flows"
    }
  }
}

Managing Flows as Code»

Whether you want to version control your flows, have a code-review driven change management process, deploy them across multiple environments, or generate them programmatically, the Flows TF Provider can help you achieve that.

Flows themselves have a YAML representation, which you can export from the UI. Without any blocks selected, click cmd+c to copy the entire flow (or with blocks selected, to copy just those), you can then paste that into a YAML file.

Here's an example of such a Flow (the #1 sample flow):

 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
blocks:
  - name: Sample Endpoint
    type:
      coreBlock: httpEndpoint
    position: [-50, 82]
    config:
      path: /hello/{name}
      methods:
        - GET
      audience: world
    inputs:
      default:
        links:
          - block: Sleep
            output: default
        config:
          statusCode: 200
          body: !expr '`Hello, ${outputs.sampleEndpoint.params.name}`'
  - name: Sleep
    type:
      coreBlock: sleep
    position: [-50, 255]
    inputs:
      default:
        links:
          - block: Sample Endpoint
            output: requests
        config:
          sleep: 5
notes:
  - body: |-
      # Simple Webhook Handler

      You can see that the **HTTP Endpoint** block produces events on its `requests` output, and expects a response to arrive at its input.

      To test this Flow, first click on the **Sample Endpoint** block, then click on the copy button next to the **Path** field. You can navigate to the copied URL using your browser, or curl it.

      Once you do that and get a response, you will also see events appear on the canvas next to various outputs. Click on an output to see the events it produced!

      You can **re-emit** those events, in order to replay parts of your flow - great for debugging, or just building up your flow piece by piece!
    position: [273, 13]
    style:
      width: 400
      height: 380
      color: '#18181b'

This is pretty self-explanatory. For config fields, you can just input raw values, or provide expressions using the !expr tag.

Some core blocks, like the Spacelift Deploy blocks, share one coreBlock value (spacelift) and a kind that picks the specific block. kind is part of a block's identity, not just another config value, and changing it deletes and recreates the block.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
blocks:
  - name: Failed Runs
    type:
      coreBlock: spacelift
      kind: runEvents
    position: [0, 0]
    config:
      states:
        - FAILED
  - name: Retry
    type:
      coreBlock: spacelift
      kind: triggerRun
    position: [0, 160]
    inputs:
      trigger:
        links:
          - block: Failed Runs
            output: runs
        config:
          stackId: !expr outputs.failedRuns.stackId

Note that a block's own config section holds the fields that shape the block itself and is evaluated once when the flow is saved. E.g., the filters on runEvents above, or the Template a deployTemplate block deploys. Everything evaluated per event, and therefore able to read outputs, goes under inputs.<socket>.config.

Things become slightly more complex if you have apps. Here you can see a Flow with app blocks used:

 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
appInstallations:
  slack:
    appName: Slack
    appVersion: 0.2.2
    id: 019a4a1d-3494-758a-9df9-67d6e5f4660b
blocks:
  - name: Mentions
    type:
      appBlock:
        block: appMentionSubscription
        appInstallation: slack
    position: [116, 0]
  - name: Is Hello Command
    description: Checks if the mention contains a hello command
    type:
      coreBlock: condition
    position: [0, 160]
    inputs:
      default:
        links:
          - block: Mentions
            output: default
        config:
          condition: !expr /^<@U\w+>\s+hello$/i.test(outputs.mentions.text.trim())
  - name: Hello Response
    description: Sends a hello message to the user
    type:
      appBlock:
        block: sendTextMessage
        appInstallation: slack
    position: [0, 293]
    inputs:
      default:
        links:
          - block: Is Hello Command
            output: then
        config:
          channelId: !expr outputs.mentions.channel
          text: !expr '`Hello <@${outputs.mentions.user}>!`'
          threadTs: !expr outputs.mentions.thread_ts || outputs.mentions.ts

Here you can see that the type of app blocks refers back to the installation they are related to, while an initial appInstallations section describes the apps used, their versions, and installation IDs.

Importantly, the YAML representation only manages the "configuration layer" of the Flow. It does not include e.g. lifecycle confirmation.

Managing Flows with the TF Provider»

With the Flows TF Provider, you can use the flows_flow resource to create and update Flows. Additionally, you are able to parameterize the app installations (so you don't have to include the IDs in the yaml file):

1
2
3
4
5
6
7
8
9
resource "flows_flow" "example" {
  project_id = "your-project-id"
  name       = "my-flow"
  definition = file("${path.module}/flow.yaml")

  app_installation_mapping = {
    my_app = "app-installation-id"
  }
}

Moreover, if you have blocks with a lifecycle, you are able to confirm them using the flows_entity_lifecycle_confirmation:

1
2
3
resource "flows_entity_lifecycle_confirmation" "example" {
  entity_id = flows_flow.example.blocks["my_entity"].id
}

Changing Block Names»

Blocks are uniquely identified by their names. This means that if you just change a block name, it will be deleted and recreated - for most blocks this is fine, but you will lose e.g. the event history of that block. Additionally, it will be problematic for blocks that manage external resources (like AWS Resource blocks).

To rename a block without losing its identity, you can use the alternativeLookupStrategy field. Here's an example of how to use it by referring to the old name of the block:

1
2
3
4
5
6
7
8
9
blocks:
- name: New Block Name
  alternativeLookupStrategy:
    name: Old Block Name
  type:
    appBlock:
      block: appMentionSubscription
      appInstallation: slack
  position: [116, 0]

Alternatively, you can also directly refer to the block ID:

1
2
3
4
5
6
7
8
9
blocks:
- name: New Block Name
  alternativeLookupStrategy:
    id: 019be5ca-3832-717a-b5d7-dfe4bc19569a
  type:
    appBlock:
      block: appMentionSubscription
      appInstallation: slack
  position: [116, 0]

Reference»

See the Flow Definition Reference for complete YAML schema documentation.