OXYGENHUB
# fbd7c8551932 oxygenhub / templates / repo_terraform.html
4.6 KiB · HTML · last changed Terraform state: list, download and restore kept versions 18 days ago source blame history raw
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>{{ 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 with <code>repo:write</code>, as someone with the write role here. Reading a state needs that too: states hold secrets.</p>
21
<h2>States</h2>
22
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %}
23
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %}
24
{% if !repo.can(crate::auth::Access::Write) %}<p class="mute">The states are shown to people who can write to this repository.</p>
25
{% else if states.is_empty() %}<p class="empty">No state yet: the first <code>tofu apply</code> through the backend makes one.</p>{% else %}
26
<div class="table"><table>
27
<tr><th>State</th><th>Serial</th><th>Last written</th><th>Lock</th><th>Versions kept</th><th></th></tr>
28
{% for v in states %}<tr><td><strong>{{ v.s.name }}</strong></td><td>{% if v.s.body.is_empty() %}<span class="mute">deleted</span>{% else %}{{ v.s.serial }}{% endif %}</td><td class="small">{% if v.s.updated_by.is_empty() %}<span class="mute">never</span>{% else %}by {{ v.s.updated_by }} <time title="{{ v.s.updated_ms|when_i }}">{{ v.s.updated_ms|ago_i }}</time>{% endif %}</td><td class="small">{% if v.s.lock_id.is_empty() %}<span class="mute">free</span>{% else %}held by {{ v.s.locked_by }}{% if let Some(t) = v.s.locked_ms %} <time title="{{ t|when_i }}">{{ t|ago_i }}</time>{% endif %}{% endif %}</td><td>{{ v.versions.len() }}</td><td class="small">{% if !v.s.body.is_empty() %}<a href="{{ repo.url }}/terraform/{{ v.s.name|urlencode }}/download">download</a>{% endif %}</td></tr>{% endfor %}
29
</table></div>
30
{% for v in states %}{% if !v.versions.is_empty() %}
31
<details class="tf-versions">
32
  <summary><strong>{{ v.s.name }}</strong>: {{ v.versions.len() }} kept {% if v.versions.len() == 1 %}version{% else %}versions{% endif %}</summary>
33
  <p class="small mute">Each write keeps the state it replaced (the last {{ crate::terraform::VERSIONS_KEPT }}). Restoring one writes it again with the next serial, so OpenTofu and Terraform take it, and keeps the current state as a version: restoring never loses anything, and a later version can be restored in turn.</p>
34
  <div class="table"><table>
35
  <tr><th>Serial</th><th>Written by</th><th>When</th><th></th></tr>
36
  {% for k in v.versions %}<tr><td>{{ k.serial }}</td><td class="small">{{ k.created_by }}</td><td class="small"><time title="{{ k.created_ms|when_i }}">{{ k.created_ms|ago_i }}</time></td><td class="small"><a href="{{ repo.url }}/terraform/{{ v.s.name|urlencode }}/download?version={{ k.id }}">download</a>{% if repo.can_maintain() %} <form class="tiny" method="post" action="{{ repo.url }}/terraform/{{ v.s.name|urlencode }}/restore" data-confirm="Restore {{ v.s.name }} to serial {{ k.serial }}? It becomes the current state; the current one is kept as a version."><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="version" value="{{ k.id }}"><button>restore</button></form>{% endif %}</td></tr>{% endfor %}
37
  </table></div>
38
</details>
39
{% endif %}{% endfor %}
40
{% endif %}
41
{% endblock %}