mirror of https://github.com/pulumi/pulumi.git
723 lines
24 KiB
Go
723 lines
24 KiB
Go
// Copyright 2023-2024, 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 tests
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
|
"gopkg.in/yaml.v2"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
|
ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var Runtimes = []string{"python", "java", "go", "yaml", "nodejs", "dotnet"}
|
|
|
|
// Mapping from the language runtime names to the common language name used by templates and the like.
|
|
var Languages = map[string]string{
|
|
"python": "python",
|
|
"java": "java",
|
|
"go": "go",
|
|
"yaml": "yaml",
|
|
"nodejs": "typescript",
|
|
"dotnet": "csharp",
|
|
}
|
|
|
|
// Quick sanity tests for each downstream language to check that a minimal example can be created and run.
|
|
//
|
|
//nolint:paralleltest // pulumi new is not parallel safe
|
|
func TestLanguageNewSmoke(t *testing.T) {
|
|
// make sure we can download needed plugins
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
for _, runtime := range Runtimes {
|
|
t.Run(runtime, func(t *testing.T) {
|
|
//nolint:paralleltest
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// `new` wants to work in an empty directory but our use of local url means we have a
|
|
// ".pulumi" directory at root.
|
|
projectDir := filepath.Join(e.RootPath, "project")
|
|
err := os.Mkdir(projectDir, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
e.CWD = projectDir
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "new", "random-"+Languages[runtime], "--yes")
|
|
e.RunCommand("pulumi", "up", "--yes")
|
|
e.RunCommand("pulumi", "destroy", "--yes")
|
|
})
|
|
}
|
|
}
|
|
|
|
// Quick sanity tests that YAML convert works.
|
|
//
|
|
//nolint:paralleltest // sets envvars
|
|
func TestYamlConvertSmoke(t *testing.T) {
|
|
// make sure we can download the yaml converter plugin
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
e.ImportDirectory("testdata/random_yaml")
|
|
|
|
// Make sure random is installed
|
|
e.RunCommand("pulumi", "plugin", "install", "resource", "random", "4.13.0")
|
|
|
|
e.RunCommand(
|
|
"pulumi", "convert", "--strict",
|
|
"--language", "pcl", "--from", "yaml", "--out", "out")
|
|
|
|
actualPcl, err := os.ReadFile(filepath.Join(e.RootPath, "out", "program.pp"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, `resource pet "random:index/randomPet:RandomPet" {
|
|
__logicalName = "pet"
|
|
}
|
|
|
|
output name {
|
|
__logicalName = "name"
|
|
value = pet.id
|
|
}
|
|
`, string(actualPcl))
|
|
}
|
|
|
|
// Quick sanity tests for each downstream language to check that convert works.
|
|
func TestLanguageConvertSmoke(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, runtime := range Runtimes {
|
|
runtime := runtime
|
|
t.Run(runtime, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
e.ImportDirectory("testdata/random_pp")
|
|
|
|
// Make sure random is installed
|
|
e.RunCommand("pulumi", "plugin", "install", "resource", "random", "4.13.0")
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand(
|
|
"pulumi", "convert", "--strict",
|
|
"--language", Languages[runtime], "--from", "pcl", "--out", "out")
|
|
e.CWD = filepath.Join(e.RootPath, "out")
|
|
e.RunCommand("pulumi", "stack", "init", "test")
|
|
|
|
e.RunCommand("pulumi", "install")
|
|
e.RunCommand("pulumi", "up", "--yes")
|
|
e.RunCommand("pulumi", "destroy", "--yes")
|
|
})
|
|
}
|
|
}
|
|
|
|
// Quick sanity tests for each downstream language to check that non-strict convert works.
|
|
func TestLanguageConvertLenientSmoke(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, runtime := range Runtimes {
|
|
runtime := runtime
|
|
t.Run(runtime, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
e.ImportDirectory("testdata/bad_random_pp")
|
|
|
|
// Make sure random is installed
|
|
e.RunCommand("pulumi", "plugin", "install", "resource", "random", "4.13.0")
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand(
|
|
"pulumi", "convert", "--generate-only",
|
|
"--language", Languages[runtime], "--from", "pcl", "--out", "out")
|
|
// We don't want care about running this program because it _will_ be incorrect.
|
|
})
|
|
}
|
|
}
|
|
|
|
// Quick sanity tests for each downstream language to check that convert with components works.
|
|
func TestLanguageConvertComponentSmoke(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, runtime := range Runtimes {
|
|
runtime := runtime
|
|
t.Run(runtime, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if runtime == "yaml" {
|
|
t.Skip("yaml doesn't support components")
|
|
}
|
|
if runtime == "java" {
|
|
t.Skip("java doesn't support components")
|
|
}
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
e.ImportDirectory("testdata/component_pp")
|
|
|
|
// Make sure random is installed
|
|
e.RunCommand("pulumi", "plugin", "install", "resource", "random", "4.13.0")
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "convert", "--language", Languages[runtime], "--from", "pcl", "--out", "out")
|
|
e.CWD = filepath.Join(e.RootPath, "out")
|
|
e.RunCommand("pulumi", "stack", "init", "test")
|
|
|
|
// TODO(https://github.com/pulumi/pulumi/issues/14339): This doesn't work for Go yet because the
|
|
// source code convert emits is not valid
|
|
if runtime != "go" {
|
|
e.RunCommand("pulumi", "install")
|
|
e.RunCommand("pulumi", "up", "--yes")
|
|
e.RunCommand("pulumi", "destroy", "--yes")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Quick sanity tests for each downstream language to check that sdk-gen works.
|
|
func TestLanguageGenerateSmoke(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, runtime := range Runtimes {
|
|
if runtime == "yaml" {
|
|
// yaml doesn't support sdks
|
|
continue
|
|
}
|
|
|
|
runtime := runtime
|
|
t.Run(runtime, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
e.ImportDirectory("testdata/simple_schema")
|
|
e.RunCommand("pulumi", "package", "gen-sdk", "--language", runtime, "schema.json")
|
|
})
|
|
}
|
|
}
|
|
|
|
//nolint:paralleltest // disabled parallel because we change the plugins cache
|
|
func TestPackageGetSchema(t *testing.T) {
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
removeRandomFromLocalPlugins := func() {
|
|
e.RunCommand("pulumi", "plugin", "rm", "resource", "random", "--all", "--yes")
|
|
}
|
|
|
|
bindSchema := func(pkg, schemaJson string) *schema.Package {
|
|
var schemaSpec *schema.PackageSpec
|
|
err := json.Unmarshal([]byte(schemaJson), &schemaSpec)
|
|
require.NoError(t, err, "Unmarshalling schema specs from %s should work", pkg)
|
|
require.NotNil(t, schemaSpec, "Specification should be non-nil")
|
|
schema, diags, err := schema.BindSpec(*schemaSpec, nil)
|
|
require.NoError(t, err, "Binding the schema spec should work")
|
|
require.False(t, diags.HasErrors(), "Binding schema spec should have no errors")
|
|
require.NotNil(t, schema)
|
|
return schema
|
|
}
|
|
|
|
// Make sure the random provider is not installed locally
|
|
// So that we can test the `package get-schema` command works if the plugin
|
|
// is not installed locally on first run.
|
|
out, _ := e.RunCommand("pulumi", "plugin", "ls")
|
|
if strings.Contains(out, "random resource") {
|
|
removeRandomFromLocalPlugins()
|
|
}
|
|
|
|
// get the schema and bind it
|
|
schemaJSON, _ := e.RunCommand("pulumi", "package", "get-schema", "random")
|
|
bindSchema("random", schemaJSON)
|
|
|
|
// try again using a specific version
|
|
removeRandomFromLocalPlugins()
|
|
schemaJSON, _ = e.RunCommand("pulumi", "package", "get-schema", "random@4.13.0")
|
|
bindSchema("random", schemaJSON)
|
|
|
|
// Now that the random provider is installed, run the command again without removing random from plugins
|
|
schemaJSON, _ = e.RunCommand("pulumi", "package", "get-schema", "random")
|
|
bindSchema("random", schemaJSON)
|
|
|
|
// Now try to get the schema from the path to the binary
|
|
binaryPath := filepath.Join(
|
|
e.HomePath,
|
|
"plugins",
|
|
"resource-random-v4.13.0",
|
|
"pulumi-resource-random")
|
|
if runtime.GOOS == "windows" {
|
|
binaryPath += ".exe"
|
|
}
|
|
|
|
schemaJSON, _ = e.RunCommand("pulumi", "package", "get-schema", binaryPath)
|
|
bindSchema("random", schemaJSON)
|
|
|
|
// Now try and get the parameterized schema from the test-provider
|
|
providerDir, err := filepath.Abs("testprovider")
|
|
require.NoError(t, err)
|
|
schemaJSON, _ = e.RunCommand("pulumi", "package", "get-schema", providerDir, "parameter")
|
|
schema := bindSchema("testprovider", schemaJSON)
|
|
// Sub-schema is a very simple empty schema with the name set from the argument given
|
|
assert.Equal(t, "parameter", schema.Name)
|
|
}
|
|
|
|
//nolint:paralleltest // disabled parallel because we change the plugins cache
|
|
func TestPackageGetMappingToFile(t *testing.T) {
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
removeRandomFromLocalPlugins := func() {
|
|
e.RunCommand("pulumi", "plugin", "rm", "resource", "random", "--all", "--yes")
|
|
}
|
|
|
|
// Make sure the random provider is not installed locally
|
|
// So that we can test the `package get-mapping` command works if the plugin
|
|
// is not installed locally on first run.
|
|
out, _ := e.RunCommand("pulumi", "plugin", "ls")
|
|
if strings.Contains(out, "random resource") {
|
|
removeRandomFromLocalPlugins()
|
|
}
|
|
|
|
stdout, result := e.RunCommand("pulumi",
|
|
"package", "get-mapping", "terraform", "random@4.13.0",
|
|
"--out", "mapping.json")
|
|
require.Empty(t, stdout)
|
|
require.Contains(t, result, "random@4.13.0 maps to provider random")
|
|
contents, err := os.ReadFile(filepath.Join(e.RootPath, "mapping.json"))
|
|
require.NoError(t, err, "Reading the generated tf mapping from file should work")
|
|
require.NotNil(t, contents, "mapping contents should be non-empty")
|
|
}
|
|
|
|
//nolint:paralleltest // disabled parallel because we change the plugins cache
|
|
func TestPackageGetMapping(t *testing.T) {
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
removeRandomFromLocalPlugins := func() {
|
|
e.RunCommand("pulumi", "plugin", "rm", "resource", "random", "--all", "--yes")
|
|
}
|
|
|
|
// Make sure the random provider is not installed locally
|
|
// So that we can test the `package get-mapping` command works if the plugin
|
|
// is not installed locally on first run.
|
|
out, _ := e.RunCommand("pulumi", "plugin", "ls")
|
|
if strings.Contains(out, "random resource") {
|
|
removeRandomFromLocalPlugins()
|
|
}
|
|
|
|
schema, result := e.RunCommand("pulumi", "package", "get-mapping", "terraform", "random@4.13.0")
|
|
require.Contains(t, result, "random@4.13.0 maps to provider random")
|
|
require.NotEmpty(t, schema, "mapping contents should be non-empty")
|
|
}
|
|
|
|
// Quick sanity tests for each downstream language to check that import works.
|
|
//
|
|
//nolint:paralleltest // pulumi new is not parallel safe
|
|
func TestLanguageImportSmoke(t *testing.T) {
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
for _, runtime := range Runtimes {
|
|
t.Run(runtime, func(t *testing.T) {
|
|
//nolint:paralleltest
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// `new` wants to work in an empty directory but our use of local url means we have a
|
|
// ".pulumi" directory at root.
|
|
projectDir := filepath.Join(e.RootPath, "project")
|
|
err := os.Mkdir(projectDir, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
e.CWD = projectDir
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "new", Languages[runtime], "--yes")
|
|
e.RunCommand("pulumi", "import", "--yes", "random:index/randomId:RandomId", "identifier", "p-9hUg")
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION disables plugin acquisition in convert.
|
|
//
|
|
//nolint:paralleltest // changes env vars and plugin cache
|
|
func TestConvertDisableAutomaticPluginAcquisition(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
e.ImportDirectory("testdata/aws_tf")
|
|
|
|
// Delete all cached plugins and disable plugin acquisition.
|
|
e.RunCommand("pulumi", "plugin", "rm", "--all", "--yes")
|
|
// Disable acquisition.
|
|
e.SetEnvVars("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION=true")
|
|
|
|
// This should fail because of no terraform converter
|
|
_, stderr := e.RunCommandExpectError(
|
|
"pulumi", "convert",
|
|
"--language", "pcl", "--from", "terraform", "--out", "out")
|
|
assert.Contains(t, stderr, "no converter plugin 'pulumi-converter-terraform' found")
|
|
|
|
// Install a _specific_ version of the terraform converter (so this test doesn't change due to a new release)
|
|
e.RunCommand("pulumi", "plugin", "install", "converter", "terraform", "v1.0.8")
|
|
// This should now convert, but won't use our full aws tokens
|
|
e.RunCommand(
|
|
"pulumi", "convert",
|
|
"--language", "pcl", "--from", "terraform", "--out", "out")
|
|
|
|
output, err := os.ReadFile(filepath.Join(e.RootPath, "out", "main.pp"))
|
|
require.NoError(t, err)
|
|
// If we had an AWS plugin and mapping this would be "aws:ec2/instance:Instance"
|
|
assert.Contains(t, string(output), "\"aws:index:instance\"")
|
|
}
|
|
|
|
// Small integration test for preview --import-file
|
|
func TestPreviewImportFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
e.ImportDirectory("testdata/import_node")
|
|
|
|
// Make sure random is installed
|
|
e.RunCommand("pulumi", "plugin", "install", "resource", "random", "4.12.0")
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "test")
|
|
e.RunCommand("pulumi", "install")
|
|
e.RunCommand("pulumi", "preview", "--import-file", "import.json")
|
|
|
|
expectedResources := []interface{}{
|
|
map[string]interface{}{
|
|
"id": "<PLACEHOLDER>",
|
|
"name": "username",
|
|
"type": "random:index/randomPet:RandomPet",
|
|
"version": "4.12.0",
|
|
},
|
|
map[string]interface{}{
|
|
"name": "component",
|
|
"type": "pkg:index:MyComponent",
|
|
"component": true,
|
|
},
|
|
map[string]interface{}{
|
|
"id": "<PLACEHOLDER>",
|
|
"logicalName": "username",
|
|
// This isn't ideal, we don't really need to change the "name" here because it isn't used as a
|
|
// parent, but currently we generate unique names for all resources rather than just unique names
|
|
// for all parent resources.
|
|
"name": "usernameRandomPet",
|
|
"type": "random:index/randomPet:RandomPet",
|
|
"version": "4.12.0",
|
|
"parent": "component",
|
|
},
|
|
}
|
|
|
|
importBytes, err := os.ReadFile(filepath.Join(e.CWD, "import.json"))
|
|
require.NoError(t, err)
|
|
var actual map[string]interface{}
|
|
err = json.Unmarshal(importBytes, &actual)
|
|
require.NoError(t, err)
|
|
assert.ElementsMatch(t, expectedResources, actual["resources"])
|
|
_, has := actual["nameTable"]
|
|
assert.False(t, has, "nameTable should not be present in import file")
|
|
}
|
|
|
|
// Small integration test for relative plugin paths. It's hard to do this test via the standard ProgramTest because that
|
|
// framework does it's own manipulation of plugin paths. Regression test for
|
|
// https://github.com/pulumi/pulumi/issues/15467.
|
|
func TestRelativePluginPath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// We can't use ImportDirectory here because we need to run this in the right directory such that the relative paths
|
|
// work.
|
|
var err error
|
|
e.CWD, err = filepath.Abs("testdata/relative_plugin_node")
|
|
require.NoError(t, err)
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "test")
|
|
e.RunCommand("pulumi", "install")
|
|
e.RunCommand("pulumi", "preview")
|
|
}
|
|
|
|
// Quick sanity tests for https://github.com/pulumi/pulumi/issues/16248. Ensure we can run plugins and auto-fetch them.
|
|
//
|
|
//nolint:paralleltest // changes env vars
|
|
func TestPluginRun(t *testing.T) {
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
removeRandomFromLocalPlugins := func() {
|
|
e.RunCommand("pulumi", "plugin", "rm", "resource", "random", "--all", "--yes")
|
|
}
|
|
removeRandomFromLocalPlugins()
|
|
|
|
_, stderr := e.RunCommandExpectError("pulumi", "plugin", "run", "--kind=resource", "random", "--", "--help")
|
|
assert.Contains(t, stderr, "flag: help requested")
|
|
_, stderr = e.RunCommandExpectError("pulumi", "plugin", "run", "--kind=resource", "random", "--", "--help")
|
|
assert.Contains(t, stderr, "flag: help requested")
|
|
}
|
|
|
|
func TestInstall(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, runtime := range Runtimes {
|
|
// Reassign runtime before capture since it changes while looping.
|
|
runtime := runtime
|
|
|
|
t.Run(runtime, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// Make sure the random provider is not installed locally
|
|
// so that we can test the `install` command works.
|
|
out, _ := e.RunCommand("pulumi", "plugin", "ls")
|
|
if strings.Contains(out, "random resource") {
|
|
e.RunCommand("pulumi", "plugin", "rm", "resource", "random", "--all", "--yes")
|
|
}
|
|
|
|
// `new` wants to work in an empty directory but our use of local url means we have a
|
|
// ".pulumi" directory at root.
|
|
projectDir := filepath.Join(e.RootPath, "project")
|
|
err := os.Mkdir(projectDir, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
e.CWD = projectDir
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
// Pass `--generate-only` so dependencies are not installed as part of the `new` command.
|
|
e.RunCommand("pulumi", "new", "random-"+Languages[runtime], "--yes", "--generate-only")
|
|
|
|
// Ensure `install` works and subsequent `up` and `destroy` operations work.
|
|
_, stderr := e.RunCommand("pulumi", "install")
|
|
assert.Regexp(t, regexp.MustCompile(`resource plugin random.+ installing`), stderr)
|
|
e.RunCommand("pulumi", "stack", "init", "test")
|
|
e.RunCommand("pulumi", "up", "--yes")
|
|
e.RunCommand("pulumi", "destroy", "--yes")
|
|
})
|
|
}
|
|
}
|
|
|
|
// A smoke test to ensure that secrets providers are correctly initialized and persisted to state upon stack creation.
|
|
// We check also that when stack configuration exists before stack initialization, any compatible secrets provider
|
|
// configuration is respected and not clobbered or overwritten.
|
|
//
|
|
//nolint:paralleltest // we set environment variables
|
|
func TestSecretsProvidersInitializationSmoke(t *testing.T) {
|
|
// Make sure we can download needed plugins
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
// Ensure we have a passphrase set for the default secrets provider.
|
|
t.Setenv("PULUMI_CONFIG_PASSPHRASE", "test-passphrase")
|
|
|
|
// This example salt must be generated using the test passphrase configured above.
|
|
testEncryptionSalt := "v1:3ZcVRCMzEbk=:v1:A4wYnaSVLIkK0AhS:2SrOnSDh9wVGmoyZt97KYJN3WfDDHA=="
|
|
|
|
cases := []struct {
|
|
name string
|
|
secretsProvider string
|
|
encryptionSalt string
|
|
}{
|
|
{
|
|
name: "default provider with no existing configuration",
|
|
secretsProvider: "",
|
|
encryptionSalt: "",
|
|
},
|
|
{
|
|
name: "default provider with existing configuration",
|
|
secretsProvider: "",
|
|
encryptionSalt: testEncryptionSalt,
|
|
},
|
|
{
|
|
name: "explicit provider with no existing configuration",
|
|
secretsProvider: "passphrase",
|
|
encryptionSalt: "",
|
|
},
|
|
{
|
|
name: "explicit provider with existing configuration",
|
|
secretsProvider: "passphrase",
|
|
encryptionSalt: testEncryptionSalt,
|
|
},
|
|
}
|
|
|
|
for _, runtime := range Runtimes {
|
|
for _, c := range cases {
|
|
c := c
|
|
name := fmt.Sprintf("%s %s", runtime, c.name)
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
//nolint:paralleltest
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
projectDir := filepath.Join(e.RootPath, "project")
|
|
err := os.Mkdir(projectDir, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
projectYAML := filepath.Join(projectDir, "Pulumi.yaml")
|
|
err = os.WriteFile(projectYAML, []byte(fmt.Sprintf(`name: project
|
|
runtime: %s
|
|
backend:
|
|
url: '%s'`,
|
|
runtime,
|
|
e.LocalURL(),
|
|
)), 0o600)
|
|
require.NoError(t, err)
|
|
|
|
stackYAML := filepath.Join(projectDir, "Pulumi.dev.yaml")
|
|
|
|
e.CWD = projectDir
|
|
|
|
// If the test case specifies an encryption salt, we'll write out a stack configuration YAML prior to running
|
|
// `stack init`, so that we can test that anything in that configuration is respected and not overwritten.
|
|
if c.encryptionSalt != "" {
|
|
err = os.WriteFile(stackYAML, []byte("encryptionsalt: "+c.encryptionSalt), 0o600)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
initArgs := []string{"--stack", "organization/project/dev", "stack", "init"}
|
|
if c.secretsProvider != "" {
|
|
initArgs = append(initArgs, "--secrets-provider", c.secretsProvider)
|
|
}
|
|
|
|
e.RunCommand("pulumi", initArgs...)
|
|
|
|
stackYAMLBytes, err := os.ReadFile(stackYAML)
|
|
require.NoError(t, err)
|
|
|
|
ps := workspace.ProjectStack{}
|
|
err = yaml.Unmarshal(stackYAMLBytes, &ps)
|
|
require.NoError(t, err)
|
|
|
|
if c.encryptionSalt != "" {
|
|
require.Equal(t, c.encryptionSalt, ps.EncryptionSalt)
|
|
}
|
|
|
|
stackJSONStr, _ := e.RunCommand("pulumi", "stack", "export")
|
|
stackJSON := apitype.UntypedDeployment{}
|
|
err = json.Unmarshal([]byte(stackJSONStr), &stackJSON)
|
|
require.NoError(t, err)
|
|
|
|
deployment := apitype.DeploymentV3{}
|
|
err = json.Unmarshal(stackJSON.Deployment, &deployment)
|
|
require.NoError(t, err)
|
|
|
|
require.Contains(t, string(deployment.SecretsProviders.State), ps.EncryptionSalt)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// A smoke test to ensure that secrets providers that are persisted to state are
|
|
// used in favour of and to restore stack YAML configuration when it is absent
|
|
// or empty and the PULUMI_FALLBACK_TO_STATE_SECRETS_MANAGER environment variable
|
|
// is set.
|
|
//
|
|
//nolint:paralleltest // pulumi new is not parallel safe, and we set environment variables
|
|
func TestSecretsProvidersFallbackSmoke(t *testing.T) {
|
|
// Make sure we can download needed plugins
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
// Ensure we have a passphrase set for the default secrets provider.
|
|
t.Setenv("PULUMI_CONFIG_PASSPHRASE", "test-passphrase")
|
|
|
|
// Enable secrets manager fallback.
|
|
t.Setenv("PULUMI_FALLBACK_TO_STATE_SECRETS_MANAGER", "true")
|
|
|
|
operations := [][]string{
|
|
{"up", "--yes"},
|
|
{"preview"},
|
|
{"refresh", "--yes"},
|
|
}
|
|
|
|
for _, runtime := range Runtimes {
|
|
t.Run(runtime, func(t *testing.T) {
|
|
//nolint:paralleltest
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer deleteIfNotFailed(e)
|
|
|
|
// `new` wants to work in an empty directory but our use of local url means we have a
|
|
// ".pulumi" directory at root.
|
|
projectDir := filepath.Join(e.RootPath, "project")
|
|
err := os.Mkdir(projectDir, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
e.CWD = projectDir
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "new", "random-"+Languages[runtime], "--yes")
|
|
e.RunCommand("pulumi", "up", "--yes")
|
|
|
|
stackJSONStr, _ := e.RunCommand("pulumi", "stack", "export")
|
|
stackJSON := apitype.UntypedDeployment{}
|
|
err = json.Unmarshal([]byte(stackJSONStr), &stackJSON)
|
|
require.NoError(t, err)
|
|
|
|
deployment := apitype.DeploymentV3{}
|
|
err = json.Unmarshal(stackJSON.Deployment, &deployment)
|
|
require.NoError(t, err)
|
|
|
|
for _, operation := range operations {
|
|
os.Remove(filepath.Join(projectDir, "Pulumi.dev.yaml"))
|
|
e.RunCommand("pulumi", operation...)
|
|
|
|
stackYamlStr, err := os.ReadFile(filepath.Join(projectDir, "Pulumi.dev.yaml"))
|
|
require.NoError(t, err)
|
|
|
|
stack := workspace.ProjectStack{}
|
|
err = yaml.Unmarshal(stackYamlStr, &stack)
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, stack.EncryptionSalt)
|
|
require.Contains(t, string(deployment.SecretsProviders.State), stack.EncryptionSalt)
|
|
}
|
|
|
|
e.RunCommand("pulumi", "destroy", "--yes")
|
|
})
|
|
}
|
|
}
|