{% extends "base.html" %} {% import "repo_tabs.html" as m %} {% block title %}Terraform state ยท {{ repo.owner }}/{{ repo.name }}{% endblock %} {% block bodyattrs %} data-repo="{{ repo.url }}"{% endblock %} {% block tabs %}{% call m::tabs(repo, "terraform") %}{% endblock %} {% block content %}

Terraform state {{ tf_states.len() }}

The hub keeps this repository's Terraform and OpenTofu state, sealed at rest, with its last {{ crate::terraform::VERSIONS_KEPT }} versions, outside the repository's history. Point a configuration at it with this backend block, naming the state in place of NAME (a repository may keep several):

terraform {
  backend "http" {
    address        = "{{ shell.base_url }}/api/v1/repos/{{ repo.owner }}/{{ repo.name }}/terraform/state/NAME"
    lock_address   = "{{ shell.base_url }}/api/v1/repos/{{ repo.owner }}/{{ repo.name }}/terraform/state/NAME"
    unlock_address = "{{ shell.base_url }}/api/v1/repos/{{ repo.owner }}/{{ repo.name }}/terraform/state/NAME"
    username       = "flow"
  }
}

From a pipeline

A job uses it with an identity token of this hub's, so it needs no cloud credential for the state. Give the job identity "{{ shell.base_url }}/terraform" {% raw %}{{% endraw %} mount-path "/run/secrets/tf" {% raw %}}{% endraw %}, run export TF_HTTP_PASSWORD=$(cat /run/secrets/tf) before tofu init, and let its sandbox reach this hub (network "egress" {% raw %}{{% endraw %} allow "{{ shell.base_url|host }}" {% raw %}}{% endraw %}, beside whatever else it reaches).

From your machine

Set TF_HTTP_PASSWORD to one of your tokens with repo:write, as someone with the write role here. Reading a state needs that too: states hold secrets.

States

{% if !repo.can(crate::auth::Access::Write) %}

The states are shown to people who can write to this repository.

{% else if tf_states.is_empty() %}

No state yet: the first tofu apply through the backend makes one.

{% else %}
{% for (s, n) in tf_states %}{% endfor %}
StateSerialLast writtenLockVersions kept
{{ s.name }}{{ s.serial }}{% if s.updated_by.is_empty() %}never{% else %}by {{ s.updated_by }} {% endif %}{% if s.lock_id.is_empty() %}free{% else %}held by {{ s.locked_by }}{% if let Some(t) = s.locked_ms %} {% endif %}{% endif %}{{ n }}
{% endif %} {% endblock %}