pulumi-component

Par pulumi · agent-skills

Guide pour la création de classes Pulumi `ComponentResource`. À utiliser lors de la création de composants d'infrastructure réutilisables, de la conception d'interfaces de composants, de la mise en place d'un support multi-langage, ou de la distribution de packages de composants.

npx skills add https://github.com/pulumi/agent-skills --skill pulumi-component

Création de composants Pulumi

Un ComponentResource groupe les ressources d'infrastructure liées dans une unité logique réutilisable. Les composants rendent l'infrastructure plus facile à comprendre, réutiliser et maintenir. Les composants apparaissent comme un nœud unique avec les enfants imbriqués en dessous dans la sortie pulumi preview/pulumi up et dans la console Pulumi Cloud.

Cette compétence couvre l'ensemble du cycle de vie de la création de composants. Pour les modèles de codage Pulumi généraux (gestion des Outputs, secrets, alias, workflows de preview), utilisez plutôt la compétence pulumi-best-practices.

Quand utiliser cette compétence

Invoquez cette compétence quand :

  • Vous créez une nouvelle classe ComponentResource
  • Vous concevez l'interface args pour un composant
  • Vous rendez un composant consommable à partir de plusieurs langages Pulumi
  • Vous publiez ou distribuez un paquet de composant
  • Vous refactorisez les ressources inline en un composant réutilisable
  • Vous déboguez le comportement d'un composant (outputs manquants, création bloquée, enfants au mauvais niveau)

Anatomie du composant

Chaque composant a quatre éléments obligatoires :

  1. Étendre ComponentResource et appeler super() avec un type URN
  2. Accepter les paramètres standards : name, args, et ComponentResourceOptions
  3. Définir parent: this sur toutes les ressources enfants
  4. Appeler registerOutputs() à la fin du constructeur

TypeScript

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

