Communitygithub.com

agents-inc/skills

Pulumiを使用したTypeScriptネイティブのInfrastructure as Code

skills とは?

このスキルは、Pulumiフレームワークを使用してTypeScriptでクラウドインフラストラクチャを定義・管理するための包括的なガイドです。AWS、GCP、Azure、Kubernetesなどのクラウドプロバイダーをサポートし、型安全なリソースAPI、再利用可能なコンポーネントリソース、スタック間のデータ共有、シークレット管理、自動化API、コンプライアンスポリシーなど、実践的なパターンを提供します。特に、`ComponentResource`の正しい使い方(子リソースへの`{ parent: this }`の受け渡し、`registerOutputs()`の呼び出し)、`Output`型の適切な操作(`pulumi.interpolate`の使用、`.apply()`内でのリソース作成禁止)、設定とシークレットの管理(`Config.requireSecret()`の使用)など、Pulumiのベストプラクティスに焦点を当てています。また、リソースのライフサイクル管理(エイリアス、プロテクト、変換)や、CLIなしでPulumiをプログラム的に実行するAutomation APIの使用方法もカバーしています。これらのパターンに従うことで、チームは宣言的で保守性の高いインフラストラクチャコードを書くことができます。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Pulumi Infrastructure as Code(Pulumiインフラストラクチャ アズ コード)

クイックガイド: 完全な型安全性を持つTypeScriptでクラウドインフラストラクチャを定義します。ComponentResourceを使用して再利用可能なインフラストラクチャパターンをカプセル化します。コンポーネント内のすべての子リソースには{ parent: this }を渡します。Outputを使った文字列構築にはpulumi.interpolateを使用します(文字列連結は使用しないでください)。.apply()内でリソースを作成しないでください。機密性の高い値にはConfig.requireSecret()を使用します。非推奨のtransformationsよりもtransformsを優先します。コンポーネントコンストラクタの最後で必ずthis.registerOutputs()を呼び出します。


<critical_requirements>

致命的: このスキルを使用する前に

すべてのコードはCLAUDE.mdのプロジェクト規約に従う必要があります(ケバブケース、名前付きエクスポート、インポート順序、import type、名前付き定数)

(ComponentResource内のすべての子リソースに{ parent: this }を渡さなければなりません -- 省略するとリソースツリーと状態追跡が壊れます)

(Outputを使った文字列構築にはpulumi.interpolateを使用しなければなりません -- 文字列連結は黙って[object Object]を生成します)

(.apply()内でリソースを作成してはいけません -- pulumi previewに表示されず、順序問題を引き起こします)

(機密性の高い値にはConfig.requireSecret()を使用しなければなりません -- Config.require()は値をプレーンテキストで状態に保存します)

(すべてのComponentResourceコンストラクタの最後でthis.registerOutputs()を呼び出さなければなりません -- 省略すると出力追跡ができなくなります)

</critical_requirements>


詳細リソース:

  • examples/core.md - リソース定義、コンポーネントリソース、命名、Output、設定/シークレット
  • examples/advanced.md - スタック参照、変換、動的プロバイダー、Automation API、ポリシーパック
  • reference.md - 意思決定フレームワーク、リソースオプションテーブル、APIリファレンス、CLIコマンド

自動検出: 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

使用する場合:

  • 型安全なリソースAPIを持つTypeScriptでクラウドインフラストラクチャを定義する
  • ComponentResourceで再利用可能なインフラストラクチャコンポーネントを作成する
  • スタック参照を使用してマルチスタックアーキテクチャを管理する
  • シークレットと環境固有の設定を処理する
  • Automation APIでセルフサービスインフラストラクチャプラットフォームを構築する
  • CrossGuardポリシーパックでコンプライアンスポリシーを作成する

使用しない場合:

  • 単一リソースを作成する一回限りのシェルスクリプト(クラウドCLIを直接使用)
  • チームにTypeScriptの経験がないプロジェクト(他のIaC言語オプションを検討)

