mirror of https://github.com/pulumi/pulumi.git
506 lines
15 KiB
Go
506 lines
15 KiB
Go
// Copyright 2016-2023, 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 (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/pulumi/esc"
|
|
"github.com/pulumi/pulumi/pkg/v3/backend"
|
|
"github.com/pulumi/pulumi/pkg/v3/secrets"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
|
"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/workspace"
|
|
)
|
|
|
|
func TestPrettyKeyForProject(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
proj := &workspace.Project{
|
|
Name: tokens.PackageName("test-package"),
|
|
Runtime: workspace.NewProjectRuntimeInfo("nodejs", nil),
|
|
}
|
|
|
|
assert.Equal(t, "foo", prettyKeyForProject(config.MustMakeKey("test-package", "foo"), proj))
|
|
assert.Equal(t, "other-package:bar", prettyKeyForProject(config.MustMakeKey("other-package", "bar"), proj))
|
|
assert.Panics(t, func() { config.MustMakeKey("other:package", "bar") })
|
|
}
|
|
|
|
func TestSecretDetection(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
assert.True(t, looksLikeSecret(config.MustMakeKey("test", "token"), "1415fc1f4eaeb5e096ee58c1480016638fff29bf"))
|
|
assert.True(t, looksLikeSecret(config.MustMakeKey("test", "apiToken"), "1415fc1f4eaeb5e096ee58c1480016638fff29bf"))
|
|
|
|
// The key name does not match the pattern, so even though this "looks like" a secret, we say it is not.
|
|
assert.False(t, looksLikeSecret(config.MustMakeKey("test", "okay"), "1415fc1f4eaeb5e096ee58c1480016638fff29bf"))
|
|
}
|
|
|
|
func TestGetStackConfigurationDoesNotGetLatestConfiguration(t *testing.T) {
|
|
t.Parallel()
|
|
// Don't check return values. Just check that GetLatestConfiguration() is not called.
|
|
_, _, _ = getStackConfiguration(
|
|
context.Background(),
|
|
stackSecretsManagerLoader{},
|
|
&backend.MockStack{
|
|
RefF: func() backend.StackReference {
|
|
return &backend.MockStackReference{
|
|
StringV: "org/project/name",
|
|
NameV: tokens.MustParseStackName("name"),
|
|
ProjectV: "project",
|
|
FullyQualifiedNameV: tokens.QName("org/project/name"),
|
|
}
|
|
},
|
|
BackendF: func() backend.Backend {
|
|
return &backend.MockBackend{
|
|
GetLatestConfigurationF: func(context.Context, backend.Stack) (config.Map, error) {
|
|
t.Fatalf("GetLatestConfiguration should not be called in typical getStackConfiguration calls.")
|
|
return config.Map{}, nil
|
|
},
|
|
}
|
|
},
|
|
},
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func TestGetStackConfigurationOrLatest(t *testing.T) {
|
|
t.Parallel()
|
|
// Don't check return values. Just check that GetLatestConfiguration() is called.
|
|
called := false
|
|
_, _, _ = getStackConfigurationOrLatest(
|
|
context.Background(),
|
|
stackSecretsManagerLoader{},
|
|
&backend.MockStack{
|
|
RefF: func() backend.StackReference {
|
|
return &backend.MockStackReference{
|
|
StringV: "org/project/name",
|
|
NameV: tokens.MustParseStackName("name"),
|
|
ProjectV: "project",
|
|
FullyQualifiedNameV: tokens.QName("org/project/name"),
|
|
}
|
|
},
|
|
DefaultSecretManagerF: func(info *workspace.ProjectStack) (secrets.Manager, error) {
|
|
return nil, nil
|
|
},
|
|
BackendF: func() backend.Backend {
|
|
return &backend.MockBackend{
|
|
GetLatestConfigurationF: func(context.Context, backend.Stack) (config.Map, error) {
|
|
called = true
|
|
return config.Map{}, nil
|
|
},
|
|
}
|
|
},
|
|
},
|
|
nil,
|
|
)
|
|
if !called {
|
|
t.Fatalf("GetLatestConfiguration should be called in getStackConfigurationOrLatest.")
|
|
}
|
|
}
|
|
|
|
func TestNeedsCrypter(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("no secrets, no env", func(t *testing.T) {
|
|
t.Parallel()
|
|
m := config.Map{config.MustMakeKey("test", "foo"): config.NewValue("bar")}
|
|
assert.False(t, needsCrypter(m, esc.Value{}))
|
|
})
|
|
|
|
t.Run("secrets, no env", func(t *testing.T) {
|
|
t.Parallel()
|
|
m := config.Map{config.MustMakeKey("test", "foo"): config.NewSecureValue("bar")}
|
|
assert.True(t, needsCrypter(m, esc.Value{}))
|
|
})
|
|
|
|
t.Run("no secrets, no secrets in env", func(t *testing.T) {
|
|
t.Parallel()
|
|
m := config.Map{config.MustMakeKey("test", "foo"): config.NewValue("bar")}
|
|
env := esc.NewValue(map[string]esc.Value{"password": esc.NewValue("hunter2")})
|
|
assert.False(t, needsCrypter(m, env))
|
|
})
|
|
|
|
t.Run("no secrets, secrets in env", func(t *testing.T) {
|
|
t.Parallel()
|
|
m := config.Map{config.MustMakeKey("test", "foo"): config.NewValue("bar")}
|
|
env := esc.NewValue(map[string]esc.Value{"password": esc.NewSecret("hunter2")})
|
|
assert.True(t, needsCrypter(m, env))
|
|
})
|
|
|
|
t.Run("no secrets, secrets in env array", func(t *testing.T) {
|
|
t.Parallel()
|
|
m := config.Map{config.MustMakeKey("test", "foo"): config.NewValue("bar")}
|
|
env := esc.NewValue(map[string]esc.Value{"password": esc.NewValue([]esc.Value{esc.NewSecret("hunter2")})})
|
|
assert.True(t, needsCrypter(m, env))
|
|
})
|
|
|
|
t.Run("secrets, secrets in env", func(t *testing.T) {
|
|
t.Parallel()
|
|
m := config.Map{config.MustMakeKey("test", "foo"): config.NewSecureValue("bar")}
|
|
env := esc.NewValue(map[string]esc.Value{"password": esc.NewSecret("hunter2")})
|
|
assert.True(t, needsCrypter(m, env))
|
|
})
|
|
}
|
|
|
|
func TestOpenStackEnvNoEnv(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
be := &backend.MockBackend{NameF: func() string { return "test" }}
|
|
stack := &backend.MockStack{BackendF: func() backend.Backend { return be }}
|
|
|
|
var projectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte(""), &projectStack)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = openStackEnv(context.Background(), stack, &projectStack)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestOpenStackEnvUnsupportedBackend(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
be := &backend.MockBackend{NameF: func() string { return "test" }}
|
|
stack := &backend.MockStack{BackendF: func() backend.Backend { return be }}
|
|
|
|
var projectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte("environment:\n - test"), &projectStack)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = openStackEnv(context.Background(), stack, &projectStack)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func getMockStackWithEnv(t *testing.T, env map[string]esc.Value) *backend.MockStack {
|
|
t.Helper()
|
|
|
|
be := &backend.MockEnvironmentsBackend{
|
|
MockBackend: backend.MockBackend{
|
|
NameF: func() string { return "test" },
|
|
},
|
|
OpenYAMLEnvironmentF: func(
|
|
ctx context.Context,
|
|
org string,
|
|
yaml []byte,
|
|
duration time.Duration,
|
|
) (*esc.Environment, apitype.EnvironmentDiagnostics, error) {
|
|
assert.Equal(t, "test-org", org)
|
|
assert.NotEmpty(t, yaml)
|
|
assert.Equal(t, 2*time.Hour, duration)
|
|
return &esc.Environment{Properties: env}, nil, nil
|
|
},
|
|
}
|
|
stack := &backend.MockStack{
|
|
OrgNameF: func() string { return "test-org" },
|
|
BackendF: func() backend.Backend { return be },
|
|
}
|
|
|
|
return stack
|
|
}
|
|
|
|
func TestOpenStackEnv(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
env := map[string]esc.Value{
|
|
"pulumiConfig": esc.NewValue(map[string]esc.Value{
|
|
"test:string": esc.NewValue("esc"),
|
|
}),
|
|
"environmentVariables": esc.NewValue(map[string]esc.Value{
|
|
"TEST_VAR": esc.NewSecret("hunter2"),
|
|
}),
|
|
"files": esc.NewValue(map[string]esc.Value{
|
|
"TEST_FILE": esc.NewSecret("sensitive"),
|
|
}),
|
|
}
|
|
|
|
stack := getMockStackWithEnv(t, env)
|
|
|
|
var projectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte("environment:\n - test"), &projectStack)
|
|
require.NoError(t, err)
|
|
|
|
openEnv, diags, err := openStackEnv(context.Background(), stack, &projectStack)
|
|
require.NoError(t, err)
|
|
assert.Len(t, diags, 0)
|
|
assert.Equal(t, env, openEnv.Properties)
|
|
}
|
|
|
|
func TestOpenStackEnvLiteral(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
env := map[string]esc.Value{
|
|
"pulumiConfig": esc.NewValue(map[string]esc.Value{
|
|
"test:string": esc.NewValue("esc"),
|
|
}),
|
|
"environmentVariables": esc.NewValue(map[string]esc.Value{
|
|
"TEST_VAR": esc.NewSecret("hunter2"),
|
|
}),
|
|
"files": esc.NewValue(map[string]esc.Value{
|
|
"TEST_FILE": esc.NewSecret("sensitive"),
|
|
}),
|
|
}
|
|
|
|
stack := getMockStackWithEnv(t, env)
|
|
|
|
var projectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte("environment:\n imports:\n - test"), &projectStack)
|
|
require.NoError(t, err)
|
|
|
|
openEnv, diags, err := openStackEnv(context.Background(), stack, &projectStack)
|
|
require.NoError(t, err)
|
|
assert.Len(t, diags, 0)
|
|
assert.Equal(t, env, openEnv.Properties)
|
|
}
|
|
|
|
func TestStackEnvConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
env := map[string]esc.Value{
|
|
"pulumiConfig": esc.NewValue(map[string]esc.Value{
|
|
"string": esc.NewValue("esc"),
|
|
"aws:region": esc.NewValue("us-west-2"),
|
|
"api:domain": esc.NewValue("test"),
|
|
"ui:domain": esc.NewValue("test"),
|
|
}),
|
|
"environmentVariables": esc.NewValue(map[string]esc.Value{
|
|
"TEST_VAR": esc.NewSecret("hunter2"),
|
|
}),
|
|
"files": esc.NewValue(map[string]esc.Value{
|
|
"TEST_FILE": esc.NewSecret("sensitive"),
|
|
}),
|
|
}
|
|
|
|
mockSecretsManager := &secrets.MockSecretsManager{
|
|
EncrypterF: func() (config.Encrypter, error) {
|
|
encrypter := &secrets.MockEncrypter{EncryptValueF: func() string { return "ciphertext" }}
|
|
return encrypter, nil
|
|
},
|
|
DecrypterF: func() (config.Decrypter, error) {
|
|
decrypter := &secrets.MockDecrypter{
|
|
DecryptValueF: func() string {
|
|
return "plaintext"
|
|
},
|
|
BulkDecryptF: func() map[string]string {
|
|
return map[string]string{
|
|
"idontknow": "whatiamdoing",
|
|
}
|
|
},
|
|
}
|
|
|
|
return decrypter, nil
|
|
},
|
|
}
|
|
|
|
getMockStack := func(name string) *backend.MockStack {
|
|
stack := getMockStackWithEnv(t, env)
|
|
stack.RefF = func() backend.StackReference {
|
|
return &backend.MockStackReference{
|
|
StringV: "org/project/" + name,
|
|
NameV: tokens.MustParseStackName(name),
|
|
ProjectV: "project",
|
|
FullyQualifiedNameV: tokens.QName("org/project/" + name),
|
|
}
|
|
}
|
|
stack.DefaultSecretManagerF = func(info *workspace.ProjectStack) (secrets.Manager, error) {
|
|
return mockSecretsManager, nil
|
|
}
|
|
|
|
return stack
|
|
}
|
|
|
|
stack := getMockStack("mystack")
|
|
|
|
var projectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte("environment:\n - test"), &projectStack)
|
|
require.NoError(t, err)
|
|
|
|
project := workspace.Project{Name: tokens.PackageName("project")}
|
|
|
|
ctx := context.Background()
|
|
cfg, err := getStackConfigurationFromProjectStack(
|
|
ctx,
|
|
stack,
|
|
&project,
|
|
mockSecretsManager,
|
|
&projectStack,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
assert.Nil(t, cfg.Config)
|
|
cfg.Config = config.Map{}
|
|
|
|
err = workspace.ApplyProjectConfig(ctx, "mystack", &project, cfg.Environment, cfg.Config, config.NopEncrypter)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, config.Map{
|
|
config.MustMakeKey("project", "string"): config.NewValue("esc"),
|
|
config.MustMakeKey("aws", "region"): config.NewValue("us-west-2"),
|
|
config.MustMakeKey("api", "domain"): config.NewValue("test"),
|
|
config.MustMakeKey("ui", "domain"): config.NewValue("test"),
|
|
}, cfg.Config)
|
|
}
|
|
|
|
func TestCopyConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
env := map[string]esc.Value{
|
|
"pulumiConfig": esc.NewValue(map[string]esc.Value{
|
|
"test:string": esc.NewValue("esc"),
|
|
}),
|
|
"environmentVariables": esc.NewValue(map[string]esc.Value{
|
|
"TEST_VAR": esc.NewSecret("hunter2"),
|
|
}),
|
|
"files": esc.NewValue(map[string]esc.Value{
|
|
"TEST_FILE": esc.NewSecret("sensitive"),
|
|
}),
|
|
}
|
|
|
|
mockSecretsManager := &secrets.MockSecretsManager{
|
|
EncrypterF: func() (config.Encrypter, error) {
|
|
encrypter := &secrets.MockEncrypter{EncryptValueF: func() string { return "ciphertext" }}
|
|
return encrypter, nil
|
|
},
|
|
DecrypterF: func() (config.Decrypter, error) {
|
|
decrypter := &secrets.MockDecrypter{
|
|
DecryptValueF: func() string {
|
|
return "plaintext"
|
|
},
|
|
BulkDecryptF: func() map[string]string {
|
|
return map[string]string{
|
|
"idontknow": "whatiamdoing",
|
|
}
|
|
},
|
|
}
|
|
|
|
return decrypter, nil
|
|
},
|
|
}
|
|
|
|
getMockStack := func(name string) *backend.MockStack {
|
|
stack := getMockStackWithEnv(t, env)
|
|
stack.RefF = func() backend.StackReference {
|
|
return &backend.MockStackReference{
|
|
StringV: "org/project/" + name,
|
|
NameV: tokens.MustParseStackName(name),
|
|
ProjectV: "project",
|
|
FullyQualifiedNameV: tokens.QName("org/project/" + name),
|
|
}
|
|
}
|
|
stack.DefaultSecretManagerF = func(info *workspace.ProjectStack) (secrets.Manager, error) {
|
|
return mockSecretsManager, nil
|
|
}
|
|
|
|
return stack
|
|
}
|
|
|
|
sourceStack := getMockStack("mystack")
|
|
|
|
var sourceProjectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte("environment:\n - test"), &sourceProjectStack)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("TestCopyConfigIncludesEnvironments", func(t *testing.T) {
|
|
destinationStack := getMockStack("mystack2")
|
|
|
|
var destinationProjectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte("environment:\n - test2"), &destinationProjectStack)
|
|
require.NoError(t, err)
|
|
|
|
requiresSaving, err := copyEntireConfigMap(
|
|
context.Background(),
|
|
stackSecretsManagerLoader{},
|
|
sourceStack,
|
|
&sourceProjectStack,
|
|
destinationStack,
|
|
&destinationProjectStack,
|
|
)
|
|
require.NoError(t, err)
|
|
assert.True(t, requiresSaving, "expected config file changes requiring saving")
|
|
|
|
// Assert that only the source stack's environment
|
|
// remains in the destination stack.
|
|
envImports := destinationProjectStack.Environment.Imports()
|
|
assert.Contains(t, envImports, "test")
|
|
assert.NotContains(t, envImports, "test2")
|
|
})
|
|
}
|
|
|
|
func TestOpenStackEnvDiags(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
be := &backend.MockEnvironmentsBackend{
|
|
MockBackend: backend.MockBackend{
|
|
NameF: func() string { return "test" },
|
|
},
|
|
OpenYAMLEnvironmentF: func(
|
|
ctx context.Context,
|
|
org string,
|
|
yaml []byte,
|
|
duration time.Duration,
|
|
) (*esc.Environment, apitype.EnvironmentDiagnostics, error) {
|
|
return nil, []apitype.EnvironmentDiagnostic{{Summary: "diag"}}, nil
|
|
},
|
|
}
|
|
stack := &backend.MockStack{
|
|
OrgNameF: func() string { return "test-org" },
|
|
BackendF: func() backend.Backend { return be },
|
|
}
|
|
|
|
var projectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte("environment:\n - test"), &projectStack)
|
|
require.NoError(t, err)
|
|
|
|
_, diags, err := openStackEnv(context.Background(), stack, &projectStack)
|
|
require.NoError(t, err)
|
|
assert.Len(t, diags, 1)
|
|
}
|
|
|
|
func TestOpenStackEnvError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
be := &backend.MockEnvironmentsBackend{
|
|
MockBackend: backend.MockBackend{
|
|
NameF: func() string { return "test" },
|
|
},
|
|
OpenYAMLEnvironmentF: func(
|
|
ctx context.Context,
|
|
org string,
|
|
yaml []byte,
|
|
duration time.Duration,
|
|
) (*esc.Environment, apitype.EnvironmentDiagnostics, error) {
|
|
return nil, nil, errors.New("error")
|
|
},
|
|
}
|
|
stack := &backend.MockStack{
|
|
OrgNameF: func() string { return "test-org" },
|
|
BackendF: func() backend.Backend { return be },
|
|
}
|
|
|
|
var projectStack workspace.ProjectStack
|
|
err := yaml.Unmarshal([]byte("environment:\n - test"), &projectStack)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = openStackEnv(context.Background(), stack, &projectStack)
|
|
assert.Error(t, err)
|
|
}
|