pulumi/pkg/cmd/pulumi/state.go
CyrusNajmabadi 66bd3f4aa8
Breaking changes due to Feature 2.0 work
* Make `async:true` the default for `invoke` calls (#3750)

* Switch away from native grpc impl. (#3728)

* Remove usage of the 'deasync' library from @pulumi/pulumi. (#3752)

* Only retry as long as we get unavailable back.  Anything else continues. (#3769)

* Handle all errors for now. (#3781)


* Do not assume --yes was present when using pulumi in non-interactive mode (#3793)

* Upgrade all paths for sdk and pkg to v2

* Backport C# invoke classes and other recent gen changes (#4288)

Adjust C# generation

* Replace IDeployment with a sealed class (#4318)

Replace IDeployment with a sealed class

* .NET: default to args subtype rather than Args.Empty (#4320)

* Adding system namespace for Dotnet code gen

This is required for using Obsolute attributes for deprecations

```
Iam/InstanceProfile.cs(142,10): error CS0246: The type or namespace name 'ObsoleteAttribute' could not be found (are you missing a using directive or an assembly reference?) [/Users/stack72/code/go/src/github.com/pulumi/pulumi-aws/sdk/dotnet/Pulumi.Aws.csproj]
Iam/InstanceProfile.cs(142,10): error CS0246: The type or namespace name 'Obsolete' could not be found (are you missing a using directive or an assembly reference?) [/Users/stack72/code/go/src/github.com/pulumi/pulumi-aws/sdk/dotnet/Pulumi.Aws.csproj]
```

* Fix the nullability of config type properties in C# codegen (#4379)
2020-04-14 09:30:25 +01:00

189 lines
6.9 KiB
Go

// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/result"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/v2/backend/display"
"github.com/pulumi/pulumi/pkg/v2/resource/deploy"
"github.com/pulumi/pulumi/pkg/v2/resource/edit"
"github.com/pulumi/pulumi/pkg/v2/resource/stack"
"github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v2/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/cmdutil"
"github.com/spf13/cobra"
survey "gopkg.in/AlecAivazis/survey.v1"
surveycore "gopkg.in/AlecAivazis/survey.v1/core"
)
func newStateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "state",
Short: "Edit the current stack's state",
Long: `Edit the current stack's state
Subcommands of this command can be used to surgically edit parts of a stack's state. These can be useful when
troubleshooting a stack or when performing specific edits that otherwise would require editing the state file by hand.`,
Args: cmdutil.NoArgs,
}
cmd.AddCommand(newStateDeleteCommand())
cmd.AddCommand(newStateUnprotectCommand())
return cmd
}
// locateStackResource attempts to find a unique resource associated with the given URN in the given snapshot. If the
// given URN is ambiguous and this is an interactive terminal, it prompts the user to select one of the resources in
// the list of resources with identical URNs to operate upon.
func locateStackResource(opts display.Options, snap *deploy.Snapshot, urn resource.URN) (*resource.State, error) {
candidateResources := edit.LocateResource(snap, urn)
switch {
case len(candidateResources) == 0: // resource was not found
return nil, errors.Errorf("No such resource %q exists in the current state", urn)
case len(candidateResources) == 1: // resource was unambiguously found
return candidateResources[0], nil
}
// If there exist multiple resources that have the requested URN, prompt the user to select one if we're running
// interactively. If we're not, early exit.
if !cmdutil.Interactive() {
errorMsg := "Resource URN ambiguously referred to multiple resources. Did you mean:\n"
for _, res := range candidateResources {
errorMsg += fmt.Sprintf(" %s\n", res.ID)
}
return nil, errors.New(errorMsg)
}
// Note: this is done to adhere to the same color scheme as the `pulumi new` picker, which also does this.
surveycore.DisableColor = true
surveycore.QuestionIcon = ""
surveycore.SelectFocusIcon = opts.Color.Colorize(colors.BrightGreen + ">" + colors.Reset)
prompt := "Multiple resources with the given URN exist, please select the one to edit:"
prompt = opts.Color.Colorize(colors.SpecPrompt + prompt + colors.Reset)
var options []string
optionMap := make(map[string]*resource.State)
for _, ambiguousResource := range candidateResources {
// Prompt the user to select from a list of IDs, since these resources are known to all have the same URN.
message := fmt.Sprintf("%q", ambiguousResource.ID)
if ambiguousResource.Protect {
message += " (Protected)"
}
if ambiguousResource.Delete {
message += " (Pending Deletion)"
}
options = append(options, message)
optionMap[message] = ambiguousResource
}
cmdutil.EndKeypadTransmitMode()
var option string
if err := survey.AskOne(&survey.Select{
Message: prompt,
Options: options,
PageSize: len(options),
}, &option, nil); err != nil {
return nil, errors.New("no resource selected")
}
return optionMap[option], nil
}
// runStateEdit runs the given state edit function on a resource with the given URN in a given stack.
func runStateEdit(stackName string, showPrompt bool, urn resource.URN, operation edit.OperationFunc) result.Result {
return runTotalStateEdit(stackName, showPrompt, func(opts display.Options, snap *deploy.Snapshot) error {
res, err := locateStackResource(opts, snap, urn)
if err != nil {
return err
}
return operation(snap, res)
})
}
// runTotalStateEdit runs a snapshot-mutating function on the entirety of the given stack's snapshot.
// Before mutating, the user may be prompted to for confirmation if the current session is interactive.
func runTotalStateEdit(
stackName string, showPrompt bool,
operation func(opts display.Options, snap *deploy.Snapshot) error) result.Result {
opts := display.Options{
Color: cmdutil.GetGlobalColorization(),
}
s, err := requireStack(stackName, true, opts, true /*setCurrent*/)
if err != nil {
return result.FromError(err)
}
snap, err := s.Snapshot(commandContext())
if err != nil {
return result.FromError(err)
}
if showPrompt && cmdutil.Interactive() {
confirm := false
surveycore.DisableColor = true
surveycore.QuestionIcon = ""
surveycore.SelectFocusIcon = opts.Color.Colorize(colors.BrightGreen + ">" + colors.Reset)
prompt := opts.Color.Colorize(colors.Yellow + "warning" + colors.Reset + ": ")
prompt += "This command will edit your stack's state directly. Confirm?"
cmdutil.EndKeypadTransmitMode()
if err = survey.AskOne(&survey.Confirm{
Message: prompt,
}, &confirm, nil); err != nil || !confirm {
fmt.Println("confirmation declined")
return result.Bail()
}
}
// The `operation` callback will mutate `snap` in-place. In order to validate the correctness of the transformation
// that we are doing here, we verify the integrity of the snapshot before the mutation. If the snapshot was valid
// before we mutated it, we'll assert that we didn't make it invalid by mutating it.
stackIsAlreadyHosed := snap.VerifyIntegrity() != nil
if err = operation(opts, snap); err != nil {
return result.FromError(err)
}
// If the stack is already broken, don't bother verifying the integrity here.
if !stackIsAlreadyHosed {
contract.AssertNoErrorf(snap.VerifyIntegrity(), "state edit produced an invalid snapshot")
}
sdep, err := stack.SerializeDeployment(snap, snap.SecretsManager)
if err != nil {
return result.FromError(errors.Wrap(err, "serializing deployment"))
}
// Once we've mutated the snapshot, import it back into the backend so that it can be persisted.
bytes, err := json.Marshal(sdep)
if err != nil {
return result.FromError(err)
}
dep := apitype.UntypedDeployment{
Version: apitype.DeploymentSchemaVersionCurrent,
Deployment: bytes,
}
return result.WrapIfNonNil(s.ImportDeployment(commandContext(), &dep))
}