Add --version flag to 'pulumi stack export' (#3906)

* Add --version flag to 'pulumi stack export'

* Update CHANGELOG

* Update/rebase with latest

* Fix lint warning
This commit is contained in:
Chris Smith 2020-02-13 12:25:57 -08:00 committed by GitHub
parent 54d344f7c1
commit ba046b063b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 11 deletions

View file

@ -17,6 +17,9 @@ CHANGELOG
- Move .NET SDK attributes to the root namespace.
[#3902](https://github.com/pulumi/pulumi/pull/3902)
- Support exporting older stack versions.
[#3906](https://github.com/pulumi/pulumi/pull/3906)
## 1.10.1 (2020-02-06)
- Support stack references in the Go SDK.
[#3829](https://github.com/pulumi/pulumi/pull/3829)

View file

@ -21,6 +21,8 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/backend"
"github.com/pulumi/pulumi/pkg/backend/display"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
)
@ -28,6 +30,7 @@ import (
func newStackExportCmd() *cobra.Command {
var file string
var stackName string
var version string
cmd := &cobra.Command{
Use: "export",
@ -40,6 +43,7 @@ func newStackExportCmd() *cobra.Command {
"in a stack's state due to failed deployments, manual changes to cloud\n" +
"resources, etc.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
ctx := commandContext()
opts := display.Options{
Color: cmdutil.GetGlobalColorization(),
}
@ -50,9 +54,28 @@ func newStackExportCmd() *cobra.Command {
return err
}
deployment, err := s.ExportDeployment(commandContext())
if err != nil {
return err
var deployment *apitype.UntypedDeployment
// Export the latest version of the checkpoint by default. Otherwise, we require that
// the backend/stack implements the ability the export previous checkpoints.
if version == "" {
deployment, err = s.ExportDeployment(ctx)
if err != nil {
return err
}
} else {
// Check that the stack and its backend supports the ability to do this.
be := s.Backend()
specificExpBE, ok := be.(backend.SpecificDeploymentExporter)
if !ok {
return errors.Errorf(
"the current backend (%s) does not provide the ability to export previous deployments",
be.Name())
}
deployment, err = specificExpBE.ExportDeploymentForVersion(ctx, s, version)
if err != nil {
return err
}
}
// Read from stdin or a specified file.
@ -77,5 +100,7 @@ func newStackExportCmd() *cobra.Command {
&stackName, "stack", "s", "", "The name of the stack to operate on. Defaults to the current stack")
cmd.PersistentFlags().StringVarP(
&file, "file", "", "", "A filename to write stack output to")
cmd.PersistentFlags().StringVarP(
&version, "version", "", "", "Previous stack version to export. (If unset, will export the latest.)")
return cmd
}

View file

@ -189,6 +189,17 @@ type Backend interface {
CurrentUser() (string, error)
}
// SpecificDeploymentExporter is an interface defining an additional capability of a Backend, specifically the
// ability to export a specific versions of a stack's deployment. This isn't a requirement for all backends and
// should be checked for dynamically.
type SpecificDeploymentExporter interface {
// ExportDeploymentForVersion exports a specific deployment from the history of a stack. The meaning of
// version is backend-specific. For the Pulumi Console, it is the numeric version. (The first update
// being version "1", the second "2", and so on.) Though this might change in the future to use some
// other type of identifier or commitish .
ExportDeploymentForVersion(ctx context.Context, stack Stack, version string) (*apitype.UntypedDeployment, error)
}
// UpdateOperation is a complete stack update operation (preview, update, refresh, or destroy).
type UpdateOperation struct {
Proj *workspace.Project

View file

@ -125,6 +125,9 @@ type cloudBackend struct {
currentProject *workspace.Project
}
// Assert we implement the backend.Backend and backend.SpecificDeploymentExporter interfaces.
var _ backend.SpecificDeploymentExporter = &cloudBackend{}
// New creates a new Pulumi backend for the given cloud API URL and token.
func New(d diag.Sink, cloudURL string) (Backend, error) {
cloudURL = ValueOrDefaultURL(cloudURL)
@ -1160,17 +1163,32 @@ func (b *cloudBackend) GetLogs(ctx context.Context, stack backend.Stack, cfg bac
func (b *cloudBackend) ExportDeployment(ctx context.Context,
stack backend.Stack) (*apitype.UntypedDeployment, error) {
return b.exportDeployment(ctx, stack.Ref())
return b.exportDeployment(ctx, stack.Ref(), nil /* latest */)
}
func (b *cloudBackend) exportDeployment(ctx context.Context,
stackRef backend.StackReference) (*apitype.UntypedDeployment, error) {
func (b *cloudBackend) ExportDeploymentForVersion(
ctx context.Context, stack backend.Stack, version string) (*apitype.UntypedDeployment, error) {
// The Pulumi Console defines versions as a positive integer. Parse the provided version string and
// ensure it is valid.
//
// The first stack update version is 1, and monotonically increasing from there.
versionNumber, err := strconv.Atoi(version)
if err != nil || versionNumber <= 0 {
return nil, errors.Errorf("%q is not a valid stack version. It should be a positive integer.", version)
}
return b.exportDeployment(ctx, stack.Ref(), &versionNumber)
}
// exportDeployment exports the checkpoint file for a stack, optionally getting a previous version.
func (b *cloudBackend) exportDeployment(
ctx context.Context, stackRef backend.StackReference, version *int) (*apitype.UntypedDeployment, error) {
stack, err := b.getCloudStackIdentifier(stackRef)
if err != nil {
return nil, err
}
deployment, err := b.client.ExportStackDeployment(ctx, stack)
deployment, err := b.client.ExportStackDeployment(ctx, stack, version)
if err != nil {
return nil, err
}

View file

@ -376,11 +376,19 @@ func (pc *Client) GetStackUpdates(ctx context.Context, stack StackIdentifier) ([
}
// ExportStackDeployment exports the indicated stack's deployment as a raw JSON message.
func (pc *Client) ExportStackDeployment(ctx context.Context,
stack StackIdentifier) (apitype.UntypedDeployment, error) {
// If version is nil, will export the latest version of the stack.
func (pc *Client) ExportStackDeployment(
ctx context.Context, stack StackIdentifier, version *int) (apitype.UntypedDeployment, error) {
path := getStackPath(stack, "export")
// Tack on a specific version as desired.
if version != nil {
path += fmt.Sprintf("/%d", *version)
}
var resp apitype.ExportStackResponse
if err := pc.restCall(ctx, "GET", getStackPath(stack, "export"), nil, nil, &resp); err != nil {
if err := pc.restCall(ctx, "GET", path, nil, nil, &resp); err != nil {
return apitype.UntypedDeployment{}, err
}

View file

@ -262,7 +262,7 @@ func (b *cloudBackend) newUpdate(ctx context.Context, stackRef backend.StackRefe
}
func (b *cloudBackend) getSnapshot(ctx context.Context, stackRef backend.StackReference) (*deploy.Snapshot, error) {
untypedDeployment, err := b.exportDeployment(ctx, stackRef)
untypedDeployment, err := b.exportDeployment(ctx, stackRef, nil /* get latest */)
if err != nil {
return nil, err
}