Skip to main content
The Serval Terraform provider manages workflows as code. Each workflow’s TypeScript lives in your repository, changes go through pull request review, and terraform apply publishes the reviewed version to Serval.
To keep workflows in Git without Terraform, use the Serval CLI. It pulls a team’s workflows and skills into files and pushes your edits back.

What Terraform manages

The serval_workflow resource covers a workflow’s code and core settings: id and has_unpublished_changes are read-only. Set these in the app instead. Terraform updates leave them unchanged:
  • Who can run the workflow
  • Service Catalog listing and the Service Catalog description
  • The access package whose connections the workflow uses. Workflows created by Terraform start in the team’s default package.
  • Approvals
Leave out the provider’s execution_scope attribute. Serval no longer reads it. Set who can run a workflow in the app.

Before you begin

You need:
  • Terraform 1.11 or later
  • Remote state storage that everyone running Terraform shares, such as an S3 bucket or HCP Terraform
  • Two API keys, both restricted to the teams Terraform manages:
    • An apply key with the workflows:read, workflows:write, and workflows:deploy permission bundles. workflows:deploy is only needed to publish. Without it, applies save drafts.
    • A plan key with only workflows:read, for running terraform plan on pull requests.
    Org Admins create keys in Organization settings, and Team Managers create team-scoped keys in Team Settings → API Keys.
  • The UUID of each team. Open Team Settings → Your Team and select Copy team UUID.
Don’t use local state for a shared configuration. A run that starts from empty state tries to create every workflow again, and the apply fails because workflow names must be unique within a team.

Set up the provider

1

Declare the provider

Turn on state locking so two runs can’t write state at once. use_lockfile does this for S3; other backends have their own setting.Pin the minor version. The provider is pre-1.0, so a new minor release can include breaking changes. ~> 0.26.0 accepts only patch releases.
2

Provide credentials

The provider reads the API key from environment variables and exchanges it for an access token on each run:
If your organization is in the EU region, also set the API URL:
Data residency lists the API URL for each region. Keep credentials out of .tf files and store them as CI secrets.

Define a workflow

Keep each workflow’s code in its own file and load it with file():
A team UUID isn’t secret, so keep it in the configuration where CI runs can read it. file() passes the code through unchanged. If you write code inline with <<-EOT instead, Terraform reads ${...} and %{...} as its own syntax. Escape them as $${...} and %%{...} so TypeScript template literals reach Serval intact.

Bring existing workflows under Terraform

Import workflows that already exist instead of recreating them. An import keeps the workflow’s ID, version history, and run history.
1

Pull the code and IDs

Run serval pull for the team. Each workflow.yaml it writes includes the workflow’s id, and index.ts holds the code to copy into your repository.
2

Add an import block next to each resource

3

Plan and apply

Run terraform plan and adjust the configuration until the plan shows only imports. Set is_published to match the workflow: true for a live workflow, false for one that has never been published. Importing an unpublished workflow with is_published = true publishes it on apply. Apply the plan, then delete the import blocks.

Review changes in pull requests

A typical setup runs terraform plan on every pull request and terraform apply after merge, so changes reach Serval only after review. A pull request can change the files a CI job runs, so give pull-request jobs only the read-only plan key. Keep the apply key in a GitHub environment whose deployment branches are limited to main. Jobs from other branches can’t read its secrets. Anyone who can open a pull request can still use the plan key to read workflows on its teams, so limit it to the teams this repository manages. This GitHub Actions example uses:
  • Repository secrets SERVAL_PLAN_CLIENT_ID and SERVAL_PLAN_CLIENT_SECRET for the plan key
  • An environment named serval with secrets SERVAL_CLIENT_ID and SERVAL_CLIENT_SECRET for the apply key
Both jobs also need credentials for your state storage.
The serval-apply concurrency group runs one apply at a time. Pull-request plans aren’t in it, so they never cancel a waiting apply. Plans skip the state lock with -lock=false, and -lock-timeout lets an apply wait out any other lock holder instead of failing. Protect main so changes merge only after review.

How applies behave

  • Publishing: With is_published = true, each apply that changes the workflow publishes a new version, which appears in version history. With is_published = false, the change is saved as a draft. It doesn’t unpublish a live workflow, and on a live workflow every plan shows is_published changing, so keep it true once a workflow is published.
  • Failed publishing: If Serval rejects the code, the apply fails before anything changes. If publishing fails for another reason, such as a connection the workflow can’t use, the new code can stay behind as a draft while the live version is unchanged.
  • Edits made in Serval: Terraform compares your files with the workflow’s latest draft. Changes made in the app or with Catalyst show up in the next plan, and an apply replaces them with the code in your repository. Make changes to Terraform-managed workflows in the repository.
  • Removing a resource: Deleting a serval_workflow block, or running terraform destroy, deletes the workflow in Serval. You can restore it from Team Settings → Recently deleted for 30 days. It comes back as an unpublished draft, so import it again before managing it with Terraform.
  • Changing team_id: Terraform deletes the workflow and creates a new one with a new ID. Anything that refers to the old ID stops working.