This change does three major things: 1. Removes the ability to be logged into multiple clouds at the same time. Previously, we supported being logged into multiple clouds at the same time and the CLI would fan out requests and join responses when needed. In general, this was only useful for Pulumi employees that wanted run against multiple copies of the service (say production and staging) but overall was very confusing (for example in the old world a stack with the same identity could appear twice (since it was in two backends) which the CLI didn't handle very well). 2. Stops treating the "local" backend as a special thing, from the point of view of the CLI. Previouly we'd always connect to the local backend and merge that data with whatever was in clouds we were connected to. We had gestures like `--local` in `pulumi stack init` that meant "use the local mode". Instead, to use the local mode now you run `pulumi login --cloud-url local://` and then you are logged in the local backend. Since you can only ever be logged into a single backend, we can remove the `--local` and `--remote` flags from `pulumi stack init`, it just now requires you to be logged in and creates a stack in whatever back end you were logged into. When logging into the local backend, you are not prompted for an access key. 3. Prompt for login in places where you have to log in, if you are not already logged in.
360 lines
12 KiB
Go
360 lines
12 KiB
Go
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
|
|
|
package ints
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource"
|
|
"github.com/pulumi/pulumi/pkg/resource/config"
|
|
ptesting "github.com/pulumi/pulumi/pkg/testing"
|
|
"github.com/pulumi/pulumi/pkg/testing/integration"
|
|
"github.com/pulumi/pulumi/pkg/workspace"
|
|
)
|
|
|
|
// 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"),
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// TestProjectMain tests out the ability to override the main entrypoint.
|
|
func TestProjectMain(t *testing.T) {
|
|
var test integration.ProgramTestOptions
|
|
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.RunCommand("git", "init")
|
|
e.RunCommand("pulumi", "init")
|
|
|
|
e.ImportDirectory("project_main_abs")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", "local://")
|
|
e.RunCommand("pulumi", "stack", "init", "main-abs")
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "update")
|
|
assert.Equal(t, "", stdout)
|
|
assert.Contains(t, stderr, "project 'main' must be a relative path")
|
|
})
|
|
|
|
t.Run("Error_ParentFolder", func(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
e.RunCommand("git", "init")
|
|
e.RunCommand("pulumi", "init")
|
|
|
|
e.ImportDirectory("project_main_parent")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", "local://")
|
|
e.RunCommand("pulumi", "stack", "init", "main-parent")
|
|
stdout, stderr := e.RunCommandExpectError("pulumi", "update")
|
|
assert.Equal(t, "", stdout)
|
|
assert.Contains(t, stderr, "project 'main' must be a subfolder")
|
|
})
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
|
|
// TestStackOutputs ensures we can export variables from a stack and have them get recorded as outputs.
|
|
func TestStackOutputs(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: "stack_outputs",
|
|
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"])
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
// 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, 8, 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)
|
|
default:
|
|
t.Fatalf("unexpected name %s", res.URN.Name())
|
|
}
|
|
}
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
// 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)
|
|
fmt.Println(latest.Resources)
|
|
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: "nodejs",
|
|
}).Save(path)
|
|
assert.NoError(t, err)
|
|
e.RunCommand("git", "init")
|
|
e.RunCommand("pulumi", "init")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", "local://")
|
|
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)
|
|
}
|
|
|
|
// 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",
|
|
},
|
|
})
|
|
}
|
|
|
|
// 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"),
|
|
Quick: true,
|
|
Config: map[string]string{
|
|
"aConfigValue": "this value is a Pythonic value",
|
|
},
|
|
Secrets: map[string]string{
|
|
"bEncryptedSecret": "this super Pythonic secret is encrypted",
|
|
},
|
|
})
|
|
}
|
|
|
|
// Tests that when `pulumi` is run, configuration is upgraded from the old format to the new format.
|
|
func TestConfigUpgrade(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer func() {
|
|
if !t.Failed() {
|
|
e.DeleteEnvironment()
|
|
}
|
|
}()
|
|
|
|
e.ImportDirectory("config_upgrade")
|
|
|
|
// Run a pulumi command, which will upgrade everything (but we have to be logged in first!)
|
|
e.RunCommand("pulumi", "login", "--cloud-url", "local://")
|
|
e.RunCommand("pulumi", "config")
|
|
|
|
validate := func(k string, v string, cfg config.Map) {
|
|
key, err := config.ParseKey("config_upgrade: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.local1.yaml"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 4, len(testStack1.Config))
|
|
testStack2, err := workspace.LoadProjectStack(filepath.Join(e.CWD, "Pulumi.local2.yaml"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, len(testStack2.Config))
|
|
|
|
validate("allKey", "allValue", testStack1.Config)
|
|
validate("allKeyOverride", "local1Overridden", testStack1.Config)
|
|
validate("allWorkspaceKey", "allWorkspaceValue", testStack1.Config)
|
|
validate("local1ProjectKey", "local1ProjectValue", testStack1.Config)
|
|
|
|
validate("allKey", "allValue", testStack2.Config)
|
|
validate("allKeyOverride", "local2Overridden", testStack2.Config)
|
|
validate("allWorkspaceKey", "allWorkspaceValue", testStack2.Config)
|
|
validate("local2WorkspaceKey", "local2WorkspaceValue", testStack2.Config)
|
|
|
|
// The stack local2 had an encrypted configuration value, ensure the EncryptionSalt was copied over
|
|
assert.NotEmpty(t, testStack2.EncryptionSalt)
|
|
|
|
// Ensure config has been removed from the old files:
|
|
w, err := workspace.NewFrom(e.CWD)
|
|
assert.NoError(t, err)
|
|
|
|
proj, err := workspace.LoadProject(filepath.Join(e.CWD, "Pulumi.yaml"))
|
|
assert.NoError(t, err)
|
|
|
|
assert.Empty(t, w.Settings().ConfigDeprecated)
|
|
assert.Empty(t, proj.ConfigDeprecated)
|
|
assert.Empty(t, proj.EncryptionSaltDeprecated)
|
|
}
|