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.
package 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 defines the infrastructure
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 attempts deploy and cleans up on failure
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 deploy with exponential backoff for transient failures
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)
}
fmt.Printf("Preview: %d changes\n", len(preview.Steps))
// 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)
}
}