Created
July 1, 2020 21:27
-
-
Save EvanBoyle/a9fd29f1531dd8a63316f05afda34a02 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"os" | |
"time" | |
"github.com/pulumi/pulumi/sdk/v2/go/pulumi" | |
"github.com/pulumi/pulumi/x/automation" | |
) | |
func main() { | |
// login is global per CLI binary | |
err := login() | |
if err != nil { | |
os.Exit(1) | |
} | |
// formalization of the stack schema | |
sd := &automation.StackDescriptor{ | |
Org: "acme", | |
Project: "api", | |
Stack: "prod", | |
} | |
stack, err := getOrCreateStack(sd) | |
maxRetries := 5 | |
for current := 1; current <= maxRetries; current++ { | |
upRes, err := runPulumiUp(stack) | |
if err != nil { | |
switch err.Type { | |
case automation.UpError.Conflict: | |
// backoff and try again | |
time.Sleep(1 * time.Minute) | |
break | |
case automation.UpError.PendingOperations: | |
_, err := repairStack(stack) | |
if err != nil { | |
// unable to resolve pending operations | |
// notify a human | |
os.Exit(1) | |
} | |
// immediately retry | |
break | |
} | |
} else { | |
// successful update | |
upSummary, err := monitorUpdateToCompletion(stack, upRes.UpdateID) | |
if err != nil { | |
os.Exit(1) | |
} | |
fmt.Printf("updated %d resources, %d unchanged.", len(upSummary.Updates), len(upSummary.NoOps)) | |
os.Exit(0) | |
} | |
} | |
// unable to execute successful update in 5 retries | |
os.Exit(1) | |
} | |
func login() error { | |
// login only supported via auth token | |
pulumiAccessToken := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
_, err := automation.Login(pulumiAccessToken) | |
return err | |
} | |
func getOrCreateStack(sd automation.StackDescriptor) (automation.Stack, error) { | |
stack, err := automation.SelectStack(sd) | |
if err != nil { | |
stack, err = automation.InitStack(sd) | |
if err != nil { | |
return nil, err | |
} | |
} | |
return stack, nil | |
} | |
func runPulumiUp(stack automation.Stack) (automation.UpResult, error) { | |
// the actual `pulumi` code. | |
upFn := func(ctx *pulumi.Context) error { | |
ctx.Export("x", pulumi.String("foo")) | |
return nil | |
} | |
upOpts := &automation.UpOptions{ | |
Message: "automated update", | |
Parallel: 8, | |
} | |
// kick off an async update `pulumi up --async` | |
// this indicates that the update started successfully | |
// will run to completion in the background | |
// returns a result object with an UpdateID that can | |
// be used to poll for update status | |
upRes, err := automation.Up(stack, upFn, upOpts) | |
return upRes, err | |
} | |
func repairStack(stack automation.Stack) (automation.RefreshResult, error) { | |
// TODO: locking https://github.com/pulumi/pulumi/issues/4939 | |
var stackBytes []byte | |
_, err := automation.StackExport(stack, stackBytes) | |
if err != nil { | |
return nil, err | |
} | |
_, err = automation.StackImport(stack, stackBytes) | |
if err != nil { | |
return nil, err | |
} | |
refreshRes, err := automation.Refresh(stack) | |
return refreshRes, err | |
} | |
func monitorUpdateToCompletion(stack automation.Stack, updateId string) (automation.UpdateSummary, error) { | |
var updateComplete bool | |
var updateStatus automation.UpdateStatus | |
for !updateComplete { | |
// a new pulumi command `pulumi stack status --updateID="xxxxxx"` | |
// updateID defaults to latest, allows for more of an "async" update style. | |
var err error | |
updateStatus, err = automation.StackUpdateStatus(stack, updateId) | |
if err != nil { | |
return nil, err | |
} | |
if automation.IsTerminalStatus(updateStatus.Status) { | |
updateComplete = true | |
} | |
time.Sleep(30 * time.Second) | |
} | |
return updateStatus, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment