Es ist möglich, imports.tf mit einem einfachen Skript zu verwalten, anstatt terraform.tfstate zu pflegen. Im Gegensatz zum Zustand enthält imports.tf keine vetraulichen Informationen und kann daher sicher committed werden.

Einleitung

Wir schätzen Terraform als Tool zur Verwaltung von Infrastruktur als Code. Mit terraform apply wird der tatsächliche Zustand der verwalteten Infrastruktur an den Sollzustand aus den .tf Dateien angepasst. Terraform verwaltet seinen Zustand in einer Datei terraform.tfstate, die laut offizieller Dokumentation erforderlich ist:

Terraform must store state about your managed infrastructure and configuration. This state is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.

Aber woher weiß Terraform, dass der Zustand aktuell ist, wenn es sich auf eine lokale oder gemeinsam genutzte Datei stützt?

Prior to any operation, Terraform does a refresh to update the state with the real infrastructure.

Zustand ist immer einer Fehler– und Komplexitätsquelle. Weiterhin kann .tfstate vertrauliche Informationen wie Zugangsdaten enthalten. Dieser Artikel zeigt, wie wir Terraform ohne .tfstate nutzen.

.tfstate mit imports.tf ersetzen

terraform.tfstate wird von Terraform benötigt, um zu verstehen, wie die in den Terraform Quelldateien beschreibenen Ressourcen mit der Infrastruktur zusammenhängen. Dies geschieht durch die Zuordnung von IDs. Terraform erkennt anhand des Zustands, ob eine Ressource erstellt oder geändert werden soll.

Was aber, wenn bereits bestehende Ressourcen mit Terraform verwaltet werden sollen? Für diesen Fall unterstützt Terraform das Importieren von Ressourcen in den Zustand mit import Blocks.

Beispiel

Der gewünschte Zustand ist eine in main.tf definierte OpenStack Public Floating IP:

resource "openstack_networking_floatingip_v2" "my_ip" {
  pool = "my-pool"
}

Nach dem Ausführen von terraform apply enthält die resultierende terraform.tfstate eine Beziehung zwischen den IDs innerhalb der Terraform-Konfiguration und denen der Infrastruktur:

{
  // ...
  "resources": [
    {
      // Identification of the resource inside the terraform files (desired state)
      "mode": "managed",
      "type": "openstack_networking_floatingip_v2",
      "name": "my_ip",
      "provider": "provider[\"registry.terraform.io/terraform-provider-openstack/openstack\"]",

      "instances": [
        {
          "attributes": {
            // Identification of the resource in the managed infrastructure
            "id": "2b13d767-7446-4a91-a9ff-6fa63a25303e",
            // ...
          }
          // ...
        }
      ]
    }
    // ...
  ]
}

Der gleiche Zustand kann durch Importieren der vorhandenen Ressource mit imports.tf und terraform refresh (oder apply) erreicht werden:

import {
  to = openstack_networking_floatingip_v2.my_ip
  id = "2b13d767-7446-4a91-a9ff-6fa63a25303e"
}

Ein Jsonnet Skript zur Erzeugung von imports.tf

Nach terraform apply oder terraform refresh ist terraform.tfstate aktuell. imports.tf kann dann mit einem Skript daraus abgeleitet werden.

Wir verwenden dieses Jsonnet -Skript hierzu:

# tfstate.jsonnet
# LICENSE: MIT. Copyright 2025 Daydream Unicorn GmbH & Co. KG

function(tfstate) 
std.join('\n', [
  ] +
  std.flattenArrays(
    std.map(
    function(e) ['import {\n  to = %(module)s%(type)s.%(name)s%(index_key)s\n  id = "%(id)s"\n}' % {
        module: if std.objectHas(e, 'module') then e.module + '.' else '',
        type: e.type,
        name: e.name,
        id: x.id,
        index_key: if std.get(x, 'index_key') == null then '' else '["' + std.get(x, 'index_key') + '"]'
      } for x in [{ id: i.attributes.id, index_key: std.get(i, 'index_key') } for i in e.instances] ],
      std.filter(
        function(e) e != null && std.objectHas(e, "mode") && e.mode == "managed",
        tfstate.resources
      )
    )
  ) +[
  ]
)

Anwendung:

#/bin/bash
jsonnet tfstate.jsonnet --tla-code-file tfstate=terraform.tfstate -S > imports.tf

Workflow

#!/bin/bash
git clone <your terraform repo>
cd <your terraform repo>

# Zustand aktualisieren, Ressourcen importieren
terraform refresh

# "Warning: Empty or non-existent state" nach dem ersten refresh kann ignoriert werden.

# Dateien wie geüwnsch editieren, dann
terraform apply

# Zum Schluss imports.tf aktualisieren
jsonnet tfstate.jsonnet --tla-code-file tfstate=terraform.tfstate -S > imports.tf
rm terraform.tfstate

git add <changed files>
git add imports.tf
  • imports.tf muss neu generiert werden, wenn sich terraform.tfstate geändert hat. Es kann zusammen mit den entsprechenden Änderungen committet werden.
  • Wenn sich die Ressourcenidentifikation in der Konfiguration ändert, z. B. aufgrund einer Umbenennung von Ressourcen innerhalb der Terraform Dateien, müssen die IDs in imports.tf und terraform.tfstate aktualisiert werden. imports.tf muss auf dem neuesten Stand sein, bevor solche Änderungen durchgeführt werden.
  • Geheimnisse können mit terraform apply -var=my_secret=(password manager command to obtain the secret) ... übergeben werden.

Einschränkungen

  • Es wurde nicht untersucht, ob resources.instances.attributes.id immer das identifizierende Feld ist. Dies kann vom terraform provider abhängen oder auch nicht.
  • Das Format von terraform.tfstate könnte sich ändern.
  • Terraform verwendet seinen Zustand auch als Cache zur Leistungsoptimierung. Das Importieren vieler Ressourcen kann eine Weile dauern. Der zustandslose Ansatz kann zu langsam sein, wenn sehr viele Ressourcen mit demselben terraform apply verwaltet werden.
  • Abhängigkeiten müssen möglicherweise in der Terraform-Konfiguration mit depends_on explizit modelliert werden
  • Einige Eigenschaften sind möglicherweise nicht aus der Infrastruktur lesbar. Dies ist zwar eine Einschränkung des jeweiligen Infrastrukturanbieters und nicht dieses Workflows, aber Terraform zeigt häufiger eine Warnung dazu an, da es davon ausgeht, dass diese Eigenschaften bekannt sind, wenn es sie aus terraform.tfstate lesen kann.