// Copyright 2016-2021, 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 auto import ( "bytes" "context" "io" "os" "os/exec" "path/filepath" "regexp" "runtime" "strconv" "strings" "sync" "testing" "github.com/pulumi/pulumi/sdk/v3/go/auto/optimport" "github.com/blang/semver" "github.com/go-git/go-git/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/pulumi/pulumi/sdk/v3/go/auto/debug" "github.com/pulumi/pulumi/sdk/v3/go/auto/events" "github.com/pulumi/pulumi/sdk/v3/go/auto/optdestroy" "github.com/pulumi/pulumi/sdk/v3/go/auto/optlist" "github.com/pulumi/pulumi/sdk/v3/go/auto/optpreview" "github.com/pulumi/pulumi/sdk/v3/go/auto/optrefresh" "github.com/pulumi/pulumi/sdk/v3/go/auto/optremove" "github.com/pulumi/pulumi/sdk/v3/go/auto/optup" "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" resourceConfig "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config" ) var pulumiOrg = getTestOrg() const ( pName = "testproj" agent = "pulumi/pulumi/test" pulumiTestOrg = "moolumi" ) type mockPulumiCommand struct { version semver.Version stdout string stderr string exitCode int err error capturedArgs []string } func (m *mockPulumiCommand) Version() semver.Version { return m.version } func (m *mockPulumiCommand) Run(ctx context.Context, workdir string, stdin io.Reader, additionalOutput []io.Writer, additionalErrorOutput []io.Writer, additionalEnv []string, args ...string, ) (string, string, int, error) { m.capturedArgs = args return m.stdout, m.stderr, m.exitCode, m.err } func TestWorkspaceSecretsProvider(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) mkstack := func(passphrase string) Stack { opts := []LocalWorkspaceOption{ SecretsProvider("passphrase"), EnvVars(map[string]string{ "PULUMI_CONFIG_PASSPHRASE": passphrase, }), } // initialize s, err := UpsertStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { c := config.New(ctx, "") ctx.Export("exp_static", pulumi.String("foo")) ctx.Export("exp_cfg", pulumi.String(c.Get("bar"))) ctx.Export("exp_secret", c.GetSecret("buzz")) return nil }, opts...) require.NoError(t, err, "failed to initialize stack") return s } s := mkstack("password") defer func() { err := os.Unsetenv("PULUMI_CONFIG_PASSPHRASE") assert.NoError(t, err, "failed to unset EnvVar.") // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() passwordVal := "Password1234!" err := s.SetConfig(ctx, "MySecretDatabasePassword", ConfigValue{Value: passwordVal, Secret: true}) if err != nil { t.Errorf("setConfig failed, err: %v", err) t.FailNow() } // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) // -- get config -- conf, err := s.GetConfig(ctx, "MySecretDatabasePassword") if err != nil { t.Errorf("GetConfig failed, err: %v", err) t.FailNow() } assert.Equal(t, passwordVal, conf.Value) assert.Equal(t, true, conf.Secret) // -- change passphrase -- newPassphrase := "newpassphrase" err = s.Workspace().ChangeStackSecretsProvider(ctx, s.Name(), "passphrase", &ChangeSecretsProviderOptions{ NewPassphrase: &newPassphrase, }) require.NoError(t, err) s = mkstack("newpassphrase") // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } //nolint:paralleltest // mutates environment variables func TestRemoveWithForce(t *testing.T) { ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize pDir := filepath.Join(".", "test", "testproj") s, err := NewStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // Set environment variables scoped to the workspace. envvars := map[string]string{ "foo": "bar", "barfoo": "foobar", } err = s.Workspace().SetEnvVars(envvars) assert.NoError(t, err, "failed to set environment values") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment values after setting many") s.Workspace().SetEnvVar("bar", "buzz") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment value after setting") s.Workspace().UnsetEnvVar("bar") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment values after unsetting.") // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) const permalinkSearchStr = "https://app.pulumi.com" startRegex := regexp.MustCompile(permalinkSearchStr) permalink, err := GetPermalink(res.StdOut) assert.NoError(t, err, "failed to get permalink.") assert.True(t, startRegex.MatchString(permalink)) if err = s.Workspace().RemoveStack(ctx, stackName, optremove.Force()); err != nil { t.Errorf("remove stack with force failed") t.FailNow() } // to make sure stack was removed err = s.Workspace().SelectStack(ctx, s.Name()) assert.ErrorContains(t, err, "no stack named") } //nolint:paralleltest // mutates environment variables func TestNewStackLocalSource(t *testing.T) { ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize pDir := filepath.Join(".", "test", "testproj") s, err := NewStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // Set environment variables scoped to the workspace. envvars := map[string]string{ "foo": "bar", "barfoo": "foobar", } err = s.Workspace().SetEnvVars(envvars) assert.NoError(t, err, "failed to set environment values") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment values after setting many") s.Workspace().SetEnvVar("bar", "buzz") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment value after setting") s.Workspace().UnsetEnvVar("bar") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment values after unsetting.") // -- pulumi up -- res, err := s.Up(ctx, optup.UserAgent(agent)) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) const permalinkSearchStr = "https://app.pulumi.com" startRegex := regexp.MustCompile(permalinkSearchStr) permalink, err := GetPermalink(res.StdOut) assert.NoError(t, err, "failed to get permalink.") assert.True(t, startRegex.MatchString(permalink)) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg := collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh), optpreview.UserAgent(agent)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 1, steps) // -- pulumi refresh -- ref, err := s.Refresh(ctx, optrefresh.UserAgent(agent)) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) // -- pulumi destroy -- dRes, err := s.Destroy(ctx, optdestroy.UserAgent(agent)) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } //nolint:paralleltest // mutates environment variables func TestUpsertStackLocalSource(t *testing.T) { ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize pDir := filepath.Join(".", "test", "testproj") s, err := UpsertStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // Set environment variables scoped to the workspace. envvars := map[string]string{ "foo": "bar", "barfoo": "foobar", } err = s.Workspace().SetEnvVars(envvars) assert.NoError(t, err, "failed to set environment values") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment values after setting many") s.Workspace().SetEnvVar("bar", "buzz") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment value after setting") s.Workspace().UnsetEnvVar("bar") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment values after unsetting.") // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg := collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 1, steps) // -- pulumi refresh -- ref, err := s.Refresh(ctx) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestNewStackRemoteSource(t *testing.T) { t.Parallel() ctx := context.Background() pName := "go_remote_proj" sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } repo := GitRepo{ URL: "https://github.com/pulumi/test-repo.git", ProjectPath: "goproj", } // initialize s, err := NewStackRemoteSource(ctx, stackName, repo) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg := collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 1, steps) // -- pulumi refresh -- ref, err := s.Refresh(ctx) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestUpsertStackRemoteSource(t *testing.T) { t.Parallel() ctx := context.Background() pName := "go_remote_proj" sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } repo := GitRepo{ URL: "https://github.com/pulumi/test-repo.git", ProjectPath: "goproj", } // initialize s, err := UpsertStackRemoteSource(ctx, stackName, repo) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg := collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 1, steps) // -- pulumi refresh -- ref, err := s.Refresh(ctx) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestNewStackRemoteSourceWithSetup(t *testing.T) { t.Parallel() ctx := context.Background() pName := "go_remote_proj" sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } binName := "examplesBinary" if runtime.GOOS == "windows" { binName = binName + ".exe" } repo := GitRepo{ URL: "https://github.com/pulumi/test-repo.git", ProjectPath: "goproj", Setup: func(ctx context.Context, workspace Workspace) error { cmd := exec.Command("go", "build", "-o", binName, "main.go") cmd.Dir = workspace.WorkDir() return cmd.Run() }, } project := workspace.Project{ Name: tokens.PackageName(pName), Runtime: workspace.NewProjectRuntimeInfo("go", map[string]interface{}{ "binary": binName, }), } // initialize s, err := NewStackRemoteSource(ctx, stackName, repo, Project(project)) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg := collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 1, steps) // -- pulumi refresh -- ref, err := s.Refresh(ctx) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestUpsertStackRemoteSourceWithSetup(t *testing.T) { t.Parallel() ctx := context.Background() pName := "go_remote_proj" sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } binName := "examplesBinary" if runtime.GOOS == "windows" { binName = binName + ".exe" } repo := GitRepo{ URL: "https://github.com/pulumi/test-repo.git", ProjectPath: "goproj", Setup: func(ctx context.Context, workspace Workspace) error { cmd := exec.Command("go", "build", "-o", binName, "main.go") cmd.Dir = workspace.WorkDir() return cmd.Run() }, } project := workspace.Project{ Name: tokens.PackageName(pName), Runtime: workspace.NewProjectRuntimeInfo("go", map[string]interface{}{ "binary": binName, }), } // initialize or select s, err := UpsertStackRemoteSource(ctx, stackName, repo, Project(project)) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg := collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 1, steps) // -- pulumi refresh -- ref, err := s.Refresh(ctx) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestNewStackInlineSource(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { c := config.New(ctx, "") ctx.Export("exp_static", pulumi.String("foo")) ctx.Export("exp_cfg", pulumi.String(c.Get("bar"))) ctx.Export("exp_secret", c.GetSecret("buzz")) return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() require.NoError(t, s.SetAllConfig(ctx, cfg)) // -- pulumi up -- res, err := s.Up(ctx, optup.UserAgent(agent), optup.Refresh()) require.NoError(t, err, "up failed") assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) assert.Greater(t, res.Summary.Version, 0) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg := collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh), optpreview.UserAgent(agent), optpreview.Refresh()) require.NoError(t, err, "preview failed") wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 2, steps) // -- pulumi refresh --preview-only -- pref, err := s.PreviewRefresh(ctx, optrefresh.UserAgent(agent)) assert.NoError(t, err, "preview-only refresh failed") assert.Equal(t, 1, pref.ChangeSummary[apitype.OpSame]) // -- pulumi refresh -- ref, err := s.Refresh(ctx, optrefresh.UserAgent(agent)) require.NoError(t, err, "refresh failed") assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) // -- pulumi destroy --preview-only -- pdRes, err := s.PreviewDestroy(ctx, optdestroy.UserAgent(agent), optdestroy.Refresh()) assert.NoError(t, err, "preview-only destroy failed") assert.Equal(t, map[apitype.OpType]int{apitype.OpDelete: 1}, pdRes.ChangeSummary) // -- pulumi destroy -- dRes, err := s.Destroy(ctx, optdestroy.UserAgent(agent), optdestroy.Refresh()) require.NoError(t, err, "destroy failed") assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestStackLifecycleInlineProgramRemoveWithoutDestroy(t *testing.T) { t.Parallel() // Arrange. ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { _, err := NewMyResource(ctx, "res") if err != nil { return err } return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } _, err = s.Up(ctx, optup.UserAgent(agent), optup.Refresh()) assert.NoError(t, err, "up failed") // Act. err = s.Workspace().RemoveStack(ctx, s.Name()) // Assert. assert.ErrorContains(t, err, "still has resources; removal rejected") } func TestStackLifecycleInlineProgramDestroyWithRemove(t *testing.T) { t.Parallel() // Arrange. ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { _, err := NewMyResource(ctx, "res") if err != nil { return err } return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } _, err = s.Up(ctx, optup.UserAgent(agent), optup.Refresh()) assert.NoError(t, err, "up failed") // Act. _, err = s.Destroy(ctx, optdestroy.Remove()) assert.NoError(t, err, "destroy failed") err = s.Workspace().SelectStack(ctx, s.Name()) // Assert. assert.ErrorContains(t, err, "no stack named") } // If not run with "-race", this test has little value over the prior test. func TestUpsertStackInlineSourceParallel(t *testing.T) { t.Parallel() for i := 0; i < 4; i++ { // Verify that shared context doesn't affect result ctx := context.Background() t.Run(strconv.Itoa(i), func(t *testing.T) { t.Parallel() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize or select s, err := UpsertStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { c := config.New(ctx, "") ctx.Export("exp_static", pulumi.String("foo")) ctx.Export("exp_cfg", pulumi.String(c.Get("bar"))) ctx.Export("exp_secret", c.GetSecret("buzz")) return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } t.Cleanup(func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }) err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg := collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 1, steps) // -- pulumi refresh -- ref, err := s.Refresh(ctx) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) }) } } func TestNestedStackFails(t *testing.T) { t.Parallel() testCtx := context.Background() sName := ptesting.RandomStackName() parentstackName := FullyQualifiedStackName(pulumiOrg, "parent", sName) nestedstackName := FullyQualifiedStackName(pulumiOrg, "nested", sName) nestedStack, err := NewStackInlineSource(testCtx, nestedstackName, "nested", func(ctx *pulumi.Context) error { ctx.Export("exp_static", pulumi.String("foo")) return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } // initialize s, err := NewStackInlineSource(testCtx, parentstackName, "parent", func(ctx *pulumi.Context) error { _, err := nestedStack.Up(testCtx) return err }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(testCtx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") err = nestedStack.Workspace().RemoveStack(testCtx, nestedStack.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() result, err := s.Up(testCtx) t.Log(result) assert.ErrorContains(t, err, "nested stack operations are not supported") // -- pulumi destroy -- dRes, err := s.Destroy(testCtx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) dRes, err = nestedStack.Destroy(testCtx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestErrorProgressStreams(t *testing.T) { t.Parallel() ctx := context.Background() pName := "inline_error_progress_streams" sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) logLevel := uint(4) debugOptions := debug.LoggingOptions{ LogToStdErr: true, LogLevel: &logLevel, } // initialize s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err := s.Workspace().RemoveStack(ctx, s.Name(), optremove.Force()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() // -- pulumi up -- var upErr bytes.Buffer upRes, err := s.Up(ctx, optup.ErrorProgressStreams(&upErr), optup.DebugLogging(debugOptions)) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, upErr.String(), upRes.StdErr, "expected stderr writers to contain same contents") assert.NotEmpty(t, upRes.StdErr) // -- pulumi refresh -- var refErr bytes.Buffer refRes, err := s.Refresh(ctx, optrefresh.ErrorProgressStreams(&refErr), optrefresh.DebugLogging(debugOptions)) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, refErr.String(), refRes.StdErr, "expected stderr writers to contain same contents") assert.NotEmpty(t, refRes.StdErr) // -- pulumi destroy -- var desErr bytes.Buffer desRes, err := s.Destroy(ctx, optdestroy.ErrorProgressStreams(&desErr), optdestroy.DebugLogging(debugOptions)) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, desErr.String(), desRes.StdErr, "expected stderr writers to contain same contents") assert.NotEmpty(t, desRes.StdErr) } func TestProgressStreams(t *testing.T) { t.Parallel() ctx := context.Background() pName := "inline_progress_streams" sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { c := config.New(ctx, "") ctx.Export("exp_static", pulumi.String("foo")) ctx.Export("exp_cfg", pulumi.String(c.Get("bar"))) ctx.Export("exp_secret", c.GetSecret("buzz")) return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // -- pulumi up -- var upOut bytes.Buffer res, err := s.Up(ctx, optup.ProgressStreams(&upOut)) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, upOut.String(), res.StdOut, "expected stdout writers to contain same contents") // -- pulumi refresh -- var refOut bytes.Buffer ref, err := s.Refresh(ctx, optrefresh.ProgressStreams(&refOut)) if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, refOut.String(), ref.StdOut, "expected stdout writers to contain same contents") // -- pulumi destroy -- var desOut bytes.Buffer dRes, err := s.Destroy(ctx, optdestroy.ProgressStreams(&desOut)) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, desOut.String(), dRes.StdOut, "expected stdout writers to contain same contents") } func TestImportExportStack(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { c := config.New(ctx, "") ctx.Export("exp_static", pulumi.String("foo")) ctx.Export("exp_cfg", pulumi.String(c.Get("bar"))) ctx.Export("exp_secret", c.GetSecret("buzz")) return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // -- pulumi up -- _, err = s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } // -- pulumi stack export -- state, err := s.Export(ctx) if err != nil { t.Errorf("export failed, err: %v", err) t.FailNow() } // -- pulumi stack import -- err = s.Import(ctx, state) if err != nil { t.Errorf("import failed, err: %v", err) t.FailNow() } // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestConfigFlagLike(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) // initialize pDir := filepath.Join(".", "test", "testproj") s, err := NewStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } err = s.SetConfig(ctx, "key", ConfigValue{"-value", false}) if err != nil { t.Error(err) } err = s.SetConfig(ctx, "secret-key", ConfigValue{"-value", true}) if err != nil { t.Error(err) } cm, err := s.GetAllConfig(ctx) if err != nil { t.Error(err) } assert.Equalf(t, "-value", cm["testproj:key"].Value, "wrong key") assert.Equalf(t, "-value", cm["testproj:secret-key"].Value, "wrong secret-key") assert.Equalf(t, false, cm["testproj:key"].Secret, "key should not be secret") assert.Equalf(t, true, cm["testproj:secret-key"].Secret, "secret-key should be secret") err = s.Workspace().RemoveStack(ctx, stackName) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") } func TestConfigWithOptions(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) // initialize pDir := filepath.Join(".", "test", "testproj") s, err := NewStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { err = s.Workspace().RemoveStack(ctx, stackName) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() // test backward compatibility err = s.SetConfigWithOptions(ctx, "key1", ConfigValue{"value1", false}, nil) if err != nil { t.Error(err) } // test new flag without subPath err = s.SetConfigWithOptions(ctx, "key2", ConfigValue{"value2", false}, &ConfigOptions{Path: false}) if err != nil { t.Error(err) } // test new flag with subPath err = s.SetConfigWithOptions(ctx, "key3.subKey1", ConfigValue{"value3", false}, &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test old method and key as secret err = s.SetConfigWithOptions(ctx, "key4", ConfigValue{"value4", true}, nil) if err != nil { t.Error(err) } // test subPath and key as secret err = s.SetConfigWithOptions(ctx, "key5.subKey1", ConfigValue{"value5", true}, &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test string with dots err = s.SetConfigWithOptions(ctx, "key6.subKey1", ConfigValue{"value6", true}, nil) if err != nil { t.Error(err) } // test string with dots err = s.SetConfigWithOptions(ctx, "key7.subKey1", ConfigValue{"value7", true}, &ConfigOptions{Path: false}) if err != nil { t.Error(err) } // test subPath err = s.SetConfigWithOptions(ctx, "key7.subKey2", ConfigValue{"value8", false}, &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test subPath err = s.SetConfigWithOptions(ctx, "key7.subKey3", ConfigValue{"value9", false}, &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test backward compatibility cv1, err := s.GetConfigWithOptions(ctx, "key1", nil) if err != nil { t.Error(err) } // test new flag without subPath cv2, err := s.GetConfigWithOptions(ctx, "key2", &ConfigOptions{Path: false}) if err != nil { t.Error(err) } // test new flag with subPath cv3, err := s.GetConfigWithOptions(ctx, "key3.subKey1", &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test old method and key as secret cv4, err := s.GetConfigWithOptions(ctx, "key4", nil) if err != nil { t.Error(err) } // test subPath and key as secret cv5, err := s.GetConfigWithOptions(ctx, "key5.subKey1", &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test string with dots cv6, err := s.GetConfigWithOptions(ctx, "key6.subKey1", nil) if err != nil { t.Error(err) } // test string with dots cv7, err := s.GetConfigWithOptions(ctx, "key7.subKey1", &ConfigOptions{Path: false}) if err != nil { t.Error(err) } // test string with dots cv8, err := s.GetConfigWithOptions(ctx, "key7.subKey2", &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test string with dots cv9, err := s.GetConfigWithOptions(ctx, "key7.subKey3", &ConfigOptions{Path: true}) if err != nil { t.Error(err) } assert.Equalf(t, "value1", cv1.Value, "wrong key") assert.Equalf(t, false, cv1.Secret, "key should not be secret") assert.Equalf(t, "value2", cv2.Value, "wrong key") assert.Equalf(t, false, cv2.Secret, "key should not be secret") assert.Equalf(t, "value3", cv3.Value, "wrong key") assert.Equalf(t, false, cv3.Secret, "sub-key should not be secret") assert.Equalf(t, "value4", cv4.Value, "wrong key") assert.Equalf(t, true, cv4.Secret, "key should be secret") assert.Equalf(t, "value5", cv5.Value, "wrong key") assert.Equalf(t, true, cv5.Secret, "key should be secret") assert.Equalf(t, "value6", cv6.Value, "wrong key") assert.Equalf(t, true, cv6.Secret, "key should be secret") assert.Equalf(t, "value7", cv7.Value, "wrong key") assert.Equalf(t, true, cv7.Secret, "key should be secret") assert.Equalf(t, "value8", cv8.Value, "wrong key") assert.Equalf(t, false, cv8.Secret, "key should be secret") assert.Equalf(t, "value9", cv9.Value, "wrong key") assert.Equalf(t, false, cv9.Secret, "key should be secret") err = s.RemoveConfigWithOptions(ctx, "key1", nil) if err != nil { t.Error(err) } err = s.RemoveConfigWithOptions(ctx, "key2", nil) if err != nil { t.Error(err) } err = s.RemoveConfigWithOptions(ctx, "key3", &ConfigOptions{Path: false}) if err != nil { t.Error(err) } err = s.RemoveConfigWithOptions(ctx, "key4", &ConfigOptions{Path: false}) if err != nil { t.Error(err) } err = s.RemoveConfigWithOptions(ctx, "key5", nil) if err != nil { t.Error(err) } err = s.RemoveConfigWithOptions(ctx, "key6.subKey1", &ConfigOptions{Path: false}) if err != nil { t.Error(err) } err = s.RemoveConfigWithOptions(ctx, "key7.subKey1", nil) if err != nil { t.Error(err) } cfg, err := s.GetAllConfig(ctx) if err != nil { t.Error(err) } assert.Equalf(t, "{\"subKey2\":\"value8\",\"subKey3\":\"value9\"}", cfg["testproj:key7"].Value, "subKey2 and subKey3 have been removed") } func TestConfigAllWithOptions(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) // initialize pDir := filepath.Join(".", "test", "testproj") s, err := NewStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { err = s.Workspace().RemoveStack(ctx, stackName) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfigWithOptions(ctx, ConfigMap{ "key1": ConfigValue{ Value: "value1", Secret: false, }, "key2": ConfigValue{ Value: "value2", Secret: true, }, "key3.subKey1": ConfigValue{ Value: "value3", Secret: false, }, "key3.subKey2": ConfigValue{ Value: "value4", Secret: false, }, "key3.subKey3": ConfigValue{ Value: "value5", Secret: false, }, "key4.subKey1": ConfigValue{ Value: "value6", Secret: true, }, }, &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test the SetAllConfigWithOptions configured the first item cv1, err := s.GetConfigWithOptions(ctx, "key1", nil) if err != nil { t.Error(err) } // test the SetAllConfigWithOptions configured the second item cv2, err := s.GetConfigWithOptions(ctx, "key2", nil) if err != nil { t.Error(err) } // test the SetAllConfigWithOptions configured the third item cv3, err := s.GetConfigWithOptions(ctx, "key3.subKey1", &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test the SetAllConfigWithOptions configured the third item cv4, err := s.GetConfigWithOptions(ctx, "key3.subKey2", &ConfigOptions{Path: true}) if err != nil { t.Error(err) } // test the SetAllConfigWithOptions configured the fourth item cv5, err := s.GetConfigWithOptions(ctx, "key4.subKey1", &ConfigOptions{Path: true}) if err != nil { t.Error(err) } assert.Equalf(t, "value1", cv1.Value, "wrong key") assert.Equalf(t, false, cv1.Secret, "key should not be secret") assert.Equalf(t, "value2", cv2.Value, "wrong key") assert.Equalf(t, true, cv2.Secret, "key should be secret") assert.Equalf(t, "value3", cv3.Value, "wrong key") assert.Equalf(t, false, cv3.Secret, "key should not be secret") assert.Equalf(t, "value4", cv4.Value, "wrong key") assert.Equalf(t, false, cv4.Secret, "key should not be secret") assert.Equalf(t, "value6", cv5.Value, "wrong key") assert.Equalf(t, true, cv5.Secret, "key should be secret") err = s.RemoveAllConfigWithOptions(ctx, []string{"key1", "key2", "key3.subKey1", "key3.subKey2", "key4"}, &ConfigOptions{Path: true}) if err != nil { t.Error(err) } cfg, err := s.GetAllConfig(ctx) if err != nil { t.Error(err) } assert.Equalf(t, "{\"subKey3\":\"value5\"}", cfg["testproj:key3"].Value, "key subKey3 has been removed") } // This test requires the existence of a Pulumi.dev.yaml file because we are reading the nested // config from the file. This means we can't remove the stack at the end of the test. // We should also not include secrets in this config, because the secret encryption is only valid within // the context of a stack and org, and running this test in different orgs will fail if there are secrets. func TestNestedConfig(t *testing.T) { t.Parallel() ctx := context.Background() stackName := FullyQualifiedStackName(pulumiOrg, "nested_config", "dev") // initialize pDir := filepath.Join(".", "test", "nested_config") s, err := UpsertStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } // Also retrieve the stack settings directly from the yaml file and // make sure the config agrees with the config loaded by Pulumi. stackSettings, err := s.Workspace().StackSettings(ctx, stackName) require.NoError(t, err) confKeys := map[string]bool{} for k := range stackSettings.Config { confKeys[k.String()] = true } allConfig, err := s.GetAllConfig(ctx) if err != nil { t.Errorf("failed to get config, err: %v", err) t.FailNow() } allConfKeys := map[string]bool{} for k := range allConfig { allConfKeys[k] = true } assert.Equal(t, confKeys, allConfKeys) assert.NotEmpty(t, confKeys) outerVal, ok := allConfig["nested_config:outer"] assert.True(t, ok) assert.False(t, outerVal.Secret) assert.JSONEq(t, "{\"inner\":\"my_value\", \"other\": \"something_else\"}", outerVal.Value) listVal, ok := allConfig["nested_config:myList"] assert.True(t, ok) assert.False(t, listVal.Secret) assert.JSONEq(t, "[\"one\",\"two\",\"three\"]", listVal.Value) outer, err := s.GetConfig(ctx, "outer") if err != nil { t.Errorf("failed to get config, err: %v", err) t.FailNow() } assert.False(t, outer.Secret) assert.JSONEq(t, "{\"inner\":\"my_value\", \"other\": \"something_else\"}", outer.Value) list, err := s.GetConfig(ctx, "myList") if err != nil { t.Errorf("failed to get config, err: %v", err) t.FailNow() } assert.False(t, list.Secret) assert.JSONEq(t, "[\"one\",\"two\",\"three\"]", list.Value) } func TestEnvFunctions(t *testing.T) { if getTestOrg() != pulumiTestOrg { t.Skip("Skipping test because the required environments are in the moolumi org.") } t.Parallel() ctx := context.Background() stackName := FullyQualifiedStackName(pulumiOrg, pName, ptesting.RandomStackName()) pDir := filepath.Join(".", "test", pName) s, err := UpsertStackLocalSource(ctx, stackName, pDir) require.NoError(t, err, "failed to initialize stack, err: %v", err) defer func() { err = s.Workspace().RemoveStack(ctx, stackName) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() // Errors when trying to add a non-existent env assert.Error(t, s.AddEnvironments(ctx, "non-existent-env")) // No error when adding an existing env require.NoError(t, s.AddEnvironments(ctx, "automation-api-test-env", "automation-api-test-env-2"), "adding environments failed, err: %v", err) envs, err := s.ListEnvironments(ctx) require.NoError(t, err, "listing environments failed, err: %v", err) assert.Equal(t, []string{"automation-api-test-env", "automation-api-test-env-2"}, envs) // Check that we can access config from the envs cfg, err := s.GetAllConfig(ctx) require.NoError(t, err, "getting config failed, err: %v", err) assert.Equal(t, "test_value", cfg["testproj:new_key"].Value) assert.Equal(t, "business", cfg["testproj:also"].Value) err = s.RemoveEnvironment(ctx, "automation-api-test-env") envs, err = s.ListEnvironments(ctx) require.NoError(t, err, "listing environments failed, err: %v", err) assert.Equal(t, []string{"automation-api-test-env-2"}, envs) require.NoError(t, err, "removing environment failed, err: %v", err) _, err = s.GetConfig(ctx, "new_key") assert.Error(t, err) v, err := s.GetConfig(ctx, "also") assert.Equal(t, "business", v.Value) err = s.RemoveEnvironment(ctx, "automation-api-test-env-2") envs, err = s.ListEnvironments(ctx) require.NoError(t, err, "listing environments failed, err: %v", err) assert.Len(t, envs, 0) require.NoError(t, err, "removing environment failed, err: %v", err) _, err = s.GetConfig(ctx, "also") assert.Error(t, err) require.NoError(t, s.AddEnvironments(ctx, "secrets-test-env-DO-NOT-DELETE"), "adding environments failed, err: %v", err) envs, err = s.ListEnvironments(ctx) require.NoError(t, err, "listing environments failed, err: %v", err) assert.Contains(t, envs, "secrets-test-env-DO-NOT-DELETE") cfg, err = s.GetAllConfig(ctx) require.NoError(t, err, "getting config failed, err: %v", err) assert.Equal(t, "this_is_my_secret", cfg["testproj:test_secret"].Value) v, err = s.GetConfig(ctx, "test_secret") require.NoError(t, err, "getting config failed, err: %v", err) assert.Equal(t, "this_is_my_secret", v.Value) } func TestTagFunctions(t *testing.T) { t.Parallel() ctx := context.Background() stackName := FullyQualifiedStackName(pulumiOrg, pName, ptesting.RandomStackName()) pDir := filepath.Join(".", "test", "testproj") s, err := UpsertStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } ws := s.Workspace() // -- lists tag values -- tags, err := ws.ListTags(ctx, stackName) if err != nil { t.Errorf("failed to list tags, err: %v", err) t.FailNow() } assert.Equal(t, pName, tags["pulumi:project"]) // -- sets tag values -- err = ws.SetTag(ctx, stackName, "foo", "bar") if err != nil { t.Errorf("set tag failed, err: %v", err) t.FailNow() } // -- gets a single tag value -- tag, err := ws.GetTag(ctx, stackName, "foo") if err != nil { t.Errorf("get tag failed, err: %v", err) t.FailNow() } assert.Equal(t, "bar", tag) // -- removes tag value -- err = ws.RemoveTag(ctx, stackName, "foo") if err != nil { t.Errorf("remove tag failed, err: %v", err) t.FailNow() } tags, _ = ws.ListTags(ctx, stackName) assert.NotContains(t, tags, "foo", "failed to remove tag") err = s.Workspace().RemoveStack(ctx, stackName) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") } //nolint:paralleltest // mutates environment variables func TestStructuredOutput(t *testing.T) { ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize pDir := filepath.Join(".", "test", "testproj") s, err := UpsertStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } // Set environment variables scoped to the workspace. envvars := map[string]string{ "foo": "bar", "barfoo": "foobar", } err = s.Workspace().SetEnvVars(envvars) assert.NoError(t, err, "failed to set environment values") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment values after setting many") s.Workspace().SetEnvVar("bar", "buzz") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment value after setting") s.Workspace().UnsetEnvVar("bar") envvars = s.Workspace().GetEnvVars() assert.NotNil(t, envvars, "failed to get environment values after unsetting.") // -- pulumi up -- var upEvents []events.EngineEvent upCh := make(chan events.EngineEvent) wg := collectEvents(upCh, &upEvents) res, err := s.Up(ctx, optup.EventStreams(upCh)) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs") assert.Equal(t, "foo", res.Outputs["exp_static"].Value) assert.False(t, res.Outputs["exp_static"].Secret) assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value) assert.False(t, res.Outputs["exp_cfg"].Secret) assert.Equal(t, "secret", res.Outputs["exp_secret"].Value) assert.True(t, res.Outputs["exp_secret"].Secret) assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) assert.True(t, containsSummary(upEvents)) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg = collectEvents(prevCh, &previewEvents) prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame]) steps := countSteps(previewEvents) assert.Equal(t, 1, steps) assert.True(t, containsSummary(previewEvents)) // -- pulumi refresh -- var refreshEvents []events.EngineEvent refCh := make(chan events.EngineEvent) wg = collectEvents(refCh, &refreshEvents) ref, err := s.Refresh(ctx, optrefresh.EventStreams(refCh)) wg.Wait() if err != nil { t.Errorf("refresh failed, err: %v", err) t.FailNow() } assert.Equal(t, "refresh", ref.Summary.Kind) assert.Equal(t, "succeeded", ref.Summary.Result) assert.True(t, containsSummary(refreshEvents)) // -- pulumi destroy -- var destroyEvents []events.EngineEvent desCh := make(chan events.EngineEvent) wg = collectEvents(desCh, &destroyEvents) dRes, err := s.Destroy(ctx, optdestroy.EventStreams(desCh)) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } wg.Wait() assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) assert.True(t, containsSummary(destroyEvents)) } func TestStackImportResources(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, "import", sName) pDir := filepath.Join(".", "test", "import") stack, err := UpsertStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } randomPluginVersion := "4.16.3" err = stack.Workspace().InstallPlugin(ctx, "random", randomPluginVersion) assert.NoError(t, err, "failed to install plugin") resourcesToImport := []*optimport.ImportResource{ { Type: "random:index/randomPassword:RandomPassword", ID: "supersecret", Name: "randomPassword", }, } importResult, err := stack.ImportResources(ctx, optimport.Resources(resourcesToImport), optimport.Protect(false)) assert.NoError(t, err, "failed to import resources") assert.Equal(t, "succeeded", importResult.Summary.Result) expectedGeneratedCode, err := os.ReadFile(filepath.Join(pDir, "expected_generated_code.yaml")) assert.NoError(t, err, "failed to read expected generated code") normalize := func(s string) string { return strings.ReplaceAll(s, "\r\n", "\n") } assert.Equal(t, normalize(string(expectedGeneratedCode)), normalize(importResult.GeneratedCode)) _, err = stack.Destroy(ctx) assert.NoError(t, err, "failed to destroy stack") } func TestSupportsStackOutputs(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "bar": ConfigValue{ Value: "abc", }, "buzz": ConfigValue{ Value: "secret", Secret: true, }, } // initialize s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { c := config.New(ctx, "") nestedObj := pulumi.Map{ "not_a_secret": pulumi.String("foo"), "is_a_secret": pulumi.ToSecret("iamsecret"), } ctx.Export("exp_static", pulumi.String("foo")) ctx.Export("exp_cfg", pulumi.String(c.Get("bar"))) ctx.Export("exp_secret", c.GetSecret("buzz")) ctx.Export("nested_obj", nestedObj) return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } assertOutputs := func(t *testing.T, outputs OutputMap) { assert.Equal(t, 4, len(outputs), "expected four outputs") assert.Equal(t, "foo", outputs["exp_static"].Value) assert.False(t, outputs["exp_static"].Secret) assert.Equal(t, "abc", outputs["exp_cfg"].Value) assert.False(t, outputs["exp_cfg"].Secret) assert.Equal(t, "secret", outputs["exp_secret"].Value) assert.True(t, outputs["exp_secret"].Secret) assert.Equal(t, map[string]interface{}{ "is_a_secret": "iamsecret", "not_a_secret": "foo", }, outputs["nested_obj"].Value) assert.True(t, outputs["nested_obj"].Secret) } initialOutputs, err := s.Outputs(ctx) if err != nil { t.Errorf("failed to get initial outputs, err: %v", err) t.FailNow() } assert.Equal(t, 0, len(initialOutputs)) // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) assert.Greater(t, res.Summary.Version, 0) assertOutputs(t, res.Outputs) outputsAfterUp, err := s.Outputs(ctx) if err != nil { t.Errorf("failed to get outputs after up, err: %v", err) t.FailNow() } assertOutputs(t, outputsAfterUp) // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) outputsAfterDestroy, err := s.Outputs(ctx) if err != nil { t.Errorf("failed to get outputs after destroy, err: %v", err) t.FailNow() } assert.Equal(t, 0, len(outputsAfterDestroy)) } func TestShallowClone(t *testing.T) { t.Parallel() ctx := context.Background() tests := []struct { name string repo GitRepo }{ { name: "no ref provided", repo: GitRepo{}, }, { name: "branch provided", repo: GitRepo{Branch: "master"}, }, { name: "commit provided", repo: GitRepo{CommitHash: "028e8c5b3c6b19c3ce3b78ed508618e9cd94df1c"}, }, { name: "branch and commit provided", repo: GitRepo{Branch: "master", CommitHash: "028e8c5b3c6b19c3ce3b78ed508618e9cd94df1c"}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() repo := GitRepo{ URL: "https://github.com/pulumi/test-repo.git", ProjectPath: "goproj", Shallow: true, Branch: tt.repo.Branch, CommitHash: tt.repo.CommitHash, } ws, err := NewLocalWorkspace(ctx, Repo(repo)) require.NoError(t, err) r, err := git.PlainOpenWithOptions(ws.WorkDir(), &git.PlainOpenOptions{DetectDotGit: true}) require.NoError(t, err) hashes, err := r.Storer.Shallow() require.NoError(t, err) assert.Equal(t, 1, len(hashes)) }) } } func TestPulumiVersion(t *testing.T) { t.Parallel() ctx := context.Background() ws, err := NewLocalWorkspace(ctx) if err != nil { t.Errorf("failed to create workspace, err: %v", err) t.FailNow() } version := ws.PulumiVersion() assert.NotEqual(t, "v0.0.0", version) assert.Regexp(t, `(\d+\.)(\d+\.)(\d+)(-.*)?`, version) } func TestPulumiCommand(t *testing.T) { t.Parallel() ctx := context.Background() pulumiCommand, err := NewPulumiCommand(nil) require.NoError(t, err, "failed to create pulumi command: %s", err) ws, err := NewLocalWorkspace(ctx, Pulumi(pulumiCommand)) require.NoError(t, err, "failed to create workspace: %s", err) version := ws.PulumiVersion() assert.NotEqual(t, "v0.0.0", version) assert.Regexp(t, `(\d+\.)(\d+\.)(\d+)(-.*)?`, version) } func TestClIWithoutRemoteSupport(t *testing.T) { t.Parallel() // We inspect the output of `pulumi preview --help` to determine if the // CLI supports remote operations. Set the output to `some output` to // simulate a CLI version without remote support. m := mockPulumiCommand{stdout: "some output"} _, err := NewLocalWorkspace(context.Background(), Pulumi(&m), remote(true)) require.ErrorContains(t, err, "does not support remote operations") } func TestByPassesRemoteCheck(t *testing.T) { t.Parallel() // We inspect the output of `pulumi preview --help` to determine if the // CLI supports remote operations. Set the output to `some output` to // simulate a CLI version without remote support. m := mockPulumiCommand{stdout: "some output"} envVars := map[string]string{"PULUMI_AUTOMATION_API_SKIP_VERSION_CHECK": "true"} _, err := NewLocalWorkspace(context.Background(), Pulumi(&m), EnvVars(envVars), remote(true)) require.NoError(t, err) } func TestProjectSettingsRespected(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() pName := "correct_project" stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) badProjectName := "project_was_overwritten" stack, err := NewStackInlineSource(ctx, stackName, badProjectName, func(ctx *pulumi.Context) error { return nil }, WorkDir(filepath.Join(".", "test", pName))) defer func() { // -- pulumi stack rm -- err = stack.Workspace().RemoveStack(ctx, stack.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() assert.NoError(t, err) projectSettings, err := stack.workspace.ProjectSettings(ctx) assert.NoError(t, err) assert.Equal(t, projectSettings.Name, tokens.PackageName("correct_project")) assert.Equal(t, *projectSettings.Description, "This is a description") } func TestSaveStackSettings(t *testing.T) { t.Parallel() ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) opts := []LocalWorkspaceOption{ SecretsProvider("passphrase"), EnvVars(map[string]string{ "PULUMI_CONFIG_PASSPHRASE": "password", }), } // initialize s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { c := config.New(ctx, "") ctx.Export("exp_static", pulumi.String("foo")) ctx.Export("exp_cfg", pulumi.String(c.Get("bar"))) ctx.Export("exp_secret", c.GetSecret("buzz")) return nil }, opts...) require.NoError(t, err, "failed to initialize stack, err: %v", err) defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() // first load settings for created stack stackConfig, err := s.Workspace().StackSettings(ctx, stackName) require.NoError(t, err) // Set the config value and save it stackConfig.Config[resourceConfig.MustMakeKey(pName, "bar")] = resourceConfig.NewValue("baz") assert.NoError(t, s.Workspace().SaveStackSettings(ctx, stackName, stackConfig)) // -- pulumi up -- res, err := s.Up(ctx) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } assert.Equal(t, "update", res.Summary.Kind) assert.Equal(t, "succeeded", res.Summary.Result) assert.Equal(t, "baz", res.Outputs["exp_cfg"].Value) reloaded, err := s.workspace.StackSettings(ctx, stackName) assert.NoError(t, err) // Check each field because if we check the whole struct it picks up the 'raw' field which does differ. assert.Equal(t, stackConfig.SecretsProvider, reloaded.SecretsProvider) assert.Equal(t, stackConfig.EncryptedKey, reloaded.EncryptedKey) assert.Equal(t, stackConfig.EncryptionSalt, reloaded.EncryptionSalt) assert.Equal(t, stackConfig.Config, reloaded.Config) // -- pulumi destroy -- dRes, err := s.Destroy(ctx) if err != nil { t.Errorf("destroy failed, err: %v", err) t.FailNow() } assert.Equal(t, "destroy", dRes.Summary.Kind) assert.Equal(t, "succeeded", dRes.Summary.Result) } func TestConfigSecretWarnings(t *testing.T) { t.Parallel() // TODO[pulumi/pulumi#7127]: Re-enabled the warning. t.Skip("Temporarily skipping test until we've re-enabled the warning - pulumi/pulumi#7127") ctx := context.Background() sName := ptesting.RandomStackName() stackName := FullyQualifiedStackName(pulumiOrg, pName, sName) cfg := ConfigMap{ "plainstr1": ConfigValue{Value: "1"}, "plainstr2": ConfigValue{Value: "2"}, "plainstr3": ConfigValue{Value: "3"}, "plainstr4": ConfigValue{Value: "4"}, "plainstr5": ConfigValue{Value: "5"}, "plainstr6": ConfigValue{Value: "6"}, "plainstr7": ConfigValue{Value: "7"}, "plainstr8": ConfigValue{Value: "8"}, "plainstr9": ConfigValue{Value: "9"}, "plainstr10": ConfigValue{Value: "10"}, "plainstr11": ConfigValue{Value: "11"}, "plainstr12": ConfigValue{Value: "12"}, "plainbool1": ConfigValue{Value: "true"}, "plainbool2": ConfigValue{Value: "true"}, "plainbool3": ConfigValue{Value: "true"}, "plainbool4": ConfigValue{Value: "true"}, "plainbool5": ConfigValue{Value: "true"}, "plainbool6": ConfigValue{Value: "true"}, "plainbool7": ConfigValue{Value: "true"}, "plainbool8": ConfigValue{Value: "true"}, "plainbool9": ConfigValue{Value: "true"}, "plainbool10": ConfigValue{Value: "true"}, "plainbool11": ConfigValue{Value: "true"}, "plainbool12": ConfigValue{Value: "true"}, "plainint1": ConfigValue{Value: "1"}, "plainint2": ConfigValue{Value: "2"}, "plainint3": ConfigValue{Value: "3"}, "plainint4": ConfigValue{Value: "4"}, "plainint5": ConfigValue{Value: "5"}, "plainint6": ConfigValue{Value: "6"}, "plainint7": ConfigValue{Value: "7"}, "plainint8": ConfigValue{Value: "8"}, "plainint9": ConfigValue{Value: "9"}, "plainint10": ConfigValue{Value: "10"}, "plainint11": ConfigValue{Value: "11"}, "plainint12": ConfigValue{Value: "12"}, "plainfloat1": ConfigValue{Value: "1.1"}, "plainfloat2": ConfigValue{Value: "2.2"}, "plainfloat3": ConfigValue{Value: "3.3"}, "plainfloat4": ConfigValue{Value: "4.4"}, "plainfloat5": ConfigValue{Value: "5.5"}, "plainfloat6": ConfigValue{Value: "6.6"}, "plainfloat7": ConfigValue{Value: "7.7"}, "plainfloat8": ConfigValue{Value: "8.8"}, "plainfloat9": ConfigValue{Value: "9.9"}, "plainfloat10": ConfigValue{Value: "10.1"}, "plainfloat11": ConfigValue{Value: "11.11"}, "plainfloat12": ConfigValue{Value: "12.12"}, "plainobj1": ConfigValue{Value: "{}"}, "plainobj2": ConfigValue{Value: "{}"}, "plainobj3": ConfigValue{Value: "{}"}, "plainobj4": ConfigValue{Value: "{}"}, "plainobj5": ConfigValue{Value: "{}"}, "plainobj6": ConfigValue{Value: "{}"}, "plainobj7": ConfigValue{Value: "{}"}, "plainobj8": ConfigValue{Value: "{}"}, "plainobj9": ConfigValue{Value: "{}"}, "plainobj10": ConfigValue{Value: "{}"}, "plainobj11": ConfigValue{Value: "{}"}, "plainobj12": ConfigValue{Value: "{}"}, "str1": ConfigValue{Value: "1", Secret: true}, "str2": ConfigValue{Value: "2", Secret: true}, "str3": ConfigValue{Value: "3", Secret: true}, "str4": ConfigValue{Value: "4", Secret: true}, "str5": ConfigValue{Value: "5", Secret: true}, "str6": ConfigValue{Value: "6", Secret: true}, "str7": ConfigValue{Value: "7", Secret: true}, "str8": ConfigValue{Value: "8", Secret: true}, "str9": ConfigValue{Value: "9", Secret: true}, "str10": ConfigValue{Value: "10", Secret: true}, "str11": ConfigValue{Value: "11", Secret: true}, "str12": ConfigValue{Value: "12", Secret: true}, "bool1": ConfigValue{Value: "true", Secret: true}, "bool2": ConfigValue{Value: "true", Secret: true}, "bool3": ConfigValue{Value: "true", Secret: true}, "bool4": ConfigValue{Value: "true", Secret: true}, "bool5": ConfigValue{Value: "true", Secret: true}, "bool6": ConfigValue{Value: "true", Secret: true}, "bool7": ConfigValue{Value: "true", Secret: true}, "bool8": ConfigValue{Value: "true", Secret: true}, "bool9": ConfigValue{Value: "true", Secret: true}, "bool10": ConfigValue{Value: "true", Secret: true}, "bool11": ConfigValue{Value: "true", Secret: true}, "bool12": ConfigValue{Value: "true", Secret: true}, "int1": ConfigValue{Value: "1", Secret: true}, "int2": ConfigValue{Value: "2", Secret: true}, "int3": ConfigValue{Value: "3", Secret: true}, "int4": ConfigValue{Value: "4", Secret: true}, "int5": ConfigValue{Value: "5", Secret: true}, "int6": ConfigValue{Value: "6", Secret: true}, "int7": ConfigValue{Value: "7", Secret: true}, "int8": ConfigValue{Value: "8", Secret: true}, "int9": ConfigValue{Value: "9", Secret: true}, "int10": ConfigValue{Value: "10", Secret: true}, "int11": ConfigValue{Value: "11", Secret: true}, "int12": ConfigValue{Value: "12", Secret: true}, "float1": ConfigValue{Value: "1.1", Secret: true}, "float2": ConfigValue{Value: "2.2", Secret: true}, "float3": ConfigValue{Value: "3.3", Secret: true}, "float4": ConfigValue{Value: "4.4", Secret: true}, "float5": ConfigValue{Value: "5.5", Secret: true}, "float6": ConfigValue{Value: "6.6", Secret: true}, "float7": ConfigValue{Value: "7.7", Secret: true}, "float8": ConfigValue{Value: "8.8", Secret: true}, "float9": ConfigValue{Value: "9.9", Secret: true}, "float10": ConfigValue{Value: "10.1", Secret: true}, "float11": ConfigValue{Value: "11.11", Secret: true}, "float12": ConfigValue{Value: "12.12", Secret: true}, "obj1": ConfigValue{Value: "{}", Secret: true}, "obj2": ConfigValue{Value: "{}", Secret: true}, "obj3": ConfigValue{Value: "{}", Secret: true}, "obj4": ConfigValue{Value: "{}", Secret: true}, "obj5": ConfigValue{Value: "{}", Secret: true}, "obj6": ConfigValue{Value: "{}", Secret: true}, "obj7": ConfigValue{Value: "{}", Secret: true}, "obj8": ConfigValue{Value: "{}", Secret: true}, "obj9": ConfigValue{Value: "{}", Secret: true}, "obj10": ConfigValue{Value: "{}", Secret: true}, "obj11": ConfigValue{Value: "{}", Secret: true}, "obj12": ConfigValue{Value: "{}", Secret: true}, } // initialize //nolint:errcheck s, err := NewStackInlineSource(ctx, stackName, pName, func(ctx *pulumi.Context) error { c := config.New(ctx, "") config.Get(ctx, "plainstr1") config.Require(ctx, "plainstr2") config.Try(ctx, "plainstr3") config.GetSecret(ctx, "plainstr4") config.RequireSecret(ctx, "plainstr5") config.TrySecret(ctx, "plainstr6") c.Get("plainstr7") c.Require("plainstr8") c.Try("plainstr9") c.GetSecret("plainstr10") c.RequireSecret("plainstr11") c.TrySecret("plainstr12") config.GetBool(ctx, "plainbool1") config.RequireBool(ctx, "plainbool2") config.TryBool(ctx, "plainbool3") config.GetSecretBool(ctx, "plainbool4") config.RequireSecretBool(ctx, "plainbool5") config.TrySecretBool(ctx, "plainbool6") c.GetBool("plainbool7") c.RequireBool("plainbool8") c.TryBool("plainbool9") c.GetSecretBool("plainbool10") c.RequireSecretBool("plainbool11") c.TrySecretBool("plainbool12") config.GetInt(ctx, "plainint1") config.RequireInt(ctx, "plainint2") config.TryInt(ctx, "plainint3") config.GetSecretInt(ctx, "plainint4") config.RequireSecretInt(ctx, "plainint5") config.TrySecretInt(ctx, "plainint6") c.GetInt("plainint7") c.RequireInt("plainint8") c.TryInt("plainint9") c.GetSecretInt("plainint10") c.RequireSecretInt("plainint11") c.TrySecretInt("plainint12") config.GetFloat64(ctx, "plainfloat1") config.RequireFloat64(ctx, "plainfloat2") config.TryFloat64(ctx, "plainfloat3") config.GetSecretFloat64(ctx, "plainfloat4") config.RequireSecretFloat64(ctx, "plainfloat5") config.TrySecretFloat64(ctx, "plainfloat6") c.GetFloat64("plainfloat7") c.RequireFloat64("plainfloat8") c.TryFloat64("plainfloat9") c.GetSecretFloat64("plainfloat10") c.RequireSecretFloat64("plainfloat11") c.TrySecretFloat64("plainfloat12") var obj interface{} config.GetObject(ctx, "plainobjj1", &obj) config.RequireObject(ctx, "plainobj2", &obj) config.TryObject(ctx, "plainobj3", &obj) config.GetSecretObject(ctx, "plainobj4", &obj) config.RequireSecretObject(ctx, "plainobj5", &obj) config.TrySecretObject(ctx, "plainobj6", &obj) c.GetObject("plainobjj7", &obj) c.RequireObject("plainobj8", &obj) c.TryObject("plainobj9", &obj) c.GetSecretObject("plainobj10", &obj) c.RequireSecretObject("plainobj11", &obj) c.TrySecretObject("plainobj12", &obj) config.Get(ctx, "str1") config.Require(ctx, "str2") config.Try(ctx, "str3") config.GetSecret(ctx, "str4") config.RequireSecret(ctx, "str5") config.TrySecret(ctx, "str6") c.Get("str7") c.Require("str8") c.Try("str9") c.GetSecret("str10") c.RequireSecret("str11") c.TrySecret("str12") config.GetBool(ctx, "bool1") config.RequireBool(ctx, "bool2") config.TryBool(ctx, "bool3") config.GetSecretBool(ctx, "bool4") config.RequireSecretBool(ctx, "bool5") config.TrySecretBool(ctx, "bool6") c.GetBool("bool7") c.RequireBool("bool8") c.TryBool("bool9") c.GetSecretBool("bool10") c.RequireSecretBool("bool11") c.TrySecretBool("bool12") config.GetInt(ctx, "int1") config.RequireInt(ctx, "int2") config.TryInt(ctx, "int3") config.GetSecretInt(ctx, "int4") config.RequireSecretInt(ctx, "int5") config.TrySecretInt(ctx, "int6") c.GetInt("int7") c.RequireInt("int8") c.TryInt("int9") c.GetSecretInt("int10") c.RequireSecretInt("int11") c.TrySecretInt("int12") config.GetFloat64(ctx, "float1") config.RequireFloat64(ctx, "float2") config.TryFloat64(ctx, "float3") config.GetSecretFloat64(ctx, "float4") config.RequireSecretFloat64(ctx, "float5") config.TrySecretFloat64(ctx, "float6") c.GetFloat64("float7") c.RequireFloat64("float8") c.TryFloat64("float9") c.GetSecretFloat64("float10") c.RequireSecretFloat64("float11") c.TrySecretFloat64("float12") config.GetObject(ctx, "obj1", &obj) config.RequireObject(ctx, "obj2", &obj) config.TryObject(ctx, "obj3", &obj) config.GetSecretObject(ctx, "obj4", &obj) config.RequireSecretObject(ctx, "obj5", &obj) config.TrySecretObject(ctx, "obj6", &obj) c.GetObject("obj7", &obj) c.RequireObject("obj8", &obj) c.TryObject("obj9", &obj) c.GetSecretObject("obj10", &obj) c.RequireSecretObject("obj11", &obj) c.TrySecretObject("obj12", &obj) return nil }) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(t, err, "failed to remove stack. Resources have leaked.") }() err = s.SetAllConfig(ctx, cfg) if err != nil { t.Errorf("failed to set config, err: %v", err) t.FailNow() } validate := func(engineEvents []events.EngineEvent) { expectedWarnings := []string{ "Configuration 'testproj:str1' value is a secret; use `GetSecret` instead of `Get`", "Configuration 'testproj:str2' value is a secret; use `RequireSecret` instead of `Require`", "Configuration 'testproj:str3' value is a secret; use `TrySecret` instead of `Try`", "Configuration 'testproj:str7' value is a secret; use `GetSecret` instead of `Get`", "Configuration 'testproj:str8' value is a secret; use `RequireSecret` instead of `Require`", "Configuration 'testproj:str9' value is a secret; use `TrySecret` instead of `Try`", "Configuration 'testproj:bool1' value is a secret; use `GetSecretBool` instead of `GetBool`", "Configuration 'testproj:bool2' value is a secret; use `RequireSecretBool` instead of `RequireBool`", "Configuration 'testproj:bool3' value is a secret; use `TrySecretBool` instead of `TryBool`", "Configuration 'testproj:bool7' value is a secret; use `GetSecretBool` instead of `GetBool`", "Configuration 'testproj:bool8' value is a secret; use `RequireSecretBool` instead of `RequireBool`", "Configuration 'testproj:bool9' value is a secret; use `TrySecretBool` instead of `TryBool`", "Configuration 'testproj:int1' value is a secret; use `GetSecretInt` instead of `GetInt`", "Configuration 'testproj:int2' value is a secret; use `RequireSecretInt` instead of `RequireInt`", "Configuration 'testproj:int3' value is a secret; use `TrySecretInt` instead of `TryInt`", "Configuration 'testproj:int7' value is a secret; use `GetSecretInt` instead of `GetInt`", "Configuration 'testproj:int8' value is a secret; use `RequireSecretInt` instead of `RequireInt`", "Configuration 'testproj:int9' value is a secret; use `TrySecretInt` instead of `TryInt`", "Configuration 'testproj:float1' value is a secret; use `GetSecretFloat64` instead of `GetFloat64`", "Configuration 'testproj:float2' value is a secret; use `RequireSecretFloat64` instead of `RequireFloat64`", "Configuration 'testproj:float3' value is a secret; use `TrySecretFloat64` instead of `TryFloat64`", "Configuration 'testproj:float7' value is a secret; use `GetSecretFloat64` instead of `GetFloat64`", "Configuration 'testproj:float8' value is a secret; use `RequireSecretFloat64` instead of `RequireFloat64`", "Configuration 'testproj:float9' value is a secret; use `TrySecretFloat64` instead of `TryFloat64`", "Configuration 'testproj:obj1' value is a secret; use `GetSecretObject` instead of `GetObject`", "Configuration 'testproj:obj2' value is a secret; use `RequireSecretObject` instead of `RequireObject`", "Configuration 'testproj:obj3' value is a secret; use `TrySecretObject` instead of `TryObject`", "Configuration 'testproj:obj7' value is a secret; use `GetSecretObject` instead of `GetObject`", "Configuration 'testproj:obj8' value is a secret; use `RequireSecretObject` instead of `RequireObject`", "Configuration 'testproj:obj9' value is a secret; use `TrySecretObject` instead of `TryObject`", } for _, warning := range expectedWarnings { var found bool for _, event := range engineEvents { if event.DiagnosticEvent != nil && event.DiagnosticEvent.Severity == "warning" && strings.Contains(event.DiagnosticEvent.Message, warning) { found = true break } } assert.True(t, found, "expected warning %q", warning) } // These keys should not be in any warning messages. unexpectedWarnings := []string{ "plainstr1", "plainstr2", "plainstr3", "plainstr4", "plainstr5", "plainstr6", "plainstr7", "plainstr8", "plainstr9", "plainstr10", "plainstr11", "plainstr12", "plainbool1", "plainbool2", "plainbool3", "plainbool4", "plainbool5", "plainbool6", "plainbool7", "plainbool8", "plainbool9", "plainbool10", "plainbool11", "plainbool12", "plainint1", "plainint2", "plainint3", "plainint4", "plainint5", "plainint6", "plainint7", "plainint8", "plainint9", "plainint10", "plainint11", "plainint12", "plainfloat1", "plainfloat2", "plainfloat3", "plainfloat4", "plainfloat5", "plainfloat6", "plainfloat7", "plainfloat8", "plainfloat9", "plainfloat10", "plainfloat11", "plainfloat12", "plainobj1", "plainobj2", "plainobj3", "plainobj4", "plainobj5", "plainobj6", "plainobj7", "plainobj8", "plainobj9", "plainobj10", "plainobj11", "plainobj12", } for _, warning := range unexpectedWarnings { for _, event := range engineEvents { if event.DiagnosticEvent != nil { assert.NotContains(t, event.DiagnosticEvent.Message, warning) } } } } // -- pulumi up -- var upEvents []events.EngineEvent upCh := make(chan events.EngineEvent) wg := collectEvents(upCh, &upEvents) _, err = s.Up(ctx, optup.EventStreams(upCh)) if err != nil { t.Errorf("up failed, err: %v", err) t.FailNow() } wg.Wait() validate(upEvents) // -- pulumi preview -- var previewEvents []events.EngineEvent prevCh := make(chan events.EngineEvent) wg = collectEvents(prevCh, &previewEvents) _, err = s.Preview(ctx, optpreview.EventStreams(prevCh)) if err != nil { t.Errorf("preview failed, err: %v", err) t.FailNow() } wg.Wait() validate(previewEvents) } func TestWhoAmIDetailed(t *testing.T) { t.Parallel() ctx := context.Background() stackName := FullyQualifiedStackName(pulumiOrg, pName, ptesting.RandomStackName()) // initialize pDir := filepath.Join(".", "test", "testproj") s, err := UpsertStackLocalSource(ctx, stackName, pDir) if err != nil { t.Errorf("failed to initialize stack, err: %v", err) t.FailNow() } whoAmIDetailedInfo, err := s.Workspace().WhoAmIDetails(ctx) if err != nil { t.Errorf("failed to get WhoAmIDetailedInfo, err: %v", err) t.FailNow() } assert.NotNil(t, whoAmIDetailedInfo.User, "failed to get WhoAmIDetailedInfo user") assert.NotNil(t, whoAmIDetailedInfo.URL, "failed to get WhoAmIDetailedInfo url") // cleanup _, err = s.Destroy(ctx) if err != nil { t.Errorf("destroy failed during cleanup, err: %v", err) t.FailNow() } err = s.Workspace().RemoveStack(ctx, s.Name()) if err != nil { t.Errorf("failed to remove stack during cleanup. Resources have leaked, err: %v", err) t.FailNow() } } func TestListStacks(t *testing.T) { t.Parallel() ctx := context.Background() pDir := filepath.Join(".", "test", "testproj") m := mockPulumiCommand{ stdout: `[{"name": "testorg1/testproj1/teststack1", "current": false, "url": "https://app.pulumi.com/testorg1/testproj1/teststack1"}, {"name": "testorg1/testproj1/teststack2", "current": false, "url": "https://app.pulumi.com/testorg1/testproj1/teststack2"}]`, stderr: "", exitCode: 0, err: nil, } workspace, err := NewLocalWorkspace(ctx, WorkDir(pDir), Pulumi(&m)) require.NoError(t, err) stacks, err := workspace.ListStacks(ctx) assert.NoError(t, err) assert.Len(t, stacks, 2) assert.Equal(t, "testorg1/testproj1/teststack1", stacks[0].Name) assert.Equal(t, false, stacks[0].Current) assert.Equal(t, "https://app.pulumi.com/testorg1/testproj1/teststack1", stacks[0].URL) assert.Equal(t, "testorg1/testproj1/teststack2", stacks[1].Name) assert.Equal(t, false, stacks[1].Current) assert.Equal(t, "https://app.pulumi.com/testorg1/testproj1/teststack2", stacks[1].URL) } func TestListStacksCorrectArgs(t *testing.T) { t.Parallel() ctx := context.Background() pDir := filepath.Join(".", "test", "testproj") m := mockPulumiCommand{ stdout: `[{"name": "testorg1/testproj1/teststack1", "current": false, "url": "https://app.pulumi.com/testorg1/testproj1/teststack1"}, {"name": "testorg1/testproj1/teststack2", "current": false, "url": "https://app.pulumi.com/testorg1/testproj1/teststack2"}]`, stderr: "", exitCode: 0, err: nil, } workspace, err := NewLocalWorkspace(ctx, WorkDir(pDir), Pulumi(&m)) require.NoError(t, err) _, err = workspace.ListStacks(ctx) assert.NoError(t, err) assert.Equal(t, []string{"stack", "ls", "--json"}, m.capturedArgs) } func TestListAllStacks(t *testing.T) { t.Parallel() ctx := context.Background() pDir := filepath.Join(".", "test", "testproj") m := mockPulumiCommand{ stdout: `[{"name": "testorg1/testproj1/teststack1", "current": false, "url": "https://app.pulumi.com/testorg1/testproj1/teststack1"}, {"name": "testorg1/testproj2/teststack2", "current": false, "url": "https://app.pulumi.com/testorg1/testproj2/teststack2"}]`, stderr: "", exitCode: 0, err: nil, } workspace, err := NewLocalWorkspace(ctx, WorkDir(pDir), Pulumi(&m)) require.NoError(t, err) stacks, err := workspace.ListStacks(ctx, optlist.All()) assert.NoError(t, err) assert.Len(t, stacks, 2) assert.Equal(t, "testorg1/testproj1/teststack1", stacks[0].Name) assert.Equal(t, false, stacks[0].Current) assert.Equal(t, "https://app.pulumi.com/testorg1/testproj1/teststack1", stacks[0].URL) assert.Equal(t, "testorg1/testproj2/teststack2", stacks[1].Name) assert.Equal(t, false, stacks[1].Current) assert.Equal(t, "https://app.pulumi.com/testorg1/testproj2/teststack2", stacks[1].URL) } func TestListStacksAllCorrectArgs(t *testing.T) { t.Parallel() ctx := context.Background() pDir := filepath.Join(".", "test", "testproj") m := mockPulumiCommand{ stdout: `[{"name": "testorg1/testproj1/teststack1", "current": false, "url": "https://app.pulumi.com/testorg1/testproj1/teststack1"}, {"name": "testorg1/testproj1/teststack2", "current": false, "url": "https://app.pulumi.com/testorg1/testproj1/teststack2"}]`, stderr: "", exitCode: 0, err: nil, } workspace, err := NewLocalWorkspace(ctx, WorkDir(pDir), Pulumi(&m)) require.NoError(t, err) _, err = workspace.ListStacks(ctx, optlist.All()) assert.NoError(t, err) assert.Equal(t, []string{"stack", "ls", "--json", "--all"}, m.capturedArgs) } func TestInstallWithOptions(t *testing.T) { t.Parallel() ctx := context.Background() pDir := filepath.Join(".", "test", "install") defer os.RemoveAll(filepath.Join(pDir, "venv")) workspace, err := NewLocalWorkspace(ctx, WorkDir(pDir)) require.NoError(t, err) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} // Run with options err = workspace.Install(ctx, &InstallOptions{ Stdout: stdout, Stderr: stderr, }) require.NoError(t, err) require.Contains(t, stdout.String(), "Creating virtual environment...") require.Contains(t, stdout.String(), "Successfully installed urllib3") require.Contains(t, stdout.String(), "Finished installing dependencies") require.Empty(t, stderr.String()) require.DirExists(t, filepath.Join(pDir, "venv")) // Run without options err = workspace.Install(ctx, nil) require.NoError(t, err) } func TestInstallOptions(t *testing.T) { t.Parallel() ctx := context.Background() pDir := filepath.Join(".", "test", "install") m := mockPulumiCommand{ // Set a version high enough to support UseLanguageVersionTools version: semver.Version{Major: 3, Minor: 130}, } workspace, err := NewLocalWorkspace(ctx, WorkDir(pDir), Pulumi(&m)) require.NoError(t, err) err = workspace.Install(ctx, &InstallOptions{}) require.NoError(t, err) require.Equal(t, []string{"install"}, m.capturedArgs) err = workspace.Install(ctx, &InstallOptions{ UseLanguageVersionTools: true, }) require.NoError(t, err) require.Equal(t, []string{"install", "--use-language-version-tools"}, m.capturedArgs) err = workspace.Install(ctx, &InstallOptions{ NoPlugins: true, }) require.NoError(t, err) require.Equal(t, []string{"install", "--no-plugins"}, m.capturedArgs) err = workspace.Install(ctx, &InstallOptions{ NoDependencies: true, }) require.NoError(t, err) require.Equal(t, []string{"install", "--no-dependencies"}, m.capturedArgs) err = workspace.Install(ctx, &InstallOptions{ Reinstall: true, }) require.NoError(t, err) require.Equal(t, []string{"install", "--reinstall"}, m.capturedArgs) err = workspace.Install(ctx, &InstallOptions{ UseLanguageVersionTools: true, NoDependencies: true, NoPlugins: true, Reinstall: true, }) require.NoError(t, err) require.Equal(t, []string{ "install", "--use-language-version-tools", "--no-plugins", "--no-dependencies", "--reinstall", }, m.capturedArgs) } func TestInstallWithUseLanguageVersionTools(t *testing.T) { t.Parallel() ctx := context.Background() pDir := filepath.Join(".", "test", "install-use-language-version-tools") // Option is not available on < 3.130 m := mockPulumiCommand{ version: semver.Version{Major: 3, Minor: 129}, } workspace, err := NewLocalWorkspace(ctx, WorkDir(pDir), Pulumi(&m)) require.NoError(t, err) err = workspace.Install(ctx, &InstallOptions{ UseLanguageVersionTools: true, }) require.ErrorContains(t, err, "UseLanguageVersionTools requires Pulumi CLI version >= 3.130.0") // Option is available on >= 3.130 m = mockPulumiCommand{ version: semver.Version{Major: 3, Minor: 130}, } workspace, err = NewLocalWorkspace(ctx, WorkDir(pDir), Pulumi(&m)) require.NoError(t, err) err = workspace.Install(ctx, &InstallOptions{ UseLanguageVersionTools: true, }) require.NoError(t, err) require.Equal(t, []string{"install", "--use-language-version-tools"}, m.capturedArgs) } func BenchmarkBulkSetConfigMixed(b *testing.B) { ctx := context.Background() stackName := FullyQualifiedStackName(pulumiOrg, "set_config_mixed", "dev") // initialize s, err := NewStackInlineSource(ctx, stackName, "set_config_mixed", func(ctx *pulumi.Context) error { return nil }) if err != nil { b.Errorf("failed to initialize stack, err: %v", err) b.FailNow() } cfg := ConfigMap{ "one": ConfigValue{Value: "one", Secret: true}, "two": ConfigValue{Value: "two"}, "three": ConfigValue{Value: "three", Secret: true}, "four": ConfigValue{Value: "four"}, "five": ConfigValue{Value: "five", Secret: true}, "six": ConfigValue{Value: "six"}, "seven": ConfigValue{Value: "seven", Secret: true}, "eight": ConfigValue{Value: "eight"}, "nine": ConfigValue{Value: "nine", Secret: true}, "ten": ConfigValue{Value: "ten"}, "eleven": ConfigValue{Value: "one", Secret: true}, "twelve": ConfigValue{Value: "two"}, "thirteen": ConfigValue{Value: "three", Secret: true}, "fourteen": ConfigValue{Value: "four"}, "fifteen": ConfigValue{Value: "five", Secret: true}, "sixteen": ConfigValue{Value: "six"}, "seventeen": ConfigValue{Value: "seven", Secret: true}, "eighteen": ConfigValue{Value: "eight"}, "nineteen": ConfigValue{Value: "nine", Secret: true}, "twenty": ConfigValue{Value: "ten"}, "one1": ConfigValue{Value: "one", Secret: true}, "two1": ConfigValue{Value: "two"}, "three1": ConfigValue{Value: "three", Secret: true}, "four1": ConfigValue{Value: "four"}, "five1": ConfigValue{Value: "five", Secret: true}, "six1": ConfigValue{Value: "six"}, "seven1": ConfigValue{Value: "seven", Secret: true}, "eight1": ConfigValue{Value: "eight"}, "nine1": ConfigValue{Value: "nine", Secret: true}, "ten1": ConfigValue{Value: "ten"}, "eleven1": ConfigValue{Value: "one", Secret: true}, "twelve1": ConfigValue{Value: "two"}, "thirteen1": ConfigValue{Value: "three", Secret: true}, "fourteen1": ConfigValue{Value: "four"}, "fifteen1": ConfigValue{Value: "five", Secret: true}, "sixteen1": ConfigValue{Value: "six"}, "seventeen1": ConfigValue{Value: "seven", Secret: true}, "eighteen1": ConfigValue{Value: "eight"}, "nineteen1": ConfigValue{Value: "nine", Secret: true}, "twenty1": ConfigValue{Value: "ten"}, } err = s.SetAllConfig(ctx, cfg) if err != nil { b.Errorf("failed to set config, err: %v", err) b.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(b, err, "failed to remove stack. Resources have leaked.") }() } func BenchmarkBulkSetConfigPlain(b *testing.B) { ctx := context.Background() stackName := FullyQualifiedStackName(pulumiOrg, "set_config_plain", "dev") // initialize s, err := NewStackInlineSource(ctx, stackName, "set_config_plain", func(ctx *pulumi.Context) error { return nil }) if err != nil { b.Errorf("failed to initialize stack, err: %v", err) b.FailNow() } cfg := ConfigMap{ "one": ConfigValue{Value: "one"}, "two": ConfigValue{Value: "two"}, "three": ConfigValue{Value: "three"}, "four": ConfigValue{Value: "four"}, "five": ConfigValue{Value: "five"}, "six": ConfigValue{Value: "six"}, "seven": ConfigValue{Value: "seven"}, "eight": ConfigValue{Value: "eight"}, "nine": ConfigValue{Value: "nine"}, "ten": ConfigValue{Value: "ten"}, "eleven": ConfigValue{Value: "one"}, "twelve": ConfigValue{Value: "two"}, "thirteen": ConfigValue{Value: "three"}, "fourteen": ConfigValue{Value: "four"}, "fifteen": ConfigValue{Value: "five"}, "sixteen": ConfigValue{Value: "six"}, "seventeen": ConfigValue{Value: "seven"}, "eighteen": ConfigValue{Value: "eight"}, "nineteen": ConfigValue{Value: "nine"}, "twenty": ConfigValue{Value: "ten"}, "one1": ConfigValue{Value: "one"}, "two1": ConfigValue{Value: "two"}, "three1": ConfigValue{Value: "three"}, "four1": ConfigValue{Value: "four"}, "five1": ConfigValue{Value: "five"}, "six1": ConfigValue{Value: "six"}, "seven1": ConfigValue{Value: "seven"}, "eight1": ConfigValue{Value: "eight"}, "nine1": ConfigValue{Value: "nine"}, "ten1": ConfigValue{Value: "ten"}, "eleven1": ConfigValue{Value: "one"}, "twelve1": ConfigValue{Value: "two"}, "thirteen1": ConfigValue{Value: "three"}, "fourteen1": ConfigValue{Value: "four"}, "fifteen1": ConfigValue{Value: "five"}, "sixteen1": ConfigValue{Value: "six"}, "seventeen1": ConfigValue{Value: "seven"}, "eighteen1": ConfigValue{Value: "eight"}, "nineteen1": ConfigValue{Value: "nine"}, "twenty1": ConfigValue{Value: "ten"}, } err = s.SetAllConfig(ctx, cfg) if err != nil { b.Errorf("failed to set config, err: %v", err) b.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(b, err, "failed to remove stack. Resources have leaked.") }() } func BenchmarkBulkSetConfigSecret(b *testing.B) { ctx := context.Background() stackName := FullyQualifiedStackName(pulumiOrg, "set_config_plain", "dev") // initialize s, err := NewStackInlineSource(ctx, stackName, "set_config_plain", func(ctx *pulumi.Context) error { return nil }) if err != nil { b.Errorf("failed to initialize stack, err: %v", err) b.FailNow() } cfg := ConfigMap{ "one": ConfigValue{Value: "one", Secret: true}, "two": ConfigValue{Value: "two", Secret: true}, "three": ConfigValue{Value: "three", Secret: true}, "four": ConfigValue{Value: "four", Secret: true}, "five": ConfigValue{Value: "five", Secret: true}, "six": ConfigValue{Value: "six", Secret: true}, "seven": ConfigValue{Value: "seven", Secret: true}, "eight": ConfigValue{Value: "eight", Secret: true}, "nine": ConfigValue{Value: "nine", Secret: true}, "ten": ConfigValue{Value: "ten", Secret: true}, "eleven": ConfigValue{Value: "one", Secret: true}, "twelve": ConfigValue{Value: "two", Secret: true}, "thirteen": ConfigValue{Value: "three", Secret: true}, "fourteen": ConfigValue{Value: "four", Secret: true}, "fifteen": ConfigValue{Value: "five", Secret: true}, "sixteen": ConfigValue{Value: "six", Secret: true}, "seventeen": ConfigValue{Value: "seven", Secret: true}, "eighteen": ConfigValue{Value: "eight", Secret: true}, "nineteen": ConfigValue{Value: "nine", Secret: true}, "1twenty": ConfigValue{Value: "ten", Secret: true}, "one1": ConfigValue{Value: "one", Secret: true}, "two1": ConfigValue{Value: "two", Secret: true}, "three1": ConfigValue{Value: "three", Secret: true}, "four1": ConfigValue{Value: "four", Secret: true}, "five1": ConfigValue{Value: "five", Secret: true}, "six1": ConfigValue{Value: "six", Secret: true}, "seven1": ConfigValue{Value: "seven", Secret: true}, "eight1": ConfigValue{Value: "eight", Secret: true}, "nine1": ConfigValue{Value: "nine", Secret: true}, "ten1": ConfigValue{Value: "ten", Secret: true}, "eleven1": ConfigValue{Value: "one", Secret: true}, "twelve1": ConfigValue{Value: "two", Secret: true}, "thirteen1": ConfigValue{Value: "three", Secret: true}, "fourteen1": ConfigValue{Value: "four", Secret: true}, "fifteen1": ConfigValue{Value: "five", Secret: true}, "sixteen1": ConfigValue{Value: "six", Secret: true}, "seventeen1": ConfigValue{Value: "seven", Secret: true}, "eighteen1": ConfigValue{Value: "eight", Secret: true}, "nineteen1": ConfigValue{Value: "nine", Secret: true}, "twenty1": ConfigValue{Value: "ten", Secret: true}, } err = s.SetAllConfig(ctx, cfg) if err != nil { b.Errorf("failed to set config, err: %v", err) b.FailNow() } defer func() { // -- pulumi stack rm -- err = s.Workspace().RemoveStack(ctx, s.Name()) assert.NoError(b, err, "failed to remove stack. Resources have leaked.") }() } func getTestOrg() string { testOrg := pulumiTestOrg if _, set := os.LookupEnv("PULUMI_TEST_ORG"); set { testOrg = os.Getenv("PULUMI_TEST_ORG") } return testOrg } func countSteps(log []events.EngineEvent) int { steps := 0 for _, e := range log { if e.ResourcePreEvent != nil { steps++ } } return steps } func containsSummary(log []events.EngineEvent) bool { hasSummary := false for _, e := range log { if e.SummaryEvent != nil { hasSummary = true } } return hasSummary } func collectEvents(eventChannel <-chan events.EngineEvent, events *[]events.EngineEvent) *sync.WaitGroup { var wg sync.WaitGroup wg.Add(1) go (func() { for event := range eventChannel { *events = append(*events, event) } wg.Done() })() return &wg } type MyResource struct { pulumi.ResourceState } func NewMyResource(ctx *pulumi.Context, name string, opts ...pulumi.ResourceOption) (*MyResource, error) { myResource := &MyResource{} err := ctx.RegisterComponentResource("my:module:MyResource", name, myResource, opts...) if err != nil { return nil, err } return myResource, nil }