Skip to content

Commit

Permalink
Don't allow stacks with the wrong project name in filestate
Browse files Browse the repository at this point in the history
  • Loading branch information
Frassle committed Feb 1, 2023
1 parent d2bd3b6 commit 1b9107f
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 9 deletions.
55 changes: 51 additions & 4 deletions pkg/backend/filestate/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,18 +340,51 @@ func (b *localBackend) DoesProjectExist(ctx context.Context, projectName string)
return false, nil
}

// Confirm the specified stack's project doesn't contradict the Pulumi.yaml of the current project. If the CWD
// is not in a Pulumi project, does not contradict. If the project name in Pulumi.yaml is "foo", a stack with a
// name of bar/foo should not work.
func currentProjectContradictsWorkspace(stack localBackendReference) bool {
if stack.project == "" {
return false
}

projPath, err := workspace.DetectProjectPath()
if err != nil {
return false
}

if projPath == "" {
return false
}

proj, err := workspace.LoadProject(projPath)
if err != nil {
return false
}

return proj.Name.String() != stack.project.String()
}

func (b *localBackend) CreateStack(ctx context.Context, stackRef backend.StackReference,
opts interface{}) (backend.Stack, error) {
localStackRef, is := stackRef.(localBackendReference)
if !is {
return nil, fmt.Errorf("bad stack reference type")
}

err := b.Lock(ctx, stackRef)
if err != nil {
return nil, err
}
defer b.Unlock(ctx, stackRef)

if currentProjectContradictsWorkspace(localStackRef) {
return nil, fmt.Errorf("provided project name %q doesn't match Pulumi.yaml", localStackRef.project)
}

contract.Requiref(opts == nil, "opts", "local stacks do not support any options")

stackName := stackRef.FullyQualifiedName()
stackName := localStackRef.FullyQualifiedName()
if stackName == "" {
return nil, errors.New("invalid empty stack name")
}
Expand All @@ -373,14 +406,19 @@ func (b *localBackend) CreateStack(ctx context.Context, stackRef backend.StackRe
return nil, err
}

stack := newStack(stackRef, file, nil, b)
stack := newStack(localStackRef, file, nil, b)
fmt.Printf("Created stack '%s'\n", stack.Ref())

return stack, nil
}

func (b *localBackend) GetStack(ctx context.Context, stackRef backend.StackReference) (backend.Stack, error) {
stackName := stackRef.FullyQualifiedName()
localStackRef, is := stackRef.(localBackendReference)
if !is {
return nil, fmt.Errorf("bad stack reference type")
}

stackName := localStackRef.FullyQualifiedName()
snapshot, path, err := b.getStack(ctx, stackName)

switch {
Expand All @@ -389,7 +427,7 @@ func (b *localBackend) GetStack(ctx context.Context, stackRef backend.StackRefer
case err != nil:
return nil, err
default:
return newStack(stackRef, path, snapshot, b), nil
return newStack(localStackRef, path, snapshot, b), nil
}
}

Expand Down Expand Up @@ -596,6 +634,15 @@ func (b *localBackend) apply(
events chan<- engine.Event) (*deploy.Plan, sdkDisplay.ResourceChanges, result.Result) {

stackRef := stack.Ref()
localStackRef, is := stackRef.(localBackendReference)
if !is {
return nil, nil, result.Error("bad stack reference type")
}

if currentProjectContradictsWorkspace(localStackRef) {
return nil, nil, result.Errorf("provided project name %q doesn't match Pulumi.yaml", localStackRef.project)
}

stackName := stackRef.FullyQualifiedName()
actionLabel := backend.ActionLabel(kind, opts.DryRun)

Expand Down
56 changes: 56 additions & 0 deletions pkg/backend/filestate/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
user "github.com/tweekmonster/luser"

"github.com/pulumi/pulumi/pkg/v3/backend"
Expand Down Expand Up @@ -565,3 +566,58 @@ func TestCanRenameStack(t *testing.T) {
assert.Equal(t, "testproj/b", newBStack.String())
assert.FileExists(t, path.Join(tmpDir, ".pulumi", "stacks", "testproj", "b.json"))
}

func chdir(t *testing.T, dir string) {
cwd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, os.Chdir(dir)) // Set directory
t.Cleanup(func() {
require.NoError(t, os.Chdir(cwd)) // Restore directory
restoredDir, err := os.Getwd()
require.NoError(t, err)
require.Equal(t, cwd, restoredDir)
})
}

//nolint:paralleltest // mutates cwd
func TestProjectNameMustMatch(t *testing.T) {
// Login to a temp dir filestate backend
tmpDir := t.TempDir()
b, err := New(cmdutil.Diag(), "file://"+filepath.ToSlash(tmpDir))
require.NoError(t, err)
ctx := context.Background()

// Create a new project
projectDir := t.TempDir()
pyaml := filepath.Join(projectDir, "Pulumi.yaml")
err = os.WriteFile(pyaml, []byte("name: my-project\nruntime: test"), 0600)
require.NoError(t, err)

chdir(t, projectDir)

// Create a new non-project stack
aRef, err := b.ParseStackReference("a")
assert.NoError(t, err)
assert.Equal(t, "a", aRef.String())
aStack, err := b.CreateStack(ctx, aRef, nil)
assert.NoError(t, err)
assert.Equal(t, "a", aStack.Ref().String())
assert.FileExists(t, path.Join(tmpDir, ".pulumi", "stacks", "a.json"))

// Create a new project stack with the wrong project name
bRef, err := b.ParseStackReference("not-my-project/b")
assert.NoError(t, err)
assert.Equal(t, "not-my-project/b", bRef.String())
bStack, err := b.CreateStack(ctx, bRef, nil)
assert.Error(t, err)
assert.Nil(t, bStack)

// Create a new project stack with the right project name
cRef, err := b.ParseStackReference("my-project/c")
assert.NoError(t, err)
assert.Equal(t, "my-project/c", cRef.String())
cStack, err := b.CreateStack(ctx, cRef, nil)
assert.NoError(t, err)
assert.Equal(t, "my-project/c", cStack.Ref().String())
assert.FileExists(t, path.Join(tmpDir, ".pulumi", "stacks", "my-project", "c.json"))
}
10 changes: 5 additions & 5 deletions pkg/backend/filestate/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ type Stack interface {

// localStack is a local stack descriptor.
type localStack struct {
ref backend.StackReference // the stack's reference (qualified name).
path string // a path to the stack's checkpoint file on disk.
snapshot *deploy.Snapshot // a snapshot representing the latest deployment state.
b *localBackend // a pointer to the backend this stack belongs to.
ref localBackendReference // the stack's reference (qualified name).
path string // a path to the stack's checkpoint file on disk.
snapshot *deploy.Snapshot // a snapshot representing the latest deployment state.
b *localBackend // a pointer to the backend this stack belongs to.
}

func newStack(ref backend.StackReference, path string, snapshot *deploy.Snapshot, b *localBackend) Stack {
func newStack(ref localBackendReference, path string, snapshot *deploy.Snapshot, b *localBackend) Stack {
return &localStack{
ref: ref,
path: path,
Expand Down

0 comments on commit 1b9107f

Please sign in to comment.