pulumi-automation-api

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pulumi Automation API

Pulumi Automation API

When to Use This Skill

何时使用该技能

Invoke this skill when:
  • Orchestrating deployments across multiple Pulumi stacks
  • Embedding Pulumi operations in custom applications
  • Building self-service infrastructure platforms
  • Replacing fragile Bash/Makefile orchestration scripts
  • Creating custom CLIs for infrastructure management
  • Building web applications that provision infrastructure
在以下场景中调用此技能:
  • 跨多个Pulumi栈编排部署
  • 在自定义应用中嵌入Pulumi操作
  • 构建自助式基础设施平台
  • 替换脆弱的Bash/Makefile编排脚本
  • 创建用于基础设施管理的自定义CLI
  • 构建可预置基础设施的Web应用

What is Automation API

什么是Automation API

Automation API provides programmatic access to Pulumi operations. Instead of running
pulumi up
from the CLI, you call functions in your code that perform the same operations.
typescript
import * as automation from "@pulumi/pulumi/automation";

// Create or select a stack
const stack = await automation.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    projectName: "my-project",
    program: async () => {
        // Your Pulumi program here
    },
});

// Run pulumi up programmatically
const upResult = await stack.up({ onOutput: console.log });
console.log(`Update summary: ${JSON.stringify(upResult.summary)}`);
Automation API提供对Pulumi操作的编程访问。无需从CLI运行
pulumi up
,你可以在代码中调用执行相同操作的函数。
typescript
import * as automation from "@pulumi/pulumi/automation";

// 创建或选择一个栈
const stack = await automation.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    projectName: "my-project",
    program: async () => {
        // 你的Pulumi程序代码
    },
});

// 以编程方式运行pulumi up
const upResult = await stack.up({ onOutput: console.log });
console.log(`更新摘要: ${JSON.stringify(upResult.summary)}`);

When to Use Automation API

何时使用Automation API

Good Use Cases

适用场景

Multi-stack orchestration:
When you split infrastructure into multiple focused projects, Automation API helps offset the added complexity by orchestrating operations across stacks:
text
infrastructure → platform → application
     ↓              ↓            ↓
   (VPC)      (Kubernetes)   (Services)
Automation API ensures correct sequencing without manual intervention.
Self-service platforms:
Build internal tools where developers request infrastructure without learning Pulumi:
  • Web portals for environment provisioning
  • Slack bots that create/destroy resources
  • Custom CLIs tailored to your organization
Embedded infrastructure:
Applications that provision their own infrastructure:
  • SaaS platforms creating per-tenant resources
  • Testing frameworks spinning up test environments
  • CI/CD systems with dynamic infrastructure needs
Replacing fragile scripts:
If you have Bash scripts or Makefiles stitching together multiple
pulumi
commands, Automation API provides:
  • Proper error handling
  • Type safety
  • Programmatic access to outputs
多栈编排:
当你将基础设施拆分为多个聚焦的项目时,Automation API可通过跨栈编排操作来抵消增加的复杂度:
text
infrastructure → platform → application
     ↓              ↓            ↓
   (VPC)      (Kubernetes)   (Services)
Automation API无需人工干预即可确保正确的执行顺序。
自助式平台:
构建内部工具,让开发者无需学习Pulumi即可申请基础设施:
  • 用于环境预置的Web门户
  • 可创建/销毁资源的Slack机器人
  • 为你的组织量身定制的自定义CLI
嵌入式基础设施:
可自行预置基础设施的应用:
  • 为每个租户创建资源的SaaS平台
  • 启动测试环境的测试框架
  • 具有动态基础设施需求的CI/CD系统
替换脆弱的脚本:
如果你使用Bash脚本或Makefile拼接多个
pulumi
命令,Automation API可提供:
  • 完善的错误处理
  • 类型安全
  • 对输出的编程访问

When NOT to Use

不适用场景

  • Single project with standard deployment needs
  • When you don't need programmatic control over operations
  • 具有标准部署需求的单一项目
  • 不需要对操作进行编程控制的场景

Architecture Choices

架构选择

Local Source vs Inline Source

本地源码 vs 内联源码

Local Source - Pulumi program in separate files:
typescript
const stack = await automation.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    workDir: "./infrastructure",  // Points to existing Pulumi project
});
When to use:
  • Different teams maintain orchestrator vs Pulumi programs
  • Pulumi programs already exist
  • Want independent version control and release cycles
  • Platform team orchestrating application team's infrastructure
Inline Source - Pulumi program embedded in orchestrator:
typescript
import * as aws from "@pulumi/aws";

