Terraform
Use Terraform to manage AlmaForge roles, locks, SSO connectors, and approval integrations. The provider connects to an existing cluster using your saved alma login. Your configuration describes the resources you want, terraform plan shows the changes, and terraform apply makes them.
Start with a role below. If you already have Terraform configured, go to the provider reference, resource and data-source chooser, or import workflow.
Before you start
You need:
- A running cluster with SSO already configured. Start with standalone deployment and OIDC setup if you are creating the cluster itself.
- The alma CLI and Terraform installed on your workstation.
- An identity permitted to manage the resources in your configuration. Being able to connect to a server does not imply permission to create roles. Your cluster administrator can grant the required administrative rules.
- A directory for this Terraform configuration. Use a separate directory and state for each cluster so you can identify which cluster a plan will change.
The provider is distributed from get.almaforge.com/almaforge/almaforge for Linux and macOS on amd64 and arm64. Keep your provider and cluster on the same release when starting a new deployment.
Start with a role
This example creates a role named developers. It permits the Linux login ubuntu on enrolled SSH servers labeled env=dev. Creating the role does not assign it to your user or enroll any servers.
1. Sign in and confirm the cluster
Replace the proxy hostname with your cluster's address, then complete the browser login:
alma login --proxy=almaforge.example.comalma statusCheck the reported cluster, username, roles, and expiry. Terraform will use this profile. For multiple clusters, select an explicit provider profile.
2. Save the configuration
Create a working directory and save main.tf inside it:
mkdir almaforge-terraformcd almaforge-terraform# Manage an API-owned role after authenticating with alma login.
terraform {
required_providers {
almaforge = {
source = "get.almaforge.com/almaforge/almaforge"
}
}
}
provider "almaforge" {}
resource "almaforge_role" "developers" {
metadata = {
name = "developers"
labels = {
managed_by = "terraform"
}
}
spec = {
allow = {
server_logins = ["ubuntu"]
server_labels = {
env = ["dev"]
}
}
}
}
There are two names in resource "almaforge_role" "developers": the first selects the resource type, and the second is Terraform's local label. Together they form the address almaforge_role.developers. metadata.name is the name stored in AlmaForge. They match in this example but serve different purposes.
If the cluster already has a role named developers, choose an unused name or import the existing role. Review the RBAC guide before changing a role that people already use.
3. Preview and apply
Run these commands from the directory containing main.tf:
terraform initterraform validateterraform plan -out=plan.tfplaninit installs the provider. validate checks the configuration's structure. plan connects to the cluster and writes the proposed changes to plan.tfplan. For this new role, expect one resource to be added and none changed or destroyed. Stop and check the cluster and configuration if the plan differs from your intent.
Applying a saved plan executes it without a second approval prompt. Apply the reviewed plan, then verify the result through both tools:
terraform apply plan.tfplanalma get role/developers -o yamlterraform planThe CLI should show the role's server login and label selector, and the last Terraform plan should report no changes.
To give a user this role, add it to the appropriate SSO role mapping or access-request policy. See SSH server access for how roles, target labels, and Linux logins work together.
Commit your .tf files and .terraform.lock.hcl. The lock file records the selected provider version and checksums. Use a version constraint in required_providers to control upgrades. Keep the provider at the cluster's version. An older provider erases fields it does not know when it updates a resource. Keep .terraform/, state files, saved plans, and secret-bearing variable files out of Git. See state and secrets before adding credentials.
Authentication and automation
An empty provider "almaforge" {} block uses your current alma profile. Select a specific saved profile when you work with more than one cluster:
export ALMA_PROXY=almaforge.example.comalma statusterraform planALMA_PROXY selects a login you have already saved, using the proxy hostname with an optional port. It does not log in to a new cluster. The CLI and provider read profiles from $XDG_CONFIG_HOME/almaforge when XDG_CONFIG_HOME is set, or ~/.config/almaforge otherwise. Set the same environment variables for planning and applying. See multiple clusters for profile locations and selection.
The provider uses the profile's certificate and cluster trust. It does not start an interactive login or renew credentials. If the certificate expires, run alma login again, create a fresh plan, and review it before applying.
For CI, use CLI workload sessions to create or refresh a profile in the same job before running Terraform. Configure the join method and permissions through the workload identity guide. Then run alma login, confirm the identity with alma status, and run Terraform with access to that profile directory. The provider reads the saved certificate. It does not perform the workload join itself.
Give the automation identity only the permissions its configuration needs. Use a state backend with access control and locking, and serialize jobs that manage the same state. Authentication to AlmaForge and access to the Terraform state backend are separate requirements.
Resources and data sources
A resource gives Terraform ownership of an object. Terraform creates it, updates it, and deletes it when you remove the resource from configuration and apply. A data source reads an existing object without taking ownership. Use a data source when another team or configuration manages the object.
Choose a managed resource and read its product guide before configuring it:
| What you want to manage | Terraform resource | Product guide |
|---|---|---|
| Access permissions | almaforge_role | RBAC |
| A user, role, or target lock | almaforge_lock | Revoking access |
| An OIDC login and claim mapping | almaforge_oidc_connector | OIDC SSO |
| GitHub login and team mapping | almaforge_github_connector | GitHub SSO |
| Slack access-request delivery | almaforge_slack_connector | Slack |
| Telegram access-request notifications | almaforge_telegram_connector | Telegram |
| Jira access-request issues | almaforge_jira_connector | Jira |
| PagerDuty incidents and on-call integration | almaforge_pagerduty_connector | PagerDuty |
Each managed kind also has a data source that reads one object by name. For example, almaforge_role takes name and returns metadata and spec. It reports an error if the object does not exist.
Users are read-only through almaforge_user. Their identities and role assignments come from SSO. For a read-only first test, download the user lookup example, use the username from alma status, and run:
terraform initterraform planterraform applyterraform outputRun those commands in the example's own directory. Replace [email protected] in the configuration with your actual username. Applying a data-only configuration records the read results and outputs in Terraform state without creating a cluster resource.
Translate a resource manifest
The configuration reference and product guides show YAML resources with metadata and spec. Terraform uses the same two objects, with snake-case attribute names:
| AlmaForge manifest field | Terraform attribute |
|---|---|
metadata.name | metadata.name |
spec.allow.serverLogins | spec.allow.server_logins |
spec.allow.serverLabels | spec.allow.server_labels |
spec.clientSecret | spec.client_secret |
spec.roleToRecipients | spec.role_to_recipients |
Map keys, role names, label values, and claim names keep their original spelling. In the role example, metadata.labels labels the role itself, while spec.allow.server_labels selects the SSH servers the role permits. Use the Terraform page's schema for the exact type, especially lists inside label maps and the values list inside connector recipient maps.
Do not copy kind or apiVersion into a Terraform resource. The resource type already selects the kind. Do not configure metadata.resource_version, which the server returns for concurrency checks. The label almaforge.com/origin and labels beginning with internal/ are reserved for the server.
Import existing resources
Import brings an existing resource into Terraform state. It does not mean your HCL already matches the object. The first plan after import is the check that prevents unintended changes.
First confirm that Terraform will be the object's only owner. If it comes from the server's configuration directory, follow the ownership transfer procedure before importing. Importing alone does not stop another writer from overwriting it.
For an existing role named developers:
- Read its current configuration with
alma get role/developers -o yaml. - Write a matching
resource "almaforge_role" "developers"block, using the Terraform schema to translate field names. - Run the following commands from the initialized configuration directory:
terraform import almaforge_role.developers developersterraform planThe first argument is the Terraform address. The second is the object's name in AlmaForge. Keep adjusting the configuration until the plan shows only the changes you intend. Omitted optional fields reset to defaults or clear when you apply.
Generate a starting configuration
If you have not written the resource block, Terraform can generate one. In a directory with the provider configured, add this import block without adding a resource at the same address:
import {
to = almaforge_role.developers
id = "developers"
}
terraform initterraform plan -generate-config-out=imported.tfimported.tf must not already exist. Review the generated file, then run a fresh plan and apply only the intended import and changes. Generation is a starting point, not a substitute for review. See Terraform's configuration generation documentation.
For connectors, supply the current secret values from your secret store because the API redacts credentials on reads. Import cannot recover those values. The provider preserves configured secrets on later refreshes, while data sources return null for redacted fields.
Changes and deletion
Configuration is authoritative. Removing a role grant from HCL revokes it on apply. Removing any optional field resets its API default or clears it when no default exists. Terraform also proposes correcting external changes to omitted fields.
| Change | What the next apply does |
|---|---|
| Change a configured field | Update the resource in place. |
Change metadata.name | Replace the resource. Review both the creation and deletion. |
| Remove an optional field | Restore its API default or clear it. |
| Delete an object outside Terraform | Recreate it while its resource block remains in configuration. |
| Remove a resource block | Delete the object. |
| Another writer changes the resource after your plan | Fail with a revision conflict. Create and review a fresh plan. |
Server-owned fields, such as a lock's creation time and creator, remain read-only. Before changing your own permissions or SSO mapping, keep a separate administrator available and review break-glass recovery.
terraform destroy deletes every managed resource in the selected state. For a single removal, delete that resource block and review a normal plan.
A lock's spec.expires_at ends enforcement but leaves the resource readable. Terraform does not replace or renew an expired lock. Use a fixed expiry in configuration, then update or destroy the lock deliberately. Follow the lock example and revocation guide for target selection and verification.
Connector secrets
Terraform marks connector credentials as sensitive, which hides them in normal plan output. They are still stored in state and saved plan files. Use a protected state backend with encryption, access control, and locking. Restrict CI artifacts and logs too. Terraform's sensitive-data guidance explains the storage behavior.
Each connector example declares sensitive input variables. Supply their TF_VAR_ values from your secret manager or protected CI environment. For example, the OIDC stack takes TF_VAR_client_secret. Edit non-secret settings, such as the issuer URL or Jira project key, directly in the example. Do not commit actual secrets, saved plans, or state.
Managing a connector does not create its external OAuth app, bot, project, or service. Complete the linked integration guide's prerequisites, apply the connector, then run that guide's end-to-end check. An accepted Terraform apply proves that the resource was stored, not that an external service delivered a notification or completed a login.
Troubleshooting
Cannot load profile
Run alma status as the same operating-system user running Terraform. Check ALMA_PROXY and XDG_CONFIG_HOME, then sign in to the intended cluster with alma login. See profile selection.
Expired credentials
Run alma login again, then create and review a fresh plan. The provider reads saved credentials and does not renew them. For CI, refresh the workload session before running Terraform.
Cannot connect to cluster
Check the proxy address in the selected profile and verify that the cluster is reachable from the machine running Terraform. Use the CLI sign-in guide and troubleshooting guide to diagnose connectivity, trust, and SSO failures.
Permission denied
Ask your administrator to check the identity's resource-management rules. SSH access alone does not grant administrative access. A data source needs read permission, while a managed resource needs the permissions for the planned writes too.
Resource already exists
Import the object if Terraform should manage it. Use a data source if Terraform should only read it. Confirm that the object has one owner before importing.
Resource not found
Check the lookup's name against the resource's exact metadata.name, and confirm the selected cluster with alma status. A data source reads an existing object. It does not create a missing one.
Revision conflict
Another writer changed the object after your plan. Identify that writer, follow the ownership rules, and create a fresh plan. Review the new changes before applying.
Field is changed by API defaults
Use the documented default or omit the field. An explicit empty string can differ from an omitted attribute. Check the resource's Terraform schema and its product guide for accepted values.
Removing a field still proposes changes
This is expected when the old value differs from the API default or empty value. Configuration is authoritative, so removing a role grant revokes it on apply. Review the planned revocation or reset using changes and deletion.
Connector applies but does nothing
Check the integration prerequisites and runtime name. The built-in approval connectors must be named slack, telegram, jira, or pagerduty respectively. Applying a connector stores its configuration. Follow the linked integration guide to test delivery, login, or approval end to end.