pulumi/pkg/resource/deploy/step_generator_test.go

941 lines
25 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 deploy
import (
"runtime"
"testing"
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
"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/stretchr/testify/assert"
)
func TestIgnoreChanges(t *testing.T) {
t.Parallel()
cases := []struct {
name string
oldInputs map[string]interface{}
newInputs map[string]interface{}
expected map[string]interface{}
ignoreChanges []string
expectFailure bool
}{
{
name: "Present in old and new sets",
oldInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": "foo",
},
},
newInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": "bar",
},
"c": 42,
},
expected: map[string]interface{}{
"a": map[string]interface{}{
"b": "foo",
},
"c": 42,
},
ignoreChanges: []string{"a.b"},
},
{
name: "Missing in new sets",
oldInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": "foo",
},
},
newInputs: map[string]interface{}{
"a": map[string]interface{}{},
"c": 42,
},
expected: map[string]interface{}{
"a": map[string]interface{}{
"b": "foo",
},
"c": 42,
},
ignoreChanges: []string{"a.b"},
},
{
name: "Present in old and new sets, using [\"\"]",
oldInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": "foo",
},
},
},
newInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": "bar",
},
},
"c": 42,
},
expected: map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": "foo",
},
},
"c": 42,
},
ignoreChanges: []string{"a.b[\"c\"]"},
},
{
name: "Missing in new sets, using [\"\"]",
oldInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": "foo",
},
},
},
newInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{},
},
"c": 42,
},
expected: map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": "foo",
},
},
"c": 42,
},
ignoreChanges: []string{"a.b[\"c\"]"},
},
{
name: "Missing in old deletes",
oldInputs: map[string]interface{}{},
newInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": "foo",
},
"c": 42,
},
expected: map[string]interface{}{
"a": map[string]interface{}{},
"c": 42,
},
ignoreChanges: []string{"a.b"},
},
{
name: "Missing keys in old and new are OK",
oldInputs: map[string]interface{}{},
newInputs: map[string]interface{}{},
ignoreChanges: []string{
"a",
"a.b",
"a.c[0]",
},
},
{
name: "Missing parent keys in only new fail",
oldInputs: map[string]interface{}{
"a": map[string]interface{}{
"b": "foo",
},
},
newInputs: map[string]interface{}{},
ignoreChanges: []string{"a.b"},
expectFailure: true,
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
olds, news := resource.NewPropertyMapFromMap(c.oldInputs), resource.NewPropertyMapFromMap(c.newInputs)
expected := olds
if c.expected != nil {
expected = resource.NewPropertyMapFromMap(c.expected)
}
processed, err := processIgnoreChanges(news, olds, c.ignoreChanges)
if c.expectFailure {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, expected, processed)
}
})
}
}
func TestApplyReplaceOnChangesEmptyDetailedDiff(t *testing.T) {
t.Parallel()
cases := []struct {
name string
diff plugin.DiffResult
replaceOnChanges []string
hasInitErrors bool
expected plugin.DiffResult
}{
{
name: "Empty diff and replaceOnChanges",
diff: plugin.DiffResult{},
replaceOnChanges: []string{},
hasInitErrors: false,
expected: plugin.DiffResult{},
},
{
name: "DiffSome and empty replaceOnChanges",
diff: plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: []resource.PropertyKey{"a"},
},
replaceOnChanges: []string{},
hasInitErrors: false,
expected: plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: []resource.PropertyKey{"a"},
},
},
{
name: "DiffSome and non-empty replaceOnChanges",
diff: plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: []resource.PropertyKey{"a"},
},
replaceOnChanges: []string{"a"},
hasInitErrors: false,
expected: plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: []resource.PropertyKey{"a"},
ReplaceKeys: []resource.PropertyKey{"a"},
},
},
{
name: "Empty diff and replaceOnChanges w/ init errors",
diff: plugin.DiffResult{},
replaceOnChanges: []string{},
hasInitErrors: true,
expected: plugin.DiffResult{},
},
{
name: "DiffSome and empty replaceOnChanges w/ init errors",
diff: plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: []resource.PropertyKey{"a"},
},
replaceOnChanges: []string{},
hasInitErrors: true,
expected: plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: []resource.PropertyKey{"a"},
},
},
{
name: "DiffSome and non-empty replaceOnChanges w/ init errors",
diff: plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: []resource.PropertyKey{"a"},
},
replaceOnChanges: []string{"a"},
hasInitErrors: true,
expected: plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: []resource.PropertyKey{"a"},
ReplaceKeys: []resource.PropertyKey{"a"},
},
},
{
name: "Empty diff and non-empty replaceOnChanges w/ init errors",
diff: plugin.DiffResult{},
replaceOnChanges: []string{"*"},
hasInitErrors: true,
expected: plugin.DiffResult{
Changes: plugin.DiffSome,
ReplaceKeys: []resource.PropertyKey{"#initerror"},
},
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
newdiff, err := applyReplaceOnChanges(c.diff, c.replaceOnChanges, c.hasInitErrors)
assert.NoError(t, err)
assert.Equal(t, c.expected, newdiff)
})
}
}
func TestEngineDiff(t *testing.T) {
t.Parallel()
cases := []struct {
name string
oldInputs, newInputs resource.PropertyMap
ignoreChanges []string
expected []resource.PropertyKey
expectedChanges plugin.DiffChanges
}{
{
name: "Empty diff",
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val1": resource.NewPropertyValue(8),
"val2": resource.NewPropertyValue("hello"),
}),
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val1": resource.NewPropertyValue(8),
"val2": resource.NewPropertyValue("hello"),
}),
expected: nil,
expectedChanges: plugin.DiffNone,
},
{
name: "All changes",
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val0": resource.NewPropertyValue(3.14),
}),
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val1": resource.NewNumberProperty(42),
"val2": resource.NewPropertyValue("world"),
}),
expected: []resource.PropertyKey{"val0", "val1", "val2"},
expectedChanges: plugin.DiffSome,
},
{
name: "Some changes",
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val1": resource.NewPropertyValue(42),
}),
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val1": resource.NewNumberProperty(42),
"val2": resource.NewPropertyValue("world"),
}),
expected: []resource.PropertyKey{"val2"},
expectedChanges: plugin.DiffSome,
},
{
name: "Ignore some changes",
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val1": resource.NewPropertyValue("hello"),
}),
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val2": resource.NewPropertyValue(8),
}),
ignoreChanges: []string{"val1"},
expected: []resource.PropertyKey{"val2"},
expectedChanges: plugin.DiffSome,
},
{
name: "Ignore all changes",
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val1": resource.NewPropertyValue("hello"),
}),
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
"val2": resource.NewPropertyValue(8),
}),
ignoreChanges: []string{"val1", "val2"},
expected: nil,
expectedChanges: plugin.DiffNone,
},
}
urn := resource.URN("urn:pulumi:dev::website-and-lambda::aws:s3/bucket:Bucket::my-bucket")
id := resource.ID("someid")
var oldOutputs resource.PropertyMap
allowUnknowns := false
provider := deploytest.Provider{}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
diff, err := diffResource(urn, id, c.oldInputs, oldOutputs, c.newInputs, &provider, allowUnknowns, c.ignoreChanges)
t.Logf("diff.ChangedKeys = %v", diff.ChangedKeys)
t.Logf("diff.StableKeys = %v", diff.StableKeys)
t.Logf("diff.ReplaceKeys = %v", diff.ReplaceKeys)
assert.NoError(t, err)
assert.Equal(t, c.expectedChanges, diff.Changes)
assert.EqualValues(t, c.expected, diff.ChangedKeys)
})
}
}
func TestGenerateAliases(t *testing.T) {
t.Parallel()
const (
project = "project"
)
stack := tokens.MustParseStackName("stack")
parentTypeAlias := resource.CreateURN("myres", "test:resource:type2", "", project, stack.String())
parentNameAlias := resource.CreateURN("myres2", "test:resource:type", "", project, stack.String())
cases := []struct {
name string
parentAlias *resource.URN
childAliases []resource.Alias
expected []resource.URN
}{
{
name: "no aliases",
expected: nil,
},
{
name: "child alias (type), no parent aliases",
childAliases: []resource.Alias{
{Type: "test:resource:child2"},
},
expected: []resource.URN{
"urn:pulumi:stack::project::test:resource:type$test:resource:child2::myres-child",
},
},
{
name: "child alias (name), no parent aliases",
childAliases: []resource.Alias{
{Name: "child2"},
},
expected: []resource.URN{
"urn:pulumi:stack::project::test:resource:type$test:resource:child::child2",
},
},
{
name: "child alias (type, noParent), no parent aliases",
childAliases: []resource.Alias{
{
Type: "test:resource:child2",
NoParent: true,
},
},
expected: []resource.URN{
"urn:pulumi:stack::project::test:resource:child2::myres-child",
},
},
{
name: "child alias (type, parent), no parent aliases",
childAliases: []resource.Alias{
{
Type: "test:resource:child2",
Parent: resource.CreateURN("originalparent", "test:resource:original", "", project, stack.String()),
},
},
expected: []resource.URN{
"urn:pulumi:stack::project::test:resource:original$test:resource:child2::myres-child",
},
},
{
name: "child alias (name), parent alias (type)",
parentAlias: &parentTypeAlias,
childAliases: []resource.Alias{
{Name: "myres-child2"},
},
expected: []resource.URN{
"urn:pulumi:stack::project::test:resource:type$test:resource:child::myres-child2",
"urn:pulumi:stack::project::test:resource:type2$test:resource:child::myres-child",
"urn:pulumi:stack::project::test:resource:type2$test:resource:child::myres-child2",
},
},
{
name: "child alias (name), parent alias (name)",
parentAlias: &parentNameAlias,
childAliases: []resource.Alias{
{Name: "myres-child2"},
},
expected: []resource.URN{
"urn:pulumi:stack::project::test:resource:type$test:resource:child::myres-child2",
"urn:pulumi:stack::project::test:resource:type$test:resource:child::myres2-child",
"urn:pulumi:stack::project::test:resource:type$test:resource:child::myres2-child2",
},
},
}
for _, tt := range cases {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
parentURN := resource.CreateURN("myres", "test:resource:type", "", project, stack.String())
goal := &resource.Goal{
Parent: parentURN,
Name: "myres-child",
Type: "test:resource:child",
Aliases: tt.childAliases,
}
sg := newStepGenerator(&Deployment{
opts: &Options{},
target: &Target{
Name: stack,
},
source: NewNullSource(project),
})
if tt.parentAlias != nil {
sg.aliases = map[resource.URN]resource.URN{
parentURN: *tt.parentAlias,
}
}
actual := sg.generateAliases(goal)
assert.Equal(t, tt.expected, actual)
})
}
}
func TestDeleteProtectedErrorUsesCorrectQuotesOnOS(t *testing.T) {
t.Parallel()
err := deleteProtectedError{urn: "resource:urn"}
expectations := map[string]string{
`windows`: `"`,
`linux`: `'`,
`darwin`: `'`,
}
t.Run(runtime.GOOS, func(t *testing.T) {
t.Parallel()
gotErrMsg := err.Error()
contains, ok := expectations[runtime.GOOS]
if !ok {
t.Skipf("no quoting expectation for %s", runtime.GOOS)
return
}
assert.Contains(t, gotErrMsg, contains)
})
}
func TestStepGenerator(t *testing.T) {
t.Parallel()
t.Run("isTargetedForUpdate (no target dependents)", func(t *testing.T) {
t.Parallel()
apUrn := resource.NewURN("test", "test", "", providers.MakeProviderType("pkgA"), "a")
apRef, err := providers.NewReference(apUrn, "0")
assert.NoError(t, err)
bpUrn := resource.NewURN("test", "test", "", providers.MakeProviderType("pkgB"), "b")
bpRef, err := providers.NewReference(bpUrn, "1")
assert.NoError(t, err)
// Arrange.
sg := &stepGenerator{
deployment: &Deployment{
opts: &Options{
TargetDependents: false,
Targets: UrnTargets{
literals: []resource.URN{"b"},
},
},
},
targetsActual: UrnTargets{
literals: []resource.URN{bpRef.URN()},
},
}
t.Run("is targeted directly", func(t *testing.T) {
t.Parallel()
// Arrange.
a := &resource.State{URN: "a"}
b := &resource.State{URN: "b"}
// Act.
aIsTargeted := sg.isTargetedForUpdate(a)
bIsTargeted := sg.isTargetedForUpdate(b)
// Assert.
assert.False(t, aIsTargeted)
assert.True(t, bIsTargeted)
})
t.Run("has a targeted provider", func(t *testing.T) {
t.Parallel()
// Arrange.
hasAAsProvider := &resource.State{Provider: apRef.String()}
hasBAsProvider := &resource.State{Provider: bpRef.String()}
// Act.
hasAAsProviderIsTargeted := sg.isTargetedForUpdate(hasAAsProvider)
hasBAsProviderIsTargeted := sg.isTargetedForUpdate(hasBAsProvider)
// Assert.
assert.False(t, hasAAsProviderIsTargeted)
assert.False(t, hasBAsProviderIsTargeted)
})
t.Run("has a targeted parent", func(t *testing.T) {
t.Parallel()
// Arrange.
hasAAsParent := &resource.State{Parent: "a"}
hasBAsParent := &resource.State{Parent: "b"}
// Act.
hasAAsParentIsTargeted := sg.isTargetedForUpdate(hasAAsParent)
hasBAsParentIsTargeted := sg.isTargetedForUpdate(hasBAsParent)
// Assert.
assert.False(t, hasAAsParentIsTargeted)
assert.False(t, hasBAsParentIsTargeted)
})
t.Run("has a targeted dependency", func(t *testing.T) {
t.Parallel()
// Arrange.
dependsOnA := &resource.State{Dependencies: []resource.URN{"a"}}
dependsOnB := &resource.State{Dependencies: []resource.URN{"a", "b"}}
// Act.
dependsOnAIsTargeted := sg.isTargetedForUpdate(dependsOnA)
dependsOnBIsTargeted := sg.isTargetedForUpdate(dependsOnB)
// Assert.
assert.False(t, dependsOnAIsTargeted)
assert.False(t, dependsOnBIsTargeted)
})
t.Run("has a targeted property dependency", func(t *testing.T) {
t.Parallel()
// Arrange.
dependsOnA := &resource.State{PropertyDependencies: map[resource.PropertyKey][]resource.URN{"p": {"a"}}}
dependsOnB := &resource.State{PropertyDependencies: map[resource.PropertyKey][]resource.URN{"p": {"a", "b"}}}
// Act.
dependsOnAIsTargeted := sg.isTargetedForUpdate(dependsOnA)
dependsOnBIsTargeted := sg.isTargetedForUpdate(dependsOnB)
// Assert.
assert.False(t, dependsOnAIsTargeted)
assert.False(t, dependsOnBIsTargeted)
})
t.Run("is deleted with a target", func(t *testing.T) {
t.Parallel()
// Arrange.
isDeletedWithA := &resource.State{DeletedWith: "a"}
isDeletedWithB := &resource.State{DeletedWith: "b"}
// Act.
isDeletedWithAIsTargeted := sg.isTargetedForUpdate(isDeletedWithA)
isDeletedWithBIsTargeted := sg.isTargetedForUpdate(isDeletedWithB)
// Assert.
assert.False(t, isDeletedWithAIsTargeted)
assert.False(t, isDeletedWithBIsTargeted)
})
})
t.Run("isTargetedForUpdate (target dependents)", func(t *testing.T) {
t.Parallel()
// Arrange.
apUrn := resource.NewURN("test", "test", "", providers.MakeProviderType("pkgA"), "a")
apRef, err := providers.NewReference(apUrn, "0")
assert.NoError(t, err)
bpUrn := resource.NewURN("test", "test", "", providers.MakeProviderType("pkgB"), "b")
bpRef, err := providers.NewReference(bpUrn, "1")
assert.NoError(t, err)
sg := &stepGenerator{
deployment: &Deployment{
opts: &Options{
TargetDependents: true,
Targets: UrnTargets{
literals: []resource.URN{"c"},
},
},
},
targetsActual: UrnTargets{
literals: []resource.URN{"b", bpRef.URN()},
},
}
t.Run("is targeted directly", func(t *testing.T) {
t.Parallel()
// Arrange.
a := &resource.State{URN: "a"}
c := &resource.State{URN: "c"}
// Act.
aIsTargeted := sg.isTargetedForUpdate(a)
cIsTargeted := sg.isTargetedForUpdate(c)
// Assert.
assert.False(t, aIsTargeted)
assert.True(t, cIsTargeted)
})
t.Run("has a targeted provider", func(t *testing.T) {
t.Parallel()
// Arrange.
hasAAsProvider := &resource.State{Provider: apRef.String()}
hasBAsProvider := &resource.State{Provider: bpRef.String()}
// Act.
hasAAsProviderIsTargeted := sg.isTargetedForUpdate(hasAAsProvider)
hasBAsProviderIsTargeted := sg.isTargetedForUpdate(hasBAsProvider)
// Assert.
assert.False(t, hasAAsProviderIsTargeted)
assert.True(t, hasBAsProviderIsTargeted)
})
t.Run("has a targeted parent", func(t *testing.T) {
t.Parallel()
// Arrange.
hasAAsParent := &resource.State{Parent: "a"}
hasBAsParent := &resource.State{Parent: "b"}
// Act.
hasAAsParentIsTargeted := sg.isTargetedForUpdate(hasAAsParent)
hasBAsParentIsTargeted := sg.isTargetedForUpdate(hasBAsParent)
// Assert.
assert.False(t, hasAAsParentIsTargeted)
assert.True(t, hasBAsParentIsTargeted)
})
t.Run("has a targeted dependency", func(t *testing.T) {
t.Parallel()
// Arrange.
dependsOnA := &resource.State{Dependencies: []resource.URN{"a"}}
dependsOnB := &resource.State{Dependencies: []resource.URN{"a", "b"}}
// Act.
dependsOnAIsTargeted := sg.isTargetedForUpdate(dependsOnA)
dependsOnBIsTargeted := sg.isTargetedForUpdate(dependsOnB)
// Assert.
assert.False(t, dependsOnAIsTargeted)
assert.True(t, dependsOnBIsTargeted)
})
t.Run("has a targeted property dependency", func(t *testing.T) {
t.Parallel()
// Arrange.
dependsOnA := &resource.State{PropertyDependencies: map[resource.PropertyKey][]resource.URN{"p": {"a"}}}
dependsOnB := &resource.State{PropertyDependencies: map[resource.PropertyKey][]resource.URN{"p": {"a", "b"}}}
// Act.
dependsOnAIsTargeted := sg.isTargetedForUpdate(dependsOnA)
dependsOnBIsTargeted := sg.isTargetedForUpdate(dependsOnB)
// Assert.
assert.False(t, dependsOnAIsTargeted)
assert.True(t, dependsOnBIsTargeted)
})
t.Run("is deleted with a target", func(t *testing.T) {
t.Parallel()
// Arrange.
isDeletedWithA := &resource.State{DeletedWith: "a"}
isDeletedWithB := &resource.State{DeletedWith: "b"}
// Act.
isDeletedWithAIsTargeted := sg.isTargetedForUpdate(isDeletedWithA)
isDeletedWithBIsTargeted := sg.isTargetedForUpdate(isDeletedWithB)
// Assert.
assert.False(t, isDeletedWithAIsTargeted)
assert.True(t, isDeletedWithBIsTargeted)
})
})
t.Run("checkParent", func(t *testing.T) {
t.Parallel()
t.Run("could not find parent resource", func(t *testing.T) {
t.Parallel()
sg := &stepGenerator{
urns: map[resource.URN]bool{},
}
_, err := sg.checkParent("does-not-exist", "")
assert.ErrorContains(t, err, "could not find parent resource")
})
})
t.Run("GenerateReadSteps", func(t *testing.T) {
t.Parallel()
t.Run("could not find parent resource", func(t *testing.T) {
t.Parallel()
sg := &stepGenerator{
urns: map[resource.URN]bool{},
}
_, err := sg.GenerateReadSteps(&readResourceEvent{
parent: "does-not-exist",
})
assert.ErrorContains(t, err, "could not find parent resource")
})
t.Run("fail generateURN", func(t *testing.T) {
t.Parallel()
sg := &stepGenerator{
urns: map[resource.URN]bool{
"urn:pulumi:stack::::::": true,
},
deployment: &Deployment{
ctx: &plugin.Context{
Diag: &deploytest.NoopSink{},
},
opts: &Options{},
target: &Target{
Name: tokens.MustParseStackName("stack"),
},
source: &nullSource{},
},
}
_, err := sg.GenerateReadSteps(&readResourceEvent{})
assert.ErrorContains(t, err, "Duplicate resource URN")
})
})
t.Run("generateSteps", func(t *testing.T) {
t.Parallel()
t.Run("could not find parent resource", func(t *testing.T) {
t.Parallel()
sg := &stepGenerator{
urns: map[resource.URN]bool{},
deployment: &Deployment{
target: &Target{
Name: tokens.MustParseStackName("stack"),
},
source: &nullSource{},
},
}
_, err := sg.generateSteps(&registerResourceEvent{
goal: &resource.Goal{
Parent: "does-not-exist",
},
})
assert.ErrorContains(t, err, "could not find parent resource")
})
})
t.Run("determineAllowedResourcesToDeleteFromTargets", func(t *testing.T) {
t.Parallel()
t.Run("handle non-existent target", func(t *testing.T) {
t.Parallel()
sg := &stepGenerator{
urns: map[resource.URN]bool{},
deployment: &Deployment{
prev: &Snapshot{
Resources: []*resource.State{
{
URN: "a",
},
},
},
olds: map[resource.URN]*resource.State{},
},
}
targets, err := sg.determineAllowedResourcesToDeleteFromTargets(UrnTargets{
literals: []resource.URN{"a"},
})
assert.NoError(t, err)
assert.Empty(t, targets)
})
})
t.Run("providerChanged", func(t *testing.T) {
t.Parallel()
t.Run("invalid old ProviderReference", func(t *testing.T) {
t.Parallel()
sg := &stepGenerator{
urns: map[resource.URN]bool{},
deployment: &Deployment{
prev: &Snapshot{},
olds: map[resource.URN]*resource.State{},
},
}
_, err := sg.providerChanged("",
&resource.State{
Provider: "invalid-old-provider",
},
&resource.State{
Provider: "urn:pulumi:stack::project::pulumi:providers:provider::name::uuid",
},
)
assert.ErrorContains(t, err, "expected '::' in provider reference 'invalid-old-provider'")
})
t.Run("invalid new ProviderReference", func(t *testing.T) {
t.Parallel()
sg := &stepGenerator{
urns: map[resource.URN]bool{},
deployment: &Deployment{
prev: &Snapshot{},
olds: map[resource.URN]*resource.State{},
},
}
_, err := sg.providerChanged("",
&resource.State{
Provider: "urn:pulumi:stack::project::pulumi:providers:provider::name::uuid",
},
&resource.State{
Provider: "invalid-new-provider",
},
)
assert.ErrorContains(t, err, "expected '::' in provider reference 'invalid-new-provider'")
})
t.Run("error getting new default provider", func(t *testing.T) {
t.Parallel()
sg := &stepGenerator{
urns: map[resource.URN]bool{},
deployment: &Deployment{
prev: &Snapshot{},
olds: map[resource.URN]*resource.State{},
providers: &providers.Registry{},
},
}
_, err := sg.providerChanged("",
&resource.State{
Provider: "urn:pulumi:stack::project::pulumi:providers:provider::default_name::uuid",
},
&resource.State{
Provider: "urn:pulumi:stack::project::pulumi:providers:provider::default_new::uuid",
},
)
assert.ErrorContains(t, err, "failed to resolve provider reference")
})
})
}