const stack = await automation.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    projectName: "my-project",
    program: async () => {
        const bucket = new aws.s3.Bucket("my-bucket");
        return { bucketName: bucket.id };
    },
});
When to use:
  • Single team owns everything
  • Tight coupling between orchestration and infrastructure is desired
  • Distributing as compiled binary (no source files needed)
  • Simpler deployment artifact
本地源码 - Pulumi程序存放在单独文件中:
typescript
const stack = await automation.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    workDir: "./infrastructure",  // 指向已有的Pulumi项目
});
适用场景:
  • 不同团队分别维护编排器和Pulumi程序
  • Pulumi程序已存在
  • 需要独立的版本控制和发布周期
  • 平台团队编排应用团队的基础设施
内联源码 - Pulumi程序嵌入在编排器中:
typescript
import * as aws from "@pulumi/aws";

const stack = await automation.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    projectName: "my-project",
    program: async () => {
        const bucket = new aws.s3.Bucket("my-bucket");
        return { bucketName: bucket.id };
    },
});
适用场景:
  • 单个团队负责所有内容
  • 希望编排与基础设施紧密耦合
  • 以编译二进制文件形式分发(无需源码文件)
  • 更简单的部署工件

Language Independence

语言独立性

The Automation API program can use a different language than the Pulumi programs it orchestrates:
text
Orchestrator (Go) → manages → Pulumi Program (TypeScript)
This enables platform teams to use their preferred language while application teams use theirs.
Automation API程序可以使用与其编排的Pulumi程序不同的语言:
text
Orchestrator (Go) → 管理 → Pulumi Program (TypeScript)
这使得平台团队可以使用自己偏好的语言,而应用团队使用他们的语言。

Common Patterns

常见模式

Multi-Stack Orchestration

多栈编排

Deploy multiple stacks in dependency order:
typescript
import * as automation from "@pulumi/pulumi/automation";

async function deploy() {
    const stacks = [
        { name: "infrastructure", dir: "./infra" },
        { name: "platform", dir: "./platform" },
        { name: "application", dir: "./app" },
    ];

    for (const stackInfo of stacks) {
        console.log(`Deploying ${stackInfo.name}...`);

        const stack = await automation.LocalWorkspace.createOrSelectStack({
            stackName: "prod",
            workDir: stackInfo.dir,
        });

        await stack.up({ onOutput: console.log });
        console.log(`${stackInfo.name} deployed successfully`);
    }
}

async function destroy() {
    // Destroy in reverse order
    const stacks = [
        { name: "application", dir: "./app" },
        { name: "platform", dir: "./platform" },
        { name: "infrastructure", dir: "./infra" },
    ];

    for (const stackInfo of stacks) {
        console.log(`Destroying ${stackInfo.name}...`);

        const stack = await automation.LocalWorkspace.selectStack({
            stackName: "prod",
            workDir: stackInfo.dir,
        });

        await stack.destroy({ onOutput: console.log });
    }
}
按依赖顺序部署多个栈:
typescript
import * as automation from "@pulumi/pulumi/automation";

async function deploy() {
    const stacks = [
        { name: "infrastructure", dir: "./infra" },
        { name: "platform", dir: "./platform" },
        { name: "application", dir: "./app" },
    ];

    for (const stackInfo of stacks) {
        console.log(`正在部署 ${stackInfo.name}...`);

        const stack = await automation.LocalWorkspace.createOrSelectStack({
            stackName: "prod",
            workDir: stackInfo.dir,
        });

        await stack.up({ onOutput: console.log });
        console.log(`${stackInfo.name} 部署成功`);
    }
}

async function destroy() {
    // 按相反顺序销毁
    const stacks = [
        { name: "application", dir: "./app" },
        { name: "platform", dir: "./platform" },
        { name: "infrastructure", dir: "./infra" },
    ];

    for (const stackInfo of stacks) {
        console.log(`正在销毁 ${stackInfo.name}...`);

        const stack = await automation.LocalWorkspace.selectStack({
            stackName: "prod",
            workDir: stackInfo.dir,
        });

        await stack.destroy({ onOutput: console.log });
    }
}

Passing Configuration

传递配置

Set stack configuration programmatically:
typescript
const stack = await automation.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    workDir: "./infrastructure",
});

// Set configuration values
await stack.setConfig("aws:region", { value: "us-west-2" });
await stack.setConfig("dbPassword", { value: "secret", secret: true });

// Then deploy
await stack.up();
以编程方式设置栈配置:
typescript
const stack = await automation.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    workDir: "./infrastructure",
});

// 设置配置值
await stack.setConfig("aws:region", { value: "us-west-2" });
await stack.setConfig("dbPassword", { value: "secret", secret: true });

// 然后进行部署
await stack.up();

Reading Outputs

读取输出

Access stack outputs after deployment:
typescript
const upResult = await stack.up();

