Communitygithub.com

agents-inc/skills

TypeScript-natives Infrastruktur als Code mit Pulumi

Was ist skills?

Dieses Skill definiert Cloud-Infrastruktur in TypeScript mit vollständiger Typsicherheit und verwendet Pulumi als Infrastructure-as-Code-Tool. Es ermöglicht die Kapselung wiederverwendbarer Infrastrukturmuster durch `ComponentResource`, wobei alle untergeordneten Ressourcen `{ parent: this }` erhalten müssen, um den Ressourcenbaum und die Zustandsverfolgung korrekt zu erhalten. Wichtige Muster umfassen die Arbeit mit `Output<T>`-Werten (mit `pulumi.interpolate` anstelle von Zeichenkettenverkettung), die Verwaltung von Geheimnissen und Konfigurationen sowie die Verwendung von Stack-Referenzen für stackübergreifenden Datenaustausch. Das Skill unterstützt Deklarationsfehlererkennung durch Schlüsselwörter wie `@pulumi/pulumi`, `pulumi.Config` und `pulumi.Output`. Es wird auf Plattformen wie Claude Code und Cursor verwendet, um die Typsicherheit von TypeScript mit der deklarativen Infrastrukturmodellierung von Pulumi zu verbinden, einschließlich erweiterter Funktionen wie Automation API für programmatisches Stack-Management und CrossGuard für Compliance-Richtlinien.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/infra-iac-pulumi

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Pulumi Infrastruktur als Code

Kurzanleitung: Definieren Sie Cloud-Infrastruktur in TypeScript mit vollständiger Typsicherheit. Verwenden Sie ComponentResource, um wiederverwendbare Infrastrukturmuster zu kapseln. Übergeben Sie { parent: this } an alle untergeordneten Ressourcen innerhalb von Komponenten. Verwenden Sie pulumi.interpolate für die Zeichenkettenbildung mit Outputs (nicht Zeichenkettenverkettung). Erstellen Sie niemals Ressourcen innerhalb von .apply(). Verwenden Sie Config.requireSecret() für sensible Werte. Bevorzugen Sie transforms gegenüber veralteten transformations. Rufen Sie immer this.registerOutputs() am Ende von Komponentenkonstruktoren auf.


<critical_requirements>

KRITISCH: Vor der Verwendung dieses Skills

Der gesamte Code muss den Projektkonventionen in CLAUDE.md folgen (kebab-case, benannte Exporte, Import-Reihenfolge, import type, benannte Konstanten)

(Sie MÜSSEN { parent: this } an ALLE untergeordneten Ressourcen innerhalb einer ComponentResource übergeben – das Weglassen unterbricht den Ressourcenbaum und die Zustandsverfolgung)

(Sie MÜSSEN pulumi.interpolate für die Zeichenkettenbildung mit Outputs verwenden – Zeichenkettenverkettung erzeugt stillschweigend [object Object])

(Sie DÜRFEN NIEMALS Ressourcen innerhalb von .apply() erstellen – sie erscheinen nicht in pulumi preview und verursachen Reihenfolgeprobleme)

(Sie MÜSSEN Config.requireSecret() für sensible Werte verwenden – Config.require() speichert Werte als Klartext im Zustand)

(Sie MÜSSEN this.registerOutputs() am Ende jedes ComponentResource-Konstruktors aufrufen – das Weglassen verhindert die Ausgabeverfolgung)

</critical_requirements>


Detaillierte Ressourcen:

  • examples/core.md – Ressourcendefinitionen, Komponentenressourcen, Benennung, Outputs, Konfiguration/Geheimnisse
  • examples/advanced.md – Stack-Referenzen, Transforms, dynamische Anbieter, Automation API, Policy Packs
  • reference.md – Entscheidungsrahmen, Ressourcenoptionstabelle, API-Referenz, CLI-Befehle

Auto-Erkennung: Pulumi, @pulumi/pulumi, @pulumi/aws, @pulumi/gcp, @pulumi/azure, @pulumi/kubernetes, pulumi.ComponentResource, pulumi.CustomResource, pulumi.Output, pulumi.Config, pulumi.interpolate, pulumi.all, registerOutputs, StackReference, ComponentResourceOptions, CustomResourceOptions, dynamic.Resource, dynamic.ResourceProvider, LocalWorkspace, InlineProgramArgs, Automation API, CrossGuard, PolicyPack

Wann zu verwenden:

  • Definieren von Cloud-Infrastruktur in TypeScript mit typsicheren Ressourcen-APIs
  • Erstellen wiederverwendbarer Infrastrukturkomponenten mit ComponentResource
  • Verwalten von Multi-Stack-Architekturen mit Stack-Referenzen
  • Umgang mit Geheimnissen und umgebungsspezifischer Konfiguration
  • Erstellen von Self-Service-Infrastrukturplattformen mit der Automation API
  • Schreiben von Compliance-Richtlinien mit CrossGuard Policy Packs

