pulumi-component について
Pulumi ComponentResource を作成する開発者向けのスキルです。コンポーネントの構造、引数と出力の設計、複数の Pulumi 言語向けのパッケージ化を扱います。
主な機能
- 01コンポーネントの構造とライフサイクル
- 02引数インターフェースの設計
- 03出力とリソースの合成
- 04多言語コンポーネントのパッケージ化
活用シーン
- 01再利用可能なインフラコンポーネントの作成
- 02インラインリソースのコンポーネント化
- 03複数言語向けコンポーネントのパッケージ化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pulumi/ agent-skills --skill pulumi-componentインストール後、Claude Code で/pulumi-componentを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamluse_cases.yaml
詳細
Pulumi コンポーネントの作成
ComponentResource は、関連するインフラストラクチャリソースを再利用可能な論理単位にまとめます。コンポーネントを使うと、インフラストラクチャを理解、再利用、保守しやすくなります。コンポーネントは、pulumi preview/pulumi up の出力や Pulumi Cloud コンソールでは、子リソースを配下に持つ単一のノードとして表示されます。
このスキルでは、コンポーネント作成のライフサイクル全体を扱います。一般的な Pulumi のコーディングパターン(Output の処理、シークレット、エイリアス、プレビューのワークフロー)については、代わりに pulumi-best-practices スキルを使用してください。
このスキルを使用するタイミング
以下の場合にこのスキルを呼び出してください。
- 新しい ComponentResource クラスを作成する
- コンポーネントの args インターフェースを設計する
- 複数の Pulumi 言語からコンポーネントを利用できるようにする
- コンポーネントパッケージを公開または配布する
- インラインリソースを再利用可能なコンポーネントにリファクタリングする
- コンポーネントの動作をデバッグする(出力がない、作成中のまま進まない、子リソースが誤った階層にあるなど)
コンポーネントの構成
すべてのコンポーネントには、次の4つの必須要素があります。
- ComponentResource を継承し、型 URN を指定して
super()を呼び出す - 標準パラメーターを受け取る:name、args、
ComponentResourceOptions - すべての子リソースに
parent: thisを設定する - コンストラクターの最後に
registerOutputs()を呼び出す
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)
型 URN の形式
super() の最初の引数は型 URN です:<package>:<module>:<type>。
| セグメント | 規則 | 例 |
|---|---|---|
| package | 組織名またはパッケージ名 | myorg、acme、pkg |
| module | 通常は index | index |
| type | PascalCase のクラス名 | StaticSite、VpcNetwork |
完全な例:myorg:index:StaticSite、acme:index:KubernetesCluster
registerOutputs は必須です
理由:registerOutputs() がないと、Pulumi コンソールでコンポーネントが「作成中」の状態のままになり、出力が状態に保存されません。
誤り:
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"
}
}
正しい例:
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 });
}
}
子リソース名はコンポーネント名から派生させる
理由:子リソース名をハードコードすると、コンポーネントを複数回インスタンス化したときに名前が衝突します。
誤り:
// Collides if two instances of this component exist
const bucket = new aws.s3.Bucket("my-bucket", {}, { parent: this });
正しい例:
// Unique per component instance
const bucket = new aws.s3.Bucket(`${name}-bucket`, {}, { parent: this });
Args インターフェースの設計
args インターフェースは、設計上最も大きな影響を及ぼす要素です。利用者が設定できる内容と、コンポーネントの組み合わせやすさを決定します。
プロパティを Input でラップする
理由:Input<T> は通常の値と、ほかのリソースからの Output<T> の両方を受け取ります。これを使わないと、利用者は .apply() を使って手動で出力をアンラップする必要があります。
誤り:
interface WebServiceArgs {
port: number; // Forces consumers to unwrap Outputs
vpcId: string; // Cannot accept vpc.id directly
}
正しい例:
interface WebServiceArgs {
port: pulumi.Input<number>; // Accepts 8080 or someOutput
vpcId: pulumi.Input<string>; // Accepts "vpc-123" or vpc.id
}
構造をフラットに保つ
深くネストした args オブジェクトは避けてください。フラットなインターフェースは使いやすく、変更にも対応しやすくなります。
// 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> };
};
}
Union 型は使用しない
Union 型は複数言語向けの SDK 生成を妨げます。Python、Go、C# では string | number を表現できません。
誤り:
interface MyArgs {
port: pulumi.Input<string | number>; // Fails in Python, Go, C#
}
正しい例:
interface MyArgs {
port: pulumi.Input<number>; // Single type, works everywhere
}
複数の形式を受け入れる必要がある場合は、個別のオプショナルプロパティを使用してください。
interface StorageArgs {
sizeGb?: pulumi.Input<number>; // Specify size in GB
sizeMb?: pulumi.Input<number>; // Or specify size in MB
}
関数やコールバックは使用しない
関数は言語の境界を越えてシリアライズできません。
誤り:
interface MyArgs {
nameTransform: (name: string) => string; // Cannot serialize
}
正しい例:
interface MyArgs {
namePrefix?: pulumi.Input<string>; // Configuration instead of callback
nameSuffix?: pulumi.Input<string>;
}
オプショナルプロパティにはデフォルト値を設定する
利用者が必要な設定だけを行えるように、コンストラクター内で妥当なデフォルト値を設定します。
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 });
出力の公開
利用者が必要とするものだけを公開する
コンポーネントは多くの内部リソースを作成することがよくあります。すべての内部リソースではなく、利用者が必要とする値だけを公開してください。
誤り:
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;
// ...
}
正しい例:
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,
});
}
}
複合出力を導出する
pulumi.interpolate または pulumi.concat を使用して、導出した値を作成します。
this.connectionString = pulumi.interpolate`postgresql://${args.username}:${args.password}@${cluster.endpoint}:${cluster.port}/${args.databaseName}`;
this.registerOutputs({ connectionString: this.connectionString });
コンポーネントの設計パターン
妥当なデフォルト値と上書き
ベストプラクティスをデフォルト値として組み込みます。利用者に固有の要件がある場合は、上書きできるようにします。
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 });
}
}
条件付きリソース作成
オプショナルな args を使って、サブリソースの作成を制御します。
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({});
}
}
合成
低レベルのコンポーネントから、より高レベルのコンポーネントを構築します。各レベルは単一の関心事を管理します。
// 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 });
}
}
プロバイダーの引き渡し
マルチリージョンまたはマルチアカウントでのデプロイでは、プロバイダーを明示的に指定できます。ComponentResourceOptions はプロバイダー設定を子リソースに自動的に引き継ぎます。
// 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],
});
{ parent: this } を指定した子リソースは、プロバイダーを自動的に継承します。コンポーネント内に追加のコードは必要ありません。
マルチ言語コンポーネント
コンポーネントが複数の Pulumi 言語(TypeScript、Python、Go、C#、Java、YAML)から利用される場合は、マルチ言語コンポーネントとしてパッケージ化します。
マルチ言語対応は必要ですか?
「このコンポーネントを、作成時とは異なる言語から利用する人はいますか?」と考えてみてください。
単一言語コンポーネント(パッケージ化は不要):
- チームが1つの言語を使用し、コンポーネントがそのコードベース内にとどまる
- コンポーネントが単一のプロジェクトまたはモノレポ内でのみ使用される
PulumiPlugin.yamlは不要です。クラスを直接インポートするだけです
マルチ言語コンポーネント(パッケージ化が必要):
- 他のチームが異なる言語でコンポーネントを利用する
- 開発者が使用する言語を選べるように、プラットフォームチームが抽象化を構築する
- YAML の利用者がアクセスする必要がある。TypeScript で作成した場合でも、YAML プログラムからコンポーネントを使うにはマルチ言語パッケージ化が必要です
- 組織内で共有するコンポーネントライブラリを構築する
- Pulumi のプライベートレジストリまたはパブリックレジストリへの公開は一般的な理由ですが、マルチ言語対応に必須ではありません
よくある間違い:TypeScript のプラットフォームチームが、TypeScript の利用者だけが使えるコンポーネントを構築してしまいます。アプリケーション開発者が Python または YAML を使う場合、マルチ言語パッケージ化をしないと、これらのコンポーネントを利用できません。
セットアップ
コンポーネントディレクトリに PulumiPlugin.yaml を作成し、ランタイムを宣言します。
runtime: nodejs
Python の場合:
runtime: python
シリアライズの制約
マルチ言語での互換性を確保するには、引数がシリアライズ可能である必要があります。これらの制約は作成時の言語にかかわらず適用されます。
| 使用可能 | 使用不可 |
|---|---|
string、number、boolean | ユニオン型(string | number) |
Input<T> ラッパー | 関数とコールバック |
| プリミティブ型の配列とマップ | 複雑なネストされたジェネリクス |
| 列挙型 | プラットフォーム固有の型 |
マルチ言語コンポーネントの利用
利用者は pulumi package add を使ってコンポーネントをインストールします。これにより、プロバイダープラグインが自動的にダウンロードされ、利用者の言語に対応するローカル SDK が生成され、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
新規チェックアウトや CI 環境では、pulumi install を実行して、すべてのパッケージ依存関係が利用可能であることを確認してください。利用者が SDK を手動で生成する必要はありません。
SDK をパッケージマネージャー(npm、PyPI など)に公開する作成者は、任意で pulumi package gen-sdk を使って、公開用の言語固有 SDK を生成できます。ほとんどのコンポーネント作成者には不要です。pulumi package add が利用者側での SDK 生成を処理します。
エントリーポイント
公開済みのマルチ言語コンポーネントには、コンポーネントプロバイダープロセスをホストするエントリーポイントが必要です。エントリーポイントのパターンは言語によって異なります。
TypeScript(runtime: nodejs):
index.ts からコンポーネントクラスをエクスポートします。個別のエントリーポイントファイルは不要です。Pulumi がエクスポートされたクラスを自動的に調べます。
// index.ts -- exports are the entry point
export { StaticSite, StaticSiteArgs } from "./staticSite";
export { SecureBucket, SecureBucketArgs } from "./secureBucket";
Python(runtime: python):
すべてのコンポーネントクラスを指定して component_provider_host を呼び出す __main__.py を作成します。
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):
プロバイダーを構築して実行する main.go を作成します。
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):
コンポーネントプロバイダーホストを提供する Program.cs を作成します。
using System.Threading.Tasks;
class Program
{
public static Task Main(string[] args) =>
Pulumi.Experimental.Provider.ComponentProviderHost.Serve(args);
}
すべての言語に対応した完全な動作例については、https://github.com/mikhailshilkov/comp-as-comp をご覧ください。
参考資料:https://www.pulumi.com/docs/iac/using-pulumi/pulumi-packages/
配布
対象ユーザーに応じて配布方法を選択します。
| 対象ユーザー | 方法 | 手順 |
|---|---|---|
| 同じプロジェクト | 直接インポート | 標準的な言語のインポート |
| 同じ組織 | プライベートレジストリ | pulumi package publish で Pulumi Cloud に公開 |
| 同じ組織 | Git リポジトリ | バージョンタグを付けて pulumi package add <repo> |
| 言語エコシステム | パッケージマネージャー | npm、PyPI、NuGet、または Maven に公開 |
| パブリックコミュニティ | Pulumi Registry | pulumi/registry GitHub リポジトリから申請 |
Pulumi Private Registry
プライベートレジストリは、組織のコンポーネントを一元管理するカタログです。すべてのチームに対して、API ドキュメントの自動生成、バージョン管理、検索性を提供します。
コンポーネントをプライベートレジストリに公開します。
pulumi package publish https://github.com/myorg/my-component --publisher myorg
v プレフィックスを付けた Git タグを使用してコンポーネントをバージョン管理します。
git tag v1.0.0
git push origin v1.0.0
公開時には README ファイルが必要です。Pulumi はこれをレジストリ内のコンポーネントのドキュメントページとして使用します。
OIDC 認証を使用し、GitHub Actions からの公開を自動化します。
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 }}
前提条件:このワークフローを使用する前に、Pulumi Cloud で GitHub OIDC インテグレーションを設定してください。
レジストリは、プライベートな GitHub リポジトリと GitLab リポジトリをサポートしています。OIDC を使用しない構成では、環境変数 GITHUB_TOKEN または GITLAB_TOKEN で認証してください。
プライベートレジストリは、公開されたコンポーネントごとに SDK ドキュメントを自動生成します。コンポーネントの入力と出力に型注釈を追加して、生成されたドキュメントを充実させてください(TypeScript では JSDoc、Python では docstring、Go では Annotate() メソッドを使用します)。
リファレンス:https://www.pulumi.com/docs/idp/get-started/private-registry/
Git リポジトリによる配布
利用者がバージョンを固定できるよう、リリースにタグを付けます。
git tag v1.0.0
git push origin v1.0.0
利用者は次のコマンドでインストールします。
pulumi package add https://github.com/myorg/[email protected]
パッケージマネージャーによる配布
言語固有のパッケージを公開し、ネイティブの依存関係管理を利用できるようにします。
- npm:TypeScript/JavaScript では
npm publish - PyPI:Python では
twine upload - NuGet:.NET では
dotnet nuget push - Maven Central:Java では標準的な Maven の公開手順
リファレンス:https://www.pulumi.com/docs/iac/using-pulumi/pulumi-packages/
アンチパターン
| アンチパターン | 問題 | 修正方法 |
|---|---|---|
apply() 内のリソース | pulumi preview に表示されない | リソースの作成を apply の外に移します(pulumi-best-practices のプラクティス 1 を参照) |
registerOutputs() の不足 | コンポーネントが「作成中」のままになる | コンストラクターの最後の行で必ず呼び出します |
parent: this の不足 | 子リソースがルートレベルに表示される | すべての子リソースに { parent: this } を渡します |
| args 内の共用体型 | Python、Go、C# の SDK で問題が発生する | 単一の型を使用し、バリエーションごとにプロパティを分けます |
| args 内の関数 | 言語間でシリアライズできない | 代わりに構成プロパティを使用します |
| 子リソース名のハードコード | 複数のインスタンスで名前が衝突する | ${name}-suffix から名前を生成します |
| 過剰な出力の公開 | 実装の詳細が漏れる | 利用者が必要とするものだけをエクスポートします |
| 一度しか使わないコンポーネント | 不必要な抽象化のオーバーヘッド | パターンが繰り返されるまではインラインリソースを使用します |
| 過度にネストされた args | 使いにくく、変更もしにくい | インターフェースは任意プロパティを使ってフラットに保ちます |
クイックリファレンス
| トピック | 要点 |
|---|---|
| 型 URN | <package>:<module>:<type>。モジュールは通常 index |
| コンストラクター | super(type, name, {}, opts) の後に子リソースを作成し、registerOutputs() を呼び出す |
| 子リソース | 常に { parent: this } を指定し、${name}-suffix から名前を生成する |
| Args インターフェース | Input<T> でラップし、共用体型と関数は使わず、フラットな構造にする |
| 出力 | 公開する readonly Output<T> プロパティ。必要なものだけを公開する |
| デフォルト値 | コンストラクター内で適切なデフォルト値を設定するには ?? 演算子を使う |
| 合成 | 低レベルコンポーネントを高レベルコンポーネントに合成する |
| マルチランゲージ | PulumiPlugin.yaml とエントリーポイントを用意する。利用者は pulumi package add を使用する |
| 配布 | プライベートレジストリ、Git タグ、パッケージマネージャー、またはパブリックな Pulumi Registry |
関連スキル
- pulumi-best-practices:Output の処理、シークレット、エイリアスなど、Pulumi の一般的なパターン
- pulumi-automation-api:統合テストや複数スタックのワークフロー向けのプログラムによるオーケストレーション
- pulumi-esc:コンポーネントのデプロイ向けの一元化されたシークレットと構成
参考資料
- 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/