Blog

So erstellen und aktualisieren Sie die Ressourcendefinitionen der magischen Module des Google Terraform-Anbieters

Mark van Holsteijn

Mark van Holsteijn

Aktualisiert Oktober 15, 2025
8 Minuten

Das Hinzufügen neuer Ressourcen oder Funktionen zum Hashicorp Terraform-Anbieter für Google erfolgt normalerweise durch die Aktualisierung der Ressourcendefinitionen von Magic Modules. In diesem Blog zeige ich Ihnen, wie Sie diese Ressourcendefinitionen mithilfe eines einfachen Dienstprogramms schnell erstellen und aktualisieren können.

Irgendwann im September dieses Jahres veröffentlichte Google die mTLS-Unterstützung für den Google Load Balancer. Ich wollte einen Blog darüber schreiben und die Konfiguration mit Terraform-Vorlagen vorstellen. Es stellte sich jedoch heraus, dass die Ressourcen, die ich benötigte, nicht unterstützt wurden, während bei anderen Ressourcen wesentliche Eigenschaften fehlten, damit es funktionierte.

Hinzufügen neuer Terraform-Ressourcen

Um eine neue Terraform-Ressource hinzuzufügen, müssen Sie neue Magic Modules-Ressourcendefinitionen erstellen. Diese Ressourcendefinitionen sind zu 90-95% aus den REST-API-Ressourcendefinitionen aus den REST-API-Ermittlungsdokumenten von Google ausgeschnitten und eingefügt.

Dies ist eine sehr mühsame, langsame, langweilige und fehleranfällige Aufgabe, die am besten automatisiert wird. Deshalb habe ich ein kleines Dienstprogramm entwickelt: den magic-module-scaffolder!

Aktualisieren einer Ressourcendefinition

Mit diesem Dienstprogramm wird das Hinzufügen fehlender Eigenschaften zu vorhandenen Ressourcen zum Kinderspiel. Ich werde dies anhand der Ressource google_cloud_run_v2_service demonstrieren. Um zu sehen, welche Eigenschaften fehlen, geben Sie einfach ein:

$ pip install magic-module-scaffolder
$ git checkout git@github.com:GoogleCloudPlatform/magic-modules.git
$ cd magic-modules
$ mm-scaffolder --inplace --resource-file mmv1/products/cloudrunv2/Service.yaml

INFO: schema type name in create method is GoogleCloudRunV2Service, not Service
WARNING: the field 'name' is missing from the API definition, but keeping it in the as it is a special name
INFO: adding satisfiesPzs as ga field to definition of Service
INFO: adding scaling as ga field to definition of Service
WARNING: mismatch of type on field Service.generation, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Integer
WARNING: mismatch of type on field Service.template.labels, existing tag !ruby/object:Api::Type::KeyValuePairs and defined !ruby/object:Api::Type::KeyValueLabels
WARNING: mismatch of type on field Service.template.annotations, existing tag !ruby/object:Api::Type::KeyValuePairs and defined !ruby/object:Api::Type::KeyValueAnnotations
INFO: adding tcpSocket as ga field to definition of Service.template.containers..livenessProbe
INFO: adding nfs as ga field to definition of Service.template.volumes.
INFO: adding gcs as ga field to definition of Service.template.volumes.
WARNING: mismatch of type on field Service.observedGeneration, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Integer
WARNING: mismatch of type on field Service.terminalCondition.state, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.terminalCondition.severity, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.terminalCondition.reason, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.terminalCondition.revisionReason, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.terminalCondition.executionReason, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.conditions..state, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.conditions..severity, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.conditions..reason, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.conditions..revisionReason, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.conditions..executionReason, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.trafficStatuses..type, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Enum
WARNING: mismatch of type on field Service.etag, existing tag !ruby/object:Api::Type::String and defined !ruby/object:Api::Type::Fingerprint

