Communitygithub.com

agents-inc/skills

使用 TypeScript 实现的基础设施即代码,基于 Pulumi 框架

skills 是什么?

本技能专注于使用 TypeScript 和 Pulumi 框架进行云基础设施管理。它提供了完整的代码模式与最佳实践,包括可复用的 ComponentResource 封装、Output 值的正确操作(避免字符串拼接,推荐使用 interpolate)、Config 与 Secrets 管理、跨栈数据共享(StackReference)、资源选项(如 protect、ignoreChanges、aliases)以及自动化 API 编程用途。技能同时强调关键反模式警告,例如禁止在 .apply() 内创建资源、必须传递 { parent: this } 以维持资源树结构。支持 AWS、GCP、Azure、Kubernetes 等主流云平台,适用于构建自服务平台、合规策略管理(CrossGuard)及多栈架构。

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

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Pulumi 基础设施即代码

快速指南: 使用完全类型安全的 TypeScript 定义云基础设施。使用 ComponentResource 封装可复用的基础设施模式。在组件内部将所有子资源传入 { parent: this }。使用 pulumi.interpolate 进行带有 Output 的字符串构建(不要使用字符串拼接)。永远不要在 .apply() 内部创建资源。对敏感值使用 Config.requireSecret()。优先使用 transforms 而非已弃用的 transformations。始终在组件构造函数末尾调用 this.registerOutputs()。


<critical_requirements>

关键:使用此技能前须知

所有代码必须遵循 CLAUDE.md 中的项目约定(kebab-case、命名导出、导入顺序、import type、命名常量)

(你必须将 { parent: this } 传递给 ComponentResource 内的所有子资源 — 省略会破坏资源树和状态跟踪)

(你必须使用 pulumi.interpolate 进行带有 Output 的字符串构建 — 字符串拼接会静默产生 [object Object])

(你绝不能在 .apply() 内部创建资源 — 它们不会出现在 pulumi preview 中,并会导致排序问题)

(你必须对敏感值使用 Config.requireSecret() — Config.require() 会将值以明文形式存储在状态中)

(你必须在每个 ComponentResource 构造函数末尾调用 this.registerOutputs() — 省略会阻止输出跟踪)

</critical_requirements>


详细资源:

  • examples/core.md - 资源定义、组件资源、命名、Output、配置/密文
  • examples/advanced.md - 栈引用、转换、动态提供程序、自动化 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

何时使用:

  • 使用 TypeScript 和类型安全的资源 API 定义云基础设施
  • 使用 ComponentResource 创建可复用的基础设施组件
  • 使用栈引用管理多栈架构
  • 处理密文和环境特定配置
  • 使用自动化 API 构建自助服务平台
  • 使用 CrossGuard 策略包编写合规策略

何时不应使用:

  • 一次性的 Shell 脚本创建单个资源(直接使用云 CLI)
  • 团队没有 TypeScript 经验的项目(考虑其他 IaC 语言选项)

涵盖的关键模式:

  • 带有类型输入和自动命名的资源定义
  • ComponentResource 封装(父级、命名、registerOutputs)
  • Output:apply、all、interpolate 和提升
  • 配置和密文管理(Config.require、Config.requireSecret、pulumi.secret)
  • 用于跨栈数据共享的栈引用
  • 资源选项(dependsOn、protect、aliases、ignoreChanges、transforms)
  • 用于自定义 CRUD 资源的动态提供程序
  • 用于程序化栈管理的自动化 API
  • 用于合规执行的 CrossGuard 策略包

哲学

Pulumi 将基础设施视为真正的代码,而非配置文件。TypeScript 提供了类型安全、IDE 自动补全、重构工具以及完整的 Node.js 生态系统。资源是对象,依赖关系是自动的,重用通过函数和类实现 — 而非自定义的模块语言。

核心原则:

  • 资源是对象:每个云资源都是一个 TypeScript 类实例,带有类型化的输入和输出
  • 依赖关系是自动的:当你将一个资源的输出作为另一个资源的输入时,Pulumi 会推断依赖关系图
  • 通过组件实现重用:ComponentResource 将多个资源封装成一个逻辑单元,具有自己的输入和输出
  • Output 是 Promise:Output<T> 表示一个值,该值可能在部署完成后才确定 — 使用 apply、all 或 interpolate 来处理它们,切勿手动解包
  • 状态被管理:Pulumi 在状态中跟踪每个资源 — 更改逻辑名称或在文件之间移动资源会触发删除并重新创建,除非你使用 aliases

核心模式

模式 1:资源定义

每个资源都接受一个逻辑名称(用于状态跟踪)、一个参数包(类型化输入)以及可选的资源选项。

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 — 转换单个输出
const upper = bucket.id.apply((id) => id.toUpperCase());

// all — 组合多个输出
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 直接作为输入传递给其他资源。

参见 examples/core.md 了解 Output 模式、pulumi.output() 封装和 apply 反模式。


模式 4:配置和密文

使用 pulumi.Config 处理栈特定值。对敏感数据使用 requireSecret — 它会在状态中加密该值。

const config = new pulumi.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`;
// connectionString 自动成为密文,因为 dbPassword 是密文

// 显式标记一个值为密文
const token = pulumi.secret(generateToken());

关键点: 任何从密文派生的 Output 都会自动标记为密文。你不需要重新标记派生值。

参见 examples/core.md 了解命名空间配置、密文输出和 config set CLI 命令。


模式 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");

参见 examples/advanced.md 了解栈引用模式和 getOutputDetails。


模式 7:转换

对资源及其子资源应用转换。使用 transforms(而非已弃用的 transformations)。Transforms 接收一个参数对象,包含 type、props 和 opts,并返回修改后的结果或 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 对象。

参见 examples/advanced.md 了解栈级转换、选项修改和从 transformations 迁移。


模式 8:自动化 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 });

关键点: 自动化 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() — 组件输出无法在状态中正确跟踪,也无法通过栈引用访问。
  • 更改逻辑名称而不使用 aliases — Pulumi 会删除并重新创建资源。使用 aliases: [{ name: "old-name" }] 安全重命名。

中优先级:

  • 在多区域设置中依赖默认提供程序 — 每个区域使用显式提供程序。在配置中设置 pulumi:disable-default-providers 以强制执行。
  • 从函数而非 ComponentResource 开始 — 后续迁移需要 aliases 或资源重新创建。从一开始就使用组件。
  • 未对非明显依赖使用 dependsOn — Pulumi 从输入/输出连接中推断依赖关系,但副作用(如 IAM 传播延迟)需要手动添加。

低优先级:

  • 未使用 pulumi.StackReference 而使用硬编码值引用跨栈输出 — 会导致配置漂移。始终引用栈引用。
  • 在大型项目中未使用命名空间配置键 — 使用点分隔键(例如 myapp:databasePassword)以避免与同名键发生冲突。

</red_flags>

Individual skills in this repo

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

相关技能