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 :
- Étendre ComponentResource et appeler
super()avec un type URN - Accepter les paramètres standards : name, args, et
ComponentResourceOptions - Définir
parent: thissur toutes les ressources enfants - 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.yamlné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 publishpour TypeScript/JavaScript - PyPI :
twine uploadpour Python - NuGet :
dotnet nuget pushpour .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
- https://www.pulumi.com/docs/iac/concepts/resources/components/
- https://www.pulumi.com/docs/iac/using-pulumi/pulumi-packages/
- https://www.pulumi.com/docs/idp/get-started/private-registry/
- https://www.pulumi.com/docs/iac/concepts/inputs-outputs/
- https://www.pulumi.com/docs/iac/concepts/resources/options/aliases/