Wann NICHT zu verwenden:

  • Einmalige Shell-Skripte, die eine einzelne Ressource erstellen (verwenden Sie direkt die Cloud-CLI)
  • Projekte, bei denen das Team keine TypeScript-Erfahrung hat (erwägen Sie andere IaC-Sprachoptionen)

Wichtige behandelte Muster:

  • Ressourcendefinitionen mit typisierten Eingaben und automatischer Benennung
  • ComponentResource-Kapselung (parent, Benennung, registerOutputs)
  • Outputs: apply, all, interpolate und Lifting
  • Konfiguration und Geheimnisverwaltung (Config.require, Config.requireSecret, pulumi.secret)
  • Stack-Referenzen für stackübergreifenden Datenaustausch
  • Ressourcenoptionen (dependsOn, protect, aliases, ignoreChanges, transforms)
  • Dynamische Anbieter für benutzerdefinierte CRUD-Ressourcen
  • Automation API für programmatisches Stack-Management
  • CrossGuard Policy Packs für Compliance-Durchsetzung

Philosophie

Pulumi behandelt Infrastruktur als echten Code, nicht als Konfigurationsdateien. TypeScript bietet Typsicherheit, IDE-Autovervollständigung, Refactoring-Tools und das gesamte Node.js-Ökosystem. Ressourcen sind Objekte, Abhängigkeiten sind automatisch, und Wiederverwendung erfolgt durch Funktionen und Klassen – nicht durch eine benutzerdefinierte Modulsprache.

Kernprinzipien:

  • Ressourcen sind Objekte: Jede Cloud-Ressource ist eine TypeScript-Klasseninstanz mit typisierten Eingaben und Ausgaben
  • Abhängigkeiten sind automatisch: Wenn Sie die Ausgabe einer Ressource als Eingabe einer anderen übergeben, leitet Pulumi den Abhängigkeitsgraphen ab
  • Wiederverwendung durch Komponenten: ComponentResource kapselt mehrere Ressourcen in einer einzigen logischen Einheit mit eigenen Ein- und Ausgaben
  • Outputs sind Promises: Output<T> repräsentiert einen Wert, der erst nach der Bereitstellung bekannt sein kann – verwenden Sie apply, all oder interpolate, um damit zu arbeiten, entpacken Sie niemals manuell
  • Zustand wird verwaltet: Pulumi verfolgt jede Ressource im Zustand – das Ändern eines logischen Namens oder das Verschieben einer Ressource zwischen Dateien löst ein Löschen und Neuerstellen aus, es sei denn, Sie verwenden aliases

Kernmuster

Muster 1: Ressourcendefinitionen

Jede Ressource benötigt einen logischen Namen (für Zustandsverfolgung), ein Argumentenbündel (typisierte Eingaben) und optionale Ressourcenoptionen.

const bucket = new aws.s3.Bucket(
  "data-bucket",
  {
    versioning: { enabled: true },
    lifecycleRules: [
      { enabled: true, expiration: { days: BUCKET_EXPIRY_DAYS } },
    ],
  },
  { protect: true },
); // Versehentliches Löschen verhindern

Wichtige Punkte: Logische Namen müssen pro Typ innerhalb eines Stacks eindeutig sein. Pulumi hängt automatisch ein zufälliges Suffix an den physischen Namen an, um Kollisionen zu vermeiden. Verwenden Sie protect: true bei kritischen Ressourcen. Exportieren Sie Outputs für die stackübergreifende Nutzung.

Siehe examples/core.md für Ressourcenbenennung, automatische Benennungskonfiguration und Anbieteroptionen.


Muster 2: ComponentResource-Kapselung

Packen Sie verwandte Ressourcen in eine ComponentResource, um wiederverwendbare Infrastruktureinheiten zu erstellen.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

interface StaticSiteArgs {
  indexDocument?: string;
  errorDocument?: string;
}

class StaticSite extends pulumi.ComponentResource {
  public readonly bucketName: pulumi.Output<string>;
  public readonly websiteUrl: pulumi.Output<string>;

  constructor(
    name: string,
    args: StaticSiteArgs,
    opts?: pulumi.ComponentResourceOptions,
  ) {
    super("myinfra:web:StaticSite", name, args, opts);

    const bucket = new aws.s3.BucketV2(`${name}-bucket`, {}, { parent: this });

    const website = new aws.s3.BucketWebsiteConfigurationV2(
      `${name}-website`,
      {
        bucket: bucket.id,
        indexDocument: { suffix: args.indexDocument ?? "index.html" },
        errorDocument: { key: args.errorDocument ?? "error.html" },
      },
      { parent: this },
    );

    this.bucketName = bucket.id;
    this.websiteUrl = website.websiteEndpoint;
    this.registerOutputs({
      bucketName: this.bucketName,
      websiteUrl: this.websiteUrl,
    });
  }
}

