Pulumi Stack Deployer
A Go program that uses the Pulumi Automation API to programmatically create infrastructure stacks, configure them, preview changes, and deploy, all without shelling out to the Pulumi CLI.
Setup
Install the Pulumi CLI first — the Automation API requires it at runtime:
# macOS
brew install pulumi
# Linux
curl -fsSL https://get.pulumi.com | shThen install the AWS plugin and set up the project:
pulumi plugin install resource aws
mkdir pulumi-stack-deployer
cd pulumi-stack-deployer
go mod init github.com/yourorg/pulumi-stack-deployer
go get github.com/pulumi/pulumi/sdk/v3@latest
go get github.com/pulumi/pulumi/sdk/v3/go/auto@latest
go get github.com/pulumi/pulumi/sdk/v3/go/auto/optup@latest
go get github.com/pulumi/pulumi/sdk/v3/go/pulumi@latest
go get github.com/pulumi/pulumi-aws/sdk/v6@latest
go get github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3@latestSet your AWS credentials:
export AWS_ACCESS_KEY_ID=your-key
export AWS_SECRET_ACCESS_KEY=your-secret
export AWS_REGION=us-west-2package main
import (
"context"
"fmt"
"os"
"time"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/auto"
"github.com/pulumi/pulumi/sdk/v3/go/auto/optup"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
// deployFunc declares the infrastructure for this stack: an S3 bucket with
// website hosting. Passed as an inline program to UpsertStackInlineSource.
func deployFunc(ctx *pulumi.Context) error {
bucket, err := s3.NewBucket(ctx, "my-bucket", &s3.BucketArgs{
Website: &s3.BucketWebsiteArgs{
IndexDocument: pulumi.String("index.html"),
},
})
if err != nil {
return err
}
ctx.Export("bucketName", bucket.Bucket)
return nil
}
// deployWithRecovery runs stack.Up and destroys partially-created resources if it fails.
func deployWithRecovery(ctx context.Context, stack auto.Stack) error {
result, err := stack.Up(ctx, optup.ProgressStreams(os.Stdout))
if err != nil {
fmt.Printf("Deploy failed: %v\n", err)
// Destroy partially-created resources
_, destroyErr := stack.Destroy(ctx)
if destroyErr != nil {
return fmt.Errorf("deploy failed and cleanup failed: %w (original: %v)", destroyErr, err)
}
return fmt.Errorf("deploy failed, resources cleaned up: %w", err)
}
fmt.Printf("Deploy succeeded: %s\n", result.Summary.Message)
return nil
}
// deployWithRetry retries stack.Up up to maxRetries times with exponential backoff.
// It refreshes state before each retry to reconcile with actual cloud resources.
func deployWithRetry(ctx context.Context, stack auto.Stack, maxRetries int) error {
for attempt := 0; attempt <= maxRetries; attempt++ {
_, err := stack.Up(ctx)
if err == nil {
return nil
}
if attempt == maxRetries {
return fmt.Errorf("deploy failed after %d attempts: %w", maxRetries, err)
}
// Refresh state to reconcile with reality before retrying
_, _ = stack.Refresh(ctx)
backoff := time.Duration(1<<uint(attempt)) * time.Second
time.Sleep(backoff)
}
return nil
}
func main() {
ctx := context.Background()
// Create or select a stack
stackName := "dev"
projectName := "infra-service"
stack, err := auto.UpsertStackInlineSource(ctx, stackName, projectName, deployFunc)
if err != nil {
panic(err)
}
// Set AWS region config
stack.SetConfig(ctx, "aws:region", auto.ConfigValue{Value: "us-west-2"})
// Set secrets (encrypted in state)
stack.SetConfig(ctx, "dbPassword", auto.ConfigValue{
Value: "supersecret",
Secret: true,
})
// Set multiple config values
stack.SetAllConfig(ctx, auto.ConfigMap{
"aws:region": {Value: "us-west-2"},
"app:replicas": {Value: "3"},
"app:apiKey": {Value: "key-123", Secret: true},
})
// Preview changes
preview, err := stack.Preview(ctx)
if err != nil {
panic(err)
}
totalChanges := 0
for _, count := range preview.ChangeSummary {
totalChanges += count
}
fmt.Printf("Preview: %d changes\n", totalChanges)
// Deploy with recovery (cleans up on failure)
if err := deployWithRecovery(ctx, stack); err != nil {
fmt.Printf("Recovery deploy failed: %v\n", err)
// Fallback: retry with backoff
if retryErr := deployWithRetry(ctx, stack, 3); retryErr != nil {
fmt.Printf("All retries exhausted: %v\n", retryErr)
os.Exit(1)
}
}
// Print outputs
outputs, err := stack.Outputs(ctx)
if err == nil {
fmt.Printf("Bucket: %s\n", outputs["bucketName"].Value)
}
}