| 1 |
{% extends "base.html" %} |
| 2 |
{% import "repo_tabs.html" as m %} |
| 3 |
{% block title %}Terraform state · {{ repo.owner }}/{{ repo.name }}{% endblock %} |
| 4 |
{% block bodyattrs %} data-repo="{{ repo.url }}"{% endblock %} |
| 5 |
{% block tabs %}{% call m::tabs(repo, "terraform") %}{% endblock %} |
| 6 |
{% block content %} |
| 7 |
<div class="head"><h1>Terraform state <small>{{ tf_states.len() }}</small></h1></div> |
| 8 |
<p>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 <code>NAME</code> (a repository may keep several):</p> |
| 9 |
<pre class="snippet">terraform { |
| 10 |
backend "http" { |
| 11 |
address = "{{ shell.base_url }}/api/v1/repos/{{ repo.owner }}/{{ repo.name }}/terraform/state/NAME" |
| 12 |
lock_address = "{{ shell.base_url }}/api/v1/repos/{{ repo.owner }}/{{ repo.name }}/terraform/state/NAME" |
| 13 |
unlock_address = "{{ shell.base_url }}/api/v1/repos/{{ repo.owner }}/{{ repo.name }}/terraform/state/NAME" |
| 14 |
username = "flow" |
| 15 |
} |
| 16 |
}</pre> |
| 17 |
<h2>From a pipeline</h2> |
| 18 |
<p>A job uses it with an identity token of this hub's, so it needs no cloud credential for the state. Give the job <code>identity "{{ shell.base_url }}/terraform" {% raw %}{{% endraw %} mount-path "/run/secrets/tf" {% raw %}}{% endraw %}</code>, run <code>export TF_HTTP_PASSWORD=$(cat /run/secrets/tf)</code> before <code>tofu init</code>, and let its sandbox reach this hub (<code>network "egress" {% raw %}{{% endraw %} allow "{{ shell.base_url|host }}" {% raw %}}{% endraw %}</code>, beside whatever else it reaches).</p> |
| 19 |
<h2>From your machine</h2> |
| 20 |
<p>Set <code>TF_HTTP_PASSWORD</code> to one of your tokens: <code>repo:write</code> to change a state, <code>repo:read</code> to read one; the repository role it acts with decides the rest.</p> |
| 21 |
<h2>States</h2> |
| 22 |
{% if tf_states.is_empty() %}<p class="empty">No state yet: the first <code>tofu apply</code> through the backend makes one.</p>{% else %} |
| 23 |
<div class="table"><table> |
| 24 |
<tr><th>State</th><th>Serial</th><th>Last written</th><th>Lock</th><th>Versions kept</th></tr> |
| 25 |
{% for (s, n) in tf_states %}<tr><td><strong>{{ s.name }}</strong></td><td>{{ s.serial }}</td><td class="small">{% if s.updated_by.is_empty() %}<span class="mute">never</span>{% else %}by {{ s.updated_by }} <time title="{{ s.updated_ms|when_i }}">{{ s.updated_ms|ago_i }}</time>{% endif %}</td><td class="small">{% if s.lock_id.is_empty() %}<span class="mute">free</span>{% else %}held by {{ s.locked_by }}{% if let Some(t) = s.locked_ms %} <time title="{{ t|when_i }}">{{ t|ago_i }}</time>{% endif %}{% endif %}</td><td>{{ n }}</td></tr>{% endfor %} |
| 26 |
</table></div>{% endif %} |
| 27 |
{% endblock %} |