Warum gut: untergeordnete Ressourcen verwenden { parent: this }, der Name wird vom übergeordneten Element abgeleitet, registerOutputs wird aufgerufen, der Typ-Token folgt dem Format pkg:module:Type

Siehe examples/core.md für vollständige Komponentenmuster mit Anbietervererbung und Multi-Ressourcen-Komponenten.


Muster 3: Arbeiten mit Outputs

Outputs repräsentieren Werte, die nach der Bereitstellung aufgelöst werden. Verwenden Sie niemals Zeichenkettenverkettung – verwenden Sie interpolate, apply oder all.

// interpolate – getaggtes Template für Zeichenkettenbildung (bevorzugt)
const url = pulumi.interpolate`https://${bucket.bucketRegionalDomainName}/index.html`;

// apply – transformiert einen einzelnen Output
const upper = bucket.id.apply((id) => id.toUpperCase());

// all – kombiniert mehrere Outputs
const endpoint = pulumi
  .all([lb.dnsName, listener.port])
  .apply(([dns, port]) => `http://${dns}:${port}`);

// Lifting – direkt auf Eigenschaften von Ressourcenoutputs zugreifen
const subnetId = vpc.subnets[0].id; // Kein apply für bekannte Eigenschaften erforderlich

Warum gut: interpolate behandelt Output-Werte transparent, all wartet auf mehrere Werte, Lifting vermeidet unnötige apply-Aufrufe

Problemzone: Ressourcen, die innerhalb von .apply() erstellt werden, erscheinen nicht in pulumi preview und können Reihenfolgeprobleme verursachen. Übergeben Sie Outputs immer direkt als Eingaben an andere Ressourcen.

Siehe examples/core.md für Output-Muster, pulumi.output()-Wrapping und das Apply-Anti-Pattern.


Muster 4: Konfiguration und Geheimnisse

Verwenden Sie pulumi.Config für stackspezifische Werte. Verwenden Sie requireSecret für sensible Daten – es verschlüsselt den Wert im Zustand.

const config = new pulumi.Config();

// Klartext-Konfigurationswerte
const region = config.require("region"); // Schlägt fehl, wenn nicht vorhanden
const nodeCount = config.getNumber("nodeCount"); // Gibt undefined zurück, wenn nicht vorhanden

// Geheime Werte – im Zustand verschlüsselt
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.getSecret("apiKey");

// Programmatische Werte als geheim markieren
const connectionString = pulumi.interpolate`postgres://admin:${dbPassword}@${db.endpoint}/mydb`;
// connectionString ist automatisch geheim, weil dbPassword geheim ist

// Wert explizit als geheim markieren
const token = pulumi.secret(generateToken());

Wichtiger Punkt: Jeder Output, der von einem Geheimnis abgeleitet ist, wird automatisch als geheim markiert. Sie müssen abgeleitete Werte nicht erneut markieren.

Siehe examples/core.md für namensraumbezogene Konfiguration, geheime Outputs und CLI-Befehle zum Setzen der Konfiguration.


Muster 5: Ressourcenoptionen

Ressourcenoptionen steuern das Lebenszyklusverhalten. Die wichtigsten sind:

const db = new aws.rds.Instance(
  "primary-db",
  {
    /* ... */
  },
  {
    protect: true, // Versehentliches Löschen verhindern
    dependsOn: [vpc, securityGroup], // Explizite Reihenfolge
    ignoreChanges: ["tags"], // Drift bei bestimmten Eigenschaften ignorieren
    aliases: [{ name: "old-db-name" }], // Umbenennen ohne Neuerstellung
    retainOnDelete: true, // Cloud-Ressource bei pulumi destroy behalten
    deleteBeforeReplace: true, // Für Ressourcen, die eindeutig sein müssen
    replaceOnChanges: ["engine"], // Erzwingt Austausch bei bestimmten Änderungen
    provider: usEastProvider, // Expliziter Anbieter (Region, Konto)
  },
);

Problemzone: Das Ändern des logischen Namens oder der übergeordneten Ressource einer Ressource führt dazu, dass Pulumi sie löscht und neu erstellt. Verwenden Sie aliases, um sicher umzubenennen.

Siehe reference.md für die vollständige Tabelle der Ressourcenoptionen.


Muster 6: Stack-Referenzen

Teilen Sie Outputs zwischen Stacks mit StackReference.

// Im Netzwerk-Stack: Outputs exportieren
export const vpcId = vpc.id;
export const subnetIds = subnets.map((s) => s.id);