Die Ausgabe zeigt, dass in der aktuellen Definition die Felder satisfiesPzs, scaling, tcpSocket, nfs und gcs in Bezug auf das API-Ermittlungsdokument fehlen.

Außerdem gibt es eine Reihe von Warnungen, wenn die aktuelle Ressourcendefinition einen anderen Typ gewählt hat, als auf der Grundlage des API-Ermittlungsdokuments erwartet wurde. Wenn Sie sich das git diff ansehen, werden Sie feststellen, dass es ziemlich ordentlich aussieht!

@@ -554,6 +537,17 @@ properties:
                         The name of the service to place in the gRPC HealthCheckRequest
                         (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).
                         If this is not specified, the default behavior is defined by gRPC.
+                - !ruby/object:Api::Type::NestedObject
+                  name: tcpSocket
+                  description: TCPSocketAction describes an action based on opening a socket
+                  properties:
+                    - !ruby/object:Api::Type::Integer
+                      name: port
+                      description: |-
+                        Port number to access on the container. Must be in the range 1 to 65535.
+                        If not specified, defaults to the exposed port of the container, which
+                        is the value of container.ports[0].containerPort.
+                      required: true
             - !ruby/object:Api::Type::NestedObject
               name: 'startupProbe'
               description: |-
@@ -749,7 +741,30 @@ properties:
                 - !ruby/object:Api::Type::String
                   name: 'sizeLimit'
                   description: |-
-                      Limit on the storage usable by this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. This field's values are of the 'Quantity' k8s type: https://kubernetes.io/docs/reference/kubernetes-api/common-definitions/quantity/. The default is nil which means that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir.
+                    Limit on the storage usable by this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. This field's values are of the 'Quantity' k8s type: https://kubernetes.io/docs/reference/kubernetes-api/common-definitions/quantity/. The default is nil which means that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir.
+            - !ruby/object:Api::Type::NestedObject
+              name: nfs
+              description: Represents an NFS mount.
+              properties:
+                - !ruby/object:Api::Type::String
+                  name: server
+                  description: Hostname or IP address of the NFS server
+                - !ruby/object:Api::Type::String
+                  name: path
+                  description: Path that is exported by the NFS server.
+                - !ruby/object:Api::Type::Boolean
+                  name: readOnly
+                  description: If true, mount the NFS volume as read only
+            - !ruby/object:Api::Type::NestedObject
+              name: gcs
+              description: Represents a GCS Bucket mounted as a volume.
+              properties:
+                - !ruby/object:Api::Type::String
+                  name: bucket
+                  description: GCS Bucket name
+                - !ruby/object:Api::Type::Boolean
+                  name: readOnly
+                  description: If true, mount the GCS bucket as read-only
       - !ruby/object:Api::Type::Enum
         name: 'executionEnvironment'
         description: |-
@@ -960,3 +975,20 @@ properties:
     output: true
     description: |
       A system-generated fingerprint for this version of the resource. May be used to detect modification conflict during updates.
+  - !ruby/object:Api::Type::Boolean
+    name: satisfiesPzs
+    description: Output only. Reserved for future use.
+    output: true
+  - !ruby/object:Api::Type::NestedObject
+    name: scaling
+    description: |-
+      Scaling settings applied at the service level rather than at the
+      revision level.
+    properties:
+      - !ruby/object:Api::Type::Integer
+        name: minInstanceCount
+        description: |-
+          total min instances for the service. This number of instances is divided
+          among all revisions with specified traffic based on the percent of
+          traffic they are receiving. (ALPHA)
+        required: true

In diesem Diff habe ich einige Formatierungsunterschiede weggelassen. Die verwendete ruamel.yaml-Bibliothek bewahrt nicht das gesamte Leerzeichen-Layout in dieser yaml.

Mit der aktualisierten Definition konnte ich eine neue Version des Google Providers generieren und einen Cloud Run-Dienst definieren, der einen GCS-Bucket und eine NFS-Freigabe in den Container einbindet, und zwar ohne irgendwelche besonderen Tricks!

