pulumi/sdk/go/x/auto/up.go

241 lines
5.9 KiB
Go
Raw Normal View History

2020-07-07 23:25:11 +02:00
package auto
import (
"bytes"
"encoding/json"
"os/exec"
2020-07-10 09:55:07 +02:00
"strings"
2020-07-07 23:25:11 +02:00
"github.com/pkg/errors"
)
2020-07-17 19:14:30 +02:00
func (s *stack) Up() (UpResult, error) {
2020-07-10 07:30:52 +02:00
var upResult UpResult
2020-07-17 19:14:30 +02:00
err := s.initOrSelectStack()
if err != nil {
2020-07-18 05:04:37 +02:00
return upResult, err
}
2020-07-18 05:04:37 +02:00
stdout, stderr, code, err := s.runCmd("pulumi", "up", "--yes")
2020-07-07 23:25:11 +02:00
if err != nil {
2020-07-18 05:04:37 +02:00
return upResult, newAutoError(err, stdout, stderr, code)
2020-07-07 23:25:11 +02:00
}
2020-07-17 19:14:30 +02:00
outs, secrets, err := s.outputs()
2020-07-07 23:25:11 +02:00
if err != nil {
return upResult, err
}
2020-07-17 19:14:30 +02:00
summary, err := s.summary()
if err != nil {
return upResult, err
}
2020-07-07 23:25:11 +02:00
return UpResult{
2020-07-10 07:30:52 +02:00
StdOut: stdout,
StdErr: stderr,
2020-07-07 23:25:11 +02:00
Outputs: outs,
SecretOutputs: secrets,
2020-07-17 19:14:30 +02:00
Summary: summary,
2020-07-07 23:25:11 +02:00
}, nil
}
type UpResult struct {
StdOut string
StdErr string
2020-07-10 07:30:52 +02:00
Outputs map[string]interface{}
SecretOutputs map[string]interface{}
Summary UpdateSummary
2020-07-07 23:25:11 +02:00
}
2020-07-17 19:14:30 +02:00
func (s *stack) initOrSelectStack() error {
2020-07-18 05:04:37 +02:00
_, _, _, err := s.runCmd("pulumi", "stack", "select", s.Name)
2020-07-10 07:30:52 +02:00
if err != nil {
2020-07-18 05:04:37 +02:00
stdout, stderr, code, err := s.runCmd("pulumi", "stack", "init", s.Name)
2020-07-10 07:30:52 +02:00
if err != nil {
2020-07-18 05:04:37 +02:00
return newAutoError(errors.Wrap(err, "unable to select or init stack"), stdout, stderr, code)
2020-07-10 07:30:52 +02:00
}
}
2020-07-17 19:14:30 +02:00
return nil
2020-07-10 07:30:52 +02:00
}
2020-07-07 23:25:11 +02:00
const secretSentinel = "[secret]"
2020-07-17 19:14:30 +02:00
func (s *stack) Outputs() (map[string]interface{}, map[string]interface{}, error) {
err := s.initOrSelectStack()
2020-07-10 09:55:07 +02:00
if err != nil {
2020-07-18 05:04:37 +02:00
return nil, nil, err
2020-07-10 09:55:07 +02:00
}
2020-07-17 19:14:30 +02:00
return s.outputs()
2020-07-10 09:55:07 +02:00
}
2020-07-17 19:14:30 +02:00
// outputs returns a set of plain outputs, secret outputs, and an error
func (s *stack) outputs() (map[string]interface{}, map[string]interface{}, error) {
2020-07-07 23:25:11 +02:00
// standard outputs
2020-07-18 05:04:37 +02:00
outStdout, outStderr, code, err := s.runCmd("pulumi", "stack", "output", "--json")
2020-07-07 23:25:11 +02:00
if err != nil {
2020-07-18 05:04:37 +02:00
return nil, nil, newAutoError(errors.Wrap(err, "could not get outputs"), outStdout, outStderr, code)
2020-07-07 23:25:11 +02:00
}
// secret outputs
2020-07-18 05:04:37 +02:00
secretStdout, secretStderr, code, err := s.runCmd("pulumi", "stack", "output", "--json", "--show-secrets")
2020-07-07 23:25:11 +02:00
if err != nil {
2020-07-18 05:04:37 +02:00
return nil, nil, newAutoError(errors.Wrap(err, "could not get secret outputs"), outStdout, outStderr, code)
2020-07-07 23:25:11 +02:00
}
2020-07-10 07:30:52 +02:00
var outputs map[string]interface{}
var secrets map[string]interface{}
2020-07-07 23:25:11 +02:00
2020-07-10 07:30:52 +02:00
if err = json.Unmarshal([]byte(outStdout), &outputs); err != nil {
return nil, nil, errors.Wrapf(err, "error unmarshalling outputs: %s", secretStderr)
2020-07-07 23:25:11 +02:00
}
2020-07-10 07:30:52 +02:00
if err = json.Unmarshal([]byte(secretStdout), &secrets); err != nil {
return nil, nil, errors.Wrapf(err, "error unmarshalling secret outputs: %s", secretStderr)
2020-07-07 23:25:11 +02:00
}
for k, v := range outputs {
if v == secretSentinel {
delete(outputs, k)
} else {
delete(secrets, k)
}
}
return outputs, secrets, nil
}
2020-07-17 19:14:30 +02:00
func (s *stack) User() (string, error) {
err := s.initOrSelectStack()
if err != nil {
return "", errors.Wrap(err, "could not initialize or select stack")
}
2020-07-18 05:04:37 +02:00
outStdout, outStderr, code, err := s.runCmd("pulumi", "whoami")
2020-07-10 09:55:07 +02:00
if err != nil {
2020-07-18 05:04:37 +02:00
return "", newAutoError(errors.Wrap(err, "could not detect user"), outStdout, outStderr, code)
2020-07-10 09:55:07 +02:00
}
return strings.TrimSpace(outStdout), nil
}
2020-07-17 19:14:30 +02:00
func (s *stack) SetConfig(config map[string]string) error {
err := s.initOrSelectStack()
if err != nil {
2020-07-18 05:04:37 +02:00
return err
2020-07-17 19:14:30 +02:00
}
return s.setConfig(config)
}
func (s *stack) setConfig(config map[string]string) error {
2020-07-18 05:04:37 +02:00
var stdout bytes.Buffer
var stderr bytes.Buffer
2020-07-17 19:14:30 +02:00
for k, v := range config {
// TODO verify escaping
2020-07-18 05:04:37 +02:00
outstr, errstr, code, err := s.runCmd("pulumi", "config", "set", k, v)
stdout.WriteString(outstr)
stderr.WriteString(errstr)
if err != nil {
2020-07-18 05:04:37 +02:00
return newAutoError(errors.Wrap(err, "unable to set config"), stdout.String(), stderr.String(), code)
}
}
2020-07-17 19:14:30 +02:00
return nil
}
2020-07-17 19:14:30 +02:00
func (s *stack) SetSecrets(secrets map[string]string) error {
err := s.initOrSelectStack()
if err != nil {
2020-07-18 05:04:37 +02:00
return err
2020-07-17 19:14:30 +02:00
}
return s.setSecrets(secrets)
}
func (s *stack) setSecrets(secrets map[string]string) error {
2020-07-18 05:04:37 +02:00
var stdout bytes.Buffer
var stderr bytes.Buffer
2020-07-17 19:14:30 +02:00
for k, v := range secrets {
// TODO verify escaping
2020-07-18 05:04:37 +02:00
outstr, errstr, code, err := s.runCmd("pulumi", "config", "set", k, v)
stdout.WriteString(outstr)
stderr.WriteString(errstr)
if err != nil {
2020-07-18 05:04:37 +02:00
return newAutoError(
errors.Wrap(err, "unable to set secret config"), stdout.String(), stderr.String(), code,
)
}
}
2020-07-17 19:14:30 +02:00
return nil
}
2020-07-17 19:14:30 +02:00
func (s *stack) Summary() (UpdateSummary, error) {
var zero UpdateSummary
2020-07-17 19:14:30 +02:00
err := s.initOrSelectStack()
if err != nil {
2020-07-18 05:04:37 +02:00
return zero, err
}
2020-07-17 19:14:30 +02:00
return s.summary()
}
2020-07-17 19:14:30 +02:00
func (s *stack) summary() (UpdateSummary, error) {
var res UpdateSummary
2020-07-18 05:04:37 +02:00
stdout, stderr, code, err := s.runCmd("pulumi", "history", "--json")
if err != nil {
2020-07-18 05:04:37 +02:00
return res, newAutoError(errors.Wrap(err, "could not get outputs"), stdout, stderr, code)
}
var history []UpdateSummary
2020-07-18 05:04:37 +02:00
err = json.Unmarshal([]byte(stdout), &history)
if err != nil {
return res, errors.Wrap(err, "unable to unmarshal history result")
}
if len(history) != 0 {
res = history[0]
}
return res, nil
}
2020-07-10 07:30:52 +02:00
2020-07-17 19:14:30 +02:00
// lifted from:
// https://github.com/pulumi/pulumi/blob/66bd3f4aa8f9a90d3de667828dda4bed6e115f6b/pkg/cmd/pulumi/history.go#L91
type UpdateSummary struct {
Kind string `json:"kind"`
StartTime string `json:"startTime"`
Message string `json:"message"`
Environment map[string]string `json:"environment"`
Config map[string]interface{} `json:"config"`
Result string `json:"result,omitempty"`
// These values are only present once the update finishes
EndTime *string `json:"endTime,omitempty"`
ResourceChanges *map[string]int `json:"resourceChanges,omitempty"`
}
2020-07-10 07:30:52 +02:00
// runCmd execs the given command with appropriate stack context
2020-07-18 05:04:37 +02:00
// returning stdout, stderr, exitcode, and an error value
func (s *stack) runCmd(name string, arg ...string) (string, string, int, error) {
2020-07-10 07:30:52 +02:00
cmd := exec.Command(name, arg...)
2020-07-17 19:14:30 +02:00
cmd.Dir = s.SourcePath
2020-07-10 07:30:52 +02:00
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
2020-07-18 05:04:37 +02:00
code := -2 // unknown
2020-07-10 07:30:52 +02:00
err := cmd.Run()
2020-07-18 05:04:37 +02:00
if exitError, ok := err.(*exec.ExitError); ok {
code = exitError.ExitCode()
}
return stdout.String(), stderr.String(), code, err
2020-07-10 07:30:52 +02:00
}