1310 lines
43 KiB
Go
1310 lines
43 KiB
Go
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
|
|
|
package ints
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/pulumi/pulumi/pkg/apitype"
|
|
"github.com/pulumi/pulumi/pkg/resource"
|
|
"github.com/pulumi/pulumi/pkg/resource/config"
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
|
|
"github.com/pulumi/pulumi/pkg/secrets/cloud"
|
|
ptesting "github.com/pulumi/pulumi/pkg/testing"
|
|
"github.com/pulumi/pulumi/pkg/testing/integration"
|
|
"github.com/pulumi/pulumi/pkg/workspace"
|
|
)
|
|
|
|
// assertPerfBenchmark implements the integration.TestStatsReporter interface, and reports test
|
|
// failures when a scenario exceeds the provided threshold.
|
|
type assertPerfBenchmark struct {
|
|
T *testing.T
|
|
MaxPreviewDuration time.Duration
|
|
MaxUpdateDuration time.Duration
|
|
}
|
|
|
|
func (t assertPerfBenchmark) ReportCommand(stats integration.TestCommandStats) {
|
|
var maxDuration *time.Duration
|
|
if strings.HasPrefix(stats.StepName, "pulumi-preview") {
|
|
maxDuration = &t.MaxPreviewDuration
|
|
}
|
|
if strings.HasPrefix(stats.StepName, "pulumi-update") {
|
|
maxDuration = &t.MaxUpdateDuration
|
|
}
|
|
|
|
if maxDuration != nil && *maxDuration != 0 {
|
|
if stats.ElapsedSeconds < maxDuration.Seconds() {
|
|
t.T.Logf(
|
|
"Test step %q was under threshold. %.2fs (max %.2fs)",
|
|
stats.StepName, stats.ElapsedSeconds, maxDuration.Seconds())
|
|
} else {
|
|
t.T.Errorf(
|
|
"Test step %q took longer than expected. %.2fs vs. max %.2fs",
|
|
stats.StepName, stats.ElapsedSeconds, maxDuration.Seconds())
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEmptyNodeJS simply tests that we can run an empty NodeJS project.
|
|
func TestEmptyNodeJS(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("empty", "nodejs"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// TestEmptyPython simply tests that we can run an empty Python project.
|
|
func TestEmptyPython(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("empty", "python"),
|
|
Dependencies: []string{
|
|
path.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// TestEmptyGo simply tests that we can build and run an empty Go project.
|
|
func TestEmptyGo(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("empty", "go"),
|
|
Quick: true,
|
|
RunBuild: true,
|
|
})
|
|
}
|
|
|
|
// TestEmptyGoRun exercises the 'go run' invocation path that doesn't require an explicit build step.
|
|
func TestEmptyGoRun(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("empty", "gorun"),
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// TestEmptyGoRunMain exercises the 'go run' invocation path with a 'main' entrypoint specified in Pulumi.yml
|
|
func TestEmptyGoRunMain(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("empty", "gorun_main"),
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// TestEmptyDotNet simply tests that we can run an empty .NET project.
|
|
func TestEmptyDotNet(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("empty", "dotnet"),
|
|
Dependencies: []string{"Pulumi"},
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// Tests emitting many engine events doesn't result in a performance problem.
|
|
func TestEngineEventPerf(t *testing.T) {
|
|
// Prior to pulumi/pulumi#2303, a preview or update would take ~40s.
|
|
// Since then, it should now be down to ~4s, with additional padding,
|
|
// since some Travis machines (especially the macOS ones) seem quite slow
|
|
// to begin with.
|
|
benchmarkEnforcer := &assertPerfBenchmark{
|
|
T: t,
|
|
MaxPreviewDuration: 8 * time.Second,
|
|
MaxUpdateDuration: 8 * time.Second,
|
|
}
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "ee_perf",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
ReportStats: benchmarkEnforcer,
|
|
// Don't run in parallel since it is sensitive to system resources.
|
|
NoParallel: true,
|
|
})
|
|
}
|
|
|
|
// TestEngineEvents ensures that the test framework properly records and reads engine events.
|
|
func TestEngineEvents(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "single_resource",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
// Ensure that we have a non-empty list of events.
|
|
assert.NotEmpty(t, stackInfo.Events)
|
|
|
|
// Ensure that we have two "ResourcePre" events: one for the stack and one for our resource.
|
|
preEventResourceTypes := []string{}
|
|
for _, e := range stackInfo.Events {
|
|
if e.ResourcePreEvent != nil {
|
|
preEventResourceTypes = append(preEventResourceTypes, e.ResourcePreEvent.Metadata.Type)
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, 2, len(preEventResourceTypes))
|
|
assert.Contains(t, preEventResourceTypes, "pulumi:pulumi:Stack")
|
|
assert.Contains(t, preEventResourceTypes, "pulumi-nodejs:dynamic:Resource")
|
|
},
|
|
})
|
|
|
|
}
|
|
|
|
// TestProjectMain tests out the ability to override the main entrypoint.
|
|
func TestProjectMain(t *testing.T) {
|
|
test := integration.ProgramTestOptions{
|
|
Dir: "project_main",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
// Simple runtime validation that just ensures the checkpoint was written and read.
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
},
|
|
}
|
|
integration.ProgramTest(t, &test)
|
|
|
|
t.Run("Error_AbsolutePath", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
e.ImportDirectory("project_main_abs")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "main-abs")
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "up", "--non-interactive", "--skip-preview")
|
|
assert.Equal(t, "Updating (main-abs):\n", stdout)
|
|
assert.Contains(t, stderr, "project 'main' must be a relative path")
|
|
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
|
})
|
|
|
|
t.Run("Error_ParentFolder", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
e.ImportDirectory("project_main_parent")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "main-parent")
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "up", "--non-interactive", "--skip-preview")
|
|
assert.Equal(t, "Updating (main-parent):\n", stdout)
|
|
assert.Contains(t, stderr, "project 'main' must be a subfolder")
|
|
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
|
})
|
|
}
|
|
|
|
// TestStackProjectName ensures we can read the Pulumi stack and project name from within the program.
|
|
func TestStackProjectName(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "stack_project_name",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// TestStackTagValidation verifies various error scenarios related to stack names and tags.
|
|
func TestStackTagValidation(t *testing.T) {
|
|
t.Run("Error_StackName", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
e.RunCommand("git", "init")
|
|
|
|
e.ImportDirectory("stack_project_name")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "stack", "init", "invalid name (spaces, parens, etc.)")
|
|
assert.Equal(t, "", stdout)
|
|
assert.Contains(t, stderr, "stack name may only contain alphanumeric, hyphens, underscores, and periods")
|
|
})
|
|
|
|
t.Run("Error_DescriptionLength", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
e.RunCommand("git", "init")
|
|
|
|
e.ImportDirectory("stack_project_name")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
|
|
prefix := "lorem ipsum dolor sit amet" // 26
|
|
prefix = prefix + prefix + prefix + prefix // 104
|
|
prefix = prefix + prefix + prefix + prefix // 416 + the current Pulumi.yaml's description
|
|
|
|
// Change the contents of the Description property of Pulumi.yaml.
|
|
yamlPath := path.Join(e.CWD, "Pulumi.yaml")
|
|
err := integration.ReplaceInFile("description: ", "description: "+prefix, yamlPath)
|
|
assert.NoError(t, err)
|
|
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "stack", "init", "valid-name")
|
|
assert.Equal(t, "", stdout)
|
|
assert.Contains(t, stderr, "error: could not create stack:")
|
|
assert.Contains(t, stderr, "validating stack properties:")
|
|
assert.Contains(t, stderr, "stack tag \"pulumi:description\" value is too long (max length 256 characters)")
|
|
})
|
|
}
|
|
|
|
func TestRemoveWithResourcesBlocked(t *testing.T) {
|
|
if os.Getenv("PULUMI_ACCESS_TOKEN") == "" {
|
|
t.Skipf("Skipping: PULUMI_ACCESS_TOKEN is not set")
|
|
}
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
|
|
stackName, err := resource.NewUniqueHex("rm-test-", 8, -1)
|
|
contract.AssertNoErrorf(err, "resource.NewUniqueHex should not fail with no maximum length is set")
|
|
|
|
e.ImportDirectory("single_resource")
|
|
e.RunCommand("pulumi", "stack", "init", stackName)
|
|
e.RunCommand("yarn", "link", "@pulumi/pulumi")
|
|
e.RunCommand("pulumi", "up", "--non-interactive", "--skip-preview")
|
|
_, stderr := e.RunCommandExpectError("pulumi", "stack", "rm", "--yes")
|
|
assert.Contains(t, stderr, "--force")
|
|
e.RunCommand("pulumi", "destroy", "--skip-preview", "--non-interactive", "--yes")
|
|
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
|
}
|
|
|
|
// TestStackOutputs ensures we can export variables from a stack and have them get recorded as outputs.
|
|
func TestStackOutputsNodeJS(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("stack_outputs", "nodejs"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
// Ensure the checkpoint contains a single resource, the Stack, with two outputs.
|
|
fmt.Printf("Deployment: %v", stackInfo.Deployment)
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
if assert.Equal(t, 1, len(stackInfo.Deployment.Resources)) {
|
|
stackRes := stackInfo.Deployment.Resources[0]
|
|
assert.NotNil(t, stackRes)
|
|
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
|
|
assert.Equal(t, 0, len(stackRes.Inputs))
|
|
assert.Equal(t, 2, len(stackRes.Outputs))
|
|
assert.Equal(t, "ABC", stackRes.Outputs["xyz"])
|
|
assert.Equal(t, float64(42), stackRes.Outputs["foo"])
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestStackOutputsPython(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("stack_outputs", "python"),
|
|
Dependencies: []string{
|
|
filepath.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
// Ensure the checkpoint contains a single resource, the Stack, with two outputs.
|
|
fmt.Printf("Deployment: %v", stackInfo.Deployment)
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
if assert.Equal(t, 1, len(stackInfo.Deployment.Resources)) {
|
|
stackRes := stackInfo.Deployment.Resources[0]
|
|
assert.NotNil(t, stackRes)
|
|
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
|
|
assert.Equal(t, 0, len(stackRes.Inputs))
|
|
assert.Equal(t, 2, len(stackRes.Outputs))
|
|
assert.Equal(t, "ABC", stackRes.Outputs["xyz"])
|
|
assert.Equal(t, float64(42), stackRes.Outputs["foo"])
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestStackOutputsDotNet(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("stack_outputs", "dotnet"),
|
|
Dependencies: []string{"Pulumi"},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
// Ensure the checkpoint contains a single resource, the Stack, with two outputs.
|
|
fmt.Printf("Deployment: %v", stackInfo.Deployment)
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
if assert.Equal(t, 1, len(stackInfo.Deployment.Resources)) {
|
|
stackRes := stackInfo.Deployment.Resources[0]
|
|
assert.NotNil(t, stackRes)
|
|
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
|
|
assert.Equal(t, 0, len(stackRes.Inputs))
|
|
assert.Equal(t, 2, len(stackRes.Outputs))
|
|
assert.Equal(t, "ABC", stackRes.Outputs["xyz"])
|
|
assert.Equal(t, float64(42), stackRes.Outputs["foo"])
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
// TestStackOutputsJSON ensures the CLI properly formats stack outputs as JSON when requested.
|
|
func TestStackOutputsJSON(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
e.ImportDirectory(filepath.Join("stack_outputs", "nodejs"))
|
|
e.RunCommand("yarn", "link", "@pulumi/pulumi")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "stack-outs")
|
|
e.RunCommand("pulumi", "up", "--non-interactive", "--skip-preview")
|
|
stdout, _ := e.RunCommand("pulumi", "stack", "output", "--json")
|
|
assert.Equal(t, `{
|
|
"foo": 42,
|
|
"xyz": "ABC"
|
|
}
|
|
`, stdout)
|
|
}
|
|
|
|
// TestStackOutputsDisplayed ensures that outputs are printed at the end of an update
|
|
func TestStackOutputsDisplayed(t *testing.T) {
|
|
stdout := &bytes.Buffer{}
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("stack_outputs", "nodejs"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: false,
|
|
Verbose: true,
|
|
Stdout: stdout,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
output := stdout.String()
|
|
|
|
// ensure we get the outputs info both for the normal update, and for the no-change update.
|
|
assert.Contains(t, output, "Outputs:\n foo: 42\n xyz: \"ABC\"\n\nResources:\n + 1 created")
|
|
assert.Contains(t, output, "Outputs:\n foo: 42\n xyz: \"ABC\"\n\nResources:\n 1 unchanged")
|
|
},
|
|
})
|
|
}
|
|
|
|
// TestStackOutputsSuppressed ensures that outputs whose values are intentionally suppresses don't show.
|
|
func TestStackOutputsSuppressed(t *testing.T) {
|
|
stdout := &bytes.Buffer{}
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("stack_outputs", "nodejs"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: false,
|
|
Verbose: true,
|
|
Stdout: stdout,
|
|
UpdateCommandlineFlags: []string{"--suppress-outputs"},
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
output := stdout.String()
|
|
assert.NotContains(t, output, "Outputs:\n foo: 42\n xyz: \"ABC\"\n")
|
|
assert.NotContains(t, output, "Outputs:\n foo: 42\n xyz: \"ABC\"\n")
|
|
},
|
|
})
|
|
}
|
|
|
|
// TestStackParenting tests out that stacks and components are parented correctly.
|
|
func TestStackParenting(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "stack_parenting",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
// Ensure the checkpoint contains resources parented correctly. This should look like this:
|
|
//
|
|
// A F
|
|
// / \ \
|
|
// B C G
|
|
// / \
|
|
// D E
|
|
//
|
|
// with the caveat, of course, that A and F will share a common parent, the implicit stack.
|
|
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
if assert.Equal(t, 9, len(stackInfo.Deployment.Resources)) {
|
|
stackRes := stackInfo.Deployment.Resources[0]
|
|
assert.NotNil(t, stackRes)
|
|
assert.Equal(t, resource.RootStackType, stackRes.Type)
|
|
assert.Equal(t, "", string(stackRes.Parent))
|
|
|
|
urns := make(map[string]resource.URN)
|
|
for _, res := range stackInfo.Deployment.Resources[1:] {
|
|
assert.NotNil(t, res)
|
|
|
|
urns[string(res.URN.Name())] = res.URN
|
|
switch res.URN.Name() {
|
|
case "a", "f":
|
|
assert.NotEqual(t, "", res.Parent)
|
|
assert.Equal(t, stackRes.URN, res.Parent)
|
|
case "b", "c":
|
|
assert.Equal(t, urns["a"], res.Parent)
|
|
case "d", "e":
|
|
assert.Equal(t, urns["c"], res.Parent)
|
|
case "g":
|
|
assert.Equal(t, urns["f"], res.Parent)
|
|
case "default":
|
|
// Default providers are not parented.
|
|
assert.Equal(t, "", string(res.Parent))
|
|
default:
|
|
t.Fatalf("unexpected name %s", res.URN.Name())
|
|
}
|
|
}
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestStackBadParenting(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "stack_bad_parenting",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
ExpectFailure: true,
|
|
})
|
|
}
|
|
|
|
// TestStackDependencyGraph tests that the dependency graph of a stack is saved
|
|
// in the checkpoint file.
|
|
func TestStackDependencyGraph(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "stack_dependencies",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
latest := stackInfo.Deployment
|
|
assert.True(t, len(latest.Resources) >= 2)
|
|
sawFirst := false
|
|
sawSecond := false
|
|
for _, res := range latest.Resources {
|
|
urn := string(res.URN)
|
|
if strings.Contains(urn, "dynamic:Resource::first") {
|
|
// The first resource doesn't depend on anything.
|
|
assert.Equal(t, 0, len(res.Dependencies))
|
|
sawFirst = true
|
|
} else if strings.Contains(urn, "dynamic:Resource::second") {
|
|
// The second resource uses an Output property of the first resource, so it
|
|
// depends directly on first.
|
|
assert.Equal(t, 1, len(res.Dependencies))
|
|
assert.True(t, strings.Contains(string(res.Dependencies[0]), "dynamic:Resource::first"))
|
|
sawSecond = true
|
|
}
|
|
}
|
|
|
|
assert.True(t, sawFirst && sawSecond)
|
|
},
|
|
})
|
|
}
|
|
|
|
// TestConfigSave ensures that config commands in the Pulumi CLI work as expected.
|
|
func TestConfigSave(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
|
|
// Initialize an empty stack.
|
|
path := filepath.Join(e.RootPath, "Pulumi.yaml")
|
|
err := (&workspace.Project{
|
|
Name: "testing-config",
|
|
Runtime: workspace.NewProjectRuntimeInfo("nodejs", nil),
|
|
}).Save(path)
|
|
assert.NoError(t, err)
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "testing-2")
|
|
e.RunCommand("pulumi", "stack", "init", "testing-1")
|
|
|
|
// Now configure and save a few different things:
|
|
e.RunCommand("pulumi", "config", "set", "configA", "value1")
|
|
e.RunCommand("pulumi", "config", "set", "configB", "value2", "--stack", "testing-2")
|
|
|
|
e.RunCommand("pulumi", "stack", "select", "testing-2")
|
|
|
|
e.RunCommand("pulumi", "config", "set", "configD", "value4")
|
|
e.RunCommand("pulumi", "config", "set", "configC", "value3", "--stack", "testing-1")
|
|
|
|
// Now read back the config using the CLI:
|
|
{
|
|
stdout, _ := e.RunCommand("pulumi", "config", "get", "configB")
|
|
assert.Equal(t, "value2\n", stdout)
|
|
}
|
|
{
|
|
// the config in a different stack, so this should error.
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "config", "get", "configA")
|
|
assert.Equal(t, "", stdout)
|
|
assert.NotEqual(t, "", stderr)
|
|
}
|
|
{
|
|
// but selecting the stack should let you see it
|
|
stdout, _ := e.RunCommand("pulumi", "config", "get", "configA", "--stack", "testing-1")
|
|
assert.Equal(t, "value1\n", stdout)
|
|
}
|
|
|
|
// Finally, check that the stack file contains what we expected.
|
|
validate := func(k string, v string, cfg config.Map) {
|
|
key, err := config.ParseKey("testing-config:config:" + k)
|
|
assert.NoError(t, err)
|
|
d, ok := cfg[key]
|
|
assert.True(t, ok, "config key %v should be set", k)
|
|
dv, err := d.Value(nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, v, dv)
|
|
}
|
|
|
|
testStack1, err := workspace.LoadProjectStack(filepath.Join(e.CWD, "Pulumi.testing-1.yaml"))
|
|
assert.NoError(t, err)
|
|
testStack2, err := workspace.LoadProjectStack(filepath.Join(e.CWD, "Pulumi.testing-2.yaml"))
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 2, len(testStack1.Config))
|
|
assert.Equal(t, 2, len(testStack2.Config))
|
|
|
|
validate("configA", "value1", testStack1.Config)
|
|
validate("configC", "value3", testStack1.Config)
|
|
|
|
validate("configB", "value2", testStack2.Config)
|
|
validate("configD", "value4", testStack2.Config)
|
|
|
|
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
|
}
|
|
|
|
// TestConfigPaths ensures that config commands with paths work as expected.
|
|
func TestConfigPaths(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
|
|
// Initialize an empty stack.
|
|
path := filepath.Join(e.RootPath, "Pulumi.yaml")
|
|
err := (&workspace.Project{
|
|
Name: "testing-config",
|
|
Runtime: workspace.NewProjectRuntimeInfo("nodejs", nil),
|
|
}).Save(path)
|
|
assert.NoError(t, err)
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "testing")
|
|
|
|
namespaces := []string{"", "my:"}
|
|
|
|
tests := []struct {
|
|
Key string
|
|
Value string
|
|
Secret bool
|
|
Path bool
|
|
TopLevelKey string
|
|
TopLevelExpectedValue string
|
|
}{
|
|
{
|
|
Key: "aConfigValue",
|
|
Value: "this value is a value",
|
|
TopLevelKey: "aConfigValue",
|
|
TopLevelExpectedValue: "this value is a value",
|
|
},
|
|
{
|
|
Key: "anotherConfigValue",
|
|
Value: "this value is another value",
|
|
TopLevelKey: "anotherConfigValue",
|
|
TopLevelExpectedValue: "this value is another value",
|
|
},
|
|
{
|
|
Key: "bEncryptedSecret",
|
|
Value: "this super secret is encrypted",
|
|
Secret: true,
|
|
TopLevelKey: "bEncryptedSecret",
|
|
TopLevelExpectedValue: "this super secret is encrypted",
|
|
},
|
|
{
|
|
Key: "anotherEncryptedSecret",
|
|
Value: "another encrypted secret",
|
|
Secret: true,
|
|
TopLevelKey: "anotherEncryptedSecret",
|
|
TopLevelExpectedValue: "another encrypted secret",
|
|
},
|
|
{
|
|
Key: "[]",
|
|
Value: "square brackets value",
|
|
TopLevelKey: "[]",
|
|
TopLevelExpectedValue: "square brackets value",
|
|
},
|
|
{
|
|
Key: "x.y",
|
|
Value: "x.y value",
|
|
TopLevelKey: "x.y",
|
|
TopLevelExpectedValue: "x.y value",
|
|
},
|
|
{
|
|
Key: "0",
|
|
Value: "0 value",
|
|
Path: true,
|
|
TopLevelKey: "0",
|
|
TopLevelExpectedValue: "0 value",
|
|
},
|
|
{
|
|
Key: "true",
|
|
Value: "value",
|
|
Path: true,
|
|
TopLevelKey: "true",
|
|
TopLevelExpectedValue: "value",
|
|
},
|
|
{
|
|
Key: `["test.Key"]`,
|
|
Value: "test key value",
|
|
Path: true,
|
|
TopLevelKey: "test.Key",
|
|
TopLevelExpectedValue: "test key value",
|
|
},
|
|
{
|
|
Key: `nested["test.Key"]`,
|
|
Value: "nested test key value",
|
|
Path: true,
|
|
TopLevelKey: "nested",
|
|
TopLevelExpectedValue: `{"test.Key":"nested test key value"}`,
|
|
},
|
|
{
|
|
Key: "outer.inner",
|
|
Value: "value",
|
|
Path: true,
|
|
TopLevelKey: "outer",
|
|
TopLevelExpectedValue: `{"inner":"value"}`,
|
|
},
|
|
{
|
|
Key: "names[0]",
|
|
Value: "a",
|
|
Path: true,
|
|
TopLevelKey: "names",
|
|
TopLevelExpectedValue: `["a"]`,
|
|
},
|
|
{
|
|
Key: "names[1]",
|
|
Value: "b",
|
|
Path: true,
|
|
TopLevelKey: "names",
|
|
TopLevelExpectedValue: `["a","b"]`,
|
|
},
|
|
{
|
|
Key: "names[2]",
|
|
Value: "c",
|
|
Path: true,
|
|
TopLevelKey: "names",
|
|
TopLevelExpectedValue: `["a","b","c"]`,
|
|
},
|
|
{
|
|
Key: "names[3]",
|
|
Value: "super secret name",
|
|
Path: true,
|
|
Secret: true,
|
|
TopLevelKey: "names",
|
|
TopLevelExpectedValue: `["a","b","c","super secret name"]`,
|
|
},
|
|
{
|
|
Key: "servers[0].port",
|
|
Value: "80",
|
|
Path: true,
|
|
TopLevelKey: "servers",
|
|
TopLevelExpectedValue: `[{"port":80}]`,
|
|
},
|
|
{
|
|
Key: "servers[0].host",
|
|
Value: "example",
|
|
Path: true,
|
|
TopLevelKey: "servers",
|
|
TopLevelExpectedValue: `[{"host":"example","port":80}]`,
|
|
},
|
|
{
|
|
Key: "a.b[0].c",
|
|
Value: "true",
|
|
Path: true,
|
|
TopLevelKey: "a",
|
|
TopLevelExpectedValue: `{"b":[{"c":true}]}`,
|
|
},
|
|
{
|
|
Key: "a.b[1].c",
|
|
Value: "false",
|
|
Path: true,
|
|
TopLevelKey: "a",
|
|
TopLevelExpectedValue: `{"b":[{"c":true},{"c":false}]}`,
|
|
},
|
|
{
|
|
Key: "tokens[0]",
|
|
Value: "shh",
|
|
Path: true,
|
|
Secret: true,
|
|
TopLevelKey: "tokens",
|
|
TopLevelExpectedValue: `["shh"]`,
|
|
},
|
|
{
|
|
Key: "foo.bar",
|
|
Value: "don't tell",
|
|
Path: true,
|
|
Secret: true,
|
|
TopLevelKey: "foo",
|
|
TopLevelExpectedValue: `{"bar":"don't tell"}`,
|
|
},
|
|
{
|
|
Key: "semiInner.a.b.c.d",
|
|
Value: "1",
|
|
Path: true,
|
|
TopLevelKey: "semiInner",
|
|
TopLevelExpectedValue: `{"a":{"b":{"c":{"d":1}}}}`,
|
|
},
|
|
{
|
|
Key: "wayInner.a.b.c.d.e.f.g.h.i.j.k",
|
|
Value: "false",
|
|
Path: true,
|
|
TopLevelKey: "wayInner",
|
|
TopLevelExpectedValue: `{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":false}}}}}}}}}}}`,
|
|
},
|
|
|
|
// Overwriting a top-level string value is allowed.
|
|
{
|
|
Key: "aConfigValue.inner",
|
|
Value: "new value",
|
|
Path: true,
|
|
TopLevelKey: "aConfigValue",
|
|
TopLevelExpectedValue: `{"inner":"new value"}`,
|
|
},
|
|
{
|
|
Key: "anotherConfigValue[0]",
|
|
Value: "new value",
|
|
Path: true,
|
|
TopLevelKey: "anotherConfigValue",
|
|
TopLevelExpectedValue: `["new value"]`,
|
|
},
|
|
{
|
|
Key: "bEncryptedSecret.inner",
|
|
Value: "new value",
|
|
Path: true,
|
|
TopLevelKey: "bEncryptedSecret",
|
|
TopLevelExpectedValue: `{"inner":"new value"}`,
|
|
},
|
|
{
|
|
Key: "anotherEncryptedSecret[0]",
|
|
Value: "new value",
|
|
Path: true,
|
|
TopLevelKey: "anotherEncryptedSecret",
|
|
TopLevelExpectedValue: `["new value"]`,
|
|
},
|
|
}
|
|
|
|
validateConfigGet := func(key string, value string, path bool) {
|
|
args := []string{"config", "get", key}
|
|
if path {
|
|
args = append(args, "--path")
|
|
}
|
|
stdout, stderr := e.RunCommand("pulumi", args...)
|
|
assert.Equal(t, fmt.Sprintf("%s\n", value), stdout)
|
|
assert.Equal(t, "", stderr)
|
|
}
|
|
|
|
for _, ns := range namespaces {
|
|
for _, test := range tests {
|
|
key := fmt.Sprintf("%s%s", ns, test.Key)
|
|
topLevelKey := fmt.Sprintf("%s%s", ns, test.TopLevelKey)
|
|
|
|
// Set the value.
|
|
args := []string{"config", "set"}
|
|
if test.Secret {
|
|
args = append(args, "--secret")
|
|
}
|
|
if test.Path {
|
|
args = append(args, "--path")
|
|
}
|
|
args = append(args, key, test.Value)
|
|
stdout, stderr := e.RunCommand("pulumi", args...)
|
|
assert.Equal(t, "", stdout)
|
|
assert.Equal(t, "", stderr)
|
|
|
|
// Get the value and validate it.
|
|
validateConfigGet(key, test.Value, test.Path)
|
|
|
|
// Get the top-level value and validate it.
|
|
validateConfigGet(topLevelKey, test.TopLevelExpectedValue, false /*path*/)
|
|
}
|
|
}
|
|
|
|
badKeys := []string{
|
|
// Syntax errors.
|
|
"root[",
|
|
`root["nested]`,
|
|
"root.array[abc]",
|
|
"root.[1]",
|
|
|
|
// First path segment must be a non-empty string.
|
|
`[""]`,
|
|
"[0]",
|
|
|
|
// Index out of range.
|
|
"names[-1]",
|
|
"names[5]",
|
|
|
|
// A "secure" key that is a map with a single string value is reserved by the system.
|
|
"key.secure",
|
|
"super.nested.map.secure",
|
|
|
|
// Type mismatch.
|
|
"outer[0]",
|
|
"names.nested",
|
|
"outer.inner.nested",
|
|
"outer.inner[0]",
|
|
}
|
|
|
|
for _, ns := range namespaces {
|
|
for _, badKey := range badKeys {
|
|
key := fmt.Sprintf("%s%s", ns, badKey)
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "config", "set", "--path", key, "value")
|
|
assert.Equal(t, "", stdout)
|
|
assert.NotEqual(t, "", stderr)
|
|
}
|
|
}
|
|
|
|
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
|
}
|
|
|
|
// Tests basic configuration from the perspective of a Pulumi program.
|
|
func TestConfigBasicNodeJS(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("config_basic", "nodejs"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"aConfigValue": "this value is a value",
|
|
},
|
|
Secrets: map[string]string{
|
|
"bEncryptedSecret": "this super secret is encrypted",
|
|
},
|
|
OrderedConfig: []integration.ConfigValue{
|
|
{Key: "outer.inner", Value: "value", Path: true},
|
|
{Key: "names[0]", Value: "a", Path: true},
|
|
{Key: "names[1]", Value: "b", Path: true},
|
|
{Key: "names[2]", Value: "c", Path: true},
|
|
{Key: "names[3]", Value: "super secret name", Path: true, Secret: true},
|
|
{Key: "servers[0].port", Value: "80", Path: true},
|
|
{Key: "servers[0].host", Value: "example", Path: true},
|
|
{Key: "a.b[0].c", Value: "true", Path: true},
|
|
{Key: "a.b[1].c", Value: "false", Path: true},
|
|
{Key: "tokens[0]", Value: "shh", Path: true, Secret: true},
|
|
{Key: "foo.bar", Value: "don't tell", Path: true, Secret: true},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestConfigCaptureNodeJS(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("config_capture_e2e", "nodejs"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"value": "it works",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInvalidVersionInPackageJson(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("invalid_package_json"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
Config: map[string]string{},
|
|
})
|
|
}
|
|
|
|
// Tests basic configuration from the perspective of a Pulumi program.
|
|
func TestConfigBasicPython(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("config_basic", "python"),
|
|
Dependencies: []string{
|
|
path.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"aConfigValue": "this value is a Pythonic value",
|
|
},
|
|
Secrets: map[string]string{
|
|
"bEncryptedSecret": "this super Pythonic secret is encrypted",
|
|
},
|
|
OrderedConfig: []integration.ConfigValue{
|
|
{Key: "outer.inner", Value: "value", Path: true},
|
|
{Key: "names[0]", Value: "a", Path: true},
|
|
{Key: "names[1]", Value: "b", Path: true},
|
|
{Key: "names[2]", Value: "c", Path: true},
|
|
{Key: "names[3]", Value: "super secret name", Path: true, Secret: true},
|
|
{Key: "servers[0].port", Value: "80", Path: true},
|
|
{Key: "servers[0].host", Value: "example", Path: true},
|
|
{Key: "a.b[0].c", Value: "true", Path: true},
|
|
{Key: "a.b[1].c", Value: "false", Path: true},
|
|
{Key: "tokens[0]", Value: "shh", Path: true, Secret: true},
|
|
{Key: "foo.bar", Value: "don't tell", Path: true, Secret: true},
|
|
},
|
|
})
|
|
}
|
|
|
|
// Tests basic configuration from the perspective of a Pulumi Go program.
|
|
func TestConfigBasicGo(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("config_basic", "go"),
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"aConfigValue": "this value is a value",
|
|
},
|
|
Secrets: map[string]string{
|
|
"bEncryptedSecret": "this super secret is encrypted",
|
|
},
|
|
OrderedConfig: []integration.ConfigValue{
|
|
{Key: "outer.inner", Value: "value", Path: true},
|
|
{Key: "names[0]", Value: "a", Path: true},
|
|
{Key: "names[1]", Value: "b", Path: true},
|
|
{Key: "names[2]", Value: "c", Path: true},
|
|
{Key: "names[3]", Value: "super secret name", Path: true, Secret: true},
|
|
{Key: "servers[0].port", Value: "80", Path: true},
|
|
{Key: "servers[0].host", Value: "example", Path: true},
|
|
{Key: "a.b[0].c", Value: "true", Path: true},
|
|
{Key: "a.b[1].c", Value: "false", Path: true},
|
|
{Key: "tokens[0]", Value: "shh", Path: true, Secret: true},
|
|
{Key: "foo.bar", Value: "don't tell", Path: true, Secret: true},
|
|
},
|
|
RunBuild: true,
|
|
})
|
|
}
|
|
|
|
// Tests basic configuration from the perspective of a Pulumi .NET program.
|
|
func TestConfigBasicDotNet(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("config_basic", "dotnet"),
|
|
Dependencies: []string{"Pulumi"},
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"aConfigValue": "this value is a value",
|
|
},
|
|
Secrets: map[string]string{
|
|
"bEncryptedSecret": "this super secret is encrypted",
|
|
},
|
|
OrderedConfig: []integration.ConfigValue{
|
|
{Key: "outer.inner", Value: "value", Path: true},
|
|
{Key: "names[0]", Value: "a", Path: true},
|
|
{Key: "names[1]", Value: "b", Path: true},
|
|
{Key: "names[2]", Value: "c", Path: true},
|
|
{Key: "names[3]", Value: "super secret name", Path: true, Secret: true},
|
|
{Key: "servers[0].port", Value: "80", Path: true},
|
|
{Key: "servers[0].host", Value: "example", Path: true},
|
|
{Key: "a.b[0].c", Value: "true", Path: true},
|
|
{Key: "a.b[1].c", Value: "false", Path: true},
|
|
{Key: "tokens[0]", Value: "shh", Path: true, Secret: true},
|
|
{Key: "foo.bar", Value: "don't tell", Path: true, Secret: true},
|
|
},
|
|
})
|
|
}
|
|
|
|
// Tests an explicit provider instance.
|
|
func TestExplicitProvider(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "explicit_provider",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
latest := stackInfo.Deployment
|
|
|
|
// Expect one stack resource, two provider resources, and two custom resources.
|
|
assert.True(t, len(latest.Resources) == 5)
|
|
|
|
var defaultProvider *apitype.ResourceV3
|
|
var explicitProvider *apitype.ResourceV3
|
|
for _, res := range latest.Resources {
|
|
urn := res.URN
|
|
switch urn.Name() {
|
|
case "default":
|
|
assert.True(t, providers.IsProviderType(res.Type))
|
|
assert.Nil(t, defaultProvider)
|
|
prov := res
|
|
defaultProvider = &prov
|
|
|
|
case "p":
|
|
assert.True(t, providers.IsProviderType(res.Type))
|
|
assert.Nil(t, explicitProvider)
|
|
prov := res
|
|
explicitProvider = &prov
|
|
|
|
case "a":
|
|
prov, err := providers.ParseReference(res.Provider)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, defaultProvider)
|
|
defaultRef, err := providers.NewReference(defaultProvider.URN, defaultProvider.ID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, defaultRef.String(), prov.String())
|
|
|
|
case "b":
|
|
prov, err := providers.ParseReference(res.Provider)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, explicitProvider)
|
|
explicitRef, err := providers.NewReference(explicitProvider.URN, explicitProvider.ID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, explicitRef.String(), prov.String())
|
|
}
|
|
}
|
|
|
|
assert.NotNil(t, defaultProvider)
|
|
assert.NotNil(t, explicitProvider)
|
|
},
|
|
})
|
|
}
|
|
|
|
// Tests that reads of unknown IDs do not fail.
|
|
func TestGetCreated(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "get_created",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// Tests that stack references work in Node.
|
|
func TestStackReferenceNodeJS(t *testing.T) {
|
|
if owner := os.Getenv("PULUMI_TEST_OWNER"); owner == "" {
|
|
t.Skipf("Skipping: PULUMI_TEST_OWNER is not set")
|
|
}
|
|
|
|
opts := &integration.ProgramTestOptions{
|
|
Dir: "stack_reference",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"org": os.Getenv("PULUMI_TEST_OWNER"),
|
|
},
|
|
EditDirs: []integration.EditDir{
|
|
{
|
|
Dir: "step1",
|
|
Additive: true,
|
|
},
|
|
{
|
|
Dir: "step2",
|
|
Additive: true,
|
|
},
|
|
},
|
|
}
|
|
integration.ProgramTest(t, opts)
|
|
}
|
|
|
|
func TestStackReferencePython(t *testing.T) {
|
|
if owner := os.Getenv("PULUMI_TEST_OWNER"); owner == "" {
|
|
t.Skipf("Skipping: PULUMI_TEST_OWNER is not set")
|
|
}
|
|
|
|
opts := &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("stack_reference", "python"),
|
|
Dependencies: []string{
|
|
filepath.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"org": os.Getenv("PULUMI_TEST_OWNER"),
|
|
},
|
|
}
|
|
integration.ProgramTest(t, opts)
|
|
}
|
|
|
|
func TestStackReferenceDotNet(t *testing.T) {
|
|
if owner := os.Getenv("PULUMI_TEST_OWNER"); owner == "" {
|
|
t.Skipf("Skipping: PULUMI_TEST_OWNER is not set")
|
|
}
|
|
|
|
opts := &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("stack_reference", "dotnet"),
|
|
Dependencies: []string{"Pulumi"},
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"org": os.Getenv("PULUMI_TEST_OWNER"),
|
|
},
|
|
}
|
|
integration.ProgramTest(t, opts)
|
|
}
|
|
|
|
// Tests that we issue an error if we fail to locate the Python command when running
|
|
// a Python example.
|
|
func TestPython3NotInstalled(t *testing.T) {
|
|
stderr := &bytes.Buffer{}
|
|
badPython := "python3000"
|
|
expectedError := fmt.Sprintf(
|
|
"error: Failed to locate '%s' on your PATH. Have you installed Python 3.6 or greater?",
|
|
badPython)
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: path.Join("empty", "python"),
|
|
Dependencies: []string{
|
|
path.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
Quick: true,
|
|
Env: []string{
|
|
// Note: we use PULUMI_PYTHON_CMD to override the default behavior of searching
|
|
// for Python 3, since anyone running tests surely already has Python 3 installed on their
|
|
// machine. The code paths are functionally the same.
|
|
fmt.Sprintf("PULUMI_PYTHON_CMD=%s", badPython),
|
|
},
|
|
ExpectFailure: true,
|
|
Stderr: stderr,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
output := stderr.String()
|
|
assert.Contains(t, output, expectedError)
|
|
},
|
|
})
|
|
}
|
|
|
|
// TestProviderSecretConfig that a first class provider can be created when it has secrets as part of its config.
|
|
func TestProviderSecretConfig(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "provider_secret_config",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// Tests dynamic provider in Python.
|
|
func TestDynamicPython(t *testing.T) {
|
|
var randomVal string
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("dynamic", "python"),
|
|
Dependencies: []string{
|
|
path.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
randomVal = stack.Outputs["random_val"].(string)
|
|
},
|
|
EditDirs: []integration.EditDir{{
|
|
Dir: "step1",
|
|
Additive: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
assert.Equal(t, randomVal, stack.Outputs["random_val"].(string))
|
|
},
|
|
}},
|
|
})
|
|
}
|
|
|
|
func TestResourceWithSecretSerialization(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "secret_outputs",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
// The program exports two resources, one named `withSecret` who's prefix property should be secret
|
|
// and one named `withoutSecret` which should not. We serialize both of the these as POJO objects, so
|
|
// they appear as maps in the output.
|
|
withSecretProps, ok := stackInfo.Outputs["withSecret"].(map[string]interface{})
|
|
assert.Truef(t, ok, "POJO output was not serialized as a map")
|
|
|
|
withoutSecretProps, ok := stackInfo.Outputs["withoutSecret"].(map[string]interface{})
|
|
assert.Truef(t, ok, "POJO output was not serialized as a map")
|
|
|
|
// The secret prop should have been serialized as a secret
|
|
secretPropValue, ok := withSecretProps["prefix"].(map[string]interface{})
|
|
assert.Truef(t, ok, "secret output was not serialized as a secret")
|
|
assert.Equal(t, resource.SecretSig, secretPropValue[resource.SigKey].(string))
|
|
|
|
// And here, the prop was not set, it should just be a string value
|
|
_, isString := withoutSecretProps["prefix"].(string)
|
|
assert.Truef(t, isString, "non-secret output was not a string")
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestStackReferenceSecrets(t *testing.T) {
|
|
owner := os.Getenv("PULUMI_TEST_OWNER")
|
|
if owner == "" {
|
|
t.Skipf("Skipping: PULUMI_TEST_OWNER is not set")
|
|
}
|
|
|
|
d := "stack_reference_secrets"
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: path.Join(d, "step1"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
Config: map[string]string{
|
|
"org": owner,
|
|
},
|
|
Quick: true,
|
|
EditDirs: []integration.EditDir{
|
|
{
|
|
Dir: path.Join(d, "step2"),
|
|
Additive: true,
|
|
ExpectNoChanges: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
_, isString := stackInfo.Outputs["refNormal"].(string)
|
|
assert.Truef(t, isString, "referenced non-secret output was not a string")
|
|
|
|
secretPropValue, ok := stackInfo.Outputs["refSecret"].(map[string]interface{})
|
|
assert.Truef(t, ok, "secret output was not serialized as a secret")
|
|
assert.Equal(t, resource.SecretSig, secretPropValue[resource.SigKey].(string))
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestCloudSecretProvider(t *testing.T) {
|
|
kmsKeyAlias := os.Getenv("PULUMI_TEST_KMS_KEY_ALIAS")
|
|
if kmsKeyAlias == "" {
|
|
t.Skipf("Skipping: PULUMI_TEST_KMS_KEY_ALIAS is not set")
|
|
}
|
|
|
|
testOptions := integration.ProgramTestOptions{
|
|
Dir: "cloud_secrets_provider",
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
SecretsProvider: fmt.Sprintf("awskms://alias/%s", kmsKeyAlias),
|
|
Secrets: map[string]string{
|
|
"mysecret": "THISISASECRET",
|
|
},
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
secretsProvider := stackInfo.Deployment.SecretsProviders
|
|
assert.NotNil(t, secretsProvider)
|
|
assert.Equal(t, secretsProvider.Type, "cloud")
|
|
|
|
_, err := cloud.NewCloudSecretsManagerFromState(secretsProvider.State)
|
|
assert.NoError(t, err)
|
|
|
|
out, ok := stackInfo.Outputs["out"].(map[string]interface{})
|
|
assert.True(t, ok)
|
|
|
|
_, ok = out["ciphertext"]
|
|
assert.True(t, ok)
|
|
},
|
|
}
|
|
|
|
localTestOptions := testOptions.With(integration.ProgramTestOptions{
|
|
CloudURL: "file://~",
|
|
})
|
|
|
|
// Run with default Pulumi service backend
|
|
t.Run("service", func(t *testing.T) { integration.ProgramTest(t, &testOptions) })
|
|
|
|
// Also run with local backend
|
|
t.Run("local", func(t *testing.T) { integration.ProgramTest(t, &localTestOptions) })
|
|
}
|
|
|
|
func TestPartialValuesNode(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("partial_values", "nodejs"),
|
|
Dependencies: []string{"@pulumi/pulumi"},
|
|
AllowEmptyPreviewChanges: true,
|
|
})
|
|
}
|
|
|
|
func TestPartialValuesPython(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("partial_values", "python"),
|
|
Dependencies: []string{
|
|
path.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
AllowEmptyPreviewChanges: true,
|
|
})
|
|
}
|