Skip to content

Commit

Permalink
filestate: Support opting into legacy layout
Browse files Browse the repository at this point in the history
With project support added,
filestate will default to project-scoped stacks
for all newly initialized buckets.

This is desirable long-term, but for the initial release,
we'd like users to have an escape hatch to go back to the old layout
until they've had a change to migrate.

This adds the ability for users to opt-out of this feature
by setting an environment variable.

Note that this only applies to new buckets.
You cannot opt out of this feature for a bucket
that is already using project-scoped stacks.
  • Loading branch information
Frassle authored and abhinav committed Mar 29, 2023
1 parent 5d38ae1 commit 6d584f1
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ changes:
description: |
Add support for project-scoped stacks.
Newly initialized storage will automatically use this mode.
Set PULUMI_SELF_MANAGED_STATE_LEGACY_LAYOUT=1 to opt-out of this.
This mode needs write access to the root of the .pulumi directory;
if you're using a cloud storage, be sure to update your ACLs.
7 changes: 7 additions & 0 deletions pkg/backend/filestate/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ var (
// to disable the warning printed by the filestate backend
// when it detects that the state has both, project-scoped and legacy stacks.
PulumiFilestateNoLegacyWarningEnvVar = env.SelfManagedStateNoLegacyWarning.Var().Name()

// PulumiFilestateLegacyLayoutEnvVar is the name of an environment variable
// that can be set to force the use of the legacy layout
// when initializing an empty bucket for filestate.
//
// This opt-out is intended to be removed in a future release.
PulumiFilestateLegacyLayoutEnvVar = env.SelfManagedStateLegacyLayout.Var().Name()
)

// Backend extends the base backend interface with specific information about local backends.
Expand Down
18 changes: 18 additions & 0 deletions pkg/backend/filestate/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,24 @@ func TestLegacyFolderStructure(t *testing.T) {
assert.FileExists(t, path.Join(tmpDir, ".pulumi", "stacks", "b.json"))
}

//nolint:paralleltest // uses t.Setenv
func TestOptIntoLegacyFolderStructure(t *testing.T) {
t.Setenv("PULUMI_SELF_MANAGED_STATE_LEGACY_LAYOUT", "true")

tmpDir := t.TempDir()
ctx := context.Background()
b, err := New(ctx, diagtest.LogSink(t), "file://"+filepath.ToSlash(tmpDir), nil)
require.NoError(t, err)

// Verify that a new stack is created in the legacy location.
foo, err := b.ParseStackReference("foo")
require.NoError(t, err)

_, err = b.CreateStack(ctx, foo, "", nil)
require.NoError(t, err)
assert.FileExists(t, filepath.Join(tmpDir, ".pulumi", "stacks", "foo.json"))
}

// Verifies that the StackReference.String method
// takes the current project name into account,
// even if the current project name changes
Expand Down
20 changes: 18 additions & 2 deletions pkg/backend/filestate/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ package filestate
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"

"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
Expand Down Expand Up @@ -57,6 +59,10 @@ type pulumiMeta struct {
//
// If the bucket is empty, this will create a new metadata file
// with the latest version number.
// This can be overridden by setting the environment variable
// "PULUMI_SELF_MANAGED_STATE_LEGACY_LAYOUT" to "1".
// ensurePulumiMeta uses the provided 'getenv' function
// to read the environment variable.
func ensurePulumiMeta(ctx context.Context, b Bucket) (*pulumiMeta, error) {
meta, err := readPulumiMeta(ctx, b)
if err != nil {
Expand All @@ -82,10 +88,20 @@ func ensurePulumiMeta(ctx context.Context, b Bucket) (*pulumiMeta, error) {
return nil, err
}

useLegacy := !empty
if empty {
meta = &pulumiMeta{Version: 1}
} else {
// Allow opting into legacy mode for new states
// by setting the environment variable.
v, err := strconv.ParseBool(os.Getenv(PulumiFilestateLegacyLayoutEnvVar))
if err == nil {
useLegacy = v
}
}

if useLegacy {
meta = &pulumiMeta{Version: 0}
} else {
meta = &pulumiMeta{Version: 1}
}

// Implementation detail:
Expand Down
31 changes: 28 additions & 3 deletions pkg/backend/filestate/meta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ import (
"gocloud.dev/blob/memblob"
)

//nolint:paralleltest // uses t.Setenv
func TestEnsurePulumiMeta(t *testing.T) {
t.Parallel()

tests := []struct {
desc string
give map[string]string // files in the bucket
Expand All @@ -41,6 +40,28 @@ func TestEnsurePulumiMeta(t *testing.T) {
desc: "empty",
want: pulumiMeta{Version: 1},
},
{
// Use legacy mode even for the new bucket
// because the environment variable is "1".
desc: "empty/legacy",
env: map[string]string{PulumiFilestateLegacyLayoutEnvVar: "1"},
want: pulumiMeta{Version: 0},
},
{
// Use legacy mode even for the new bucket
// because the environment variable is "true".
desc: "empty/legacy/true",
env: map[string]string{PulumiFilestateLegacyLayoutEnvVar: "true"},
want: pulumiMeta{Version: 0},
},
{
// Legacy mode is disabled by setting the env var
// to "false".
// This is also the default behavior.
desc: "empty/legacy/false",
env: map[string]string{PulumiFilestateLegacyLayoutEnvVar: "false"},
want: pulumiMeta{Version: 1},
},
{
// Non-empty bucket without a version file
// should get version 0 for legacy mode.
Expand All @@ -58,6 +79,8 @@ func TestEnsurePulumiMeta(t *testing.T) {
want: pulumiMeta{Version: 0},
},
{
// Non-empty bucket with a version file
// should get whatever is in the file.
desc: "version 1",
give: map[string]string{
".pulumi/meta.yaml": `version: 1`,
Expand All @@ -76,7 +99,9 @@ func TestEnsurePulumiMeta(t *testing.T) {
for _, tt := range tests {
tt := tt
t.Run(tt.desc, func(t *testing.T) {
t.Parallel()
for k, v := range tt.env {
t.Setenv(k, v)
}

b := memblob.OpenBucket(nil)
ctx := context.Background()
Expand Down
3 changes: 3 additions & 0 deletions sdk/go/common/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,7 @@ The variable should be set to the log file to which gRPC debug traces will be se
var (
SelfManagedStateNoLegacyWarning = env.Bool("SELF_MANAGED_STATE_NO_LEGACY_WARNING",
"Disables the warning about legacy stack files mixed with project-scoped stack files.")

SelfManagedStateLegacyLayout = env.Bool("SELF_MANAGED_STATE_LEGACY_LAYOUT",
"Uses the legacy layout for new buckets, which currently default to project-scoped stacks.")
)

0 comments on commit 6d584f1

Please sign in to comment.