Show a better error when --force
needs to be passed to stack rm
When `pulumi stack rm` is run against a stack with resources, the service will respond with an error if `--force` is not passed. Previously we would just dump the contents of this error and it looked something like: `error: [400] Bad Request: Stack still has resources.` We now handle this case more gracefully, showing our usual "this stack still has resources" error like we would for the local backend. Fixes #2431
This commit is contained in:
parent
6f386567f6
commit
687a780b20
|
@ -2,6 +2,8 @@
|
|||
|
||||
### Improvements
|
||||
|
||||
- When trying to `stack rm` a stack managed by pulumi.com that has resources, the error message now informs you to pass `--force` if you really want to remove a stack that still has resources under management, as this would orphan these resources (fixes [pulumi/pulumi#2431](https://github.com/pulumi/pulumi/issues/2431)).
|
||||
|
||||
## 0.16.14 (Released January 31th, 2019)
|
||||
|
||||
### Improvements
|
||||
|
|
|
@ -243,13 +243,27 @@ func (pc *Client) CreateStack(
|
|||
// DeleteStack deletes the indicated stack. If force is true, the stack is deleted even if it contains resources.
|
||||
func (pc *Client) DeleteStack(ctx context.Context, stack StackIdentifier, force bool) (bool, error) {
|
||||
path := getStackPath(stack)
|
||||
if force {
|
||||
path += "?force=true"
|
||||
queryObj := struct {
|
||||
Force bool `url:"force"`
|
||||
}{
|
||||
Force: force,
|
||||
}
|
||||
|
||||
// TODO[pulumi/pulumi-service#196] When the service returns a well known response for "this stack still has
|
||||
// resources and `force` was not true", we should sniff for that message and return a true for the boolean.
|
||||
return false, pc.restCall(ctx, "DELETE", path, nil, nil, nil)
|
||||
err := pc.restCall(ctx, "DELETE", path, queryObj, nil, nil)
|
||||
return isStackHasResourcesError(err), err
|
||||
}
|
||||
|
||||
func isStackHasResourcesError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errRsp, ok := err.(*apitype.ErrorResponse)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return errRsp.Code == 400 && errRsp.Message == "Bad Request: Stack still contains resources."
|
||||
}
|
||||
|
||||
// EncryptValue encrypts a plaintext value in the context of the indicated stack.
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/apitype"
|
||||
|
@ -207,6 +209,31 @@ func TestStackTagValidation(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestRemoveWithResourcesBlocked(t *testing.T) {
|
||||
if os.Getenv("PULUMI_ACCESS_TOKEN") == "" {
|
||||
t.Skipf("Skipping: PULUMI_ACCESS_TOKEN is not set")
|
||||
}
|
||||
|
||||
e := ptesting.NewEnvironment(t)
|
||||
defer func() {
|
||||
if !t.Failed() {
|
||||
e.DeleteEnvironment()
|
||||
}
|
||||
}()
|
||||
|
||||
stackName, err := resource.NewUniqueHex("rm-test-", 8, -1)
|
||||
contract.AssertNoErrorf(err, "resource.NewUniqueHex sould not fail with no maximum length is set")
|
||||
|
||||
e.ImportDirectory(filepath.Join("empty", "nodejs"))
|
||||
e.RunCommand("pulumi", "stack", "init", stackName)
|
||||
e.RunCommand("yarn", "link", "@pulumi/pulumi")
|
||||
e.RunCommand("pulumi", "up", "--non-interactive", "--skip-preview")
|
||||
_, stderr := e.RunCommandExpectError("pulumi", "stack", "rm", "--yes")
|
||||
assert.Contains(t, stderr, "--force")
|
||||
e.RunCommand("pulumi", "destroy", "--skip-preview", "--non-interactive", "--yes")
|
||||
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
||||
}
|
||||
|
||||
// TestStackOutputs ensures we can export variables from a stack and have them get recorded as outputs.
|
||||
func TestStackOutputsNodeJS(t *testing.T) {
|
||||
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
||||
|
|
Loading…
Reference in a new issue