resource "google_cloud_run_v2_service" "default" {
  name     = "service"

  location     = "us-central1"
  ingress      = "INGRESS_TRAFFIC_ALL"
  launch_stage = "BETA"

  template {
    execution_environment = "EXECUTION_ENVIRONMENT_GEN2"
    containers {
      image = "us-docker.pkg.dev/cloudrun/container/hello:latest"
      volume_mounts {
        name       = "nfs"
        mount_path = "/mnt/nfs"
      }
      volume_mounts {
        name       = "gcs"
        mount_path = "/mnt/bucket"
      }

    }
    vpc_access {
      network_interfaces {
        network    = "default"
        subnetwork = "default"
      }
    }

    volumes {
      name = "nfs"
      nfs {
        server = google_filestore_instance.default.networks[0].ip_addresses[0]
        path   = "/share1"
      }
    }
    volumes {
      name = "bucket"
      gcs {
        bucket = google_storage_bucket.default.name
      }
    }
  }
}

resource "google_storage_bucket" "default" {
    name     = "my-bucket"
    location = "US"
}

resource "google_filestore_instance" "default" {
  name     = "my-filestore"
  location = "us-central1-b"

  file_shares {
    capacity_gb = 1024
    name        = "share1"
  }

  networks {
    network = "default"
    modes   = ["MODE_IPV4"]
  }
}

Diese Änderungen führten zur Pull-Anfrage #9728 zu den magischen Modulen!

Generierung neuer Ressourcendefinitionen

Da alles mit der Notwendigkeit begann, dem Google-Anbieter neue Ressourcen hinzuzufügen, werde ich Ihnen zeigen, wie das geht. Die Ressourcen, die ich brauchte, waren serverTlsPolicies und addressGroups. Sie sind jetzt im Provider vorhanden, aber sehen wir mal, was passiert, wenn ich sie aus dem Discovery-Dokument generiere:

$ mm-scaffolder generate --product-directory mmv1/products/networksecurity serverTlsPolicies addressGroups
INFO: Writing to definition of ServerTlsPolicy to mmv1/products/networksecurity/ServerTlsPolicy.yaml
INFO: Writing to definition of AddressGroup to mmv1/products/networksecurity/AddressGroup.yaml

Wenn Sie git diff ausführen, werden Sie sehen, dass die generierten Ressourcendefinitionen ziemlich gut mit der definierten Ressource übereinstimmen und ein wirklich guter Anfang sind!

Ein Wort der Warnung

Verwenden Sie den Scaffolder nicht, um blindlings PR für die Magic-Module einzureichen, ohne die generierten Änderungen sorgfältig zu prüfen und zu testen. Die Änderungen werden auf der Grundlage dessen generiert, was ich aus der Feldbeschreibung im Discovery-Dokument ableiten kann, und das ist möglicherweise nicht immer korrekt.

Verwenden Sie es als Möglichkeit, Aktualisierungen zu starten und festzustellen, ob neue Funktionen in Bezug auf die bestehende Ressourcendefinition hinzugefügt wurden.

Fazit

Die Ressourcendefinitionen der Magic Modules sind größtenteils Transformationen aus dem Google API Discovery Dokument. Das Dienstprogramm ist nicht perfekt, da ich viele der Zuordnungen erraten musste. Aber ich bin zuversichtlich, dass es dazu beitragen wird, die Pflege der vom Google-Anbieter unterstützten Terraform-Ressourcen zu beschleunigen.


von DALLÂ-E 2 generiertes Bild.

Verfasst von

Mark van Holsteijn

Mark van Holsteijn is a senior software systems architect at Xebia Cloud-native solutions. He is passionate about removing waste in the software delivery process and keeping things clear and simple.

Contact

Let’s discuss how we can support your journey.