mirror of https://github.com/pulumi/pulumi.git
3329 lines
99 KiB
Go
3329 lines
99 KiB
Go
// 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
|
|
}
|