interface StaticSiteArgs {
    indexDocument?: pulumi.Input<string>;
    errorDocument?: pulumi.Input<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) {
        // 1. Call super with type URN: <package>:<module>:<type>
        super("myorg:index:StaticSite", name, {}, opts);

        // 2. Create child resources with parent: this
        const bucket = new aws.s3.Bucket(`${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 });

        // 3. Expose outputs as class properties
        this.bucketName = bucket.id;
        this.websiteUrl = website.websiteEndpoint;

        // 4. Register outputs -- always the last line
        this.registerOutputs({
            bucketName: this.bucketName,
            websiteUrl: this.websiteUrl,
        });
    }
}

// Usage
const site = new StaticSite("marketing", {
    indexDocument: "index.html",
});
export const url = site.websiteUrl;

Python

import pulumi
import pulumi_aws as aws

class StaticSiteArgs:
    def __init__(self,
                 index_document: pulumi.Input[str] = "index.html",
                 error_document: pulumi.Input[str] = "error.html"):
        self.index_document = index_document
        self.error_document = error_document

class StaticSite(pulumi.ComponentResource):
    bucket_name: pulumi.Output[str]
    website_url: pulumi.Output[str]

    def __init__(self, name: str, args: StaticSiteArgs,
                 opts: pulumi.ResourceOptions = None):
        super().__init__("myorg:index:StaticSite", name, None, opts)

        bucket = aws.s3.Bucket(f"{name}-bucket",
            opts=pulumi.ResourceOptions(parent=self))

        website = aws.s3.BucketWebsiteConfigurationV2(f"{name}-website",
            bucket=bucket.id,
            index_document=aws.s3.BucketWebsiteConfigurationV2IndexDocumentArgs(
                suffix=args.index_document,
            ),
            error_document=aws.s3.BucketWebsiteConfigurationV2ErrorDocumentArgs(
                key=args.error_document,
            ),
            opts=pulumi.ResourceOptions(parent=self))

        self.bucket_name = bucket.id
        self.website_url = website.website_endpoint

        self.register_outputs({
            "bucket_name": self.bucket_name,
            "website_url": self.website_url,
        })

site = StaticSite("marketing", StaticSiteArgs())
pulumi.export("url", site.website_url)

Format du type URN

Le premier argument de super() est le type URN : <package>:<module>:<type>.

Segment Convention Exemple
package Nom de l'organisation ou du paquet myorg, acme, pkg
module Généralement index index
type Nom de la classe en PascalCase StaticSite, VpcNetwork

Exemples complets : myorg:index:StaticSite, acme:index:KubernetesCluster

registerOutputs est obligatoire

Pourquoi : Sans registerOutputs(), le composant apparaît bloqué dans l'état "créé" dans la console Pulumi et les outputs ne sont pas persistés dans l'état.

Incorrect :

class MyComponent extends pulumi.ComponentResource {
    public readonly url: pulumi.Output<string>;

    constructor(name: string, args: MyArgs, opts?: pulumi.ComponentResourceOptions) {
        super("myorg:index:MyComponent", name, {}, opts);
        const bucket = new aws.s3.Bucket(`${name}-bucket`, {}, { parent: this });
        this.url = bucket.bucketRegionalDomainName;
        // Missing registerOutputs -- component stuck "creating"
    }
}

Correct :

class MyComponent extends pulumi.ComponentResource {
    public readonly url: pulumi.Output<string>;

    constructor(name: string, args: MyArgs, opts?: pulumi.ComponentResourceOptions) {
        super("myorg:index:MyComponent", name, {}, opts);
        const bucket = new aws.s3.Bucket(`${name}-bucket`, {}, { parent: this });
        this.url = bucket.bucketRegionalDomainName;

        this.registerOutputs({ url: this.url });
    }
}

Dériver les noms des enfants à partir du nom du composant

Pourquoi : Les noms des enfants codés en dur causent des collisions quand le composant est instancié plusieurs fois.

Incorrect :

// Collides if two instances of this component exist
const bucket = new aws.s3.Bucket("my-bucket", {}, { parent: this });

Correct :

// Unique per component instance
const bucket = new aws.s3.Bucket(`${name}-bucket`, {}, { parent: this });

Concevoir l'interface des arguments

L'interface des arguments est la décision de conception la plus impactante. Elle définit ce que les consommateurs peuvent configurer et comment le composant est composable.

Envelopper les propriétés dans Input<T>

Pourquoi : Input<T> accepte à la fois les valeurs simples et les Output<T> d'autres ressources. Sans cela, les consommateurs doivent dépacker les outputs manuellement avec .apply().

Incorrect :

interface WebServiceArgs {
    port: number;            // Forces consumers to unwrap Outputs
    vpcId: string;           // Cannot accept vpc.id directly
}

Correct :

interface WebServiceArgs {
    port: pulumi.Input<number>;     // Accepts 8080 or someOutput
    vpcId: pulumi.Input<string>;    // Accepts "vpc-123" or vpc.id
}

Garder les structures plates

Évitez les objets args profondément imbriqués. Les interfaces plates sont plus faciles à utiliser et à faire évoluer.

// Prefer flat
interface DatabaseArgs {
    instanceClass: pulumi.Input<string>;
    storageGb: pulumi.Input<number>;
    enableBackups?: pulumi.Input<boolean>;
    backupRetentionDays?: pulumi.Input<number>;
}

// Avoid deep nesting
interface DatabaseArgs {
    instance: {
        compute: { class: pulumi.Input<string> };
        storage: { sizeGb: pulumi.Input<number> };
    };
    backup: {
        config: { enabled: pulumi.Input<boolean>; retention: pulumi.Input<number> };
    };
}

Pas de types union

Les types union cassent la génération du SDK multi-langage. Python, Go et C# ne peuvent pas représenter string | number.

Incorrect :

interface MyArgs {
    port: pulumi.Input<string | number>;  // Fails in Python, Go, C#
}

Correct :

interface MyArgs {
    port: pulumi.Input<number>;  // Single type, works everywhere
}

Si vous devez accepter plusieurs formes, utilisez des propriétés optionnelles séparées :

interface StorageArgs {
    sizeGb?: pulumi.Input<number>;      // Specify size in GB
    sizeMb?: pulumi.Input<number>;      // Or specify size in MB
}

Pas de fonctions ou de callbacks

Les fonctions ne peuvent pas être sérialisées à travers les frontières des langues.

Incorrect :

interface MyArgs {
    nameTransform: (name: string) => string;  // Cannot serialize
}

Correct :

interface MyArgs {
    namePrefix?: pulumi.Input<string>;   // Configuration instead of callback
    nameSuffix?: pulumi.Input<string>;
}

Utiliser les valeurs par défaut pour les propriétés optionnelles

Définissez des valeurs par défaut sensées à l'intérieur du constructeur afin que les consommateurs ne configurent que ce dont ils ont besoin :

interface SecureBucketArgs {
    enableVersioning?: pulumi.Input<boolean>;   // Defaults to true
    enableEncryption?: pulumi.Input<boolean>;   // Defaults to true
    blockPublicAccess?: pulumi.Input<boolean>;  // Defaults to true
}

class SecureBucket extends pulumi.ComponentResource {
    constructor(name: string, args: SecureBucketArgs, opts?: pulumi.ComponentResourceOptions) {
        super("myorg:index:SecureBucket", name, {}, opts);

        const enableVersioning = args.enableVersioning ?? true;
        const enableEncryption = args.enableEncryption ?? true;
        const blockPublicAccess = args.blockPublicAccess ?? true;

        // Apply defaults...
    }
}

// Consumer only overrides what they need
const bucket = new SecureBucket("data", { enableVersioning: false });

Exposer les outputs

Exposer uniquement ce dont les consommateurs ont besoin

Les composants créent souvent de nombreuses ressources internes. Exposez uniquement les valeurs dont les consommateurs ont besoin, pas chaque ressource interne.

Incorrect :

class Database extends pulumi.ComponentResource {
    // Exposes everything -- consumers see implementation details
    public readonly cluster: aws.rds.Cluster;
    public readonly primaryInstance: aws.rds.ClusterInstance;
    public readonly replicaInstance: aws.rds.ClusterInstance;
    public readonly subnetGroup: aws.rds.SubnetGroup;
    public readonly securityGroup: aws.ec2.SecurityGroup;
    public readonly parameterGroup: aws.rds.ClusterParameterGroup;
    // ...
}

Correct :

class Database extends pulumi.ComponentResource {
    // Exposes only what consumers need
    public readonly endpoint: pulumi.Output<string>;
    public readonly port: pulumi.Output<number>;
    public readonly securityGroupId: pulumi.Output<string>;

    constructor(name: string, args: DatabaseArgs, opts?: pulumi.ComponentResourceOptions) {
        super("myorg:index:Database", name, {}, opts);

        const sg = new aws.ec2.SecurityGroup(`${name}-sg`, { /* ... */ }, { parent: this });
        const cluster = new aws.rds.Cluster(`${name}-cluster`, { /* ... */ }, { parent: this });

        this.endpoint = cluster.endpoint;
        this.port = cluster.port;
        this.securityGroupId = sg.id;

        this.registerOutputs({
            endpoint: this.endpoint,
            port: this.port,
            securityGroupId: this.securityGroupId,
        });
    }
}

Dériver les outputs composites

Utilisez pulumi.interpolate ou pulumi.concat pour construire des valeurs dérivées :

this.connectionString = pulumi.interpolate`postgresql://${args.username}:${args.password}@${cluster.endpoint}:${cluster.port}/${args.databaseName}`;

this.registerOutputs({ connectionString: this.connectionString });

Modèles de conception de composants

Valeurs par défaut sensées avec possibilité de remplacement

Encodez les meilleures pratiques comme valeurs par défaut. Permettez aux consommateurs de les remplacer quand ils ont des exigences spécifiques.

interface SecureBucketArgs {
    enableVersioning?: pulumi.Input<boolean>;
    enableEncryption?: pulumi.Input<boolean>;
    blockPublicAccess?: pulumi.Input<boolean>;
    tags?: pulumi.Input<Record<string, pulumi.Input<string>>>;
}

class SecureBucket extends pulumi.ComponentResource {
    public readonly bucketId: pulumi.Output<string>;
    public readonly arn: pulumi.Output<string>;

    constructor(name: string, args: SecureBucketArgs = {}, opts?: pulumi.ComponentResourceOptions) {
        super("myorg:index:SecureBucket", name, {}, opts);

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

        // Versioning on by default
        if (args.enableVersioning !== false) {
            new aws.s3.BucketVersioningV2(`${name}-versioning`, {
                bucket: bucket.id,
                versioningConfiguration: { status: "Enabled" },
            }, { parent: this });
        }

        // Encryption on by default
        if (args.enableEncryption !== false) {
            new aws.s3.BucketServerSideEncryptionConfigurationV2(`${name}-encryption`, {
                bucket: bucket.id,
                rules: [{ applyServerSideEncryptionByDefault: { sseAlgorithm: "AES256" } }],
            }, { parent: this });
        }

        // Public access blocked by default
        if (args.blockPublicAccess !== false) {
            new aws.s3.BucketPublicAccessBlock(`${name}-public-access`, {
                bucket: bucket.id,
                blockPublicAcls: true,
                blockPublicPolicy: true,
                ignorePublicAcls: true,
                restrictPublicBuckets: true,
            }, { parent: this });
        }

        this.bucketId = bucket.id;
        this.arn = bucket.arn;
        this.registerOutputs({ bucketId: this.bucketId, arn: this.arn });
    }
}

Création conditionnelle de ressources

Utilisez les arguments optionnels pour autoriser ou non la création de sous-ressources :

interface WebServiceArgs {
    image: pulumi.Input<string>;
    port: pulumi.Input<number>;
    enableMonitoring?: pulumi.Input<boolean>;
    alarmEmail?: pulumi.Input<string>;
}

class WebService extends pulumi.ComponentResource {
    constructor(name: string, args: WebServiceArgs, opts?: pulumi.ComponentResourceOptions) {
        super("myorg:index:WebService", name, {}, opts);

        const service = new aws.ecs.Service(`${name}-service`, {
            // ...service config...
        }, { parent: this });

        // Only create alarm infrastructure when monitoring is enabled
        if (args.enableMonitoring) {
            const topic = new aws.sns.Topic(`${name}-alerts`, {}, { parent: this });

            if (args.alarmEmail) {
                new aws.sns.TopicSubscription(`${name}-alert-email`, {
                    topic: topic.arn,
                    protocol: "email",
                    endpoint: args.alarmEmail,
                }, { parent: this });
            }

            new aws.cloudwatch.MetricAlarm(`${name}-cpu-alarm`, {
                // ...alarm config referencing service...
                alarmActions: [topic.arn],
            }, { parent: this });
        }

        this.registerOutputs({});
    }
}

Composition

Construisez des composants de haut niveau à partir d'autres de bas niveau. Chaque niveau gère une seule responsabilité.

// Lower-level component
class VpcNetwork extends pulumi.ComponentResource {
    public readonly vpcId: pulumi.Output<string>;
    public readonly publicSubnetIds: pulumi.Output<string>[];
    public readonly privateSubnetIds: pulumi.Output<string>[];

    constructor(name: string, args: VpcNetworkArgs, opts?: pulumi.ComponentResourceOptions) {
        super("myorg:index:VpcNetwork", name, {}, opts);
        // ...create VPC, subnets, route tables...
        this.registerOutputs({ vpcId: this.vpcId });
    }
}

// Higher-level component that uses VpcNetwork
class Platform extends pulumi.ComponentResource {
    public readonly kubeconfig: pulumi.Output<string>;

    constructor(name: string, args: PlatformArgs, opts?: pulumi.ComponentResourceOptions) {
        super("myorg:index:Platform", name, {}, opts);

        // Compose lower-level components
        const network = new VpcNetwork(`${name}-network`, {
            cidrBlock: args.cidrBlock,
        }, { parent: this });

        const cluster = new aws.eks.Cluster(`${name}-cluster`, {
            vpcConfig: {
                subnetIds: network.privateSubnetIds,
            },
        }, { parent: this });

        this.kubeconfig = cluster.kubeconfig;
        this.registerOutputs({ kubeconfig: this.kubeconfig });
    }
}

Passage du provider

Acceptez les providers explicites pour les déploiements multi-région ou multi-compte. ComponentResourceOptions transporte automatiquement la configuration du provider aux enfants :

// Consumer passes a provider for a different region
const usWest = new aws.Provider("us-west", { region: "us-west-2" });
const site = new StaticSite("west-site", { indexDocument: "index.html" }, {
    providers: [usWest],
});

Les enfants avec { parent: this } héritent automatiquement du provider. Aucun code supplémentaire n'est nécessaire à l'intérieur du composant.


Composants multi-langage

Si votre composant sera consommé à partir de plusieurs langages Pulumi (TypeScript, Python, Go, C#, Java, YAML), packagisez-le comme un composant multi-langage.

Avez-vous besoin du multi-langage ?

Posez-vous la question : « Quelqu'un consommera-t-il ce composant à partir d'une langue différente de celle dans laquelle il a été créé ? »

Composant mono-langage (aucun packagisage nécessaire) :

  • Votre équipe utilise une seule langue et le composant reste au sein de ce codebase
  • Le composant est interne à un seul projet ou monorepo
  • Aucun PulumiPlugin.yaml nécessaire -- il suffit d'importer la classe directement

Composant multi-langage (packagisage requis) :

  • D'autres équipes consomment votre composant dans des langues différentes
  • Les équipes de plateforme construisent des abstractions pour les développeurs qui choisissent leur propre langage
  • Les consommateurs YAML ont besoin d'accès -- même si vous créez en TypeScript, les programmes YAML nécessitent un packagisage multi-langage pour utiliser votre composant
  • Création d'une bibliothèque de composants partagés pour votre organisation
  • La publication sur le registre privé Pulumi ou le registre public est une raison courante, mais non requise pour le support multi-langage

Erreur courante : Une équipe de plateforme TypeScript crée des composants que seuls leurs utilisateurs TypeScript peuvent consommer. Si les développeurs d'applications utilisent Python ou YAML, ces composants leur sont invisibles sans packagisage multi-langage.

Configuration

Créez un PulumiPlugin.yaml dans le répertoire du composant pour déclarer le runtime :

runtime: nodejs

Ou pour Python :

runtime: python

Contraintes de sérialisation

Pour la compatibilité multi-langage, les arguments doivent être sérialisables. Ces contraintes s'appliquent indépendamment du langage de création :

Autorisé Non autorisé
string, number, boolean Types union (string \| number)
Enveloppes Input<T> Fonctions et callbacks
Tableaux et maps de primitives Génériques imbriqués complexes
Énumérations Types spécifiques à une plateforme

Consommer des composants multi-langage

Les consommateurs installent le composant avec pulumi package add, ce qui télécharge automatiquement le plugin du provider, génère un SDK local dans la langue du consommateur et met à jour Pulumi.yaml :

# From a Git repository
pulumi package add <git-repo-url>

# From a specific version tag
pulumi package add <git-repo-url>@v1.0.0

Pour les clones fraîches ou les environnements CI, exécutez pulumi install pour vous assurer que toutes les dépendances de paquet sont disponibles. Le consommateur n'a pas besoin de générer manuellement les SDK.

Les auteurs qui publient des SDK sur les gestionnaires de paquets (npm, PyPI, etc.) peuvent optionnellement utiliser pulumi package gen-sdk pour générer des SDK spécifiques à un langage à des fins de publication. La plupart des auteurs de composants n'en ont pas besoin -- pulumi package add gère la génération du SDK côté consommateur.

Points d'entrée

Les composants multi-langage publiés nécessitent un point d'entrée qui héberge le processus du provider de composant. Le modèle du point d'entrée diffère selon le langage.

TypeScript (runtime: nodejs) :

Exportez les classes de composants à partir de index.ts. Aucun fichier de point d'entrée séparé n'est nécessaire. Pulumi introspect automatiquement les classes exportées.

// index.ts -- exports are the entry point
export { StaticSite, StaticSiteArgs } from "./staticSite";
export { SecureBucket, SecureBucketArgs } from "./secureBucket";

Python (runtime: python) :

Créez un __main__.py qui appelle component_provider_host avec toutes les classes de composant :

from pulumi.provider.experimental import component_provider_host
from static_site import StaticSite
from secure_bucket import SecureBucket

if __name__ == "__main__":
    component_provider_host(
        name="my-components",
        components=[StaticSite, SecureBucket],
    )

Go (runtime: go) :

Créez un main.go qui construit et exécute le provider :

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/pulumi/pulumi-go-provider/infer"
)

func main() {
    p, err := infer.NewProviderBuilder().
        WithComponents(
            infer.ComponentF(NewStaticSite),
            infer.ComponentF(NewSecureBucket),
        ).
        Build()
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    if err := p.Run(context.Background(), "my-components", "0.1.0"); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

C# (runtime: dotnet) :

Créez un Program.cs qui serve l'hôte du provider de composant :

using System.Threading.Tasks;

class Program
{
    public static Task Main(string[] args) =>
        Pulumi.Experimental.Provider.ComponentProviderHost.Serve(args);
}

Pour un exemple de travail complet dans tous les langages, voir https://github.com/mikhailshilkov/comp-as-comp.

Référence : https://www.pulumi.com/docs/iac/using-pulumi/pulumi-packages/


Distribution

Choisissez une méthode de distribution en fonction de votre audience :

Audience Méthode Comment
Même projet Import direct Import standard du langage
Même organisation Registre privé pulumi package publish vers Pulumi Cloud
Même organisation Référentiel Git pulumi package add <repo> avec des balises de version
Écosystème de langage Gestionnaire de paquets Publiez sur npm, PyPI, NuGet, ou Maven
Communauté publique Registre Pulumi Soumettez via le référentiel GitHub pulumi/registry

Registre privé Pulumi

Le registre privé est le catalogue centralisé des composants de votre organisation. Il fournit la documentation automatique de l'API, la gestion des versions et la détectabilité pour toutes les équipes.

Publiez un composant sur le registre privé :

pulumi package publish https://github.com/myorg/my-component --publisher myorg

Versionnez les composants en utilisant les balises git avec un préfixe v :

git tag v1.0.0
git push origin v1.0.0

Un fichier README est requis lors de la publication. Pulumi l'utilise comme page de documentation du composant dans le registre.

Automatisez la publication à partir de GitHub Actions en utilisant l'authentification OIDC :

name: Publish Component
on:
  push:
    tags:
      - "v*"

permissions:
  id-token: write
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    env:
      PULUMI_ORG: myorg
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: pulumi/auth-actions@v1
        with:
          organization: ${{ env.PULUMI_ORG }}
          requested-token-type: urn:pulumi:token-type:access_token:organization
      - run: pulumi package publish https://github.com/${{ github.repository }} --publisher ${{ env.PULUMI_ORG }}

Conditions préalables : Configurez l'intégration OIDC de GitHub avec Pulumi Cloud avant d'utiliser ce workflow.

Le registre supporte les référentiels privés GitHub et GitLab. Pour les configurations sans OIDC, authentifiez-vous avec les variables d'environnement GITHUB_TOKEN ou GITLAB_TOKEN.

Le registre privé génère automatiquement la documentation du SDK pour chaque composant publié. Enrichissez la documentation générée en ajoutant des annotations de type aux entrées et sorties de votre composant (JSDoc en TypeScript, docstrings en Python, méthodes Annotate() en Go).

Référence : https://www.pulumi.com/docs/idp/get-started/private-registry/

Distribution via référentiel Git

Balisez les versions pour que les consommateurs puissent les fixer :

git tag v1.0.0
git push origin v1.0.0

Les consommateurs installent avec :

pulumi package add https://github.com/myorg/my-component@v1.0.0

Distribution via gestionnaire de paquets

Publiez des paquets spécifiques à un langage pour la gestion des dépendances native :

  • npm : npm publish pour TypeScript/JavaScript
  • PyPI : twine upload pour Python
  • NuGet : dotnet nuget push pour .NET
  • Maven Central : Publication Maven standard pour Java

Référence : https://www.pulumi.com/docs/iac/using-pulumi/pulumi-packages/


Anti-modèles

Anti-modèle Problème Solution
Ressources à l'intérieur de apply() Non visibles dans pulumi preview Déplacez la création de ressources en dehors de apply (voir la pratique 1 de pulumi-best-practices)
registerOutputs() manquant Composant bloqué dans l'état "créé" Appelez toujours à la dernière ligne du constructeur
parent: this manquant Les enfants apparaissent au niveau racine Passez { parent: this } à toutes les ressources enfants
Types union dans args Casse les SDK Python, Go, C# Utilisez des types uniques ; propriétés séparées pour les variantes
Fonctions dans args Ne peuvent pas être sérialisées à travers les langues Utilisez des propriétés de configuration au lieu de cela
Noms des enfants codés en dur Collisions avec plusieurs instances Dérivez les noms à partir de ${name}-suffix
Outputs surexposés Fuit les détails d'implémentation Exportez uniquement ce dont les consommateurs ont besoin
Composant à usage unique Surcharge d'abstraction inutile Utilisez les ressources inline jusqu'à ce qu'un modèle se répète
Args profondément imbriqués Difficiles à utiliser et à faire évoluer Gardez les interfaces plates avec des propriétés optionnelles

Référence rapide

Sujet Point clé
Type URN <package>:<module>:<type>, module généralement index
Constructeur super(type, name, {}, opts) puis enfants puis registerOutputs()
Ressources enfants Toujours { parent: this }, dérivez le nom de ${name}-suffix
Interface args Enveloppez dans Input<T>, pas d'unions, pas de fonctions, structure plate
Outputs Propriétés Output<T> en lecture seule publiques, exposez seulement les essentiels
Valeurs par défaut Utilisez l'opérateur ?? pour appliquer des valeurs par défaut sensées dans le constructeur
Composition Composants de bas niveau composés en composants de haut niveau
Multi-langage PulumiPlugin.yaml + point d'entrée ; les consommateurs utilisent pulumi package add
Distribution Registre privé, balises git, gestionnaires de paquets, ou registre public Pulumi

Compétences liées

  • pulumi-best-practices : Modèles Pulumi généraux incluant la gestion des Outputs, les secrets et les alias
  • pulumi-automation-api : Orchestration programmatique pour les tests d'intégration et les workflows multi-stacks
  • pulumi-esc : Secrets et configuration centralisés pour les déploiements de composants

Références

Skills similaires