mirror of https://github.com/pulumi/pulumi.git
1845 lines
58 KiB
Go
1845 lines
58 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 lifecycletest
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/blang/semver"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/display"
|
|
. "github.com/pulumi/pulumi/pkg/v3/engine" //nolint:revive
|
|
lt "github.com/pulumi/pulumi/pkg/v3/engine/lifecycletest/framework"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
|
|
"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/workspace"
|
|
)
|
|
|
|
func TestPlannedUpdate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var ins resource.PropertyMap
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Generate a plan.
|
|
computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": map[string]interface{}{
|
|
"a": 42,
|
|
"b": computed,
|
|
},
|
|
"qux": []interface{}{
|
|
computed,
|
|
24,
|
|
},
|
|
"zed": computed,
|
|
})
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Attempt to run an update using the plan.
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"qux": []interface{}{
|
|
"alpha",
|
|
24,
|
|
},
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
|
|
"properties changed: +-baz[{map[a:{42} b:output<string>{}]}], +-foo[{bar}]<{%reset%}>\n"))
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 1) {
|
|
return
|
|
}
|
|
|
|
// Change the provider's planned operation to a same step.
|
|
// Remove the provider from the plan.
|
|
plan.ResourcePlans["urn:pulumi:test::test::pulumi:providers:pkgA::default"].Ops = []display.StepOp{deploy.OpSame}
|
|
|
|
// Attempt to run an update using the plan.
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": map[string]interface{}{
|
|
"a": 42,
|
|
"b": "alpha",
|
|
},
|
|
"qux": []interface{}{
|
|
"beta",
|
|
24,
|
|
},
|
|
"zed": "grr",
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 2) {
|
|
return
|
|
}
|
|
|
|
expected := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": map[string]interface{}{
|
|
"a": 42,
|
|
"b": "alpha",
|
|
},
|
|
"qux": []interface{}{
|
|
"beta",
|
|
24,
|
|
},
|
|
"zed": "grr",
|
|
})
|
|
assert.Equal(t, expected, snap.Resources[1].Outputs)
|
|
}
|
|
|
|
func TestUnplannedCreate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
createResource := false
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
if createResource {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
SkipDisplayTests: true,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create a plan to do nothing
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Now set the flag for the language runtime to create a resource, and run update with the plan
|
|
createResource = true
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>create is not allowed by the plan: no steps were expected for this resource<{%reset%}>\n"))
|
|
snap, err := lt.TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
|
|
assert.NoError(t, err)
|
|
|
|
// Check nothing was was created
|
|
assert.NotNil(t, snap)
|
|
if !assert.Len(t, snap.Resources, 0) {
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestUnplannedDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
DeleteF: func(context.Context, plugin.DeleteRequest) (plugin.DeleteResponse, error) {
|
|
return plugin.DeleteResponse{}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
createAllResources := true
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
if createAllResources {
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial snapshot that resA and resB exist
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Create a plan that resA and resB won't change
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Now set the flag for the language runtime to not create resB and run an update with
|
|
// the no-op plan, this should block the delete
|
|
createAllResources = false
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>delete is not allowed by the plan: this resource is constrained to same<{%reset%}>\n"))
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate, "1")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Check both resources and the provider are still listed in the snapshot
|
|
if !assert.Len(t, snap.Resources, 3) {
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestExpectedDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
DeleteF: func(context.Context, plugin.DeleteRequest) (plugin.DeleteResponse, error) {
|
|
return plugin.DeleteResponse{}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
createAllResources := true
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
if createAllResources {
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial snapshot that resA and resB exist
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Create a plan that resA is same and resB is deleted
|
|
createAllResources = false
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Now run but set the runtime to return resA and resB, given we expected resB to be deleted
|
|
// this should be an error
|
|
createAllResources = true
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resB violates plan: "+
|
|
"resource unexpectedly not deleted<{%reset%}>\n"))
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate, "1")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Check both resources and the provider are still listed in the snapshot
|
|
if !assert.Len(t, snap.Resources, 3) {
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestExpectedCreate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
createAllResources := false
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
if createAllResources {
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial snapshot that resA exists
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Create a plan that resA is same and resB is created
|
|
createAllResources = true
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Now run but set the runtime to return resA, given we expected resB to be created
|
|
// this should be an error
|
|
createAllResources = false
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>expected resource operations for "+
|
|
"urn:pulumi:test::test::pkgA:m:typA::resB but none were seen<{%reset%}>\n"))
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate, "1")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Check resA and the provider are still listed in the snapshot
|
|
if !assert.Len(t, snap.Resources, 2) {
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestPropertySetChange(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "baz",
|
|
})
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial plan to create resA
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Now change the runtime to not return property "frob", this should error
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
|
|
"properties changed: +-frob[{baz}]<{%reset%}>\n"))
|
|
snap, err := lt.TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestExpectedUnneededCreate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create a plan that resA needs creating
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Create an a snapshot that resA exists
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Now run again with the plan set but the snapshot that resA already exists
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Check resA and the provider are still listed in the snapshot
|
|
if !assert.Len(t, snap.Resources, 2) {
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestExpectedUnneededDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
DeleteF: func(context.Context, plugin.DeleteRequest) (plugin.DeleteResponse, error) {
|
|
return plugin.DeleteResponse{}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
createResource := true
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
if createResource {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial snapshot that resA exists
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Create a plan that resA is deleted
|
|
createResource = false
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Now run to delete resA
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Now run again with the plan set but the snapshot that resA is already deleted
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "2")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resources are still gone
|
|
if !assert.Len(t, snap.Resources, 0) {
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestResoucesWithSames(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// This test checks that if between generating a constraint and running the update that if new resources have been
|
|
// added to the stack that the update doesn't change those resources in any way that they don't cause constraint
|
|
// errors.
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var ins resource.PropertyMap
|
|
createA := false
|
|
createB := false
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
if createA {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
if createB {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
|
Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"X": "Y",
|
|
}),
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Generate a plan to create A
|
|
createA = true
|
|
createB = false
|
|
computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": computed,
|
|
})
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Run an update that creates B
|
|
createA = false
|
|
createB = true
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 2) {
|
|
return
|
|
}
|
|
|
|
expected := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"X": "Y",
|
|
})
|
|
assert.Equal(t, expected, snap.Resources[1].Outputs)
|
|
|
|
// Attempt to run an update with the plan on the stack that creates A and sames B
|
|
createA = true
|
|
createB = true
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": 24,
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 3) {
|
|
return
|
|
}
|
|
|
|
expected = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"X": "Y",
|
|
})
|
|
assert.Equal(t, expected, snap.Resources[2].Outputs)
|
|
|
|
expected = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": 24,
|
|
})
|
|
assert.Equal(t, expected, snap.Resources[1].Outputs)
|
|
}
|
|
|
|
func TestPlannedPreviews(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// This checks that plans work in previews, this is very similar to TestPlannedUpdate except we only do previews
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var ins resource.PropertyMap
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Generate a plan.
|
|
computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": map[string]interface{}{
|
|
"a": 42,
|
|
"b": computed,
|
|
},
|
|
"qux": []interface{}{
|
|
computed,
|
|
24,
|
|
},
|
|
"zed": computed,
|
|
})
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Attempt to run a new preview using the plan, given we've changed the property set this should fail
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"qux": []interface{}{
|
|
"alpha",
|
|
24,
|
|
},
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: properties changed: "+
|
|
"+-baz[{map[a:{42} b:output<string>{}]}], +-foo[{bar}]<{%reset%}>\n"))
|
|
_, err = lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, validate)
|
|
assert.NoError(t, err)
|
|
|
|
// Attempt to run an preview using the plan, such that the property set is now valid
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": map[string]interface{}{
|
|
"a": 42,
|
|
"b": computed,
|
|
},
|
|
"qux": []interface{}{
|
|
"beta",
|
|
24,
|
|
},
|
|
"zed": "grr",
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
_, err = lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPlannedUpdateChangedStack(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// This tests the case that we run a planned update against a stack that has changed between preview and update
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var ins resource.PropertyMap
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Set initial data for foo and zed
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": 24,
|
|
})
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Generate a plan that we want to change foo
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "baz",
|
|
"zed": 24,
|
|
})
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Change zed in the stack
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": 26,
|
|
})
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NoError(t, err)
|
|
|
|
// Attempt to run an update using the plan but where we haven't updated our program for the change of zed
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "baz",
|
|
"zed": 24,
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
|
|
"properties changed: =~zed[{24}]<{%reset%}>\n"))
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate, "2")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state we shouldn't of changed anything because the update failed
|
|
if !assert.Len(t, snap.Resources, 2) {
|
|
return
|
|
}
|
|
|
|
expected := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": 26,
|
|
})
|
|
assert.Equal(t, expected, snap.Resources[1].Outputs)
|
|
}
|
|
|
|
func TestPlannedOutputChanges(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
outs := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "baz",
|
|
})
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
resp, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
|
|
assert.NoError(t, err)
|
|
|
|
err = monitor.RegisterResourceOutputs(resp.URN, outs)
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial plan to create resA and the outputs
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Now change the runtime to not return property "frob", this should error
|
|
outs = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>resource violates plan: properties changed: +-frob[{baz}]<{%reset%}>\n"))
|
|
snap, err := lt.TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPlannedInputOutputDifferences(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// This tests that plans are working on the program inputs, not the provider outputs
|
|
|
|
createOutputs := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "baz",
|
|
"baz": 24,
|
|
})
|
|
updateOutputs := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "newBazzer",
|
|
"baz": 24,
|
|
})
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: createOutputs,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
UpdateF: func(_ context.Context, req plugin.UpdateRequest) (plugin.UpdateResponse, error) {
|
|
return plugin.UpdateResponse{
|
|
Properties: updateOutputs,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
inputs := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "baz",
|
|
})
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: inputs,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial plan to create resA
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Check we can create resA even though its outputs are different to the planned inputs
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Make a plan to change resA
|
|
inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "newBazzer",
|
|
})
|
|
p.Options.Plan = nil
|
|
plan, err = lt.TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Test the plan fails if we don't pass newBazzer
|
|
inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "differentBazzer",
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
|
|
"properties changed: ~~frob[{newBazzer}!={differentBazzer}]<{%reset%}>\n"))
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate, "1")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Check the plan succeeds if we do pass newBazzer
|
|
inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "newBazzer",
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "2")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestAliasWithPlans(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// This tests that if a resource has an alias the plan for it is still used
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
resourceName := "resA"
|
|
var aliases []resource.URN
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"frob": "baz",
|
|
})
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", resourceName, true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
AliasURNs: aliases,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial ResA
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Update the name and alias and make a plan for resA
|
|
resourceName = "newResA"
|
|
aliases = make([]resource.URN, 1)
|
|
aliases[0] = resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA")
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Now try and run with the plan
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestComputedCanBeDropped(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// This tests that values that show as <computed> in the plan can be dropped in the update (because they may of
|
|
// resolved to undefined). We're testing both RegisterResource and RegisterResourceOutputs here.
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var resourceInputs resource.PropertyMap
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
resp, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
|
|
assert.NoError(t, err)
|
|
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
|
Inputs: resourceInputs,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
// We're using the same property set on purpose, this is not a test bug
|
|
err = monitor.RegisterResourceOutputs(resp.URN, resourceInputs)
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// The three property sets we'll use in this test
|
|
computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
|
|
computedPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": map[string]interface{}{
|
|
"a": 42,
|
|
"b": computed,
|
|
},
|
|
"qux": []interface{}{
|
|
computed,
|
|
24,
|
|
},
|
|
"zed": computed,
|
|
})
|
|
fullPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": map[string]interface{}{
|
|
"a": 42,
|
|
"b": "alpha",
|
|
},
|
|
"qux": []interface{}{
|
|
"beta",
|
|
24,
|
|
},
|
|
"zed": "grr",
|
|
})
|
|
partialPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": map[string]interface{}{
|
|
"a": 42,
|
|
},
|
|
"qux": []interface{}{
|
|
nil, // computed values that resolve to undef don't get dropped from arrays, they just become null
|
|
24,
|
|
},
|
|
})
|
|
|
|
// Generate a plan.
|
|
resourceInputs = computedPropertySet
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Attempt to run an update using the plan with all computed values removed
|
|
resourceInputs = partialPropertySet
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 3) {
|
|
return
|
|
}
|
|
|
|
assert.Equal(t, partialPropertySet, snap.Resources[1].Outputs)
|
|
assert.Equal(t, partialPropertySet, snap.Resources[2].Outputs)
|
|
|
|
// Now run an update to set the values of the computed properties...
|
|
resourceInputs = fullPropertySet
|
|
p.Options.Plan = nil
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 3) {
|
|
return
|
|
}
|
|
|
|
assert.Equal(t, fullPropertySet, snap.Resources[1].Outputs)
|
|
assert.Equal(t, fullPropertySet, snap.Resources[2].Outputs)
|
|
|
|
// ...and then build a new plan where they're computed updates (vs above where its computed creates)
|
|
resourceInputs = computedPropertySet
|
|
plan, err = lt.TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Now run the an update with the plan and check the update is allowed to remove these properties
|
|
resourceInputs = partialPropertySet
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "2")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 3) {
|
|
return
|
|
}
|
|
|
|
assert.Equal(t, partialPropertySet, snap.Resources[1].Outputs)
|
|
assert.Equal(t, partialPropertySet, snap.Resources[2].Outputs)
|
|
}
|
|
|
|
func TestPlannedUpdateWithNondeterministicCheck(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
CheckF: func(
|
|
_ context.Context,
|
|
req plugin.CheckRequest,
|
|
) (plugin.CheckResponse, error) {
|
|
// If we have name use it, else use olds name, else make one up
|
|
if _, has := req.News["name"]; has {
|
|
return plugin.CheckResponse{Properties: req.News}, nil
|
|
}
|
|
if _, has := req.Olds["name"]; has {
|
|
result := req.News.Copy()
|
|
result["name"] = req.Olds["name"]
|
|
return plugin.CheckResponse{Properties: result}, nil
|
|
}
|
|
|
|
name, err := resource.NewUniqueHex(req.URN.Name(), 8, 512)
|
|
assert.NoError(t, err)
|
|
|
|
result := req.News.Copy()
|
|
result["name"] = resource.NewStringProperty(name)
|
|
return plugin.CheckResponse{Properties: result}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var ins resource.PropertyMap
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
resp, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
|
Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"other": resp.Outputs["name"].StringValue(),
|
|
}),
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
SkipDisplayTests: true,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Generate a plan.
|
|
computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": computed,
|
|
})
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
// Attempt to run an update using the plan.
|
|
// This should fail because of the non-determinism
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": "baz",
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
|
|
validate := ExpectDiagMessage(t,
|
|
"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
|
|
"properties changed: \\+\\+name\\[{res[\\d\\w]{9}}!={res[\\d\\w]{9}}\\]<{%reset%}>\\n")
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate, "4")
|
|
assert.NoError(t, err)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 1) {
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestPlannedUpdateWithCheckFailure(t *testing.T) {
|
|
// Regression test for https://github.com/pulumi/pulumi/issues/9247
|
|
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
CheckF: func(
|
|
_ context.Context,
|
|
req plugin.CheckRequest,
|
|
) (plugin.CheckResponse, error) {
|
|
if req.News["foo"].StringValue() == "bad" {
|
|
return plugin.CheckResponse{
|
|
Failures: []plugin.CheckFailure{{
|
|
Property: resource.PropertyKey("foo"),
|
|
Reason: "Bad foo",
|
|
}},
|
|
}, nil
|
|
}
|
|
return plugin.CheckResponse{Properties: req.News}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var ins resource.PropertyMap
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Generate a plan with bad inputs
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bad",
|
|
})
|
|
validate := ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>pkgA:m:typA resource 'resA': property foo value {bad} has a problem: Bad foo<{%reset%}>\n"))
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, validate)
|
|
assert.Nil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
// Generate a plan with good inputs
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "good",
|
|
})
|
|
plan, err = lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.Contains(t, plan.ResourcePlans, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"))
|
|
assert.NoError(t, err)
|
|
|
|
// Try and run against the plan with inputs that will fail Check
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bad",
|
|
})
|
|
p.Options.Plan = plan.Clone()
|
|
validate = ExpectDiagMessage(t, regexp.QuoteMeta(
|
|
"<{%reset%}>pkgA:m:typA resource 'resA': property foo value {bad} has a problem: Bad foo<{%reset%}>\n"))
|
|
snap, err := lt.TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, snap)
|
|
|
|
// Check the resource's state.
|
|
if !assert.Len(t, snap.Resources, 1) {
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestPluginsAreDownloaded(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{}, nil
|
|
}),
|
|
}
|
|
|
|
semver10 := semver.MustParse("1.0.0")
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
}, workspace.PluginSpec{Name: "pkgA"}, workspace.PluginSpec{Name: "pkgB", Version: &semver10})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.Contains(t, plan.ResourcePlans, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"))
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestProviderDeterministicPreview(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var generatedName resource.PropertyValue
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CheckF: func(
|
|
_ context.Context,
|
|
req plugin.CheckRequest,
|
|
) (plugin.CheckResponse, error) {
|
|
// make a deterministic autoname
|
|
if _, has := req.News["name"]; !has {
|
|
if name, has := req.Olds["name"]; has {
|
|
req.News["name"] = name
|
|
} else {
|
|
name, err := resource.NewUniqueName(req.RandomSeed, req.URN.Name(), -1, -1, nil)
|
|
assert.NoError(t, err)
|
|
generatedName = resource.NewStringProperty(name)
|
|
req.News["name"] = generatedName
|
|
}
|
|
}
|
|
|
|
return plugin.CheckResponse{Properties: req.News}, nil
|
|
},
|
|
DiffF: func(_ context.Context, req plugin.DiffRequest) (plugin.DiffResult, error) {
|
|
if !req.OldOutputs["foo"].DeepEquals(req.NewInputs["foo"]) {
|
|
// If foo changes do a replace, we use this to check we get a new name
|
|
return plugin.DiffResult{
|
|
Changes: plugin.DiffSome,
|
|
ReplaceKeys: []resource.PropertyKey{"foo"},
|
|
}, nil
|
|
}
|
|
return plugin.DiffResult{}, nil
|
|
},
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}, deploytest.WithoutGrpc),
|
|
}
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
SkipDisplayTests: true,
|
|
UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
|
|
},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Run a preview, this should want to create resA with a given name
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
assert.True(t, generatedName.IsString())
|
|
assert.NotEqual(t, "", generatedName.StringValue())
|
|
expectedName := generatedName
|
|
|
|
// Run an update, we should get the same name as we saw in preview
|
|
p.Options.Plan = plan
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, snap)
|
|
assert.Len(t, snap.Resources, 2)
|
|
assert.Equal(t, expectedName, snap.Resources[1].Inputs["name"])
|
|
assert.Equal(t, expectedName, snap.Resources[1].Outputs["name"])
|
|
|
|
// Run a new update which will cause a replace and check we get a new name
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "baz",
|
|
})
|
|
p.Options.Plan = nil
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, snap)
|
|
assert.Len(t, snap.Resources, 2)
|
|
assert.NotEqual(t, expectedName, snap.Resources[1].Inputs["name"])
|
|
assert.NotEqual(t, expectedName, snap.Resources[1].Outputs["name"])
|
|
}
|
|
|
|
func TestPlannedUpdateWithDependentDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var diffResult *plugin.DiffResult
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: resource.ID("created-id-" + req.URN.Name()),
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
CheckF: func(
|
|
_ context.Context,
|
|
req plugin.CheckRequest,
|
|
) (plugin.CheckResponse, error) {
|
|
return plugin.CheckResponse{Properties: req.News}, nil
|
|
},
|
|
DiffF: func(_ context.Context, req plugin.DiffRequest) (plugin.DiffResult, error) {
|
|
if strings.Contains(string(req.URN), "resA") || strings.Contains(string(req.URN), "resB") {
|
|
assert.NotNil(t, diffResult, "Diff was called but diffResult wasn't set")
|
|
return *diffResult, nil
|
|
}
|
|
return plugin.DiffResult{}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var ins resource.PropertyMap
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
respA, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: ins,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
_, err = monitor.RegisterResource("pkgA:m:typB", "resB", true, deploytest.ResourceOptions{
|
|
Inputs: respA.Outputs,
|
|
Dependencies: []resource.URN{respA.URN},
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF, UpdateOptions: UpdateOptions{GeneratePlan: true}},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create an initial ResA and resB
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "bar",
|
|
"zed": "baz",
|
|
})
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
|
|
// Update the input and mark it as a replace, check that both A and B are marked as replacements
|
|
ins = resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
"foo": "frob",
|
|
"zed": "baz",
|
|
})
|
|
diffResult = &plugin.DiffResult{
|
|
Changes: plugin.DiffSome,
|
|
ReplaceKeys: []resource.PropertyKey{"foo"},
|
|
StableKeys: []resource.PropertyKey{"zed"},
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
"foo": {
|
|
Kind: plugin.DiffUpdateReplace,
|
|
InputDiff: true,
|
|
},
|
|
},
|
|
DeleteBeforeReplace: true,
|
|
}
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
|
|
assert.NotNil(t, plan)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 3, len(plan.ResourcePlans["urn:pulumi:test::test::pkgA:m:typA::resA"].Ops))
|
|
assert.Equal(t, 3, len(plan.ResourcePlans["urn:pulumi:test::test::pkgA:m:typB::resB"].Ops))
|
|
|
|
// Now try and run with the plan
|
|
p.Options.Plan = plan.Clone()
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.NotNil(t, snap)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// TestResourcesTargeted checks that a plan created with targets specified captures only those targets and
|
|
// default providers. It checks that trying to construct a new resource that was not targeted in the plan
|
|
// fails and that the update with the same --targets specified is compatible with the plan (roundtripped).
|
|
func TestResourcesTargeted(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{}
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create the update plan with only targeted resources.
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{
|
|
Experimental: true,
|
|
GeneratePlan: true,
|
|
Targets: deploy.NewUrnTargets([]string{
|
|
"urn:pulumi:test::test::pkgA:m:typA::resB",
|
|
}),
|
|
},
|
|
}, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, plan)
|
|
|
|
// Check that running an update with everything targeted fails due to our plan being constrained
|
|
// to the resource.
|
|
_, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{
|
|
// Clone the plan as the plan will be mutated by the engine and useless in future runs.
|
|
Plan: plan.Clone(),
|
|
Experimental: true,
|
|
},
|
|
}, false, p.BackendClient, nil, "0")
|
|
assert.Error(t, err)
|
|
|
|
// Check that running an update with the same Targets as the Plan succeeds.
|
|
_, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
UpdateOptions: UpdateOptions{
|
|
// Clone the plan as the plan will be mutated by the engine and useless in future runs.
|
|
Plan: plan.Clone(),
|
|
Experimental: true,
|
|
Targets: deploy.NewUrnTargets([]string{
|
|
"urn:pulumi:test::test::pkgA:m:typA::resB",
|
|
}),
|
|
},
|
|
}, false, p.BackendClient, nil, "1")
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// This test checks that registering resource outputs does not fail for the root stack resource when it
|
|
// is not specified by the update targets.
|
|
func TestStackOutputsWithTargetedPlan(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := <.TestPlan{}
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
resp, err := monitor.RegisterResource("pulumi:pulumi:Stack", "test-test", false)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
err = monitor.RegisterResourceOutputs(resp.URN, resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
|
|
p.Options.HostF = deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
project := p.GetProject()
|
|
|
|
// Create the update plan without targeting the root stack.
|
|
plan, err := lt.TestOp(Update).Plan(project, p.GetTarget(t, nil), lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: p.Options.HostF,
|
|
UpdateOptions: UpdateOptions{
|
|
Experimental: true,
|
|
GeneratePlan: true,
|
|
Targets: deploy.NewUrnTargetsFromUrns([]resource.URN{
|
|
resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"),
|
|
}),
|
|
},
|
|
}, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, plan)
|
|
|
|
// Check that update succeeds despite the root stack not being targeted.
|
|
_, err = lt.TestOp(Update).Run(project, p.GetTarget(t, nil), lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: p.Options.HostF,
|
|
UpdateOptions: UpdateOptions{
|
|
GeneratePlan: true,
|
|
Experimental: true,
|
|
Targets: deploy.NewUrnTargetsFromUrns([]resource.URN{
|
|
resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"),
|
|
}),
|
|
},
|
|
}, false, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
}
|