mirror of https://github.com/pulumi/pulumi.git
921 lines
37 KiB
Go
921 lines
37 KiB
Go
// Copyright 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 (
|
|
"embed"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/pulumi/pulumi/cmd/pulumi-test-language/providers"
|
|
"github.com/pulumi/pulumi/pkg/v3/display"
|
|
"github.com/pulumi/pulumi/pkg/v3/engine"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// lorem is a long string used for testing large string values.
|
|
const lorem string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit," +
|
|
" sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." +
|
|
" Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." +
|
|
" Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
|
|
" Excepteur sint occaecat cupidatat non proident," +
|
|
" sunt in culpa qui officia deserunt mollit anim id est laborum."
|
|
|
|
//go:embed testdata
|
|
var LanguageTestdata embed.FS
|
|
|
|
var LanguageTests = map[string]LanguageTest{
|
|
// ==========
|
|
// L2 (Tests using providers)
|
|
// ==========
|
|
"l2-explicit-provider": {
|
|
Providers: []plugin.Provider{&providers.SimpleProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
|
|
// Check we have the one simple resource in the snapshot, its provider and the stack.
|
|
require.Len(l, snap.Resources, 3, "expected 3 resources in snapshot")
|
|
|
|
provider := snap.Resources[1]
|
|
assert.Equal(l, "pulumi:providers:simple", provider.Type.String(), "expected simple provider")
|
|
assert.Equal(l, "prov", provider.URN.Name(), "expected explicit provider resource")
|
|
|
|
simple := snap.Resources[2]
|
|
assert.Equal(l, "simple:index:Resource", simple.Type.String(), "expected simple resource")
|
|
assert.Equal(l, string(provider.URN)+"::"+string(provider.ID), simple.Provider)
|
|
|
|
want := resource.NewPropertyMapFromMap(map[string]any{"value": true})
|
|
assert.Equal(l, want, simple.Inputs, "expected inputs to be {value: true}")
|
|
assert.Equal(l, simple.Inputs, simple.Outputs, "expected inputs and outputs to match")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-engine-update-options": {
|
|
Providers: []plugin.Provider{&providers.SimpleProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
UpdateOptions: engine.UpdateOptions{
|
|
Targets: deploy.NewUrnTargets([]string{
|
|
"**target**",
|
|
}),
|
|
},
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
require.Len(l, snap.Resources, 3, "expected 2 resource in snapshot")
|
|
|
|
// Check that we have the target in the snapshot, but not the other resource.
|
|
stack := snap.Resources[0]
|
|
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
|
|
provider := snap.Resources[1]
|
|
assert.Equal(l, "pulumi:providers:simple", provider.Type.String(), "expected simple provider")
|
|
target := snap.Resources[2]
|
|
require.Equal(l, "simple:index:Resource", target.Type.String(), "expected simple resource")
|
|
require.Equal(l, "target", target.URN.Name(), "expected target resource")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-destroy": {
|
|
Providers: []plugin.Provider{&providers.SimpleProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
require.Len(l, snap.Resources, 4, "expected 4 resources in snapshot")
|
|
|
|
// check that both expected resources are in the snapshot
|
|
provider := snap.Resources[1]
|
|
assert.Equal(l, "pulumi:providers:simple", provider.Type.String(), "expected simple provider")
|
|
|
|
// Make sure we can assert the resource names in a consistent order
|
|
sort.Slice(snap.Resources[2:4], func(i, j int) bool {
|
|
i = i + 2
|
|
j = j + 2
|
|
return snap.Resources[i].URN.Name() < snap.Resources[j].URN.Name()
|
|
})
|
|
|
|
simple := snap.Resources[2]
|
|
assert.Equal(l, "simple:index:Resource", simple.Type.String(), "expected simple resource")
|
|
assert.Equal(l, "aresource", simple.URN.Name(), "expected aresource resource")
|
|
simple2 := snap.Resources[3]
|
|
assert.Equal(l, "simple:index:Resource", simple2.Type.String(), "expected simple resource")
|
|
assert.Equal(l, "other", simple2.URN.Name(), "expected other resource")
|
|
},
|
|
},
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
assert.Equal(l, 1, changes[deploy.OpDelete], "expected a delete operation")
|
|
require.Len(l, snap.Resources, 3, "expected 3 resources in snapshot")
|
|
|
|
// No need to sort here, since we have only resources that depend on each other in a chain.
|
|
provider := snap.Resources[1]
|
|
assert.Equal(l, "pulumi:providers:simple", provider.Type.String(), "expected simple provider")
|
|
// check that only the expected resource is left in the snapshot
|
|
simple := snap.Resources[2]
|
|
assert.Equal(l, "simple:index:Resource", simple.Type.String(), "expected simple resource")
|
|
assert.Equal(l, "aresource", simple.URN.Name(), "expected aresource resource")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-target-up-with-new-dependency": {
|
|
Providers: []plugin.Provider{&providers.SimpleProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
require.Len(l, snap.Resources, 4, "expected 4 resources in snapshot")
|
|
err = snap.VerifyIntegrity()
|
|
require.NoError(l, err, "expected snapshot to be valid")
|
|
|
|
sort.Slice(snap.Resources, func(i, j int) bool {
|
|
return snap.Resources[i].URN.Name() < snap.Resources[j].URN.Name()
|
|
})
|
|
|
|
target := snap.Resources[2]
|
|
require.Equal(l, "simple:index:Resource", target.Type.String(), "expected simple resource")
|
|
require.Equal(l, "targetOnly", target.URN.Name(), "expected target resource")
|
|
unrelated := snap.Resources[3]
|
|
require.Equal(l, "simple:index:Resource", unrelated.Type.String(), "expected simple resource")
|
|
require.Equal(l, "unrelated", unrelated.URN.Name(), "expected target resource")
|
|
require.Equal(l, 0, len(unrelated.Dependencies), "expected no dependencies")
|
|
},
|
|
},
|
|
{
|
|
UpdateOptions: engine.UpdateOptions{
|
|
Targets: deploy.NewUrnTargets([]string{
|
|
"**targetOnly**",
|
|
}),
|
|
},
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
require.Len(l, snap.Resources, 4, "expected 4 resources in snapshot")
|
|
|
|
sort.Slice(snap.Resources, func(i, j int) bool {
|
|
return snap.Resources[i].URN.Name() < snap.Resources[j].URN.Name()
|
|
})
|
|
|
|
target := snap.Resources[2]
|
|
require.Equal(l, "simple:index:Resource", target.Type.String(), "expected simple resource")
|
|
require.Equal(l, "targetOnly", target.URN.Name(), "expected target resource")
|
|
unrelated := snap.Resources[3]
|
|
require.Equal(l, "simple:index:Resource", unrelated.Type.String(), "expected simple resource")
|
|
require.Equal(l, "unrelated", unrelated.URN.Name(), "expected target resource")
|
|
require.Equal(l, 0, len(unrelated.Dependencies), "expected still no dependencies")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-failed-create-continue-on-error": {
|
|
Providers: []plugin.Provider{&providers.SimpleProvider{}, &providers.FailOnCreateProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
UpdateOptions: engine.UpdateOptions{
|
|
ContinueOnError: true,
|
|
},
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
require.True(l, result.IsBail(err), "expected a bail result")
|
|
require.Equal(l, 1, len(changes), "expected 1 StepOp")
|
|
require.Equal(l, 2, changes[deploy.OpCreate], "expected 2 Creates")
|
|
require.NotNil(l, snap, "expected snapshot to be non-nil")
|
|
require.Len(l, snap.Resources, 4, "expected 4 resources in snapshot") // 1 stack, 2 providers, 1 resource
|
|
require.NoError(l, snap.VerifyIntegrity(), "expected snapshot to be valid")
|
|
|
|
sort.Slice(snap.Resources, func(i, j int) bool {
|
|
return snap.Resources[i].URN.Name() < snap.Resources[j].URN.Name()
|
|
})
|
|
|
|
require.Equal(l, "independent", snap.Resources[2].URN.Name(), "expected independent resource")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-large-string": {
|
|
Providers: []plugin.Provider{&providers.LargeProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
require.Len(l, snap.Resources, 3, "expected 3 resources in snapshot")
|
|
|
|
// Check that the large string is in the snapshot
|
|
largeString := resource.NewStringProperty(strings.Repeat("hello world", 9532509))
|
|
large := snap.Resources[2]
|
|
require.Equal(l, "large:index:String", large.Type.String(), "expected large string resource")
|
|
require.Equal(l,
|
|
resource.NewStringProperty("hello world"),
|
|
large.Inputs["value"],
|
|
)
|
|
require.Equal(l,
|
|
largeString,
|
|
large.Outputs["value"],
|
|
)
|
|
|
|
// Check the stack output value is as well
|
|
stack := snap.Resources[0]
|
|
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
|
|
require.Equal(l, largeString, stack.Outputs["output"], "expected large string stack output")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-provider-grpc-config": {
|
|
Providers: []plugin.Provider{&providers.ConfigGrpcProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
g := &grpcTestContext{l: l, s: snap}
|
|
|
|
r := g.CheckConfigReq("config")
|
|
assert.Equal(l, "", r.News.Fields["string1"].AsInterface(), "string1")
|
|
assert.Equal(l, "x", r.News.Fields["string2"].AsInterface(), "string2")
|
|
assert.Equal(l, "{}", r.News.Fields["string3"].AsInterface(), "string3")
|
|
AssertEqualOrJSONEncoded(l, float64(0), r.News.Fields["int1"].AsInterface(), "int1")
|
|
AssertEqualOrJSONEncoded(l, float64(42), r.News.Fields["int2"].AsInterface(), "int2")
|
|
AssertEqualOrJSONEncoded(l, float64(0), r.News.Fields["num1"].AsInterface(), "num1")
|
|
AssertEqualOrJSONEncoded(l, float64(42.42), r.News.Fields["num2"].AsInterface(), "num2")
|
|
AssertEqualOrJSONEncoded(l, true, r.News.Fields["bool1"].AsInterface(), "bool1")
|
|
AssertEqualOrJSONEncoded(l, false, r.News.Fields["bool2"].AsInterface(), "bool2")
|
|
AssertEqualOrJSONEncoded(l, []any{}, r.News.Fields["listString1"].AsInterface(), "listString1")
|
|
AssertEqualOrJSONEncoded(l, []any{"", "foo"}, r.News.Fields["listString2"].AsInterface(), "listString2")
|
|
AssertEqualOrJSONEncoded(l,
|
|
[]any{float64(1), float64(2)},
|
|
r.News.Fields["listInt1"].AsInterface(), "listInt1")
|
|
|
|
AssertEqualOrJSONEncoded(l, map[string]any{}, r.News.Fields["mapString1"].AsInterface(), "mapString1")
|
|
|
|
AssertEqualOrJSONEncoded(l,
|
|
map[string]any{"key1": "value1", "key2": "value2"},
|
|
r.News.Fields["mapString2"].AsInterface(), "mapString2")
|
|
|
|
AssertEqualOrJSONEncoded(l,
|
|
map[string]any{"key1": float64(0), "key2": float64(42)},
|
|
r.News.Fields["mapInt1"].AsInterface(), "mapInt1")
|
|
|
|
AssertEqualOrJSONEncoded(l, map[string]any{}, r.News.Fields["objString1"].AsInterface(), "objString1")
|
|
|
|
AssertEqualOrJSONEncoded(l, map[string]any{"x": "x-value"},
|
|
r.News.Fields["objString2"].AsInterface(), "objString2")
|
|
|
|
AssertEqualOrJSONEncoded(l,
|
|
map[string]any{"x": float64(42)},
|
|
r.News.Fields["objInt1"].AsInterface(), "objInt1")
|
|
|
|
// Check what schemaprov received in ConfigureRequest.
|
|
c := g.ConfigureReq("config")
|
|
assert.Equal(l, "", c.Args.Fields["string1"].AsInterface(), "string1")
|
|
assert.Equal(l, "x", c.Args.Fields["string2"].AsInterface(), "string2")
|
|
assert.Equal(l, "{}", c.Args.Fields["string3"].AsInterface(), "string3")
|
|
AssertEqualOrJSONEncoded(l, float64(0), c.Args.Fields["int1"].AsInterface(), "int1")
|
|
AssertEqualOrJSONEncoded(l, float64(42), c.Args.Fields["int2"].AsInterface(), "int2")
|
|
AssertEqualOrJSONEncoded(l, float64(0), c.Args.Fields["num1"].AsInterface(), "num1")
|
|
AssertEqualOrJSONEncoded(l, float64(42.42), c.Args.Fields["num2"].AsInterface(), "num2")
|
|
AssertEqualOrJSONEncoded(l, true, c.Args.Fields["bool1"].AsInterface(), "bool1")
|
|
AssertEqualOrJSONEncoded(l, false, c.Args.Fields["bool2"].AsInterface(), "bool2")
|
|
AssertEqualOrJSONEncoded(l, []any{}, c.Args.Fields["listString1"].AsInterface(), "listString1")
|
|
AssertEqualOrJSONEncoded(l, []any{"", "foo"}, c.Args.Fields["listString2"].AsInterface(), "listString2")
|
|
AssertEqualOrJSONEncoded(l,
|
|
[]any{float64(1), float64(2)},
|
|
c.Args.Fields["listInt1"].AsInterface(), "listInt1")
|
|
|
|
AssertEqualOrJSONEncoded(l, map[string]any{}, c.Args.Fields["mapString1"].AsInterface(), "mapString1")
|
|
|
|
AssertEqualOrJSONEncoded(l,
|
|
map[string]any{"key1": "value1", "key2": "value2"},
|
|
c.Args.Fields["mapString2"].AsInterface(), "mapString2")
|
|
|
|
AssertEqualOrJSONEncoded(l,
|
|
map[string]any{"key1": float64(0), "key2": float64(42)},
|
|
c.Args.Fields["mapInt1"].AsInterface(), "mapInt1")
|
|
|
|
AssertEqualOrJSONEncoded(l, map[string]any{}, c.Args.Fields["objString1"].AsInterface(), "objString1")
|
|
|
|
AssertEqualOrJSONEncoded(l, map[string]any{"x": "x-value"},
|
|
c.Args.Fields["objString2"].AsInterface(), "objString2")
|
|
|
|
AssertEqualOrJSONEncoded(l,
|
|
map[string]any{"x": float64(42)},
|
|
c.Args.Fields["objInt1"].AsInterface(), "objInt1")
|
|
|
|
v := c.GetVariables()
|
|
assert.Equal(l, "", v["config-grpc:config:string1"], "string1")
|
|
assert.Equal(l, "x", v["config-grpc:config:string2"], "string2")
|
|
assert.Equal(l, "{}", v["config-grpc:config:string3"], "string3")
|
|
assert.Equal(l, "0", v["config-grpc:config:int1"], "int1")
|
|
assert.Equal(l, "42", v["config-grpc:config:int2"], "int2")
|
|
assert.Equal(l, "0", v["config-grpc:config:num1"], "num1")
|
|
assert.Equal(l, "42.42", v["config-grpc:config:num2"], "num2")
|
|
assert.Equal(l, "true", v["config-grpc:config:bool1"], "bool1")
|
|
assert.Equal(l, "false", v["config-grpc:config:bool2"], "bool2")
|
|
assert.JSONEq(l, "[]", v["config-grpc:config:listString1"], "listString1")
|
|
assert.JSONEq(l, "[\"\",\"foo\"]", v["config-grpc:config:listString2"], "listString2")
|
|
assert.JSONEq(l, "[1,2]", v["config-grpc:config:listInt1"], "listInt1")
|
|
assert.JSONEq(l, "{}", v["config-grpc:config:mapString1"], "mapString1")
|
|
assert.JSONEq(l, "{\"key1\":\"value1\",\"key2\":\"value2\"}", v["config-grpc:config:mapString2"], "mapString2")
|
|
assert.JSONEq(l, "{\"key1\":0,\"key2\":42}", v["config-grpc:config:mapInt1"], "mapInt1")
|
|
assert.JSONEq(l, "{}", v["config-grpc:config:objString1"], "objString1")
|
|
assert.JSONEq(l, "{\"x\":\"x-value\"}", v["config-grpc:config:objString2"], "objString2")
|
|
assert.JSONEq(l, "{\"x\":42}", v["config-grpc:config:objInt1"], "objInt1")
|
|
|
|
AssertNoSecretLeaks(l, snap, AssertNoSecretLeaksOpts{
|
|
// ConfigFetcher is a test helper that retains secret material in its
|
|
// state by design, and should not be part of the check.
|
|
IgnoreResourceTypes: []tokens.Type{"config-grpc:index:ConfigFetcher"},
|
|
Secrets: []string{"SECRET", "SECRET2"},
|
|
})
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// Looks like in the test setup, proper partitioning of provider space is not yet working and Configure calls
|
|
// race with Create calls when talking to a provider. It makes it too difficult to test more than one explicit
|
|
// provider per test case. To compensate, more test cases are added.
|
|
"l2-provider-grpc-config-secret": {
|
|
// Check what schemaprov received in CheckRequest.
|
|
Providers: []plugin.Provider{&providers.ConfigGrpcProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
g := &grpcTestContext{l: l, s: snap}
|
|
|
|
// Now check first-class secrets for programsecretprov.
|
|
r := g.CheckConfigReq("config")
|
|
|
|
// These asserts do not look right, but are based on Go behavior. Should SECRET
|
|
// be wrapped in secret tags instead when passing to CheckConfig? Or not?
|
|
assert.Equal(l, "SECRET", r.News.Fields["string1"].AsInterface(), "string1")
|
|
AssertEqualOrJSONEncoded(l, float64(1234567890), r.News.Fields["int1"].AsInterface(), "int1")
|
|
AssertEqualOrJSONEncoded(l, float64(123456.789), r.News.Fields["num1"].AsInterface(), "num1")
|
|
AssertEqualOrJSONEncoded(l, true, r.News.Fields["bool1"].AsInterface(), "bool1")
|
|
AssertEqualOrJSONEncoded(l, []any{"SECRET", "SECRET2"},
|
|
r.News.Fields["listString1"].AsInterface(), "listString1")
|
|
AssertEqualOrJSONEncoded(l, []any{"VALUE", "SECRET"},
|
|
r.News.Fields["listString2"].AsInterface(), "listString2")
|
|
AssertEqualOrJSONEncoded(l, map[string]any{"key1": "value1", "key2": "SECRET"},
|
|
r.News.Fields["mapString2"].AsInterface(), "mapString2")
|
|
AssertEqualOrJSONEncoded(l, map[string]any{"x": "SECRET"},
|
|
r.News.Fields["objString2"].AsInterface(), "objString2")
|
|
|
|
// The secret versions have two options, JSON-encoded or not. Languages do not
|
|
// agree yet on which form to use.
|
|
c := g.ConfigureReq("config")
|
|
assert.Equal(l, Secret("SECRET"), c.Args.Fields["string1"].AsInterface(), "string1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret(float64(1234567890)),
|
|
float64(1234567890),
|
|
c.Args.Fields["int1"].AsInterface(), "int1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret(float64(123456.789)),
|
|
float64(123456.789),
|
|
c.Args.Fields["num1"].AsInterface(), "num1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret(true),
|
|
true,
|
|
c.Args.Fields["bool1"].AsInterface(), "bool1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret([]any{"SECRET", "SECRET2"}),
|
|
[]any{"SECRET", "SECRET2"},
|
|
c.Args.Fields["listString1"].AsInterface(), "listString1")
|
|
|
|
// Secret floating happened here, perhaps []any{"VALUE", Secret("SECRET")}
|
|
// would be preferable instead at some point.
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret([]any{"VALUE", "SECRET"}),
|
|
[]any{"VALUE", "SECRET"},
|
|
c.Args.Fields["listString2"].AsInterface(), "listString2")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
map[string]any{"key1": "value1", "key2": Secret("SECRET")},
|
|
map[string]any{"key1": "value1", "key2": "SECRET"},
|
|
c.Args.Fields["mapString2"].AsInterface(), "mapString2")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
map[string]any{"x": Secret("SECRET")},
|
|
map[string]any{"x": "SECRET"},
|
|
c.Args.Fields["objString2"].AsInterface(), "objString2")
|
|
|
|
// Secretness is not exposed in GetVariables. Instead the data is JSON-encoded.
|
|
v := c.GetVariables()
|
|
assert.Equal(l, "SECRET", v["config-grpc:config:string1"], "string1")
|
|
assert.Equal(l, "1234567890", v["config-grpc:config:int1"], "int1")
|
|
assert.Equal(l, "123456.789", v["config-grpc:config:num1"], "num1")
|
|
assert.Equal(l, "true", v["config-grpc:config:bool1"], "bool1")
|
|
assert.JSONEq(l, "[\"SECRET\",\"SECRET2\"]", v["config-grpc:config:listString1"], "listString1")
|
|
assert.JSONEq(l, "[\"VALUE\",\"SECRET\"]", v["config-grpc:config:listString2"], "listString2")
|
|
assert.JSONEq(l, "{\"key1\":\"value1\",\"key2\":\"SECRET\"}", v["config-grpc:config:mapString2"], "mapString2")
|
|
assert.JSONEq(l, "{\"x\":\"SECRET\"}", v["config-grpc:config:objString2"], "objString2")
|
|
|
|
AssertNoSecretLeaks(l, snap, AssertNoSecretLeaksOpts{
|
|
// ConfigFetcher is a test helper that retains secret material in its
|
|
// state by design, and should not be part of the check.
|
|
IgnoreResourceTypes: []tokens.Type{"config-grpc:index:ConfigFetcher"},
|
|
Secrets: []string{"SECRET", "SECRET2"},
|
|
})
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// This test checks how SDKs propagate properties marked as secret to the provider Configure on the gRPC level.
|
|
"l2-provider-grpc-config-schema-secret": {
|
|
Providers: []plugin.Provider{&providers.ConfigGrpcProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
g := &grpcTestContext{l: l, s: snap}
|
|
|
|
// Verify the CheckConfig request received by the provider.
|
|
r := g.CheckConfigReq("config")
|
|
|
|
// TODO[pulumi/pulumi#16876]: CheckConfig request gets the secrets in the plain.
|
|
// This is suspect, probably has to do with secret negotiation happening later
|
|
// in the gRPC provider cycle.
|
|
assert.Equal(l, "SECRET",
|
|
r.News.Fields["secretString1"].AsInterface(), "secretString1")
|
|
|
|
AssertEqualOrJSONEncoded(l, float64(16),
|
|
r.News.Fields["secretInt1"].AsInterface(), "secretInt1")
|
|
|
|
AssertEqualOrJSONEncoded(l, float64(123456.7890),
|
|
r.News.Fields["secretNum1"].AsInterface(), "secretNum1")
|
|
|
|
AssertEqualOrJSONEncoded(l, true,
|
|
r.News.Fields["secretBool1"].AsInterface(), "secretBool1")
|
|
|
|
AssertEqualOrJSONEncoded(l, []any{"SECRET", "SECRET2"},
|
|
r.News.Fields["listSecretString1"].AsInterface(), "listSecretString1")
|
|
|
|
AssertEqualOrJSONEncoded(l, map[string]any{"key1": "SECRET", "key2": "SECRET2"},
|
|
r.News.Fields["mapSecretString1"].AsInterface(), "mapSecretString1")
|
|
|
|
// Now verify the Configure request.
|
|
c := g.ConfigureReq("config")
|
|
|
|
// All the fields are coming in as secret-wrapped fields into Configure.
|
|
assert.Equal(l, Secret("SECRET"),
|
|
c.Args.Fields["secretString1"].AsInterface(), "secretString1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret(float64(16)), float64(16),
|
|
c.Args.Fields["secretInt1"].AsInterface(), "secretInt1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret(float64(123456.7890)), float64(123456.7890),
|
|
c.Args.Fields["secretNum1"].AsInterface(), "secretNum1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret(true), true,
|
|
c.Args.Fields["secretBool1"].AsInterface(), "secretBool1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret([]any{"SECRET", "SECRET2"}),
|
|
[]any{"SECRET", "SECRET2"},
|
|
c.Args.Fields["listSecretString1"].AsInterface(), "listSecretString1")
|
|
|
|
AssertEqualOrJSONEncodedSecret(l,
|
|
Secret(map[string]any{"key1": "SECRET", "key2": "SECRET2"}),
|
|
map[string]any{"key1": "SECRET", "key2": "SECRET2"},
|
|
c.Args.Fields["mapSecretString1"].AsInterface(), "mapSecretString1")
|
|
|
|
// Secretness is not exposed in GetVariables. Instead the data is JSON-encoded.
|
|
v := c.GetVariables()
|
|
assert.Equal(l, "SECRET", v["config-grpc:config:secretString1"], "secretString1")
|
|
assert.JSONEq(l, "16", v["config-grpc:config:secretInt1"], "secretInt1")
|
|
assert.JSONEq(l, "123456.7890", v["config-grpc:config:secretNum1"], "secretNum1")
|
|
assert.JSONEq(l, "true", v["config-grpc:config:secretBool1"], "secretBool1")
|
|
assert.JSONEq(l, `["SECRET", "SECRET2"]`, v["config-grpc:config:listSecretString1"], "listSecretString1")
|
|
|
|
assert.JSONEq(l, `{"key1":"SECRET","key2":"SECRET2"}`,
|
|
v["config-grpc:config:mapSecretString1"], "mapSecretString1")
|
|
|
|
// TODO[pulumi/pulumi#17652] Languages do not agree on the object property
|
|
// casing sent to CheckConfig, Node and Go send "secretX", Python sends
|
|
// "secret_x" though.
|
|
//
|
|
// AssertEqualOrJSONEncoded(l, map[string]any{"secretX": "SECRET"},
|
|
// r.News.Fields["objSecretString1"].AsInterface(), "objSecretString1")
|
|
// AssertEqualOrJSONEncodedSecret(l,
|
|
// map[string]any{"secretX": Secret("SECRET")}, map[string]any{"secretX": "SECRET"},
|
|
// r.Args.Fields["objSecretString1"].AsInterface(), "objSecretString1")
|
|
// assert.JSONEq(l, `{"secretX":"SECRET"}`,
|
|
// v["config-grpc:config:objectSecretString1"], "objSecretString1")
|
|
|
|
AssertNoSecretLeaks(l, snap, AssertNoSecretLeaksOpts{
|
|
// ConfigFetcher is a test helper that retains secret material in its
|
|
// state by design, and should not be part of the check.
|
|
IgnoreResourceTypes: []tokens.Type{"config-grpc:index:ConfigFetcher"},
|
|
Secrets: []string{"SECRET", "SECRET2"},
|
|
})
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-primitive-ref": {
|
|
Providers: []plugin.Provider{&providers.PrimitiveRefProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
|
|
// Check we have the one simple resource in the snapshot, its provider and the stack.
|
|
require.Len(l, snap.Resources, 3, "expected 3 resources in snapshot")
|
|
|
|
provider := snap.Resources[1]
|
|
assert.Equal(l, "pulumi:providers:primitive-ref", provider.Type.String(), "expected primitive-ref provider")
|
|
|
|
simple := snap.Resources[2]
|
|
assert.Equal(l, "primitive-ref:index:Resource", simple.Type.String(), "expected primitive-ref resource")
|
|
|
|
want := resource.NewPropertyMapFromMap(map[string]any{
|
|
"data": resource.NewPropertyMapFromMap(map[string]any{
|
|
"boolean": false,
|
|
"float": 2.17,
|
|
"integer": -12,
|
|
"string": "Goodbye",
|
|
"boolArray": []interface{}{false, true},
|
|
"stringMap": map[string]interface{}{
|
|
"two": "turtle doves",
|
|
"three": "french hens",
|
|
},
|
|
}),
|
|
})
|
|
assert.Equal(l, want, simple.Inputs, "expected inputs to be %v", want)
|
|
assert.Equal(l, simple.Inputs, simple.Outputs, "expected inputs and outputs to match")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-ref-ref": {
|
|
Providers: []plugin.Provider{&providers.RefRefProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
|
|
// Check we have the one simple resource in the snapshot, its provider and the stack.
|
|
require.Len(l, snap.Resources, 3, "expected 3 resources in snapshot")
|
|
|
|
provider := snap.Resources[1]
|
|
assert.Equal(l, "pulumi:providers:ref-ref", provider.Type.String(), "expected ref-ref provider")
|
|
|
|
simple := snap.Resources[2]
|
|
assert.Equal(l, "ref-ref:index:Resource", simple.Type.String(), "expected ref-ref resource")
|
|
|
|
want := resource.NewPropertyMapFromMap(map[string]any{
|
|
"data": resource.NewPropertyMapFromMap(map[string]any{
|
|
"innerData": resource.NewPropertyMapFromMap(map[string]any{
|
|
"boolean": false,
|
|
"float": 2.17,
|
|
"integer": -12,
|
|
"string": "Goodbye",
|
|
"boolArray": []interface{}{false, true},
|
|
"stringMap": map[string]interface{}{
|
|
"two": "turtle doves",
|
|
"three": "french hens",
|
|
},
|
|
}),
|
|
"boolean": true,
|
|
"float": 4.5,
|
|
"integer": 1024,
|
|
"string": "Hello",
|
|
"boolArray": []interface{}{},
|
|
"stringMap": map[string]interface{}{
|
|
"x": "100",
|
|
"y": "200",
|
|
},
|
|
}),
|
|
})
|
|
assert.Equal(l, want, simple.Inputs, "expected inputs to be %v", want)
|
|
assert.Equal(l, simple.Inputs, simple.Outputs, "expected inputs and outputs to match")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-plain": {
|
|
Providers: []plugin.Provider{&providers.PlainProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
|
|
// Check we have the one simple resource in the snapshot, its provider and the stack.
|
|
require.Len(l, snap.Resources, 3, "expected 3 resources in snapshot")
|
|
|
|
provider := snap.Resources[1]
|
|
assert.Equal(l, "pulumi:providers:plain", provider.Type.String(), "expected plain provider")
|
|
|
|
plain := snap.Resources[2]
|
|
assert.Equal(l, "plain:index:Resource", plain.Type.String(), "expected plain resource")
|
|
|
|
want := resource.NewPropertyMapFromMap(map[string]any{
|
|
"data": resource.NewPropertyMapFromMap(map[string]any{
|
|
"innerData": resource.NewPropertyMapFromMap(map[string]any{
|
|
"boolean": false,
|
|
"float": 2.17,
|
|
"integer": -12,
|
|
"string": "Goodbye",
|
|
"boolArray": []interface{}{false, true},
|
|
"stringMap": map[string]interface{}{
|
|
"two": "turtle doves",
|
|
"three": "french hens",
|
|
},
|
|
}),
|
|
"boolean": true,
|
|
"float": 4.5,
|
|
"integer": 1024,
|
|
"string": "Hello",
|
|
"boolArray": []interface{}{true, false},
|
|
"stringMap": map[string]interface{}{
|
|
"x": "100",
|
|
"y": "200",
|
|
},
|
|
}),
|
|
})
|
|
assert.Equal(l, want, plain.Inputs, "expected inputs to be %v", want)
|
|
assert.Equal(l, plain.Inputs, plain.Outputs, "expected inputs and outputs to match")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-parameterized-resource": {
|
|
Providers: []plugin.Provider{&providers.ParameterizedProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
stack := snap.Resources[0]
|
|
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
|
|
require.Equal(l,
|
|
resource.NewStringProperty("HelloWorld"),
|
|
stack.Outputs["parameterValue"],
|
|
"parameter value should be correct")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-map-keys": {
|
|
Providers: []plugin.Provider{
|
|
&providers.PrimitiveProvider{}, &providers.PrimitiveRefProvider{},
|
|
&providers.RefRefProvider{}, &providers.PlainProvider{},
|
|
},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
|
|
require.Len(l, snap.Resources, 9, "expected 9 resources in snapshot")
|
|
|
|
RequireSingleResource(l, snap.Resources, "pulumi:providers:primitive")
|
|
primResource := RequireSingleResource(l, snap.Resources, "primitive:index:Resource")
|
|
RequireSingleResource(l, snap.Resources, "pulumi:providers:primitive-ref")
|
|
refResource := RequireSingleResource(l, snap.Resources, "primitive-ref:index:Resource")
|
|
RequireSingleResource(l, snap.Resources, "pulumi:providers:ref-ref")
|
|
rrefResource := RequireSingleResource(l, snap.Resources, "ref-ref:index:Resource")
|
|
RequireSingleResource(l, snap.Resources, "pulumi:providers:plain")
|
|
plainResource := RequireSingleResource(l, snap.Resources, "plain:index:Resource")
|
|
|
|
want := resource.NewPropertyMapFromMap(map[string]any{
|
|
"boolean": false,
|
|
"float": 2.17,
|
|
"integer": -12,
|
|
"string": "Goodbye",
|
|
"numberArray": []interface{}{0, 1},
|
|
"booleanMap": map[string]interface{}{
|
|
"my key": false,
|
|
"my.key": true,
|
|
"my-key": false,
|
|
"my_key": true,
|
|
"MY_KEY": false,
|
|
"myKey": true,
|
|
},
|
|
})
|
|
assert.Equal(l, want, primResource.Inputs, "expected inputs to be %v", want)
|
|
assert.Equal(l, primResource.Inputs, primResource.Outputs, "expected inputs and outputs to match")
|
|
|
|
want = resource.NewPropertyMapFromMap(map[string]any{
|
|
"data": resource.NewPropertyMapFromMap(map[string]any{
|
|
"boolean": false,
|
|
"float": 2.17,
|
|
"integer": -12,
|
|
"string": "Goodbye",
|
|
"boolArray": []interface{}{false, true},
|
|
"stringMap": map[string]interface{}{
|
|
"my key": "one",
|
|
"my.key": "two",
|
|
"my-key": "three",
|
|
"my_key": "four",
|
|
"MY_KEY": "five",
|
|
"myKey": "six",
|
|
},
|
|
}),
|
|
})
|
|
assert.Equal(l, want, refResource.Inputs, "expected inputs to be %v", want)
|
|
assert.Equal(l, refResource.Inputs, refResource.Outputs, "expected inputs and outputs to match")
|
|
|
|
want = resource.NewPropertyMapFromMap(map[string]any{
|
|
"data": resource.NewPropertyMapFromMap(map[string]any{
|
|
"innerData": resource.NewPropertyMapFromMap(map[string]any{
|
|
"boolean": false,
|
|
"float": -2.17,
|
|
"integer": 123,
|
|
"string": "Goodbye",
|
|
"boolArray": []interface{}{},
|
|
"stringMap": map[string]interface{}{
|
|
"my key": "one",
|
|
"my.key": "two",
|
|
"my-key": "three",
|
|
"my_key": "four",
|
|
"MY_KEY": "five",
|
|
"myKey": "six",
|
|
},
|
|
}),
|
|
"boolean": true,
|
|
"float": 4.5,
|
|
"integer": 1024,
|
|
"string": "Hello",
|
|
"boolArray": []interface{}{},
|
|
"stringMap": map[string]interface{}{
|
|
"my key": "one",
|
|
"my.key": "two",
|
|
"my-key": "three",
|
|
"my_key": "four",
|
|
"MY_KEY": "five",
|
|
"myKey": "six",
|
|
},
|
|
}),
|
|
})
|
|
assert.Equal(l, want, rrefResource.Inputs, "expected inputs to be %v", want)
|
|
assert.Equal(l, rrefResource.Inputs, rrefResource.Outputs, "expected inputs and outputs to match")
|
|
|
|
want = resource.NewPropertyMapFromMap(map[string]any{
|
|
"data": resource.NewPropertyMapFromMap(map[string]any{
|
|
"innerData": resource.NewPropertyMapFromMap(map[string]any{
|
|
"boolean": false,
|
|
"float": 2.17,
|
|
"integer": -12,
|
|
"string": "Goodbye",
|
|
"boolArray": []interface{}{false, true},
|
|
"stringMap": map[string]interface{}{
|
|
"my key": "one",
|
|
"my.key": "two",
|
|
"my-key": "three",
|
|
"my_key": "four",
|
|
"MY_KEY": "five",
|
|
"myKey": "six",
|
|
},
|
|
}),
|
|
"boolean": true,
|
|
"float": 4.5,
|
|
"integer": 1024,
|
|
"string": "Hello",
|
|
"boolArray": []interface{}{true, false},
|
|
"stringMap": map[string]interface{}{
|
|
"my key": "one",
|
|
"my.key": "two",
|
|
"my-key": "three",
|
|
"my_key": "four",
|
|
"MY_KEY": "five",
|
|
"myKey": "six",
|
|
},
|
|
}),
|
|
"nonPlainData": resource.NewPropertyMapFromMap(map[string]any{
|
|
"innerData": resource.NewPropertyMapFromMap(map[string]any{
|
|
"boolean": false,
|
|
"float": 2.17,
|
|
"integer": -12,
|
|
"string": "Goodbye",
|
|
"boolArray": []interface{}{false, true},
|
|
"stringMap": map[string]interface{}{
|
|
"my key": "one",
|
|
"my.key": "two",
|
|
"my-key": "three",
|
|
"my_key": "four",
|
|
"MY_KEY": "five",
|
|
"myKey": "six",
|
|
},
|
|
}),
|
|
"boolean": true,
|
|
"float": 4.5,
|
|
"integer": 1024,
|
|
"string": "Hello",
|
|
"boolArray": []interface{}{true, false},
|
|
"stringMap": map[string]interface{}{
|
|
"my key": "one",
|
|
"my.key": "two",
|
|
"my-key": "three",
|
|
"my_key": "four",
|
|
"MY_KEY": "five",
|
|
"myKey": "six",
|
|
},
|
|
}),
|
|
})
|
|
assert.Equal(l, want, plainResource.Inputs, "expected inputs to be %v", want)
|
|
assert.Equal(l, plainResource.Inputs, plainResource.Outputs, "expected inputs and outputs to match")
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"l2-explicit-parameterized-provider": {
|
|
Providers: []plugin.Provider{&providers.ParameterizedProvider{}},
|
|
Runs: []TestRun{
|
|
{
|
|
Assert: func(l *L,
|
|
projectDirectory string, err error,
|
|
snap *deploy.Snapshot, changes display.ResourceChanges,
|
|
) {
|
|
RequireStackResource(l, err, changes)
|
|
|
|
// Check we have the one resource in the snapshot, its provider and the stack.
|
|
require.Len(l, snap.Resources, 3, "expected 3 resources in snapshot")
|
|
|
|
stack := snap.Resources[0]
|
|
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
|
|
require.Equal(l,
|
|
resource.NewStringProperty("Goodbye World"),
|
|
stack.Outputs["parameterValue"],
|
|
"parameter value and provider config should be correct")
|
|
|
|
provider := snap.Resources[1]
|
|
assert.Equal(l, "pulumi:providers:goodbye", provider.Type.String(), "expected goodbye provider")
|
|
assert.Equal(l, "prov", provider.URN.Name(), "expected explicit provider resource")
|
|
|
|
simple := snap.Resources[2]
|
|
assert.Equal(l, "goodbye:index:Goodbye", simple.Type.String(), "expected Goodbye resource")
|
|
assert.Equal(l, string(provider.URN)+"::"+string(provider.ID), simple.Provider)
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|