pulumi/pkg/engine/lifeycletest/resource_reference_test.go

226 lines
7.4 KiB
Go

//nolint:goconst
package lifecycletest
import (
"testing"
"github.com/blang/semver"
"github.com/stretchr/testify/assert"
. "github.com/pulumi/pulumi/pkg/v3/engine"
"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"
)
// TestResourceReferences tests that resource references can be marshaled between the engine, language host,
// resource providers, and statefile if each entity supports resource references.
func TestResourceReferences(t *testing.T) {
var urnA resource.URN
var urnB resource.URN
var idB resource.ID
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
v := &deploytest.Provider{
CreateF: func(urn resource.URN, news resource.PropertyMap,
timeout float64, preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
id := "created-id"
if preview {
id = ""
}
if urn.Name() == "resC" {
assert.True(t, news.DeepEquals(resource.PropertyMap{
"resA": resource.MakeComponentResourceReference(urnA, ""),
"resB": resource.MakeCustomResourceReference(urnB, idB, ""),
}))
}
return resource.ID(id), news, resource.StatusOK, nil
},
ReadF: func(urn resource.URN, id resource.ID,
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
},
}
return v, nil
}),
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
var err error
urnA, _, _, err = monitor.RegisterResource("component", "resA", false)
assert.NoError(t, err)
err = monitor.RegisterResourceOutputs(urnA, resource.PropertyMap{})
assert.NoError(t, err)
urnB, idB, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true)
assert.NoError(t, err)
_, _, props, err := monitor.RegisterResource("pkgA:m:typA", "resC", true, deploytest.ResourceOptions{
Inputs: resource.PropertyMap{
"resA": resource.MakeComponentResourceReference(urnA, ""),
"resB": resource.MakeCustomResourceReference(urnB, idB, ""),
},
})
assert.NoError(t, err)
assert.True(t, props.DeepEquals(resource.PropertyMap{
"resA": resource.MakeComponentResourceReference(urnA, ""),
"resB": resource.MakeCustomResourceReference(urnB, idB, ""),
}))
return nil
})
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{Host: host},
Steps: MakeBasicLifecycleSteps(t, 4),
}
p.Run(t, nil)
}
// TestResourceReferences_DownlevelSDK tests that resource references are properly marshaled as URNs (for references to
// component resources) or IDs (for references to custom resources) if the SDK does not support resource references.
func TestResourceReferences_DownlevelSDK(t *testing.T) {
var urnA resource.URN
var urnB resource.URN
var idB resource.ID
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
v := &deploytest.Provider{
CreateF: func(urn resource.URN, news resource.PropertyMap,
timeout float64, preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
id := "created-id"
if preview {
id = ""
}
state := resource.PropertyMap{}
if urn.Name() == "resC" {
state = resource.PropertyMap{
"resA": resource.MakeComponentResourceReference(urnA, ""),
"resB": resource.MakeCustomResourceReference(urnB, idB, ""),
}
}
return resource.ID(id), state, resource.StatusOK, nil
},
ReadF: func(urn resource.URN, id resource.ID,
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
},
}
return v, nil
}),
}
opts := deploytest.ResourceOptions{DisableResourceReferences: true}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
var err error
urnA, _, _, err = monitor.RegisterResource("component", "resA", false, opts)
assert.NoError(t, err)
err = monitor.RegisterResourceOutputs(urnA, resource.PropertyMap{})
assert.NoError(t, err)
urnB, idB, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, opts)
assert.NoError(t, err)
_, _, props, err := monitor.RegisterResource("pkgA:m:typA", "resC", true, opts)
assert.NoError(t, err)
assert.Equal(t, resource.NewStringProperty(string(urnA)), props["resA"])
if idB != "" {
assert.Equal(t, resource.NewStringProperty(string(idB)), props["resB"])
} else {
assert.True(t, props["resB"].IsComputed())
}
return nil
})
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{Host: host},
Steps: MakeBasicLifecycleSteps(t, 4),
}
p.Run(t, nil)
}
// TestResourceReferences_DownlevelEngine tests an SDK that supports resource references communicating with an engine
// that does not.
func TestResourceReferences_DownlevelEngine(t *testing.T) {
var urnA resource.URN
var refB resource.PropertyValue
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
v := &deploytest.Provider{
CreateF: func(urn resource.URN, news resource.PropertyMap,
timeout float64, preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
id := "created-id"
if preview {
id = ""
}
// If we have resource references here, the engine has not properly disabled them.
if urn.Name() == "resC" {
assert.Equal(t, resource.NewStringProperty(string(urnA)), news["resA"])
assert.Equal(t, refB.ResourceReferenceValue().ID, news["resB"])
}
return resource.ID(id), news, resource.StatusOK, nil
},
ReadF: func(urn resource.URN, id resource.ID,
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
},
}
return v, nil
}),
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
var err error
urnA, _, _, err = monitor.RegisterResource("component", "resA", false)
assert.NoError(t, err)
err = monitor.RegisterResourceOutputs(urnA, resource.PropertyMap{})
assert.NoError(t, err)
urnB, idB, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true)
assert.NoError(t, err)
refB = resource.MakeCustomResourceReference(urnB, idB, "")
_, _, props, err := monitor.RegisterResource("pkgA:m:typA", "resC", true, deploytest.ResourceOptions{
Inputs: resource.PropertyMap{
"resA": resource.MakeComponentResourceReference(urnA, ""),
"resB": refB,
},
})
assert.NoError(t, err)
assert.Equal(t, resource.NewStringProperty(string(urnA)), props["resA"])
if refB.ResourceReferenceValue().ID.IsComputed() {
assert.True(t, props["resB"].IsComputed())
} else {
assert.True(t, refB.ResourceReferenceValue().ID.DeepEquals(props["resB"]))
}
return nil
})
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{Host: host, DisableResourceReferences: true},
Steps: MakeBasicLifecycleSteps(t, 4),
}
p.Run(t, nil)
}