// Im Anwendungs-Stack: Outputs konsumieren
const networkStack = new pulumi.StackReference("myorg/networking/prod");
const vpcId = networkStack.getOutput("vpcId");
const subnetIds = networkStack.getOutput("subnetIds");

// requireOutput schlägt fehl, wenn der Output nicht existiert (sicherer als getOutput)
const vpcIdRequired = networkStack.requireOutput("vpcId");

Siehe examples/advanced.md für Stack-Referenz-Muster und getOutputDetails.


Muster 7: Transforms

Wenden Sie Transformationen auf Ressourcen und ihre untergeordneten Elemente an. Verwenden Sie transforms (nicht die veralteten transformations). Transforms erhalten ein Argumentobjekt mit type, props und opts und geben ein modifiziertes Ergebnis oder undefined zurück, um zu überspringen.

// Auf Ressourcenebene: Tags auf alle tagbaren untergeordneten Elemente einer Komponente anwenden
const vpc = new MyVpcComponent(
  "vpc",
  {},
  {
    transforms: [
      (args) => {
        if (isTaggable(args.type)) {
          return {
            props: {
              ...args.props,
              tags: { ...args.props["tags"], ManagedBy: "pulumi" },
            },
            opts: args.opts,
          };
        }
        return undefined;
      },
    ],
  },
);

Wichtiger Unterschied zu veralteten transformations: transforms unterstützen das Modifizieren von gepackten Komponenten-Unterelementen (awsx, eks), unterstützen asynchrone Callbacks und übergeben kein Resource-Objekt.

Siehe examples/advanced.md für Stack-Level-Transforms, Optionsmodifikation und Migration von transformations.


Muster 8: Automation API

Führen Sie Pulumi programmatisch ohne CLI aus – für Self-Service-Plattformen, Integrationstests oder benutzerdefinierte Bereitstellungstools.

const stack = await LocalWorkspace.createOrSelectStack({
  stackName: "dev",
  projectName: "my-platform",
  program: async () => {
    const bucket = new aws.s3.Bucket("auto-bucket");
    return { bucketName: bucket.id };
  },
});
const upResult = await stack.up({ onOutput: console.log });

Wichtiger Punkt: Die Automation API erfordert, dass die Pulumi-CLI installiert und im PATH ist – sie verwendet die Engine der CLI im Hintergrund.

Siehe examples/advanced.md für Preview, Destroy, lokalen Programmmodus, Konfigurationseinrichtung und Stack-Output-Abruf.


<red_flags>

ROTE FLAGGEN

Hohe Priorität:

  • Ressourcen innerhalb von .apply() erstellen – Sie erscheinen nicht in pulumi preview, verursachen Reihenfolgeprobleme und unterbrechen den Abhängigkeitsgraph. Übergeben Sie Outputs direkt als Ressourceneingaben.
  • Fehlendes { parent: this } in ComponentResource-Unterelementen – Ressourcen erscheinen auf der Wurzelebene des Zustandsbaums und unterbrechen die logische Gruppierung und das kaskadierende Löschen von Komponenten.
  • Zeichenkettenverkettung mit Outputs – "https://" + bucket.id erzeugt [object Object]. Verwenden Sie stattdessen pulumi.interpolate.
  • Verwenden von Config.require() für Passwörter/Schlüssel – Speichert den Wert als Klartext im Zustand. Verwenden Sie Config.requireSecret(), um zu verschlüsseln.
  • Vergessen von this.registerOutputs() – Komponenten-Outputs werden nicht ordnungsgemäß im Zustand verfolgt oder über Stack-Referenzen verfügbar sein.
  • Ändern logischer Namen ohne Aliase – Pulumi löscht und erstellt die Ressource neu. Verwenden Sie aliases: [{ name: "old-name" }], um sicher umzubenennen.

Mittlere Priorität:

  • Verlassen auf Standardanbieter in Multi-Region-Setups – Verwenden Sie explizite Anbieter pro Region. Setzen Sie pulumi:disable-default-providers in der Konfiguration, um dies zu erzwingen.
  • Mit Funktionen statt ComponentResource beginnen – Eine spätere Migration erfordert Aliase oder Ressourcenneuerstellung. Verwenden Sie von Anfang an Komponenten.
  • dependsOn nicht für nicht offensichtliche Abhängigkeiten verwenden – Pulumi leitet Abhängigkeiten aus der Input/Output-Verdrahtung ab, aber Seiteneffekte (IAM-Ausbreitung, DNS-Lösung) benötigen möglicherweise explizites dependsOn.

Niedrige Priorität:

  • pulumi.all([...]) anstelle von pulumi.all([...]).apply(([...]) => ...) – all muss mit apply abgeschlossen werden, um einen tatsächlichen Wert zu erhalten.

</red_flags>

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

Verwandte Skills