[backend/filestate] Allow pulumi stack ls
to see all stacks regardless of passphrase (#7660)
* [backend/filestate] Allow `pulumi stack ls` to see all stacks regardless of passphrase The information exposed via `pulumi stack ls` does not require being able to decrypt state files, but the existing logic for `pulumi stack ls` with the filestate backend was to fully decrypt the state file anyway, silently skipping any stacks that could not be decrypted. This led to surprising results from `pulumi stack ls`. After these changes, `pulumi stack ls` with the filestate backend will list *all* stacks that are available. Notably, because there is no notion of "project" scoping in the fielstate backend (yet), `pulumi stack ls` will list all stacks independent of the project name. Fixes #4798.
This commit is contained in:
parent
fddeea8a88
commit
fa6375999e
|
@ -4,6 +4,10 @@
|
|||
- [sdk/go] - Add stack output helpers for numeric types.
|
||||
[#7410](https://github.com/pulumi/pulumi/pull/7410)
|
||||
|
||||
- [backend/filestate] - Allow pulumi stack ls to see all stacks regardless of passphrase.
|
||||
[#7660](https://github.com/pulumi/pulumi/pull/7660)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [sdk/{go,python,nodejs}] - Rehydrate provider resources in `Construct`.
|
||||
|
|
|
@ -53,7 +53,6 @@ import (
|
|||
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
||||
)
|
||||
|
@ -340,13 +339,15 @@ func (b *localBackend) ListStacks(
|
|||
// organizations and tags aren't persisted in the local backend.
|
||||
var results []backend.StackSummary
|
||||
for _, stackName := range stacks {
|
||||
stack, err := b.GetStack(ctx, localBackendReference{name: stackName})
|
||||
chk, err := b.getCheckpoint(stackName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
localStack, ok := stack.(*localStack)
|
||||
contract.Assertf(ok, "localBackend GetStack returned non-localStack")
|
||||
results = append(results, newLocalStackSummary(localStack))
|
||||
stackRef, err := b.ParseStackReference(string(stackName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, newLocalStackSummary(stackRef, chk))
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
@ -854,11 +855,6 @@ func (b *localBackend) getLocalStacks() ([]tokens.QName, error) {
|
|||
|
||||
// Read in this stack's information.
|
||||
name := tokens.QName(stackfn[:len(stackfn)-len(ext)])
|
||||
_, _, err := b.getStack(name)
|
||||
if err != nil {
|
||||
logging.V(5).Infof("error reading stack: %v (%v) skipping", name, err)
|
||||
continue // failure reading the stack information.
|
||||
}
|
||||
|
||||
stacks = append(stacks, name)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package filestate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
@ -8,9 +12,16 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
user "github.com/tweekmonster/luser"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v3/backend"
|
||||
"github.com/pulumi/pulumi/pkg/v3/operations"
|
||||
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
||||
"github.com/pulumi/pulumi/pkg/v3/resource/stack"
|
||||
"github.com/pulumi/pulumi/pkg/v3/secrets/passphrase"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
|
||||
)
|
||||
|
||||
func TestMassageBlobPath(t *testing.T) {
|
||||
|
@ -80,3 +91,98 @@ func TestGetLogsForTargetWithNoSnapshot(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Nil(t, res)
|
||||
}
|
||||
|
||||
func makeUntypedDeployment(name tokens.QName, phrase, state string) (*apitype.UntypedDeployment, error) {
|
||||
sm, err := passphrase.NewPassphaseSecretsManager(phrase, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources := []*resource.State{
|
||||
{
|
||||
URN: resource.NewURN("a", "proj", "d:e:f", "a:b:c", name),
|
||||
Type: "a:b:c",
|
||||
Inputs: resource.PropertyMap{
|
||||
resource.PropertyKey("secret"): resource.MakeSecret(resource.NewStringProperty("s3cr3t")),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
snap := deploy.NewSnapshot(deploy.Manifest{}, sm, resources, nil)
|
||||
|
||||
sdep, err := stack.SerializeDeployment(snap, snap.SecretsManager, false /* showSecrsts */)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(sdep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apitype.UntypedDeployment{
|
||||
Version: 3,
|
||||
Deployment: json.RawMessage(data),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestListStacksWithMultiplePassphrases(t *testing.T) {
|
||||
// Login to a temp dir filestate backend
|
||||
tmpDir, err := ioutil.TempDir("", "filestatebackend")
|
||||
assert.NoError(t, err)
|
||||
b, err := New(cmdutil.Diag(), "file://"+filepath.ToSlash(tmpDir))
|
||||
assert.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create stack "a" and import a checkpoint with a secret
|
||||
aStackRef, err := b.ParseStackReference("a")
|
||||
assert.NoError(t, err)
|
||||
aStack, err := b.CreateStack(ctx, aStackRef, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, aStack)
|
||||
defer func() {
|
||||
err = os.Setenv("PULUMI_CONFIG_PASSPHRASE", "abc123")
|
||||
_, err := b.RemoveStack(ctx, aStack, true)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
deployment, err := makeUntypedDeployment("a", "abc123",
|
||||
"v1:4iF78gb0nF0=:v1:Co6IbTWYs/UdrjgY:FSrAWOFZnj9ealCUDdJL7LrUKXX9BA==")
|
||||
assert.NoError(t, err)
|
||||
err = os.Setenv("PULUMI_CONFIG_PASSPHRASE", "abc123")
|
||||
assert.NoError(t, err)
|
||||
err = b.ImportDeployment(ctx, aStack, deployment)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create stack "b" and import a checkpoint with a secret
|
||||
bStackRef, err := b.ParseStackReference("b")
|
||||
assert.NoError(t, err)
|
||||
bStack, err := b.CreateStack(ctx, bStackRef, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, bStack)
|
||||
defer func() {
|
||||
err = os.Setenv("PULUMI_CONFIG_PASSPHRASE", "123abc")
|
||||
_, err := b.RemoveStack(ctx, bStack, true)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
deployment, err = makeUntypedDeployment("b", "123abc",
|
||||
"v1:C7H2a7/Ietk=:v1:yfAd1zOi6iY9DRIB:dumdsr+H89VpHIQWdB01XEFqYaYjAg==")
|
||||
assert.NoError(t, err)
|
||||
err = os.Setenv("PULUMI_CONFIG_PASSPHRASE", "123abc")
|
||||
assert.NoError(t, err)
|
||||
err = b.ImportDeployment(ctx, bStack, deployment)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Remove the config passphrase so that we can no longer deserialize the checkpoints
|
||||
err = os.Unsetenv("PULUMI_CONFIG_PASSPHRASE")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Ensure that we can list the stacks we created even without a passphrase
|
||||
stacks, err := b.ListStacks(ctx, backend.ListStacksFilter{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, stacks, 2)
|
||||
for _, stack := range stacks {
|
||||
assert.NotNil(t, stack.ResourceCount())
|
||||
assert.Equal(t, 1, *stack.ResourceCount())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -103,21 +103,21 @@ func (s *localStack) ImportDeployment(ctx context.Context, deployment *apitype.U
|
|||
}
|
||||
|
||||
type localStackSummary struct {
|
||||
s *localStack
|
||||
name backend.StackReference
|
||||
chk *apitype.CheckpointV3
|
||||
}
|
||||
|
||||
func newLocalStackSummary(s *localStack) localStackSummary {
|
||||
return localStackSummary{s}
|
||||
func newLocalStackSummary(name backend.StackReference, chk *apitype.CheckpointV3) localStackSummary {
|
||||
return localStackSummary{name: name, chk: chk}
|
||||
}
|
||||
|
||||
func (lss localStackSummary) Name() backend.StackReference {
|
||||
return lss.s.Ref()
|
||||
return lss.name
|
||||
}
|
||||
|
||||
func (lss localStackSummary) LastUpdate() *time.Time {
|
||||
snap := lss.s.snapshot
|
||||
if snap != nil {
|
||||
if t := snap.Manifest.Time; !t.IsZero() {
|
||||
if lss.chk != nil && lss.chk.Latest != nil {
|
||||
if t := lss.chk.Latest.Manifest.Time; !t.IsZero() {
|
||||
return &t
|
||||
}
|
||||
}
|
||||
|
@ -125,9 +125,8 @@ func (lss localStackSummary) LastUpdate() *time.Time {
|
|||
}
|
||||
|
||||
func (lss localStackSummary) ResourceCount() *int {
|
||||
snap := lss.s.snapshot
|
||||
if snap != nil {
|
||||
count := len(snap.Resources)
|
||||
if lss.chk != nil && lss.chk.Latest != nil {
|
||||
count := len(lss.chk.Latest.Resources)
|
||||
return &count
|
||||
}
|
||||
return nil
|
||||
|
|
Loading…
Reference in a new issue