// Get all outputs
const outputs = await stack.outputs();
console.log(`VPC ID: ${outputs["vpcId"].value}`);

// Or from the up result
console.log(`Outputs: ${JSON.stringify(upResult.outputs)}`);
部署后访问栈输出:
typescript
const upResult = await stack.up();

// 获取所有输出
const outputs = await stack.outputs();
console.log(`VPC ID: ${outputs["vpcId"].value}`);

// 或从up结果中获取
console.log(`输出: ${JSON.stringify(upResult.outputs)}`);

Error Handling

错误处理

Handle deployment failures gracefully:
typescript
try {
    const result = await stack.up({ onOutput: console.log });

    if (result.summary.result === "failed") {
        console.error("Deployment failed");
        process.exit(1);
    }
} catch (error) {
    console.error(`Deployment error: ${error}`);
    throw error;
}
优雅地处理部署失败:
typescript
try {
    const result = await stack.up({ onOutput: console.log });

    if (result.summary.result === "failed") {
        console.error("部署失败");
        process.exit(1);
    }
} catch (error) {
    console.error(`部署错误: ${error}`);
    throw error;
}

Parallel Stack Operations

并行栈操作

When stacks are independent, deploy in parallel:
typescript
const independentStacks = [
    { name: "service-a", dir: "./service-a" },
    { name: "service-b", dir: "./service-b" },
    { name: "service-c", dir: "./service-c" },
];

await Promise.all(independentStacks.map(async (stackInfo) => {
    const stack = await automation.LocalWorkspace.createOrSelectStack({
        stackName: "prod",
        workDir: stackInfo.dir,
    });
    return stack.up({ onOutput: (msg) => console.log(`[${stackInfo.name}] ${msg}`) });
}));
当栈相互独立时,可并行部署:
typescript
const independentStacks = [
    { name: "service-a", dir: "./service-a" },
    { name: "service-b", dir: "./service-b" },
    { name: "service-c", dir: "./service-c" },
];

await Promise.all(independentStacks.map(async (stackInfo) => {
    const stack = await automation.LocalWorkspace.createOrSelectStack({
        stackName: "prod",
        workDir: stackInfo.dir,
    });
    return stack.up({ onOutput: (msg) => console.log(`[${stackInfo.name}] ${msg}`) });
}));

Best Practices

最佳实践

Separate Configuration from Code

将配置与代码分离

Externalize configuration into files or environment variables:
typescript
import * as fs from "fs";

interface DeployConfig {
    stacks: Array<{ name: string; dir: string; }>;
    environment: string;
}

const config: DeployConfig = JSON.parse(
    fs.readFileSync("./deploy-config.json", "utf-8")
);

for (const stackInfo of config.stacks) {
    const stack = await automation.LocalWorkspace.createOrSelectStack({
        stackName: config.environment,
        workDir: stackInfo.dir,
    });
    await stack.up();
}
This enables distributing compiled binaries without exposing source code.
将配置外部化到文件或环境变量中:
typescript
import * as fs from "fs";

interface DeployConfig {
    stacks: Array<{ name: string; dir: string; }>;
    environment: string;
}

const config: DeployConfig = JSON.parse(
    fs.readFileSync("./deploy-config.json", "utf-8")
);

for (const stackInfo of config.stacks) {
    const stack = await automation.LocalWorkspace.createOrSelectStack({
        stackName: config.environment,
        workDir: stackInfo.dir,
    });
    await stack.up();
}
这使得可以分发编译后的二进制文件,而无需暴露源代码。

Stream Output for Long Operations

为长时间操作流式输出

Use
onOutput
callback for real-time feedback:
typescript
await stack.up({
    onOutput: (message) => {
        process.stdout.write(message);
        // Or send to logging system, websocket, etc.
    },
});
使用
onOutput
回调获取实时反馈:
typescript
await stack.up({
    onOutput: (message) => {
        process.stdout.write(message);
        // 或发送到日志系统、WebSocket等
    },
});

Quick Reference

快速参考

ScenarioApproach
Existing Pulumi projectsLocal source with workDir
New embedded infrastructureInline source with program function
Different teamsLocal source for independence
Compiled binary distributionInline source or bundled local
Multi-stack dependenciesSequential deployment in order
Independent stacksParallel deployment with Promise.all
场景方法
现有Pulumi项目使用workDir的本地源码
新的嵌入式基础设施使用program函数的内联源码
不同团队负责本地源码以保持独立性
编译二进制文件分发内联源码或捆绑的本地源码
多栈依赖按顺序依次部署
独立栈使用Promise.all并行部署

Related Skills

相关技能

  • pulumi-best-practices: Code-level patterns for Pulumi programs
  • pulumi-best-practices: Pulumi程序的代码级模式

References

参考资料