カバーする主要パターン:

  • 型付き入力と自動命名によるリソース定義
  • ComponentResourceのカプセル化(parent、命名、registerOutputs)
  • Output: apply、all、interpolate、リフティング
  • Configとシークレット管理(Config.require、Config.requireSecret、pulumi.secret)
  • スタック間データ共有のためのスタック参照
  • リソースオプション(dependsOn、protect、aliases、ignoreChanges、transforms)
  • カスタムCRUDリソースのための動的プロバイダー
  • プログラムによるスタック管理のためのAutomation API
  • コンプライアンス実施のためのCrossGuardポリシーパック

哲学

Pulumiはインフラストラクチャを構成ファイルではなく、実際のコードとして扱います。TypeScriptは型安全性、IDEオートコンプリート、リファクタリングツール、そしてNode.jsエコシステム全体を提供します。リソースはオブジェクトであり、依存関係は自動的に解決され、再利用はカスタムモジュール言語ではなく関数とクラスを通じて行われます。

中核原則:

  • リソースはオブジェクト: すべてのクラウドリソースは、型付きの入力と出力を持つTypeScriptクラスのインスタンスです
  • 依存関係は自動的: あるリソースの出力を別のリソースの入力として渡すと、Pulumiが依存関係グラフを推論します
  • コンポーネントによる再利用: ComponentResourceは複数のリソースを、独自の入力と出力を持つ単一の論理ユニットにカプセル化します
  • Outputはプロミス: Output<T>はデプロイ後まで不明な可能性がある値を表します -- これらを操作するにはapply、all、またはinterpolateを使用し、手動でアンラップしないでください
  • 状態は管理される: Pulumiはすべてのリソースを状態で追跡します -- 論理名を変更したり、リソースをファイル間で移動したりすると、aliasesを使用しない限り削除と再作成がトリガーされます

コアパターン

パターン1: リソース定義

すべてのリソースは、論理名(状態追跡に使用)、argsバッグ(型付き入力)、およびオプションのリソースオプションを受け取ります。

const bucket = new aws.s3.Bucket(
  "data-bucket",
  {
    versioning: { enabled: true },
    lifecycleRules: [
      { enabled: true, expiration: { days: BUCKET_EXPIRY_DAYS } },
    ],
  },
  { protect: true },
); // 偶発的な削除を防止

重要なポイント: 論理名はスタック内でタイプごとに一意でなければなりません。Pulumiは物理名にランダムなサフィックスを自動追加して衝突を防ぎます。重要なリソースにはprotect: trueを使用します。クロススタック消費のために出力をエクスポートします。

リソースの命名、自動命名設定、プロバイダーオプションについてはexamples/core.mdを参照してください。


パターン2: ComponentResourceのカプセル化

関連するリソースをComponentResourceでラップして、再利用可能なインフラストラクチャユニットを作成します。

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,
    });
  }
}

優れている理由: 子リソースは{ parent: this }を使用し、名前は親からプレフィックス付けされ、registerOutputsが呼び出され、型トークンはpkg:module:Type形式に従います

プロバイダーの継承と複数リソースコンポーネントを含む完全なコンポーネントパターンについてはexamples/core.mdを参照してください。


パターン3: Outputの操作

Outputはデプロイ後に解決される値を表します。文字列連結は絶対に使用しないでください -- interpolate、apply、またはallを使用します。

// interpolate -- 文字列構築のためのタグ付きテンプレート(推奨)
const url = pulumi.interpolate`https://${bucket.bucketRegionalDomainName}/index.html`;

// apply -- 単一のOutputを変換
const upper = bucket.id.apply((id) => id.toUpperCase());

// all -- 複数のOutputを組み合わせる
const endpoint = pulumi
  .all([lb.dnsName, listener.port])
  .apply(([dns, port]) => `http://${dns}:${port}`);

