Compare commits

...

1 commit

Author SHA1 Message Date
evanboyle 448eb648dd pulumi cancel, export, import 2020-09-14 17:54:34 -07:00
5 changed files with 168 additions and 1 deletions

View file

@ -70,7 +70,7 @@ func newCancelCmd() *cobra.Command {
// Ensure the user really wants to do this.
stackName := string(s.Ref().Name())
prompt := fmt.Sprintf("This will irreversibly cancel the currently running update for '%s'!", stackName)
if !yes && !confirmPrompt(prompt, stackName, opts) {
if cmdutil.Interactive() && (!yes && !confirmPrompt(prompt, stackName, opts)) {
fmt.Println("confirmation declined")
return result.Bail()
}

View file

@ -24,7 +24,9 @@ import (
"strings"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v2/go/common/workspace"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
)
@ -409,6 +411,62 @@ func (l *LocalWorkspace) SetProgram(fn pulumi.RunFunc) {
l.program = fn
}
// ExportStack exports the deployment state of the stack matching the given name.
// This can be combined with ImportStack to edit a stack's state (such as recovery from failed deployments).
func (l *LocalWorkspace) ExportStack(ctx context.Context, stackName string) (apitype.UntypedDeployment, error) {
var state apitype.UntypedDeployment
err := l.SelectStack(ctx, stackName)
if err != nil {
return state, errors.Wrapf(err, "could not export stack, unable to select stack %s.", stackName)
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "export", "--show-secrets")
if err != nil {
return state, newAutoError(errors.Wrap(err, "could not export stack."), stdout, stderr, errCode)
}
err = json.Unmarshal([]byte(stdout), &state)
if err != nil {
return state, newAutoError(
errors.Wrap(err, "failed to export stack, unable to unmarshall stack state."), stdout, stderr, errCode,
)
}
return state, nil
}
// ImportStack imports the specified deployment state into a pre-existing stack.
// This can be combined with ExportStack to edit a stack's state (such as recovery from failed deployments).
func (l *LocalWorkspace) ImportStack(ctx context.Context, stackName string, state apitype.UntypedDeployment) error {
err := l.SelectStack(ctx, stackName)
if err != nil {
return errors.Wrapf(err, "could not import stack, failed to select stack %s.", stackName)
}
f, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
return errors.Wrap(err, "could not import stack. failed to get allocate temp file.")
}
defer func() { contract.IgnoreError(os.Remove(f.Name())) }()
bytes, err := json.Marshal(state)
if err != nil {
return errors.Wrap(err, "could not import stack, failed to marshal stack state.")
}
_, err = f.Write(bytes)
if err != nil {
return errors.Wrap(err, "could not import stack. failed to write out stack intermediate.")
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "import", "--file", f.Name())
if err != nil {
return newAutoError(errors.Wrap(err, "could not import stack."), stdout, stderr, errCode)
}
return nil
}
func (l *LocalWorkspace) runPulumiCmdSync(
ctx context.Context,
args ...string,

View file

@ -930,6 +930,78 @@ func TestNestedStackFails(t *testing.T) {
assert.Equal(t, "succeeded", dRes.Summary.Result)
}
func TestImportExportStack(t *testing.T) {
ctx := context.Background()
sName := fmt.Sprintf("int_test%d", rangeIn(10000000, 99999999))
stackName := FullyQualifiedStackName(pulumiOrg, pName, sName)
cfg := ConfigMap{
"bar": ConfigValue{
Value: "abc",
},
"buzz": ConfigValue{
Value: "secret",
Secret: true,
},
}
// initialize
s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error {
c := config.New(ctx, "")
ctx.Export("exp_static", pulumi.String("foo"))
ctx.Export("exp_cfg", pulumi.String(c.Get("bar")))
ctx.Export("exp_secret", c.GetSecret("buzz"))
return nil
})
if err != nil {
t.Errorf("failed to initialize stack, err: %v", err)
t.FailNow()
}
defer func() {
// -- pulumi stack rm --
err = s.Workspace().RemoveStack(ctx, s.Name())
assert.Nil(t, err, "failed to remove stack. Resources have leaked.")
}()
err = s.SetAllConfig(ctx, cfg)
if err != nil {
t.Errorf("failed to set config, err: %v", err)
t.FailNow()
}
// -- pulumi up --
_, err = s.Up(ctx)
if err != nil {
t.Errorf("up failed, err: %v", err)
t.FailNow()
}
// -- pulumi stack export --
state, err := s.Export(ctx)
if err != nil {
t.Errorf("export failed, err: %v", err)
t.FailNow()
}
// -- pulumi stack import --
err = s.Import(ctx, state)
if err != nil {
t.Errorf("import failed, err: %v", err)
t.FailNow()
}
// -- pulumi destroy --
dRes, err := s.Destroy(ctx)
if err != nil {
t.Errorf("destroy failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, "destroy", dRes.Summary.Kind)
assert.Equal(t, "succeeded", dRes.Summary.Result)
}
func getTestOrg() string {
testOrg := "pulumi-test"
if _, set := os.LookupEnv("PULUMI_TEST_ORG"); set {

View file

@ -568,6 +568,36 @@ func (s *Stack) Info(ctx context.Context) (StackSummary, error) {
return info, nil
}
// Cancel stops a stack's currently running update. It returns an error if no update is currently running.
// Note that this operation is _very dangerous_, and may leave the stack in an inconsistent state
// if a resource operation was pending when the update was canceled.
// This command is not supported for local backends.
func (s *Stack) Cancel(ctx context.Context) error {
err := s.Workspace().SelectStack(ctx, s.Name())
if err != nil {
return errors.Wrap(err, "failed to cancel update")
}
stdout, stderr, errCode, err := s.runPulumiCmdSync(ctx, "cancel", "--yes")
if err != nil {
return newAutoError(errors.Wrap(err, "failed to cancel update"), stdout, stderr, errCode)
}
return nil
}
// Export exports the deployment state of the stack.
// This can be combined with Stack.Import to edit a stack's state (such as recovery from failed deployments).
func (s *Stack) Export(ctx context.Context) (apitype.UntypedDeployment, error) {
return s.Workspace().ExportStack(ctx, s.Name())
}
// Import imports the specified deployment state into the stack.
// This can be combined with Stack.Export to edit a stack's state (such as recovery from failed deployments).
func (s *Stack) Import(ctx context.Context, state apitype.UntypedDeployment) error {
return s.Workspace().ImportStack(ctx, s.Name(), state)
}
// UpdateSummary provides a summary of a Stack lifecycle operation (up/preview/refresh/destroy).
type UpdateSummary struct {
Kind string `json:"kind"`

View file

@ -17,6 +17,7 @@ package auto
import (
"context"
"github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v2/go/common/workspace"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
)
@ -96,6 +97,12 @@ type Workspace interface {
Program() pulumi.RunFunc
// SetProgram sets the program associated with the Workspace to the specified `pulumi.RunFunc`.
SetProgram(pulumi.RunFunc)
// ExportStack exports the deployment state of the stack matching the given name.
// This can be combined with ImportStack to edit a stack's state (such as recovery from failed deployments).
ExportStack(context.Context, string) (apitype.UntypedDeployment, error)
// ImportStack imports the specified deployment state into a pre-existing stack.
// This can be combined with ExportStack to edit a stack's state (such as recovery from failed deployments).
ImportStack(context.Context, string, apitype.UntypedDeployment) error
}
// ConfigValue is a configuration value used by a Pulumi program.