Skip to content

Terraform Provider

Everything an operator configures through Platform Admin, and everything an org admin configures through Organization Settings, is reachable over the REST API — which means it can be managed as code. OpenTremor-terraform-provider is a Terraform provider covering that surface.

It does not provision OpenTremor itself. Containers, Helm charts and MongoDB stay Deployment’s job; this provider configures an instance that is already running. The two compose naturally: deploy the instance, then point Terraform at it.

ResourceWhat it manages
opentremor_organizationTenants — creation, plan tier, lock state, org-wide 2FA policy
opentremor_platform_settingsInstance-wide runtime settings (logging, registration, retention, GitHub)
opentremor_pricingThe metered price list
opentremor_organization_settingsAn organization’s LLM spend cap and report-link TTL
opentremor_api_keyService accounts, with role and IP allowlist
opentremor_sso_connectionAn organization’s OIDC identity provider
opentremor_scim_configAn organization’s SCIM 2.0 provisioning, including token rotation
opentremor_team / opentremor_team_memberTeams and their membership
opentremor_rule_category / opentremor_ruleCustom security rules and their grouping
opentremor_report_templateOrg-owned Jinja2 report templates

The provider authenticates as two of the five credential shapes described in Authentication — Access scope at a glance:

provider "opentremor" {
base_url = "https://api.example.com"
# Organization-scoped resources.
api_key = var.opentremor_api_key
# Platform-admin resources, and organization creation.
admin_email = var.opentremor_admin_email
admin_password = var.opentremor_admin_password
}

Set at least one. Which you need depends on the resources you use:

  • Organization-scoped resources (/orgs/{org_id}/*, /auth/keys) work with either. The API key is preferred when both are set — it is already bound to the right organization and needs no login round-trip.
  • Platform-admin resources (/admin/*) require admin_email/admin_password. OpenTremor never grants is_superadmin to a stored API key, so no key can reach these — not even the bootstrap key, which is auto-superadmin but has no user_id.
  • Creating an organization (POST /orgs) requires the session specifically, because the endpoint checks for a real user_id rather than for the superadmin flag.

Both credential types are single-org, so every organization-scoped resource’s org_id defaults to whichever organization the provider’s credential belongs to — resolved once per run via GET /auth/me. There is nothing to configure in the common case.

Setting org_id to a different organization is rejected before the request goes out. That mirrors require_same_org, which answers every cross-org request on /orgs/{org_id}/* with a 404 rather than a 403 — deliberately, so one organization can never confirm another’s resources exist. The provider raises the same refusal with an explanation attached.

To manage several organizations, use one provider alias per organization:

provider "opentremor" {
alias = "acme"
base_url = "https://api.example.com"
api_key = var.acme_api_key
}
resource "opentremor_team" "acme_platform" {
provider = opentremor.acme
name = "Platform"
}

A single block cannot span organizations, because re-scoping a session (POST /auth/session/switch) mutates one shared cookie while Terraform applies resources concurrently — a switch issued for one resource would silently retarget every in-flight request for another.

# Platform-wide, requires the admin session.
resource "opentremor_platform_settings" "this" {
overrides = jsonencode({
logging_level = "INFO"
registration_allow_public_signup = false
retention_audit_days = 730
})
}
# Organization-scoped, works with the API key.
resource "opentremor_organization_settings" "this" {
monthly_budget_usd = 500
report_link_ttl_hours = 72
}
resource "opentremor_sso_connection" "entra" {
display_name = "Acme Corp"
issuer = "https://login.microsoftonline.com/${var.tenant_id}/v2.0"
client_id = var.client_id
client_secret = var.client_secret
default_role = "member"
allowed_domains = ["acme.com"]
}
resource "opentremor_rule_category" "storage" {
name = "Storage & data"
}
resource "opentremor_rule" "no_public_buckets" {
title = "No publicly readable object storage"
description = "Flag any bucket whose ACL or policy would let an unauthenticated principal read objects."
category = opentremor_rule_category.storage.id
severity = "HIGH"
analyzers = ["terraform-plan"]
}

Platform settings are JSON, not typed attributes

Section titled “Platform settings are JSON, not typed attributes”

opentremor_platform_settings.overrides takes a JSON object of flat {section}_{field} keys rather than a fixed set of Terraform attributes. That is deliberate: the settings field vocabulary is built at runtime from a registry that every installed domain contributes its own sections to (see Data Retention for a section added exactly that way). A fixed schema in the provider would silently refuse any field added on the server after the provider binary was built.

The resource’s effective attribute reports every editable field with its value as actually in force, which is the authoritative list for a given instance:

Terminal window
terraform state show opentremor_platform_settings.this

Settings that are excluded from this surface — storage, cors, auth, jwt, llm, telemetry, deployment — stay file/env-driven and cannot be set here. GET /admin/settings reports each exclusion with its reason.

Behaviour worth knowing before you rely on it

Section titled “Behaviour worth knowing before you rely on it”

Some of these follow from what the API does or does not offer, rather than from a choice the provider made:

  • Organizations cannot be renamed or deleted. There is no endpoint for either. Changing name creates a new organization; terraform destroy drops the resource from state and warns that the organization is left behind on the server.
  • API keys are replace-only, and cannot be imported. The raw key exists only in the create response, and nothing about a stored key is editable. Any change to name, role or ip_allowlist issues a new key and revokes the old one — hand the replacement out before applying, not after.
  • Secrets cannot be read back. An SSO client_secret is stored encrypted and a SCIM token as a hash, so Terraform cannot detect drift on either. An imported SSO connection shows a client_secret change on its first plan (accurate — Terraform genuinely does not know the current value), and an imported SCIM config has no token until it is rotated.
  • SCIM provisioning and opentremor_team overlap. A SCIM group maps one-to-one onto a team, so an IdP with provisioning enabled creates and deletes teams and memberships itself. Managing the same teams from Terraform as well puts two writers on one set of rows.
  • Enabling any custom rule for an analyzer replaces its built-in ruleset for that analyzer+variant — OpenTremor stops rendering the built-in bullets entirely. Manage the whole set for those analyzers rather than adding a single rule.
  • opentremor_pricing cannot be destroyed back to the defaults. There is no endpoint to clear a stored price list, and the API reports the built-in defaults and an identical custom list the same way. Destroy warns and leaves the tiers in force.

The product-side configuration surface: LLM models and credentials, per-organization analyzer enablement, and integrations. Those live in OpenTremor Core rather than the platform layer and are the next batch, not an oversight.