mirror of https://github.com/pulumi/pulumi.git
476 lines
14 KiB
Go
476 lines
14 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 deploy
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource"
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy/deploytest"
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
|
|
"github.com/pulumi/pulumi/pkg/resource/plugin"
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
|
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
|
"github.com/pulumi/pulumi/pkg/workspace"
|
|
)
|
|
|
|
type testRegEvent struct {
|
|
goal *resource.Goal
|
|
result *RegisterResult
|
|
}
|
|
|
|
var _ RegisterResourceEvent = (*testRegEvent)(nil)
|
|
|
|
func (g *testRegEvent) event() {}
|
|
|
|
func (g *testRegEvent) Goal() *resource.Goal {
|
|
return g.goal
|
|
}
|
|
|
|
func (g *testRegEvent) Done(result *RegisterResult) {
|
|
contract.Assertf(g.result == nil, "Attempt to invoke testRegEvent.Done more than once")
|
|
g.result = result
|
|
}
|
|
|
|
func fixedProgram(steps []RegisterResourceEvent) deploytest.ProgramFunc {
|
|
return func(_ plugin.RunInfo, resmon *deploytest.ResourceMonitor) error {
|
|
for _, s := range steps {
|
|
g := s.Goal()
|
|
urn, id, outs, err := resmon.RegisterResource(g.Type, string(g.Name), g.Custom, g.Parent, g.Protect,
|
|
g.Dependencies, g.Provider, g.Properties)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.Done(&RegisterResult{
|
|
State: resource.NewState(g.Type, urn, g.Custom, false, id, g.Properties, outs, g.Parent, g.Protect,
|
|
false, g.Dependencies, nil, g.Provider),
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func newTestPluginContext(program deploytest.ProgramFunc) (*plugin.Context, error) {
|
|
sink := cmdutil.Diag()
|
|
lang := deploytest.NewLanguageRuntime(program)
|
|
host := deploytest.NewPluginHost(sink, lang)
|
|
return plugin.NewContext(sink, host, nil, nil, "", nil, nil)
|
|
}
|
|
|
|
type testProviderSource struct {
|
|
providers map[providers.Reference]plugin.Provider
|
|
m sync.RWMutex
|
|
}
|
|
|
|
func (s *testProviderSource) registerProvider(ref providers.Reference, provider plugin.Provider) {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
|
|
s.providers[ref] = provider
|
|
}
|
|
|
|
func (s *testProviderSource) GetProvider(ref providers.Reference) (plugin.Provider, bool) {
|
|
s.m.RLock()
|
|
defer s.m.RUnlock()
|
|
|
|
provider, ok := s.providers[ref]
|
|
return provider, ok
|
|
}
|
|
|
|
func newProviderEvent(pkg, name string, inputs resource.PropertyMap, parent resource.URN) RegisterResourceEvent {
|
|
if inputs == nil {
|
|
inputs = resource.PropertyMap{}
|
|
}
|
|
goal := &resource.Goal{
|
|
Type: providers.MakeProviderType(tokens.Package(pkg)),
|
|
Name: tokens.QName(name),
|
|
Custom: true,
|
|
Properties: inputs,
|
|
Parent: parent,
|
|
}
|
|
return &testRegEvent{goal: goal}
|
|
}
|
|
|
|
func TestRegisterNoDefaultProviders(t *testing.T) {
|
|
runInfo := &EvalRunInfo{
|
|
Proj: &workspace.Project{Name: "test"},
|
|
Target: &Target{Name: "test"},
|
|
}
|
|
|
|
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
|
|
var pt tokens.Type
|
|
if parent != "" {
|
|
pt = parent.Type()
|
|
}
|
|
return resource.NewURN(runInfo.Target.Name, runInfo.Proj.Name, pt, t, tokens.QName(name))
|
|
}
|
|
|
|
newProviderURN := func(pkg tokens.Package, name string, parent resource.URN) resource.URN {
|
|
return newURN(providers.MakeProviderType(pkg), name, parent)
|
|
}
|
|
|
|
componentURN := newURN("component", "component", "")
|
|
|
|
providerARef, err := providers.NewReference(newProviderURN("pkgA", "providerA", ""), "id1")
|
|
assert.NoError(t, err)
|
|
providerBRef, err := providers.NewReference(newProviderURN("pkgA", "providerB", componentURN), "id2")
|
|
assert.NoError(t, err)
|
|
providerCRef, err := providers.NewReference(newProviderURN("pkgC", "providerC", ""), "id1")
|
|
assert.NoError(t, err)
|
|
|
|
steps := []RegisterResourceEvent{
|
|
// Register a provider.
|
|
newProviderEvent("pkgA", "providerA", nil, ""),
|
|
// Register a component resource.
|
|
&testRegEvent{
|
|
goal: resource.NewGoal(componentURN.Type(), componentURN.Name(), false, resource.PropertyMap{}, "", false,
|
|
nil, ""),
|
|
},
|
|
// Register a couple resources using provider A.
|
|
&testRegEvent{
|
|
goal: resource.NewGoal("pkgA:index:typA", "res1", true, resource.PropertyMap{}, componentURN, false, nil,
|
|
providerARef.String()),
|
|
},
|
|
&testRegEvent{
|
|
goal: resource.NewGoal("pkgA:index:typA", "res2", true, resource.PropertyMap{}, componentURN, false, nil,
|
|
providerARef.String()),
|
|
},
|
|
// Register two more providers.
|
|
newProviderEvent("pkgA", "providerB", nil, ""),
|
|
newProviderEvent("pkgC", "providerC", nil, componentURN),
|
|
// Register a few resources that use the new providers.
|
|
&testRegEvent{
|
|
goal: resource.NewGoal("pkgB:index:typB", "res3", true, resource.PropertyMap{}, "", false, nil,
|
|
providerBRef.String()),
|
|
},
|
|
&testRegEvent{
|
|
goal: resource.NewGoal("pkgB:index:typC", "res4", true, resource.PropertyMap{}, "", false, nil,
|
|
providerCRef.String()),
|
|
},
|
|
}
|
|
|
|
// Create and iterate an eval source.
|
|
ctx, err := newTestPluginContext(fixedProgram(steps))
|
|
assert.NoError(t, err)
|
|
|
|
iter, err := NewEvalSource(ctx, runInfo, nil, false).Iterate(Options{}, &testProviderSource{})
|
|
assert.NoError(t, err)
|
|
|
|
processed := 0
|
|
for {
|
|
event, err := iter.Next()
|
|
assert.NoError(t, err)
|
|
|
|
if event == nil {
|
|
break
|
|
}
|
|
|
|
reg := event.(RegisterResourceEvent)
|
|
|
|
goal := reg.Goal()
|
|
if providers.IsProviderType(goal.Type) {
|
|
assert.NotEqual(t, "default", goal.Name)
|
|
}
|
|
urn := newURN(goal.Type, string(goal.Name), goal.Parent)
|
|
id := resource.ID("")
|
|
if goal.Custom {
|
|
id = "id"
|
|
}
|
|
reg.Done(&RegisterResult{
|
|
State: resource.NewState(goal.Type, urn, goal.Custom, false, id, goal.Properties, resource.PropertyMap{},
|
|
goal.Parent, goal.Protect, false, goal.Dependencies, nil, goal.Provider),
|
|
})
|
|
|
|
processed++
|
|
}
|
|
|
|
assert.Equal(t, len(steps), processed)
|
|
}
|
|
|
|
func TestRegisterDefaultProviders(t *testing.T) {
|
|
runInfo := &EvalRunInfo{
|
|
Proj: &workspace.Project{Name: "test"},
|
|
Target: &Target{Name: "test"},
|
|
}
|
|
|
|
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
|
|
var pt tokens.Type
|
|
if parent != "" {
|
|
pt = parent.Type()
|
|
}
|
|
return resource.NewURN(runInfo.Target.Name, runInfo.Proj.Name, pt, t, tokens.QName(name))
|
|
}
|
|
|
|
componentURN := newURN("component", "component", "")
|
|
|
|
steps := []RegisterResourceEvent{
|
|
// Register a component resource.
|
|
&testRegEvent{
|
|
goal: resource.NewGoal(componentURN.Type(), componentURN.Name(), false, resource.PropertyMap{}, "", false,
|
|
nil, ""),
|
|
},
|
|
// Register a couple resources from package A.
|
|
&testRegEvent{
|
|
goal: resource.NewGoal("pkgA:m:typA", "res1", true, resource.PropertyMap{}, componentURN, false, nil, ""),
|
|
},
|
|
&testRegEvent{
|
|
goal: resource.NewGoal("pkgA:m:typA", "res2", true, resource.PropertyMap{}, componentURN, false, nil, ""),
|
|
},
|
|
// Register a few resources from other packages.
|
|
&testRegEvent{
|
|
goal: resource.NewGoal("pkgB:m:typB", "res3", true, resource.PropertyMap{}, "", false, nil, ""),
|
|
},
|
|
&testRegEvent{
|
|
goal: resource.NewGoal("pkgB:m:typC", "res4", true, resource.PropertyMap{}, "", false, nil, ""),
|
|
},
|
|
}
|
|
|
|
// Create and iterate an eval source.
|
|
ctx, err := newTestPluginContext(fixedProgram(steps))
|
|
assert.NoError(t, err)
|
|
|
|
iter, err := NewEvalSource(ctx, runInfo, nil, false).Iterate(Options{}, &testProviderSource{})
|
|
assert.NoError(t, err)
|
|
|
|
processed, defaults := 0, make(map[string]struct{})
|
|
for {
|
|
event, err := iter.Next()
|
|
assert.NoError(t, err)
|
|
|
|
if event == nil {
|
|
break
|
|
}
|
|
|
|
reg := event.(RegisterResourceEvent)
|
|
|
|
goal := reg.Goal()
|
|
urn := newURN(goal.Type, string(goal.Name), goal.Parent)
|
|
id := resource.ID("")
|
|
if goal.Custom {
|
|
id = "id"
|
|
}
|
|
|
|
if providers.IsProviderType(goal.Type) {
|
|
assert.Equal(t, "default", string(goal.Name))
|
|
ref, err := providers.NewReference(urn, id)
|
|
assert.NoError(t, err)
|
|
_, ok := defaults[ref.String()]
|
|
assert.False(t, ok)
|
|
defaults[ref.String()] = struct{}{}
|
|
} else if goal.Custom {
|
|
assert.NotEqual(t, "", goal.Provider)
|
|
_, ok := defaults[goal.Provider]
|
|
assert.True(t, ok)
|
|
}
|
|
|
|
reg.Done(&RegisterResult{
|
|
State: resource.NewState(goal.Type, urn, goal.Custom, false, id, goal.Properties, resource.PropertyMap{},
|
|
goal.Parent, goal.Protect, false, goal.Dependencies, nil, goal.Provider),
|
|
})
|
|
|
|
processed++
|
|
}
|
|
|
|
assert.Equal(t, len(steps)+len(defaults), processed)
|
|
}
|
|
|
|
func TestReadInvokeNoDefaultProviders(t *testing.T) {
|
|
runInfo := &EvalRunInfo{
|
|
Proj: &workspace.Project{Name: "test"},
|
|
Target: &Target{Name: "test"},
|
|
}
|
|
|
|
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
|
|
var pt tokens.Type
|
|
if parent != "" {
|
|
pt = parent.Type()
|
|
}
|
|
return resource.NewURN(runInfo.Target.Name, runInfo.Proj.Name, pt, t, tokens.QName(name))
|
|
}
|
|
|
|
newProviderURN := func(pkg tokens.Package, name string, parent resource.URN) resource.URN {
|
|
return newURN(providers.MakeProviderType(pkg), name, parent)
|
|
}
|
|
|
|
providerARef, err := providers.NewReference(newProviderURN("pkgA", "providerA", ""), "id1")
|
|
assert.NoError(t, err)
|
|
providerBRef, err := providers.NewReference(newProviderURN("pkgA", "providerB", ""), "id2")
|
|
assert.NoError(t, err)
|
|
providerCRef, err := providers.NewReference(newProviderURN("pkgC", "providerC", ""), "id1")
|
|
assert.NoError(t, err)
|
|
|
|
invokes := int32(0)
|
|
noopProvider := &deploytest.Provider{
|
|
InvokeF: func(tokens.ModuleMember, resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
|
atomic.AddInt32(&invokes, 1)
|
|
return resource.PropertyMap{}, nil, nil
|
|
},
|
|
}
|
|
|
|
providerSource := &testProviderSource{
|
|
providers: map[providers.Reference]plugin.Provider{
|
|
providerARef: noopProvider,
|
|
providerBRef: noopProvider,
|
|
providerCRef: noopProvider,
|
|
},
|
|
}
|
|
|
|
expectedReads, expectedInvokes := 3, 3
|
|
program := func(_ plugin.RunInfo, resmon *deploytest.ResourceMonitor) error {
|
|
// Perform some reads and invokes with explicit provider references.
|
|
_, _, perr := resmon.ReadResource("pkgA:m:typA", "resA", "id1", "", nil, providerARef.String())
|
|
assert.NoError(t, perr)
|
|
_, _, perr = resmon.ReadResource("pkgA:m:typB", "resB", "id1", "", nil, providerBRef.String())
|
|
assert.NoError(t, perr)
|
|
_, _, perr = resmon.ReadResource("pkgC:m:typC", "resC", "id1", "", nil, providerCRef.String())
|
|
assert.NoError(t, perr)
|
|
|
|
_, _, perr = resmon.Invoke("pkgA:m:funcA", nil, providerARef.String())
|
|
assert.NoError(t, perr)
|
|
_, _, perr = resmon.Invoke("pkgA:m:funcB", nil, providerBRef.String())
|
|
assert.NoError(t, perr)
|
|
_, _, perr = resmon.Invoke("pkgC:m:funcC", nil, providerCRef.String())
|
|
assert.NoError(t, perr)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Create and iterate an eval source.
|
|
ctx, err := newTestPluginContext(program)
|
|
assert.NoError(t, err)
|
|
|
|
iter, err := NewEvalSource(ctx, runInfo, nil, false).Iterate(Options{}, providerSource)
|
|
assert.NoError(t, err)
|
|
|
|
reads := 0
|
|
for {
|
|
event, err := iter.Next()
|
|
assert.NoError(t, err)
|
|
if event == nil {
|
|
break
|
|
}
|
|
|
|
read := event.(ReadResourceEvent)
|
|
urn := newURN(read.Type(), string(read.Name()), read.Parent())
|
|
read.Done(&ReadResult{
|
|
State: resource.NewState(read.Type(), urn, true, false, read.ID(), read.Properties(),
|
|
resource.PropertyMap{}, read.Parent(), false, false, read.Dependencies(), nil, read.Provider()),
|
|
})
|
|
reads++
|
|
}
|
|
|
|
assert.Equal(t, expectedReads, reads)
|
|
assert.Equal(t, expectedInvokes, int(invokes))
|
|
}
|
|
|
|
func TestReadInvokeDefaultProviders(t *testing.T) {
|
|
runInfo := &EvalRunInfo{
|
|
Proj: &workspace.Project{Name: "test"},
|
|
Target: &Target{Name: "test"},
|
|
}
|
|
|
|
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
|
|
var pt tokens.Type
|
|
if parent != "" {
|
|
pt = parent.Type()
|
|
}
|
|
return resource.NewURN(runInfo.Target.Name, runInfo.Proj.Name, pt, t, tokens.QName(name))
|
|
}
|
|
|
|
invokes := int32(0)
|
|
noopProvider := &deploytest.Provider{
|
|
InvokeF: func(tokens.ModuleMember, resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
|
atomic.AddInt32(&invokes, 1)
|
|
return resource.PropertyMap{}, nil, nil
|
|
},
|
|
}
|
|
|
|
expectedReads, expectedInvokes := 3, 3
|
|
program := func(_ plugin.RunInfo, resmon *deploytest.ResourceMonitor) error {
|
|
// Perform some reads and invokes with default provider references.
|
|
_, _, err := resmon.ReadResource("pkgA:m:typA", "resA", "id1", "", nil, "")
|
|
assert.NoError(t, err)
|
|
_, _, err = resmon.ReadResource("pkgA:m:typB", "resB", "id1", "", nil, "")
|
|
assert.NoError(t, err)
|
|
_, _, err = resmon.ReadResource("pkgC:m:typC", "resC", "id1", "", nil, "")
|
|
assert.NoError(t, err)
|
|
|
|
_, _, err = resmon.Invoke("pkgA:m:funcA", nil, "")
|
|
assert.NoError(t, err)
|
|
_, _, err = resmon.Invoke("pkgA:m:funcB", nil, "")
|
|
assert.NoError(t, err)
|
|
_, _, err = resmon.Invoke("pkgC:m:funcC", nil, "")
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Create and iterate an eval source.
|
|
ctx, err := newTestPluginContext(program)
|
|
assert.NoError(t, err)
|
|
|
|
providerSource := &testProviderSource{providers: make(map[providers.Reference]plugin.Provider)}
|
|
|
|
iter, err := NewEvalSource(ctx, runInfo, nil, false).Iterate(Options{}, providerSource)
|
|
assert.NoError(t, err)
|
|
|
|
reads, registers := 0, 0
|
|
for {
|
|
event, err := iter.Next()
|
|
assert.NoError(t, err)
|
|
|
|
if event == nil {
|
|
break
|
|
}
|
|
|
|
switch e := event.(type) {
|
|
case RegisterResourceEvent:
|
|
goal := e.Goal()
|
|
urn, id := newURN(goal.Type, string(goal.Name), goal.Parent), resource.ID("id")
|
|
|
|
assert.True(t, providers.IsProviderType(goal.Type))
|
|
assert.Equal(t, "default", string(goal.Name))
|
|
ref, err := providers.NewReference(urn, id)
|
|
assert.NoError(t, err)
|
|
_, ok := providerSource.GetProvider(ref)
|
|
assert.False(t, ok)
|
|
providerSource.registerProvider(ref, noopProvider)
|
|
|
|
e.Done(&RegisterResult{
|
|
State: resource.NewState(goal.Type, urn, goal.Custom, false, id, goal.Properties, resource.PropertyMap{},
|
|
goal.Parent, goal.Protect, false, goal.Dependencies, nil, goal.Provider),
|
|
})
|
|
registers++
|
|
|
|
case ReadResourceEvent:
|
|
urn := newURN(e.Type(), string(e.Name()), e.Parent())
|
|
e.Done(&ReadResult{
|
|
State: resource.NewState(e.Type(), urn, true, false, e.ID(), e.Properties(),
|
|
resource.PropertyMap{}, e.Parent(), false, false, e.Dependencies(), nil, e.Provider()),
|
|
})
|
|
reads++
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, len(providerSource.providers), registers)
|
|
assert.Equal(t, expectedReads, int(reads))
|
|
assert.Equal(t, expectedInvokes, int(invokes))
|
|
}
|