pulumi/pkg/resource/deploy/providers/registry_test.go
Sean Gillespie 26cc1085b1
Install missing plugins on startup (#2560)
* Install missing plugins on startup

This commit addresses the problem of missing plugins by scanning the
snapshot and language host on startup for the list of required plugins
and, if there are any plugins that are required but not installed,
installs them. The mechanism by which plugins are installed is exactly
the same as 'pulumi plugin install'.

The installation of missing plugins is best-effort and, if it fails,
will not fail the update.

This commit addresses pulumi/pulumi-azure#200, where users using Pulumi
in CI often found themselves missing plugins.

* Add CHANGELOG

* Skip downloading plugins if no client provided

* Reduce excessive test output

* Update Gopkg.lock

* Update pkg/engine/destroy.go

Co-Authored-By: swgillespie <sean@pulumi.com>

* CR: make pluginSet a newtype

* CR: Assign loop induction var to local var
2019-03-15 15:01:37 -07:00

734 lines
21 KiB
Go

// Copyright 2016-2018, 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 providers
import (
"testing"
"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/workspace"
)
type testPluginHost struct {
t *testing.T
provider func(pkg tokens.Package, version *semver.Version) (plugin.Provider, error)
closeProvider func(provider plugin.Provider) error
}
func (host *testPluginHost) SignalCancellation() error {
return nil
}
func (host *testPluginHost) Close() error {
return nil
}
func (host *testPluginHost) ServerAddr() string {
host.t.Fatalf("Host RPC address not available")
return ""
}
func (host *testPluginHost) Log(sev diag.Severity, urn resource.URN, msg string, streamID int32) {
host.t.Logf("[%v] %v@%v: %v", sev, urn, streamID, msg)
}
func (host *testPluginHost) LogStatus(sev diag.Severity, urn resource.URN, msg string, streamID int32) {
host.t.Logf("[%v] %v@%v: %v", sev, urn, streamID, msg)
}
func (host *testPluginHost) Analyzer(nm tokens.QName) (plugin.Analyzer, error) {
return nil, errors.New("unsupported")
}
func (host *testPluginHost) Provider(pkg tokens.Package, version *semver.Version) (plugin.Provider, error) {
return host.provider(pkg, version)
}
func (host *testPluginHost) CloseProvider(provider plugin.Provider) error {
return host.closeProvider(provider)
}
func (host *testPluginHost) LanguageRuntime(runtime string) (plugin.LanguageRuntime, error) {
return nil, errors.New("unsupported")
}
func (host *testPluginHost) ListPlugins() []workspace.PluginInfo {
return nil
}
func (host *testPluginHost) EnsurePlugins(plugins []workspace.PluginInfo, kinds plugin.Flags) error {
return nil
}
func (host *testPluginHost) GetRequiredPlugins(info plugin.ProgInfo,
kinds plugin.Flags) ([]workspace.PluginInfo, error) {
return nil, nil
}
type testProvider struct {
pkg tokens.Package
version semver.Version
configured bool
checkConfig func(resource.PropertyMap, resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error)
diffConfig func(resource.PropertyMap, resource.PropertyMap) (plugin.DiffResult, error)
config func(resource.PropertyMap) error
}
func (prov *testProvider) SignalCancellation() error {
return nil
}
func (prov *testProvider) Close() error {
return nil
}
func (prov *testProvider) Pkg() tokens.Package {
return prov.pkg
}
func (prov *testProvider) CheckConfig(olds,
news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return prov.checkConfig(olds, news)
}
func (prov *testProvider) DiffConfig(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
return prov.diffConfig(olds, news)
}
func (prov *testProvider) Configure(inputs resource.PropertyMap) error {
if err := prov.config(inputs); err != nil {
return err
}
prov.configured = true
return nil
}
func (prov *testProvider) Check(urn resource.URN,
olds, news resource.PropertyMap, _ bool) (resource.PropertyMap, []plugin.CheckFailure, error) {
return nil, nil, errors.New("unsupported")
}
func (prov *testProvider) Create(urn resource.URN, props resource.PropertyMap) (resource.ID,
resource.PropertyMap, resource.Status, error) {
return "", nil, resource.StatusOK, errors.New("unsupported")
}
func (prov *testProvider) Read(urn resource.URN, id resource.ID,
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
return plugin.ReadResult{}, resource.StatusUnknown, errors.New("unsupported")
}
func (prov *testProvider) Diff(urn resource.URN, id resource.ID,
olds resource.PropertyMap, news resource.PropertyMap, _ bool) (plugin.DiffResult, error) {
return plugin.DiffResult{}, errors.New("unsupported")
}
func (prov *testProvider) Update(urn resource.URN, id resource.ID,
olds resource.PropertyMap, news resource.PropertyMap) (resource.PropertyMap, resource.Status, error) {
return nil, resource.StatusOK, errors.New("unsupported")
}
func (prov *testProvider) Delete(urn resource.URN,
id resource.ID, props resource.PropertyMap) (resource.Status, error) {
return resource.StatusOK, errors.New("unsupported")
}
func (prov *testProvider) Invoke(tok tokens.ModuleMember,
args resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return nil, nil, errors.New("unsupported")
}
func (prov *testProvider) GetPluginInfo() (workspace.PluginInfo, error) {
return workspace.PluginInfo{
Name: "testProvider",
Version: &prov.version,
}, nil
}
type providerLoader struct {
pkg tokens.Package
version semver.Version
load func() (plugin.Provider, error)
}
func newPluginHost(t *testing.T, loaders []*providerLoader) plugin.Host {
return &testPluginHost{
t: t,
provider: func(pkg tokens.Package, ver *semver.Version) (plugin.Provider, error) {
var best *providerLoader
for _, l := range loaders {
if l.pkg != pkg {
continue
}
if ver != nil && l.version.LT(*ver) {
continue
}
if best == nil || l.version.GT(best.version) {
best = l
}
}
if best == nil {
return nil, nil
}
return best.load()
},
closeProvider: func(provider plugin.Provider) error {
return nil
},
}
}
func newLoader(t *testing.T, pkg, version string,
load func(tokens.Package, semver.Version) (plugin.Provider, error)) *providerLoader {
var ver semver.Version
if version != "" {
v, err := semver.ParseTolerant(version)
assert.NoError(t, err)
ver = v
}
return &providerLoader{
pkg: tokens.Package(pkg),
version: ver,
load: func() (plugin.Provider, error) {
return load(tokens.Package(pkg), ver)
},
}
}
func newSimpleLoader(t *testing.T, pkg, version string, config func(resource.PropertyMap) error) *providerLoader {
if config == nil {
config = func(resource.PropertyMap) error {
return nil
}
}
return newLoader(t, pkg, version, func(pkg tokens.Package, ver semver.Version) (plugin.Provider, error) {
return &testProvider{
pkg: pkg,
version: ver,
checkConfig: func(olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return news, nil, nil
},
diffConfig: func(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
return plugin.DiffResult{}, nil
},
config: config,
}, nil
})
}
func newProviderState(pkg, name, id string, delete bool, inputs resource.PropertyMap) *resource.State {
typ := MakeProviderType(tokens.Package(pkg))
urn := resource.NewURN("test", "test", "", typ, tokens.QName(name))
if inputs == nil {
inputs = resource.PropertyMap{}
}
return &resource.State{
Type: typ,
URN: urn,
Custom: true,
Delete: delete,
ID: resource.ID(id),
Inputs: inputs,
}
}
func TestNewRegistryNoOldState(t *testing.T) {
r, err := NewRegistry(&testPluginHost{}, nil, false, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
r, err = NewRegistry(&testPluginHost{}, nil, true, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
}
func TestNewRegistryOldState(t *testing.T) {
olds := []*resource.State{
// Two providers from package A, each with a unique name and ID
newProviderState("pkgA", "a", "id1", false, nil),
newProviderState("pkgA", "b", "id2", false, nil),
// Two providers from package B, each with a unique name and ID
newProviderState("pkgB", "a", "id1", false, nil),
newProviderState("pkgB", "b", "id2", false, nil),
// Two providers from package C, both with the same name but with unique IDs and one marked for deletion
newProviderState("pkgC", "a", "id1", false, nil),
newProviderState("pkgC", "a", "id2", true, nil),
// One provider from package D with a version
newProviderState("pkgD", "a", "id1", false, resource.PropertyMap{
"version": resource.NewStringProperty("1.0.0"),
}),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
newSimpleLoader(t, "pkgB", "", nil),
newSimpleLoader(t, "pkgC", "", nil),
newSimpleLoader(t, "pkgD", "1.0.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
assert.Equal(t, len(olds), len(r.providers))
for _, old := range olds {
ref, err := NewReference(old.URN, old.ID)
assert.NoError(t, err)
p, ok := r.GetProvider(ref)
assert.True(t, ok)
assert.NotNil(t, p)
assert.True(t, p.(*testProvider).configured)
assert.Equal(t, GetProviderPackage(old.Type), p.Pkg())
ver, err := GetProviderVersion(old.Inputs)
assert.NoError(t, err)
if ver != nil {
info, err := p.GetPluginInfo()
assert.NoError(t, err)
assert.True(t, info.Version.GTE(*ver))
}
}
}
func TestNewRegistryOldStateNoProviders(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
}
host := newPluginHost(t, []*providerLoader{})
r, err := NewRegistry(host, olds, false, nil)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateWrongPackage(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgB", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false, nil)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateWrongVersion(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, resource.PropertyMap{
"version": resource.NewStringProperty("1.0.0"),
}),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "0.5.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false, nil)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateNoID(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false, nil)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateUnknownID(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", UnknownID, false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false, nil)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateDuplicates(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
newProviderState("pkgA", "a", "id1", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false, nil)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestCRUD(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
newProviderState("pkgB", "a", "id1", false, nil),
newProviderState("pkgC", "a", "id1", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
newSimpleLoader(t, "pkgB", "", nil),
newSimpleLoader(t, "pkgC", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
assert.Equal(t, len(olds), len(r.providers))
for _, old := range olds {
ref, err := NewReference(old.URN, old.ID)
assert.NoError(t, err)
p, ok := r.GetProvider(ref)
assert.True(t, ok)
assert.NotNil(t, p)
assert.Equal(t, GetProviderPackage(old.Type), p.Pkg())
}
// Create a new provider for each package.
for _, l := range loaders {
typ := MakeProviderType(l.pkg)
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is not a preview, the provider should not yet be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.False(t, p.(*testProvider).configured)
// Create
id, outs, status, err := r.Create(urn, inputs)
assert.NoError(t, err)
assert.NotEqual(t, "", id)
assert.NotEqual(t, UnknownID, id)
assert.Equal(t, resource.PropertyMap{}, outs)
assert.Equal(t, resource.StatusOK, status)
p2, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.Equal(t, p, p2)
assert.True(t, p2.(*testProvider).configured)
}
// Update the existing provider for the first entry in olds.
{
urn, id := olds[0].URN, olds[0].ID
olds, news := olds[0].Inputs, olds[0].Inputs
// Fetch the old provider instance.
old, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is not a preview, the provider should not yet be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.False(t, p == old)
assert.False(t, p.(*testProvider).configured)
// Diff
diff, err := r.Diff(urn, id, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, plugin.DiffResult{}, diff)
// The old provider should still be registered.
p2, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.Equal(t, old, p2)
// Update
outs, status, err := r.Update(urn, id, olds, inputs)
assert.NoError(t, err)
assert.Equal(t, resource.PropertyMap{}, outs)
assert.Equal(t, resource.StatusOK, status)
p3, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.True(t, p3 == p)
assert.True(t, p3.(*testProvider).configured)
}
// Delete the existingv provider for the last entry in olds.
{
urn, id := olds[len(olds)-1].URN, olds[len(olds)-1].ID
// Fetch the old provider instance.
_, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
// Delete
status, err := r.Delete(urn, id, resource.PropertyMap{})
assert.NoError(t, err)
assert.Equal(t, resource.StatusOK, status)
_, ok = r.GetProvider(Reference{urn: urn, id: id})
assert.False(t, ok)
}
}
func TestCRUDPreview(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
newProviderState("pkgB", "a", "id1", false, nil),
newProviderState("pkgC", "a", "id1", false, nil),
newProviderState("pkgD", "a", "id1", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
newSimpleLoader(t, "pkgB", "", nil),
newSimpleLoader(t, "pkgC", "", nil),
newLoader(t, "pkgD", "", func(pkg tokens.Package, ver semver.Version) (plugin.Provider, error) {
return &testProvider{
pkg: pkg,
version: ver,
checkConfig: func(olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return news, nil, nil
},
diffConfig: func(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
// Always reuquire replacement.
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"id"}}, nil
},
config: func(inputs resource.PropertyMap) error {
return nil
},
}, nil
}),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, true, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
assert.Equal(t, len(olds), len(r.providers))
for _, old := range olds {
ref, err := NewReference(old.URN, old.ID)
assert.NoError(t, err)
p, ok := r.GetProvider(ref)
assert.True(t, ok)
assert.NotNil(t, p)
assert.Equal(t, GetProviderPackage(old.Type), p.Pkg())
}
// Create a new provider for each package.
for _, l := range loaders {
typ := MakeProviderType(l.pkg)
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is a preview, the provider should be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.True(t, p.(*testProvider).configured)
}
// Update the existing provider for the first entry in olds.
{
urn, id := olds[0].URN, olds[0].ID
olds, news := olds[0].Inputs, olds[0].Inputs
// Fetch the old provider instance.
old, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is a preview, the provider should be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.False(t, p == old)
assert.True(t, p.(*testProvider).configured)
// Diff
diff, err := r.Diff(urn, id, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, plugin.DiffResult{}, diff)
// The new provider should be registered.
p2, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.False(t, p2 == old)
assert.True(t, p2 == p)
}
// Replace the existing provider for the last entry in olds.
{
urn, id := olds[len(olds)-1].URN, olds[len(olds)-1].ID
olds, news := olds[len(olds)-1].Inputs, olds[len(olds)-1].Inputs
// Fetch the old provider instance.
old, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is a preview, the provider should be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.False(t, p == old)
assert.True(t, p.(*testProvider).configured)
// Diff
diff, err := r.Diff(urn, id, olds, news, false)
assert.NoError(t, err)
assert.True(t, diff.Replace())
// The new provider should be not be registered; the registered provider should still be the original.
p2, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.True(t, p2 == old)
assert.False(t, p2 == p)
}
}
func TestCRUDNoProviders(t *testing.T) {
host := newPluginHost(t, []*providerLoader{})
r, err := NewRegistry(host, []*resource.State{}, false, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.Error(t, err)
assert.Empty(t, failures)
assert.Nil(t, inputs)
}
func TestCRUDWrongPackage(t *testing.T) {
loaders := []*providerLoader{
newSimpleLoader(t, "pkgB", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, []*resource.State{}, false, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.Error(t, err)
assert.Empty(t, failures)
assert.Nil(t, inputs)
}
func TestCRUDWrongVersion(t *testing.T) {
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "0.5.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, []*resource.State{}, false, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{"version": resource.NewStringProperty("1.0.0")}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.Error(t, err)
assert.Empty(t, failures)
assert.Nil(t, inputs)
}
func TestCRUDBadVersionNotString(t *testing.T) {
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "1.0.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, []*resource.State{}, false, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{"version": resource.NewBoolProperty(true)}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Len(t, failures, 1)
assert.Equal(t, "version", string(failures[0].Property))
assert.Nil(t, inputs)
}
func TestCRUDBadVersion(t *testing.T) {
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "1.0.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, []*resource.State{}, false, nil)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{"version": resource.NewStringProperty("foo")}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Len(t, failures, 1)
assert.Equal(t, "version", string(failures[0].Property))
assert.Nil(t, inputs)
}