pulumi/cmd/pulumi-test-language/tests.go

1078 lines
40 KiB
Go

// Copyright 2016-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 main
import (
"embed"
"math"
"path/filepath"
"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/config"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/deepcopy"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testRun struct {
config config.Map
// This can be used to set a main value for the test.
main string
// TODO: This should just return "string", if == "" then ok, else fail
assert func(*L, string, error, *deploy.Snapshot, display.ResourceChanges)
// updateOptions can be used to set the update options for the engine.
updateOptions engine.UpdateOptions
}
type languageTest struct {
// TODO: This should be a function so we don't have to load all providers in memory all the time.
providers []plugin.Provider
// stackReferences specifies other stack data that this test depends on.
stackReferences map[string]resource.PropertyMap
// runs is a list of test runs to execute.
runs []testRun
}
// 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{
// ==========
// INTERNAL
// ==========
"internal-bad-schema": {
providers: []plugin.Provider{&providers.BadProvider{}},
runs: []testRun{{}},
},
// ==========
// L1 (Tests not using providers)
// ==========
"l1-empty": {
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
assertStackResource(l, err, changes)
},
},
},
},
"l1-output-bool": {
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
// Check we have two outputs in the stack for true and false
require.NotEmpty(l, snap.Resources, "expected at least 1 resource")
stack := snap.Resources[0]
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
outputs := stack.Outputs
assertPropertyMapMember(l, outputs, "output_true", resource.NewBoolProperty(true))
assertPropertyMapMember(l, outputs, "output_false", resource.NewBoolProperty(false))
},
},
},
},
"l1-output-number": {
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
require.NotEmpty(l, snap.Resources, "expected at least 1 resource")
stack := snap.Resources[0]
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
outputs := stack.Outputs
assert.Len(l, outputs, 6, "expected 6 outputs")
assertPropertyMapMember(l, outputs, "zero", resource.NewNumberProperty(0))
assertPropertyMapMember(l, outputs, "one", resource.NewNumberProperty(1))
assertPropertyMapMember(l, outputs, "e", resource.NewNumberProperty(2.718))
assertPropertyMapMember(l, outputs, "minInt32", resource.NewNumberProperty(math.MinInt32))
assertPropertyMapMember(l, outputs, "max", resource.NewNumberProperty(math.MaxFloat64))
assertPropertyMapMember(l, outputs, "min", resource.NewNumberProperty(math.SmallestNonzeroFloat64))
},
},
},
},
"l1-output-string": {
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
require.NotEmpty(l, snap.Resources, "expected at least 1 resource")
stack := snap.Resources[0]
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
outputs := stack.Outputs
assert.Len(l, outputs, 6, "expected 6 outputs")
assertPropertyMapMember(l, outputs, "empty", resource.NewStringProperty(""))
assertPropertyMapMember(l, outputs, "small", resource.NewStringProperty("Hello world!"))
assertPropertyMapMember(l, outputs, "emoji", resource.NewStringProperty("👋 \"Hello \U0001019b!\" 😊"))
assertPropertyMapMember(l, outputs, "escape", resource.NewStringProperty(
"Some ${common} \"characters\" 'that' need escaping: "+
"\\ (backslash), \t (tab), \u001b (escape), \u0007 (bell), \u0000 (null), \U000e0021 (tag space)"))
assertPropertyMapMember(l, outputs, "escapeNewline", resource.NewStringProperty(
"Some ${common} \"characters\" 'that' need escaping: "+
"\\ (backslash), \n (newline), \t (tab), \u001b (escape), \u0007 (bell), \u0000 (null), \U000e0021 (tag space)"))
large := strings.Repeat(lorem+"\n", 150)
assertPropertyMapMember(l, outputs, "large", resource.NewStringProperty(large))
},
},
},
},
"l1-output-array": {
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
require.NotEmpty(l, snap.Resources, "expected at least 1 resource")
stack := snap.Resources[0]
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
outputs := stack.Outputs
assert.Len(l, outputs, 5, "expected 5 outputs")
assertPropertyMapMember(l, outputs, "empty", resource.NewArrayProperty([]resource.PropertyValue{}))
assertPropertyMapMember(l, outputs, "small", resource.NewArrayProperty([]resource.PropertyValue{
resource.NewStringProperty("Hello"),
resource.NewStringProperty("World"),
}))
assertPropertyMapMember(l, outputs, "numbers", resource.NewArrayProperty([]resource.PropertyValue{
resource.NewNumberProperty(0), resource.NewNumberProperty(1), resource.NewNumberProperty(2),
resource.NewNumberProperty(3), resource.NewNumberProperty(4), resource.NewNumberProperty(5),
}))
assertPropertyMapMember(l, outputs, "nested", resource.NewArrayProperty([]resource.PropertyValue{
resource.NewArrayProperty([]resource.PropertyValue{
resource.NewNumberProperty(1), resource.NewNumberProperty(2), resource.NewNumberProperty(3),
}),
resource.NewArrayProperty([]resource.PropertyValue{
resource.NewNumberProperty(4), resource.NewNumberProperty(5), resource.NewNumberProperty(6),
}),
resource.NewArrayProperty([]resource.PropertyValue{
resource.NewNumberProperty(7), resource.NewNumberProperty(8), resource.NewNumberProperty(9),
}),
}))
large := []resource.PropertyValue{}
for i := 0; i < 150; i++ {
large = append(large, resource.NewStringProperty(lorem))
}
assertPropertyMapMember(l, outputs, "large", resource.NewArrayProperty(large))
},
},
},
},
"l1-main": {
runs: []testRun{
{
main: "subdir",
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
// Check we have an output in the stack for true
require.NotEmpty(l, snap.Resources, "expected at least 1 resource")
stack := snap.Resources[0]
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
outputs := stack.Outputs
assertPropertyMapMember(l, outputs, "output_true", resource.NewBoolProperty(true))
},
},
},
},
"l1-stack-reference": {
stackReferences: map[string]resource.PropertyMap{
"organization/other/dev": {
"plain": resource.NewStringProperty("plain"),
"secret": resource.MakeSecret(resource.NewStringProperty("secret")),
},
},
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 at least 3 resources")
stack := snap.Resources[0]
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
prov := snap.Resources[1]
require.Equal(l, "pulumi:providers:pulumi", prov.Type.String(), "expected a default pulumi provider resource")
ref := snap.Resources[2]
require.Equal(l, "pulumi:pulumi:StackReference", ref.Type.String(), "expected a stack reference resource")
outputs := stack.Outputs
assert.Len(l, outputs, 2, "expected 2 outputs")
assertPropertyMapMember(l, outputs, "plain", resource.NewStringProperty("plain"))
assertPropertyMapMember(l, outputs, "secret", resource.MakeSecret(resource.NewStringProperty("secret")))
},
},
},
},
"l1-builtin-info": {
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
require.NotEmpty(l, snap.Resources, "expected at least 1 resource")
stack := snap.Resources[0]
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
outputs := stack.Outputs
assert.Len(l, outputs, 3, "expected 3 outputs")
assertPropertyMapMember(l, outputs, "stackOutput", resource.NewStringProperty("test"))
assertPropertyMapMember(l, outputs, "projectOutput", resource.NewStringProperty("l1-builtin-info"))
assertPropertyMapMember(l, outputs, "organizationOutput", resource.NewStringProperty("organization"))
},
},
},
},
"l1-output-map": {
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
require.NotEmpty(l, snap.Resources, "expected at least 1 resource")
stack := snap.Resources[0]
require.Equal(l, resource.RootStackType, stack.Type, "expected a stack resource")
outputs := stack.Outputs
assert.Len(l, outputs, 4, "expected 4 outputs")
assertPropertyMapMember(l, outputs, "empty", resource.NewObjectProperty(resource.PropertyMap{}))
assertPropertyMapMember(l, outputs, "strings", resource.NewObjectProperty(resource.PropertyMap{
"greeting": resource.NewStringProperty("Hello, world!"),
"farewell": resource.NewStringProperty("Goodbye, world!"),
}))
assertPropertyMapMember(l, outputs, "numbers", resource.NewObjectProperty(resource.PropertyMap{
"1": resource.NewNumberProperty(1),
"2": resource.NewNumberProperty(2),
}))
assertPropertyMapMember(l, outputs, "keys", resource.NewObjectProperty(resource.PropertyMap{
"my.key": resource.NewNumberProperty(1),
"my-key": resource.NewNumberProperty(2),
"my_key": resource.NewNumberProperty(3),
"MY_KEY": resource.NewNumberProperty(4),
"mykey": resource.NewNumberProperty(5),
"MYKEY": resource.NewNumberProperty(6),
}))
},
},
},
},
// ==========
// L2 (Tests using providers)
// ==========
"l2-resource-simple": {
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")
simple := snap.Resources[2]
assert.Equal(l, "simple:index:Resource", simple.Type.String(), "expected simple resource")
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-resource-primitives": {
providers: []plugin.Provider{&providers.PrimitiveProvider{}},
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", provider.Type.String(), "expected primitive provider")
simple := snap.Resources[2]
assert.Equal(l, "primitive:index:Resource", simple.Type.String(), "expected primitive resource")
want := resource.NewPropertyMapFromMap(map[string]any{
"boolean": true,
"float": 3.14,
"integer": 42,
"string": "hello",
"numberArray": []interface{}{-1.0, 0.0, 1.0},
"booleanMap": map[string]interface{}{"t": true, "f": false},
})
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-resource-alpha": {
providers: []plugin.Provider{&providers.AlphaProvider{}},
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:alpha", provider.Type.String(), "expected alpha provider")
simple := snap.Resources[2]
assert.Equal(l, "alpha:index:Resource", simple.Type.String(), "expected alpha resource")
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-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-resource-asset-archive": {
providers: []plugin.Provider{&providers.AssetArchiveProvider{}},
runs: []testRun{
{
main: "subdir",
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
// Check we have the the asset, archive, and folder resources in the snapshot, the provider and the stack.
require.Len(l, snap.Resources, 7, "expected 7 resources in snapshot")
provider := snap.Resources[1]
assert.Equal(l, "pulumi:providers:asset-archive", provider.Type.String(), "expected asset-archive provider")
// We don't know what order the resources will be in so we map by name
resources := map[string]*resource.State{}
for _, r := range snap.Resources[2:] {
resources[r.URN.Name()] = r
}
asset, ok := resources["ass"]
require.True(l, ok, "expected asset resource")
assert.Equal(l, "asset-archive:index:AssetResource", asset.Type.String(), "expected asset resource")
archive, ok := resources["arc"]
require.True(l, ok, "expected archive resource")
assert.Equal(l, "asset-archive:index:ArchiveResource", archive.Type.String(), "expected archive resource")
folder, ok := resources["dir"]
require.True(l, ok, "expected folder resource")
assert.Equal(l, "asset-archive:index:ArchiveResource", folder.Type.String(), "expected archive resource")
assarc, ok := resources["assarc"]
require.True(l, ok, "expected asset archive resource")
assert.Equal(l, "asset-archive:index:ArchiveResource", assarc.Type.String(), "expected archive resource")
remoteass, ok := resources["remoteass"]
require.True(l, ok, "expected remote asset resource")
assert.Equal(l, "asset-archive:index:AssetResource", remoteass.Type.String(), "expected asset resource")
main := filepath.Join(projectDirectory, "subdir")
assetValue, err := resource.NewPathAssetWithWD("../test.txt", main)
require.NoError(l, err)
assert.Equal(l, "982d9e3eb996f559e633f4d194def3761d909f5a3b647d1a851fead67c32c9d1", assetValue.Hash)
want := resource.NewPropertyMapFromMap(map[string]any{
"value": assetValue,
})
assert.Equal(l, want, asset.Inputs, "expected inputs to be {value: %v}", assetValue)
assert.Equal(l, asset.Inputs, asset.Outputs, "expected inputs and outputs to match")
archiveValue, err := resource.NewPathArchiveWithWD("../archive.tar", main)
require.NoError(l, err)
assert.Equal(l, "2eee410fe85d360552a8c21238d67d43f4b64e60288914f893b67165e8ebfbcf", archiveValue.Hash)
want = resource.NewPropertyMapFromMap(map[string]any{
"value": archiveValue,
})
assert.Equal(l, want, archive.Inputs, "expected inputs to be {value: %v}", archiveValue)
assert.Equal(l, archive.Inputs, archive.Outputs, "expected inputs and outputs to match")
folderValue, err := resource.NewPathArchiveWithWD("../folder", main)
require.NoError(l, err)
assert.Equal(l, "25df47ed6b3c8e07479e5d9c908eff93d624ec693b6aa7559a9bcb084db70774", folderValue.Hash)
want = resource.NewPropertyMapFromMap(map[string]any{
"value": folderValue,
})
assert.Equal(l, want, folder.Inputs, "expected inputs to be {value: %v}", folderValue)
assert.Equal(l, folder.Inputs, folder.Outputs, "expected inputs and outputs to match")
stringAsset, err := resource.NewTextAsset("file contents")
require.NoError(l, err)
assarcValue, err := resource.NewAssetArchiveWithWD(map[string]interface{}{
"string": stringAsset,
"file": assetValue,
"folder": folderValue,
"archive": archiveValue,
}, main)
require.NoError(l, err)
want = resource.NewPropertyMapFromMap(map[string]any{
"value": assarcValue,
})
assert.Equal(l, want, assarc.Inputs, "expected inputs to be {value: %v}", assarcValue)
assert.Equal(l, assarc.Inputs, assarc.Outputs, "expected inputs and outputs to match")
remoteassValue, err := resource.NewURIAsset(
"https://raw.githubusercontent.com/pulumi/pulumi/master" +
"/cmd/pulumi-test-language/testdata/l2-resource-asset-archive/test.txt",
)
require.NoError(l, err)
want = resource.NewPropertyMapFromMap(map[string]any{
"value": remoteassValue,
})
assert.Equal(l, want, remoteass.Inputs, "expected inputs to be {value: %v}", remoteassValue)
assert.Equal(l, remoteass.Inputs, remoteass.Outputs, "expected inputs and outputs to match")
bs, err := remoteassValue.Bytes()
require.NoError(l, err)
assert.Equal(l, "text", string(bs))
assert.Equal(l, "982d9e3eb996f559e633f4d194def3761d909f5a3b647d1a851fead67c32c9d1", remoteassValue.Hash)
},
},
},
},
"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-resource-config": {
providers: []plugin.Provider{&providers.ConfigProvider{}},
runs: []testRun{
{
config: config.Map{
config.MustParseKey("config:name"): config.NewValue("hello"),
},
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")
explicitProvider := snap.Resources[1]
require.Equal(l, "pulumi:providers:config", explicitProvider.Type.String(), "expected explicit provider resource")
expectedOutputs := resource.NewPropertyMapFromMap(map[string]interface{}{
"name": "my config",
"pluginDownloadURL": "not the same as the pulumi resource option",
"version": "9.0.0",
})
expectedInputs := deepcopy.Copy(expectedOutputs).(resource.PropertyMap)
// inputs should also have the __internal key
expectedInputs[resource.PropertyKey("__internal")] = resource.NewObjectProperty(
resource.NewPropertyMapFromMap(map[string]interface{}{
"pluginDownloadURL": "http://example.com",
}))
require.Equal(l, expectedInputs, explicitProvider.Inputs)
require.Equal(l, expectedOutputs, explicitProvider.Outputs)
defaultProvider := snap.Resources[2]
require.Equal(l, "pulumi:providers:config", defaultProvider.Type.String(), "expected default provider resource")
require.Equal(l, "default_9_0_0_http_/example.com", defaultProvider.URN.Name())
expectedOutputs = resource.NewPropertyMapFromMap(map[string]interface{}{
"version": "9.0.0",
"name": "hello",
})
expectedInputs = deepcopy.Copy(expectedOutputs).(resource.PropertyMap)
// inputs should also have the __internal key
expectedInputs[resource.PropertyKey("__internal")] = resource.NewObjectProperty(
resource.NewPropertyMapFromMap(map[string]interface{}{
"pluginDownloadURL": "http://example.com",
}))
require.Equal(l, expectedInputs, defaultProvider.Inputs)
require.Equal(l, expectedOutputs, defaultProvider.Outputs)
},
},
},
},
"l2-invoke-simple": {
providers: []plugin.Provider{&providers.SimpleInvokeProvider{}},
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, 2, "expected 2 resource")
// TODO: the root stack must be the first resource to be registered
// such that snap.Resources[0].Type == resource.RootStackType
// however with the python SDK, that is not the case, instead the default
// provider gets registered first. This is indicating that something might be wrong
// with the how python SDK registers resources
var stack *resource.State
for _, r := range snap.Resources {
if r.Type == resource.RootStackType {
stack = r
break
}
}
require.NotNil(l, stack, "expected a stack resource")
outputs := stack.Outputs
assertPropertyMapMember(l, outputs, "hello", resource.NewStringProperty("hello world"))
assertPropertyMapMember(l, outputs, "goodbye", resource.NewStringProperty("goodbye world"))
},
},
},
},
"l2-invoke-variants": {
providers: []plugin.Provider{&providers.SimpleInvokeProvider{}},
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 resource")
// TODO: the root stack must be the first resource to be registered
// such that snap.Resources[0].Type == resource.RootStackType
// however with the python SDK, that is not the case, instead the default
// provider gets registered first. This is indicating that something might be wrong
// with the how python SDK registers resources
var stack *resource.State
for _, r := range snap.Resources {
if r.Type == resource.RootStackType {
stack = r
break
}
}
require.NotNil(l, stack, "expected a stack resource")
outputs := stack.Outputs
assertPropertyMapMember(l, outputs, "outputInput", resource.NewStringProperty("Goodbye world"))
assertPropertyMapMember(l, outputs, "unit", resource.NewStringProperty("Hello world"))
},
},
},
},
"l2-invoke-secrets": {
providers: []plugin.Provider{
&providers.SimpleInvokeProvider{},
&providers.SimpleProvider{},
},
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
var stack *resource.State
for _, r := range snap.Resources {
if r.Type == resource.RootStackType {
stack = r
break
}
}
require.NotNil(l, stack, "expected a stack resource")
outputs := stack.Outputs
assertPropertyMapMember(l, outputs, "nonSecret",
resource.NewStringProperty("hello world"))
assertPropertyMapMember(l, outputs, "firstSecret",
resource.MakeSecret(resource.NewStringProperty("hello world")))
assertPropertyMapMember(l, outputs, "secondSecret",
resource.MakeSecret(resource.NewStringProperty("goodbye world")))
},
},
},
},
"l2-invoke-dependencies": {
providers: []plugin.Provider{
&providers.SimpleInvokeProvider{},
&providers.SimpleProvider{},
},
runs: []testRun{
{
assert: func(l *L,
projectDirectory string, err error,
snap *deploy.Snapshot, changes display.ResourceChanges,
) {
requireStackResource(l, err, changes)
var first *resource.State
var second *resource.State
for _, r := range snap.Resources {
if r.URN.Name() == "first" {
first = r
}
if r.URN.Name() == "second" {
second = r
}
}
require.NotNil(l, first, "expected first resource")
require.NotNil(l, second, "expected second resource")
require.Empty(l, first.Dependencies, "expected no dependencies")
require.Len(l, second.Dependencies, 1, "expected one dependency")
dependencies, ok := second.PropertyDependencies["value"]
require.True(l, ok, "expected dependency on property 'value'")
require.Len(l, dependencies, 1, "expected one dependency")
require.Equal(l, first.URN, dependencies[0], "expected second to depend on first")
require.Equal(l, first.URN, second.Dependencies[0], "expected second to depend on first")
},
},
},
},
"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")
},
},
},
},
}