// リフティング -- リソース出力のプロパティに直接アクセス
const subnetId = vpc.subnets[0].id; // 既知のプロパティはapply不要

優れている理由: interpolateはOutput値を透過的に処理し、allは複数の値を待ち、リフティングは不要なapply呼び出しを回避します

落とし穴: .apply()内で作成されたリソースはpulumi previewに表示されず、順序問題を引き起こす可能性があります。常にOutputを他のリソースの入力として直接渡してください。

Outputパターン、pulumi.output()ラッピング、applyのアンチパターンについてはexamples/core.mdを参照してください。


パターン4: Configとシークレット

スタック固有の値にはpulumi.Configを使用します。機密データにはrequireSecretを使用します -- これにより値が状態で暗号化されます。

const config = new pulumi.Config();

// プレーンなConfig値
const region = config.require("region"); // ない場合は失敗
const nodeCount = config.getNumber("nodeCount"); // ない場合はundefinedを返す

// シークレット値 -- 状態で暗号化
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.getSecret("apiKey");

// プログラム上の値をシークレットとしてマーク
const connectionString = pulumi.interpolate`postgres://admin:${dbPassword}@${db.endpoint}/mydb`;
// dbPasswordがシークレットなのでconnectionStringは自動的にシークレットになる

// 値を明示的にシークレットとしてマーク
const token = pulumi.secret(generateToken());

重要なポイント: シークレットから派生したOutputは自動的にシークレットとしてマークされます。派生値を再マークする必要はありません。

名前空間付きConfig、シークレット出力、Config設定CLIコマンドについてはexamples/core.mdを参照してください。


パターン5: リソースオプション

リソースオプションはライフサイクル動作を制御します。最も重要なものは以下の通りです:

const db = new aws.rds.Instance(
  "primary-db",
  {
    /* ... */
  },
  {
    protect: true, // 偶発的な削除を防止
    dependsOn: [vpc, securityGroup], // 明示的な順序付け
    ignoreChanges: ["tags"], // 特定のプロパティのドリフトを無視
    aliases: [{ name: "old-db-name" }], // 再作成せずに名前変更
    retainOnDelete: true, // pulumi destroyでクラウドリソースを保持
    deleteBeforeReplace: true, // 一意である必要があるリソース用
    replaceOnChanges: ["engine"], // 特定の変更で強制的に置き換え
    provider: usEastProvider, // 明示的なプロバイダー(リージョン、アカウント)
  },
);

落とし穴: リソースの論理名や親を変更すると、Pulumiはそのリソースを削除して再作成します。安全に名前変更するにはaliasesを使用します。

完全なリソースオプションテーブルについてはreference.mdを参照してください。


パターン6: スタック参照

StackReferenceを使用して、スタック間で出力を共有します。

// ネットワーキングスタック内: 出力をエクスポート
export const vpcId = vpc.id;
export const subnetIds = subnets.map((s) => s.id);

// アプリケーションスタック内: 出力を消費
const networkStack = new pulumi.StackReference("myorg/networking/prod");
const vpcId = networkStack.getOutput("vpcId");
const subnetIds = networkStack.getOutput("subnetIds");

// requireOutputは出力が存在しない場合に失敗する(getOutputより安全)
const vpcIdRequired = networkStack.requireOutput("vpcId");

スタック参照パターンとgetOutputDetailsについてはexamples/advanced.mdを参照してください。


パターン7: Transform

リソースとその子に変換を適用します。transformsを使用します(非推奨のtransformationsではなく)。Transformはtype、props、optsを持つargsオブジェクトを受け取り、変更された結果を返すか、スキップするためにundefinedを返します。

// リソースレベル: コンポーネントのタグ付け可能なすべての子にタグを適用
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;
      },
    ],
  },
);

非推奨のtransformationsとの主な違い: transformsはパッケージ化されたコンポーネントの子(awsx、eks)の変更をサポートし、非同期コールバックをサポートし、Resourceオブジェクトを渡しません。

