pulumi/pkg/engine/lifeycletest/resource_reference_test.go
Pat Gavlin 9b6a7a4397
Improve resource ref unit tests. (#5960)
- Add component ref coverage to the existing test
- Add coverage for a downlevel SDK communicating with an engine that
  supports resource refs
- Add coverage for a downlevel engine communicating with an SDK that
  supports resource refs

As part of improving coverage, these changes add a knob to explicitly
disable resource refs in the engine without the use of the environment
variable. The environment variable is now only read by the CLI, and has
been restored to its prior polarity (i.e. `PULUMI_ENABLE_RESOURCE_REFERENCES`).
2020-12-16 12:38:20 -08:00

222 lines
7.3 KiB
Go

//nolint:goconst
package lifecycletest
import (
"testing"
"github.com/blang/semver"
"github.com/stretchr/testify/assert"
. "github.com/pulumi/pulumi/pkg/v2/engine"
"github.com/pulumi/pulumi/pkg/v2/resource/deploy/deploytest"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
"github.com/pulumi/pulumi/sdk/v2/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.MakeResourceReference(urnA, "", false, ""),
"resB": resource.MakeResourceReference(urnB, idB, true, ""),
}))
}
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.MakeResourceReference(urnA, "", false, ""),
"resB": resource.MakeResourceReference(urnB, idB, true, ""),
},
})
assert.NoError(t, err)
assert.True(t, props.DeepEquals(resource.PropertyMap{
"resA": resource.MakeResourceReference(urnA, "", false, ""),
"resB": resource.MakeResourceReference(urnB, idB, true, ""),
}))
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.MakeResourceReference(urnA, "", false, ""),
"resB": resource.MakeResourceReference(urnB, idB, true, ""),
}
}
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.True(t, props.DeepEquals(resource.PropertyMap{
"resA": resource.NewStringProperty(string(urnA)),
"resB": resource.NewStringProperty(string(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_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 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 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, resource.NewStringProperty(string(idB)), 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)
_, _, props, err := monitor.RegisterResource("pkgA:m:typA", "resC", true, deploytest.ResourceOptions{
Inputs: resource.PropertyMap{
"resA": resource.MakeResourceReference(urnA, "", false, ""),
"resB": resource.MakeResourceReference(urnB, idB, true, ""),
},
})
assert.NoError(t, err)
assert.True(t, props.DeepEquals(resource.PropertyMap{
"resA": resource.NewStringProperty(string(urnA)),
"resB": resource.NewStringProperty(string(idB)),
}))
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)
}