The sartar Terraform provider

The provider lets you describe your monitoring as code: folders, monitors, scenarios, notification channels and lists, roles and users. It works with Terraform and with OpenTofu. Every terraform command on this page works the same with tofu.

Install

There is nothing to download by hand. Declare the provider, and terraform init installs it.

terraform {
  required_providers {
    sartar = {
      source  = "sartar.app/harskogr/sartar"
      version = "~> 2.0"
    }
  }
}
terraform init

terraform init finds the provider from the host name in source, downloads the build for your platform, and verifies its signature and its checksum. It then records the checksums in .terraform.lock.hcl. Commit that file.

The source must be written in full, host name included. The provider is served by sartar, not by the public Terraform registry.

Supported platforms

Linux, macOS, Windows and FreeBSD, on amd64 and arm64. Linux, Windows and FreeBSD are also built for 386, and Linux and FreeBSD for arm.

Signing key

Releases are signed with this key. terraform init prints its identifier when it installs the provider.

Key ID       FDC7EE9C5F903C58
Fingerprint  598F C4D4 D2CB 22E0 A197  7BD3 FDC7 EE9C 5F90 3C58

Without network access

Download the archive of the provider on a machine that has access:

https://sartar.app/dl/terraform-provider-sartar/<version>/terraform-provider-sartar_<version>_<os>_<arch>.zip

Then extract it into the plugin directory of Terraform:

~/.terraform.d/plugins/sartar.app/harskogr/sartar/<version>/<os>_<arch>/

Configure

The provider needs the address of the API and an API key. Create the key in the web dashboard: account menu, API keys, Create key. See Before you start.

provider "sartar" {
  endpoint = "https://sartar.app/api"
  token    = var.sartar_token
}

variable "sartar_token" {
  type      = string
  sensitive = true
}
Argument Environment variable Value
endpoint SARTAR_ENDPOINT https://sartar.app/api
token SARTAR_TOKEN your API key

Both arguments are required, in the configuration or in the environment. Keep the key out of your repository. The environment is the simplest way:

export SARTAR_ENDPOINT=https://sartar.app/api
export SARTAR_TOKEN=<your API key>
terraform plan

Which key for which command

Command Key needed
terraform plan, terraform import, refresh a Read-only key is enough
terraform apply, terraform destroy a Read / write key

A good habit is to plan with a read-only key, and to keep the read / write key for the apply step of your pipeline.

A first configuration

This creates a folder, a notification channel, and a monitor inside the folder that alerts on the channel.

resource "sartar_folder" "prod" {
  name  = "Production"
  notes = "Customer-facing endpoints."
}

resource "sartar_notification_channel" "slack" {
  type = "slack"
  name = "ops-alerts"
  config = {
    webhook_url = var.slack_webhook_url
  }
}

variable "slack_webhook_url" {
  type      = string
  sensitive = true
}

resource "sartar_monitor" "api_health" {
  name    = "api health"
  url     = "https://api.example.com/health"
  period  = 60
  regions = ["fr"]
  folder  = "/${sartar_folder.prod.name}"

  http_check = {
    status_code = "200"
  }

  notification = {
    channel_id = sartar_notification_channel.slack.id
  }
}
terraform plan
terraform apply

Writing the folder as "/${sartar_folder.prod.name}" rather than as plain text tells Terraform that the monitor depends on the folder. The folder is then always created first.

Resources

Resource What it manages
sartar_folder a folder, and the settings inherited by what it contains
sartar_monitor an HTTP monitor
sartar_scenario a scenario and its steps
sartar_notification_channel a Slack, PagerDuty, Google Chat or webhook alert target
sartar_notification_list a named group of alert targets
sartar_mailbox an inbound e-mail address
sartar_role a named set of permissions
sartar_user a user of the account and their role
sartar_account_settings the default settings of the account
sartar_account_properties the properties of the account

sartar_folder

Attribute Type Notes
id string, computed uuid of the folder
name string, required unique in the account, must not contain /
notes string
parent_uuid string the parent folder, omitted for a folder at the root
properties map of strings
notification object alerting inherited by everything inside, same shape as on a monitor
resource "sartar_folder" "prod_eu" {
  name        = "Production EU"
  parent_uuid = sartar_folder.prod.id
}

sartar_monitor

Attribute Type Notes
id string, computed uuid of the monitor
name string, required
url string, required
period number seconds between two checks
regions set of strings exactly one region
enabled bool true by default
is_manual bool never scheduled, run on demand only
notes string
folder string path of the folder, such as /prod/edge. Empty means the root
calendar object restricts the schedule. See below
properties map of strings
http_request object method, body, preserve_cookies, real_hostname, headers
http_check object status_code, trust_certificate, retry, follow_redirect, and the checks on the response: headers, cookies, body, body_json
notification object the alert target. See below

Changing folder to a path that does not exist makes the apply fail. Create the folder first.

Schedule. Omit calendar to check around the clock.

# Weekdays only, from 08:00 to 18:00.
calendar = {
  days = [1, 2, 3, 4, 5]   # 0 is Sunday, 6 is Saturday
  from = "08:00"
  to   = "18:00"
}

# Once a day, at a fixed time. `period` is then ignored.
calendar = {
  daily_at = "02:30"
}

Request and checks.

http_request = {
  method = "POST"
  body   = "{\"ping\":true}"
  headers = [
    { header = "Content-Type", value = "application/json" },
  ]
}

http_check = {
  status_code = "2xx"
  body_json = [
    { key = "status", operator = "equals", value = "up" },
  ]
}

status_code accepts an exact code such as "200", or a class such as "2xx". retry and follow_redirect are true by default. Set one to false to turn it off.

Alert target. Set exactly one of email, channel_id, channel_name, list_id or list_name.