スタックレベルの変換、オプション変更、transformationsからの移行についてはexamples/advanced.mdを参照してください。


パターン8: Automation API

CLIなしでPulumiをプログラム的に実行 -- セルフサービスのプラットフォーム、統合テスト、カスタムデプロイツールに使用します。

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 });

重要なポイント: Automation APIはPulumi CLIがインストールされ、PATHに含まれている必要があります -- 内部でCLIのエンジンを使用します。

プレビュー、破棄、ローカルプログラムモード、設定セットアップ、スタック出力の取得についてはexamples/advanced.mdを参照してください。


<red_flags>

危険信号

高優先度:

  • .apply()内でのリソース作成 -- pulumi previewに表示されず、順序問題を引き起こし、依存関係グラフを壊します。Outputをリソース入力として直接渡してください。
  • ComponentResourceの子での{ parent: this }の欠落 -- リソースが状態ツリーのルートに表示され、論理グループ化とコンポーネント削除のカスケードが壊れます。
  • Outputとの文字列連結 -- "https://" + bucket.idは[object Object]を生成します。代わりにpulumi.interpolateを使用してください。
  • パスワード/キーへのConfig.require()の使用 -- 値をプレーンテキストで状態に保存します。暗号化するにはConfig.requireSecret()を使用してください。
  • this.registerOutputs()の忘れ -- コンポーネントの出力が状態で適切に追跡されず、スタック参照で利用できなくなります。
  • エイリアスなしでの論理名の変更 -- Pulumiはリソースを削除して再作成します。安全に名前変更するにはaliases: [{ name: "old-name" }]を使用してください。

中優先度:

  • マルチリージョン設定でのデフォルトプロバイダーへの依存 -- リージョンごとに明示的なプロバイダーを使用します。設定でpulumi:disable-default-providersを設定して強制します。
  • ComponentResourceではなく関数から始める -- 後で移行するにはエイリアスまたはリソースの再作成が必要です。最初からコンポーネントを使用してください。
  • 明白でない依存関係にdependsOnを使用しない -- PulumiはInput/Outputの配線から依存関係を推論しますが、副作用(IAM伝搬の遅延など)には明示的なdependsOnが必要です。
  • コンポーネントコンストラクタでのthis.registerOutputs()の前にawaitを使用する -- これにより親への未完了の出力が公開され、状態が破損する可能性があります。コンストラクタを同期的に保ち、レジスタンスの前に非同期初期化を解決するにはファクトリ関数を使用します。
  • pulumi.output()でOutputをラップする際のネストされたOutput -- pulumi.output(pulumi.output(value))は予期しない動作を引き起こします。pulumi.output()の前に入力を平坦化してください。

低優先度:

  • すべてに対するapplyの多用 -- applyは高階関数です。可能な限りリフティングとinterpolateを優先します。
  • Outputの結果からの型アサーションas SomeType -- Output値を型アサーションする代わりに、pulumi.output()と明示的な型パラメータを使用します。
  • リソース作成での非同期IIFE -- コンストラクタ内でリソースを同期的に作成します。非同期コードが必要な場合は、ファクトリ/ビルダーパターンを使用します。

</red_flags>


<common_examples>

一般的な例

例: S3バケットポリシー

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

const bucket = new aws.s3.Bucket("my-bucket");

const bucketPolicy = new aws.s3.BucketPolicy("my-bucket-policy", {
  bucket: bucket.id,
  policy: pulumi.interpolate`{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::${bucket.id}/*"
    }]
  }`,
});

例: ECSクラスター

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

const cluster = new awsx.ecs.Cluster("my-cluster");

const service = new awsx.ecs.FargateService("web", {
  cluster,
  taskDefinitionArgs: {
    container: {
      image: "nginx:latest",
      memory: 512,
      portMappings: [{ containerPort: 80 }],
    },
  },
});

export const url = service.loadBalancer.dnsName;

</common_examples>

Individual skills in this repo

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

関連スキル