notification = {
  channel_name         = "ops-alerts"
  checks_before_alert  = 2
  checks_before_resume = 1
}

Omit a block to keep what the monitor inherits from its folders and from the defaults of the account.

sartar_scenario

Attribute Type Notes
id string, computed uuid of the scenario
name string, required unique in the account
region string one region for all the steps
period number seconds between two runs
enabled, is_manual, notes, folder, calendar same as on a monitor
notification object same shape as on a monitor
steps list, required the steps, in order

A step is either a check or a pause.

resource "sartar_scenario" "checkout" {
  name   = "checkout flow"
  region = "fr"
  period = 600

  steps = [
    {
      type = "monitor"
      name = "login"
      url  = "https://example.com/login"
      http_request = {
        method = "POST"
        body   = "{\"user\":\"demo\"}"
      }
      http_check = {
        status_code = "2xx"
      }
    },
    {
      type        = "wait"
      min_seconds = 3
      max_seconds = 8
    },
    {
      type = "monitor"
      name = "dashboard"
      url  = "https://example.com/dashboard"
      http_check = {
        status_code = "200"
        body = [
          { operator = "contains", value = "Welcome" },
        ]
      }
    },
  ]
}

A scenario alerts when the outcome of a run changes, from success to failure or back. It does not alert for each step.

Any change to a scenario recreates all of its steps.

sartar_notification_channel

Attribute Type Notes
id string, computed
type string, required slack, pagerduty, googlechat or webhook. Changing it replaces the channel
name string, required
config map of strings, sensitive depends on the type
Type Keys of config
slack webhook_url
pagerduty routing_key
googlechat webhook_url
webhook url, method, body

sartar_notification_list

A list delivers an alert to every one of its members. Each member sets exactly one of channel_id, channel_name or email.

resource "sartar_notification_list" "oncall" {
  name = "on-call"
  members = [
    { channel_id = sartar_notification_channel.slack.id },
    { email = "alice@example.com" },
  ]
}

An e-mail address must belong to a user of the account. A list that something still alerts to cannot be deleted.

sartar_mailbox

An e-mail address that receives mail for your account. You choose the name, and sartar generates the address.

resource "sartar_mailbox" "signup" {
  name = "signup-flow"
}

output "signup_address" {
  value = sartar_mailbox.signup.address
}

Mailboxes must be enabled on your account.

sartar_role and sartar_user

A role is a named set of permissions. rights maps a kind of object to the actions allowed on it.

resource "sartar_role" "ci" {
  name        = "CI scenarios"
  description = "Runs scenarios and reads their results"
  rights = {
    scenario = ["read", "run", "progress"]
  }
}

resource "sartar_user" "alice" {
  email   = "alice@example.com"
  role_id = sartar_role.ci.id
}

A user can also take one of the built-in roles with role = "administrator", "editor" or "reader".

The key Terraform runs with can never grant more than it holds itself.

sartar_account_settings and sartar_account_properties

Both exist once per account. Declare each at most once.

resource "sartar_account_properties" "account" {
  properties = {
    env = "prod"
  }
}

sartar_account_properties owns the whole map: a key you remove from the configuration is removed from the account.

sartar_account_settings takes its values as JSON, in settings_json. A key you remove from it keeps its last value. To change a setting back, state the value you want.

Data sources

Data source Use
sartar_regions the regions your account can use
sartar_notification_channel a channel that Terraform does not manage, found by name
sartar_mailbox a mailbox found by name, to read its address
data "sartar_notification_channel" "ops" {
  name = "ops-alerts"
}

resource "sartar_monitor" "web" {
  name    = "example.com"
  url     = "https://example.com"
  period  = 300
  regions = ["fr"]

  notification = {
    channel_id = data.sartar_notification_channel.ops.id
  }
}

Import one object

Every resource can be imported by its identifier. For a folder, a monitor or a scenario, this is its uuid. The sartar CLI prints it:

sartar monitor show --name "/prod/api health" --quiet
terraform import sartar_monitor.api_health <uuid>

The two account resources import with the identifier account.

Adopt an existing account

You already have monitors, and you want Terraform to manage them without creating a second copy. The web dashboard writes the configuration for you, and the CLI tells Terraform which existing object each resource is.

You need the sartar CLI for step 3.

1. Export the account

In the web dashboard, open the account menu and choose Export to Terraform. Save the document as sartar.tf, in a directory of its own.

Choose a private directory. The state file of Terraform will be written next to sartar.tf, and it holds every secret of the configuration in clear text.

The export contains everything the provider can manage, except the users.

2. Fill in the secrets

The export replaces secrets, such as webhook addresses, with variables. Give them a value through the environment:

export TF_VAR_slack_alerts_webhook_url='https://hooks.slack.com/services/…'

Do this before you apply. Applying a placeholder would overwrite the real value, and the alerts would stop arriving.

3. Generate the import blocks

sartar terraform imports --file sartar.tf > imports.tf

The CLI matches each resource of the document with the object of the same name in your account. It writes one import block for each match. What it cannot match with certainty is left out and listed in a comment at the end of the file. Nothing is guessed.

A read-only key is enough.

4. Read the plan

terraform init
terraform plan

You want a plan that imports, and changes nothing. Two differences are expected:

Any other change means the document and the account disagree. Fix the document, then plan again.

5. Apply once

terraform apply

This step needs a read / write key. Afterwards, delete imports.tf. It has done its job.

From now on the configuration is the reference. Edit the .tf files, plan, then apply.

Upgrade the provider

Raise the version constraint if needed, then:

terraform init -upgrade

The provider and the platform must agree on the version of the API. When they no longer do, Terraform stops with this message:

Error: Provider and sartar API versions have diverged

Upgrading the provider fixes it.

Good to know