[sdk/go] Transitive component dependencies. (#7737)
Implement Node/.NET-style dependency semantics for component resources. Depending on a component implicitly depends on all of the component's children. The exact set of children depends on exactly when the component resource is observed. Part of #7542.
This commit is contained in:
parent
7361e719dc
commit
2d70324b56
|
@ -1,4 +1,8 @@
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
|
- [sdk/go] - Depending on a component now depends on the transitive closure of its
|
||||||
|
child resources.
|
||||||
|
[#7732](https://github.com/pulumi/pulumi/pull/7732)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
|
@ -587,7 +587,7 @@ func (ctx *Context) ReadResource(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the inputs for an impending operation.
|
// Prepare the inputs for an impending operation.
|
||||||
inputs, err = ctx.prepareResourceInputs(props, t, options, res, false)
|
inputs, err = ctx.prepareResourceInputs(resource, props, t, options, res, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -703,6 +703,9 @@ func (ctx *Context) registerResource(
|
||||||
}
|
}
|
||||||
|
|
||||||
_, custom := resource.(CustomResource)
|
_, custom := resource.(CustomResource)
|
||||||
|
if !custom && remote {
|
||||||
|
resource.markRemoteComponent()
|
||||||
|
}
|
||||||
|
|
||||||
if _, isProvider := resource.(ProviderResource); isProvider && !strings.HasPrefix(t, "pulumi:providers:") {
|
if _, isProvider := resource.(ProviderResource); isProvider && !strings.HasPrefix(t, "pulumi:providers:") {
|
||||||
return errors.New("provider resource type must begin with \"pulumi:providers:\"")
|
return errors.New("provider resource type must begin with \"pulumi:providers:\"")
|
||||||
|
@ -767,7 +770,7 @@ func (ctx *Context) registerResource(
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Prepare the inputs for an impending operation.
|
// Prepare the inputs for an impending operation.
|
||||||
inputs, err = ctx.prepareResourceInputs(props, t, options, resState, remote)
|
inputs, err = ctx.prepareResourceInputs(resource, props, t, options, resState, remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1150,12 +1153,12 @@ type resourceInputs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareResourceInputs prepares the inputs for a resource operation, shared between read and register.
|
// prepareResourceInputs prepares the inputs for a resource operation, shared between read and register.
|
||||||
func (ctx *Context) prepareResourceInputs(props Input, t string, opts *resourceOptions, resource *resourceState,
|
func (ctx *Context) prepareResourceInputs(res Resource, props Input, t string, opts *resourceOptions,
|
||||||
remote bool) (*resourceInputs, error) {
|
state *resourceState, remote bool) (*resourceInputs, error) {
|
||||||
|
|
||||||
// Get the parent and dependency URNs from the options, in addition to the protection bit. If there wasn't an
|
// Get the parent and dependency URNs from the options, in addition to the protection bit. If there wasn't an
|
||||||
// explicit parent, and a root stack resource exists, we will automatically parent to that.
|
// explicit parent, and a root stack resource exists, we will automatically parent to that.
|
||||||
resOpts, err := ctx.getOpts(t, resource.provider, opts, remote)
|
resOpts, err := ctx.getOpts(res, t, state.provider, opts, remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("resolving options: %w", err)
|
return nil, fmt.Errorf("resolving options: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -1180,15 +1183,11 @@ func (ctx *Context) prepareResourceInputs(props Input, t string, opts *resourceO
|
||||||
// Convert the property dependencies map for RPC and remove duplicates.
|
// Convert the property dependencies map for RPC and remove duplicates.
|
||||||
rpcPropertyDeps := make(map[string]*pulumirpc.RegisterResourceRequest_PropertyDependencies)
|
rpcPropertyDeps := make(map[string]*pulumirpc.RegisterResourceRequest_PropertyDependencies)
|
||||||
for k, deps := range propertyDeps {
|
for k, deps := range propertyDeps {
|
||||||
sort.Slice(deps, func(i, j int) bool { return deps[i] < deps[j] })
|
urns := make([]string, len(deps))
|
||||||
|
|
||||||
urns := make([]string, 0, len(deps))
|
|
||||||
for i, d := range deps {
|
for i, d := range deps {
|
||||||
if i > 0 && urns[i-1] == string(d) {
|
urns[i] = string(d)
|
||||||
continue
|
|
||||||
}
|
|
||||||
urns = append(urns, string(d))
|
|
||||||
}
|
}
|
||||||
|
sort.Strings(urns)
|
||||||
|
|
||||||
rpcPropertyDeps[k] = &pulumirpc.RegisterResourceRequest_PropertyDependencies{
|
rpcPropertyDeps[k] = &pulumirpc.RegisterResourceRequest_PropertyDependencies{
|
||||||
Urns: urns,
|
Urns: urns,
|
||||||
|
@ -1197,18 +1196,18 @@ func (ctx *Context) prepareResourceInputs(props Input, t string, opts *resourceO
|
||||||
|
|
||||||
// Merge all dependencies with what we got earlier from property marshaling, and remove duplicates.
|
// Merge all dependencies with what we got earlier from property marshaling, and remove duplicates.
|
||||||
var deps []string
|
var deps []string
|
||||||
depMap := make(map[URN]bool)
|
depSet := urnSet{}
|
||||||
for _, dep := range append(resOpts.depURNs, rpcDeps...) {
|
for _, dep := range append(resOpts.depURNs, rpcDeps...) {
|
||||||
if _, has := depMap[dep]; !has {
|
if !depSet.has(dep) {
|
||||||
deps = append(deps, string(dep))
|
deps = append(deps, string(dep))
|
||||||
depMap[dep] = true
|
depSet.add(dep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(deps)
|
sort.Strings(deps)
|
||||||
|
|
||||||
// Await alias URNs
|
// Await alias URNs
|
||||||
aliases := make([]string, len(resource.aliases))
|
aliases := make([]string, len(state.aliases))
|
||||||
for i, alias := range resource.aliases {
|
for i, alias := range state.aliases {
|
||||||
urn, _, _, err := alias.awaitURN(context.Background())
|
urn, _, _, err := alias.awaitURN(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error waiting for alias URN to resolve: %w", err)
|
return nil, fmt.Errorf("error waiting for alias URN to resolve: %w", err)
|
||||||
|
@ -1231,7 +1230,7 @@ func (ctx *Context) prepareResourceInputs(props Input, t string, opts *resourceO
|
||||||
ignoreChanges: resOpts.ignoreChanges,
|
ignoreChanges: resOpts.ignoreChanges,
|
||||||
aliases: aliases,
|
aliases: aliases,
|
||||||
additionalSecretOutputs: resOpts.additionalSecretOutputs,
|
additionalSecretOutputs: resOpts.additionalSecretOutputs,
|
||||||
version: resource.version,
|
version: state.version,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1260,7 +1259,7 @@ type resourceOpts struct {
|
||||||
|
|
||||||
// getOpts returns a set of resource options from an array of them. This includes the parent URN, any dependency URNs,
|
// getOpts returns a set of resource options from an array of them. This includes the parent URN, any dependency URNs,
|
||||||
// a boolean indicating whether the resource is to be protected, and the URN and ID of the resource's provider, if any.
|
// a boolean indicating whether the resource is to be protected, and the URN and ID of the resource's provider, if any.
|
||||||
func (ctx *Context) getOpts(t string, provider ProviderResource, opts *resourceOptions, remote bool,
|
func (ctx *Context) getOpts(res Resource, t string, provider ProviderResource, opts *resourceOptions, remote bool,
|
||||||
) (resourceOpts, error) {
|
) (resourceOpts, error) {
|
||||||
|
|
||||||
var importID ID
|
var importID ID
|
||||||
|
@ -1274,6 +1273,8 @@ func (ctx *Context) getOpts(t string, provider ProviderResource, opts *resourceO
|
||||||
|
|
||||||
var parentURN URN
|
var parentURN URN
|
||||||
if opts.Parent != nil {
|
if opts.Parent != nil {
|
||||||
|
opts.Parent.addChild(res)
|
||||||
|
|
||||||
urn, _, _, err := opts.Parent.URN().awaitURN(context.TODO())
|
urn, _, _, err := opts.Parent.URN().awaitURN(context.TODO())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resourceOpts{}, err
|
return resourceOpts{}, err
|
||||||
|
@ -1283,14 +1284,13 @@ func (ctx *Context) getOpts(t string, provider ProviderResource, opts *resourceO
|
||||||
|
|
||||||
var depURNs []URN
|
var depURNs []URN
|
||||||
if opts.DependsOn != nil {
|
if opts.DependsOn != nil {
|
||||||
depURNs = make([]URN, len(opts.DependsOn))
|
depSet := urnSet{}
|
||||||
for i, r := range opts.DependsOn {
|
for _, r := range opts.DependsOn {
|
||||||
urn, _, _, err := r.URN().awaitURN(context.TODO())
|
if err := addDependency(context.TODO(), depSet, r); err != nil {
|
||||||
if err != nil {
|
|
||||||
return resourceOpts{}, err
|
return resourceOpts{}, err
|
||||||
}
|
}
|
||||||
depURNs[i] = urn
|
|
||||||
}
|
}
|
||||||
|
depURNs = depSet.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
var providerRef string
|
var providerRef string
|
||||||
|
|
|
@ -16,6 +16,7 @@ package pulumi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||||
)
|
)
|
||||||
|
@ -33,50 +34,74 @@ var providerResourceStateType = reflect.TypeOf(ProviderResourceState{})
|
||||||
|
|
||||||
// ResourceState is the base
|
// ResourceState is the base
|
||||||
type ResourceState struct {
|
type ResourceState struct {
|
||||||
|
m sync.RWMutex
|
||||||
|
|
||||||
urn URNOutput `pulumi:"urn"`
|
urn URNOutput `pulumi:"urn"`
|
||||||
|
|
||||||
providers map[string]ProviderResource
|
children resourceSet
|
||||||
|
providers map[string]ProviderResource
|
||||||
provider ProviderResource
|
provider ProviderResource
|
||||||
|
version string
|
||||||
version string
|
aliases []URNOutput
|
||||||
|
name string
|
||||||
aliases []URNOutput
|
|
||||||
|
|
||||||
name string
|
|
||||||
|
|
||||||
transformations []ResourceTransformation
|
transformations []ResourceTransformation
|
||||||
|
|
||||||
|
remoteComponent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ResourceState) URN() URNOutput {
|
func (s *ResourceState) URN() URNOutput {
|
||||||
return s.urn
|
return s.urn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ResourceState) GetProvider(token string) ProviderResource {
|
func (s *ResourceState) GetProvider(token string) ProviderResource {
|
||||||
return s.providers[getPackage(token)]
|
return s.providers[getPackage(token)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ResourceState) getProviders() map[string]ProviderResource {
|
func (s *ResourceState) getChildren() []Resource {
|
||||||
|
s.m.RLock()
|
||||||
|
defer s.m.RUnlock()
|
||||||
|
|
||||||
|
var children []Resource
|
||||||
|
if len(s.children) != 0 {
|
||||||
|
children = make([]Resource, 0, len(s.children))
|
||||||
|
for r := range s.children {
|
||||||
|
children = append(children, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ResourceState) addChild(r Resource) {
|
||||||
|
s.m.Lock()
|
||||||
|
defer s.m.Unlock()
|
||||||
|
|
||||||
|
if s.children == nil {
|
||||||
|
s.children = resourceSet{}
|
||||||
|
}
|
||||||
|
s.children.add(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ResourceState) getProviders() map[string]ProviderResource {
|
||||||
return s.providers
|
return s.providers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ResourceState) getProvider() ProviderResource {
|
func (s *ResourceState) getProvider() ProviderResource {
|
||||||
return s.provider
|
return s.provider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ResourceState) getVersion() string {
|
func (s *ResourceState) getVersion() string {
|
||||||
return s.version
|
return s.version
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ResourceState) getAliases() []URNOutput {
|
func (s *ResourceState) getAliases() []URNOutput {
|
||||||
return s.aliases
|
return s.aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ResourceState) getName() string {
|
func (s *ResourceState) getName() string {
|
||||||
return s.name
|
return s.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ResourceState) getTransformations() []ResourceTransformation {
|
func (s *ResourceState) getTransformations() []ResourceTransformation {
|
||||||
return s.transformations
|
return s.transformations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,12 +109,23 @@ func (s *ResourceState) addTransformation(t ResourceTransformation) {
|
||||||
s.transformations = append(s.transformations, t)
|
s.transformations = append(s.transformations, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ResourceState) isResource() {}
|
func (s *ResourceState) markRemoteComponent() {
|
||||||
|
s.remoteComponent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ResourceState) isRemoteComponent() bool {
|
||||||
|
return s.remoteComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ResourceState) isResource() {}
|
||||||
|
|
||||||
func (ctx *Context) newDependencyResource(urn URN) Resource {
|
func (ctx *Context) newDependencyResource(urn URN) Resource {
|
||||||
var res ResourceState
|
var res ResourceState
|
||||||
res.urn.OutputState = ctx.newOutputState(res.urn.ElementType(), &res)
|
res.urn.OutputState = ctx.newOutputState(res.urn.ElementType(), &res)
|
||||||
res.urn.resolve(urn, true, false, nil)
|
res.urn.resolve(urn, true, false, nil)
|
||||||
|
|
||||||
|
// For the purposes of dependency management, dependency resources are treated like remote components.
|
||||||
|
res.remoteComponent = true
|
||||||
return &res
|
return &res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,11 +135,11 @@ type CustomResourceState struct {
|
||||||
id IDOutput `pulumi:"id"`
|
id IDOutput `pulumi:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CustomResourceState) ID() IDOutput {
|
func (s *CustomResourceState) ID() IDOutput {
|
||||||
return s.id
|
return s.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (CustomResourceState) isCustomResource() {}
|
func (*CustomResourceState) isCustomResource() {}
|
||||||
|
|
||||||
func (ctx *Context) newDependencyCustomResource(urn URN, id ID) CustomResource {
|
func (ctx *Context) newDependencyCustomResource(urn URN, id ID) CustomResource {
|
||||||
var res CustomResourceState
|
var res CustomResourceState
|
||||||
|
@ -120,7 +156,7 @@ type ProviderResourceState struct {
|
||||||
pkg string
|
pkg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ProviderResourceState) getPackage() string {
|
func (s *ProviderResourceState) getPackage() string {
|
||||||
return s.pkg
|
return s.pkg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +175,12 @@ type Resource interface {
|
||||||
// URN is this resource's stable logical URN used to distinctly address it before, during, and after deployments.
|
// URN is this resource's stable logical URN used to distinctly address it before, during, and after deployments.
|
||||||
URN() URNOutput
|
URN() URNOutput
|
||||||
|
|
||||||
|
// getChildren returns the resource's children.
|
||||||
|
getChildren() []Resource
|
||||||
|
|
||||||
|
// addChild adds a child to the resource.
|
||||||
|
addChild(r Resource)
|
||||||
|
|
||||||
// getProviders returns the provider map for this resource.
|
// getProviders returns the provider map for this resource.
|
||||||
getProviders() map[string]ProviderResource
|
getProviders() map[string]ProviderResource
|
||||||
|
|
||||||
|
@ -162,6 +204,12 @@ type Resource interface {
|
||||||
|
|
||||||
// addTransformation adds a single transformation to the resource.
|
// addTransformation adds a single transformation to the resource.
|
||||||
addTransformation(t ResourceTransformation)
|
addTransformation(t ResourceTransformation)
|
||||||
|
|
||||||
|
// markRemoteComponent marks this resource as a remote component resource.
|
||||||
|
markRemoteComponent()
|
||||||
|
|
||||||
|
// isRemoteComponent returns true if this is not a local (i.e. in-process) component resource.
|
||||||
|
isRemoteComponent() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomResource is a cloud resource whose create, read, update, and delete (CRUD) operations are managed by performing
|
// CustomResource is a cloud resource whose create, read, update, and delete (CRUD) operations are managed by performing
|
||||||
|
|
20
sdk/go/pulumi/resource_set.go
Normal file
20
sdk/go/pulumi/resource_set.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package pulumi
|
||||||
|
|
||||||
|
type resourceSet map[Resource]struct{}
|
||||||
|
|
||||||
|
func (s resourceSet) add(r Resource) {
|
||||||
|
s[r] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s resourceSet) any() bool {
|
||||||
|
return len(s) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s resourceSet) delete(r Resource) {
|
||||||
|
delete(s, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s resourceSet) has(r Resource) bool {
|
||||||
|
_, ok := s[r]
|
||||||
|
return ok
|
||||||
|
}
|
|
@ -63,14 +63,96 @@ func mapStructTypes(from, to reflect.Type) func(reflect.Value, int) (reflect.Str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type urnSet map[URN]struct{}
|
||||||
|
|
||||||
|
func (s urnSet) add(v URN) {
|
||||||
|
s[v] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s urnSet) has(v URN) bool {
|
||||||
|
_, ok := s[v]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s urnSet) union(other urnSet) {
|
||||||
|
for v := range other {
|
||||||
|
s.add(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s urnSet) values() []URN {
|
||||||
|
values := make([]URN, 0, len(s))
|
||||||
|
for v := range s {
|
||||||
|
values = append(values, v)
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// addDependency adds a dependency on the given resource to the set of deps.
|
||||||
|
//
|
||||||
|
// The behavior of this method depends on whether or not the resource is a custom resource, a local component resource,
|
||||||
|
// or a remote component resource:
|
||||||
|
//
|
||||||
|
// - Custom resources are added directly to the set, as they are "real" nodes in the dependency graph.
|
||||||
|
// - Local component resources act as aggregations of their descendents. Rather than adding the component resource
|
||||||
|
// itself, each child resource is added as a dependency.
|
||||||
|
// - Remote component resources are added directly to the set, as they naturally act as aggregations of their children
|
||||||
|
// with respect to dependencies: the construction of a remote component always waits on the construction of its
|
||||||
|
// children.
|
||||||
|
//
|
||||||
|
// In other words, if we had:
|
||||||
|
//
|
||||||
|
// Comp1
|
||||||
|
// / | \
|
||||||
|
// Cust1 Comp2 Remote1
|
||||||
|
// / \ \
|
||||||
|
// Cust2 Cust3 Comp3
|
||||||
|
// / \
|
||||||
|
// Cust4 Cust5
|
||||||
|
//
|
||||||
|
// Then the transitively reachable resources of Comp1 will be [Cust1, Cust2, Cust3, Remote1].
|
||||||
|
// It will *not* include:
|
||||||
|
// * Cust4 because it is a child of a custom resource
|
||||||
|
// * Comp2 because it is a non-remote component resoruce
|
||||||
|
// * Comp3 and Cust5 because Comp3 is a child of a remote component resource
|
||||||
|
func addDependency(ctx context.Context, deps urnSet, res Resource) error {
|
||||||
|
if _, custom := res.(CustomResource); !custom {
|
||||||
|
for _, child := range res.getChildren() {
|
||||||
|
if err := addDependency(ctx, deps, child); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !res.isRemoteComponent() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urn, _, _, err := res.URN().awaitURN(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
deps.add(urn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandDependencies expands the given slice of Resources into a set of URNs.
|
||||||
|
func expandDependencies(ctx context.Context, deps []Resource) (urnSet, error) {
|
||||||
|
urns := urnSet{}
|
||||||
|
for _, r := range deps {
|
||||||
|
if err := addDependency(ctx, urns, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urns, nil
|
||||||
|
}
|
||||||
|
|
||||||
// marshalInputs turns resource property inputs into a map suitable for marshaling.
|
// marshalInputs turns resource property inputs into a map suitable for marshaling.
|
||||||
func marshalInputs(props Input) (resource.PropertyMap, map[string][]URN, []URN, error) {
|
func marshalInputs(props Input) (resource.PropertyMap, map[string][]URN, []URN, error) {
|
||||||
var depURNs []URN
|
deps := urnSet{}
|
||||||
depset := map[URN]bool{}
|
|
||||||
pmap, pdeps := resource.PropertyMap{}, map[string][]URN{}
|
pmap, pdeps := resource.PropertyMap{}, map[string][]URN{}
|
||||||
|
|
||||||
if props == nil {
|
if props == nil {
|
||||||
return pmap, pdeps, depURNs, nil
|
return pmap, pdeps, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
marshalProperty := func(pname string, pv interface{}, pt reflect.Type) error {
|
marshalProperty := func(pname string, pv interface{}, pt reflect.Type) error {
|
||||||
|
@ -81,24 +163,14 @@ func marshalInputs(props Input) (resource.PropertyMap, map[string][]URN, []URN,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record all dependencies accumulated from reading this property.
|
// Record all dependencies accumulated from reading this property.
|
||||||
var deps []URN
|
allDeps, err := expandDependencies(context.TODO(), resourceDeps)
|
||||||
pdepset := map[URN]bool{}
|
if err != nil {
|
||||||
for _, dep := range resourceDeps {
|
return err
|
||||||
depURN, _, _, err := dep.URN().awaitURN(context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !pdepset[depURN] {
|
|
||||||
deps = append(deps, depURN)
|
|
||||||
pdepset[depURN] = true
|
|
||||||
}
|
|
||||||
if !depset[depURN] {
|
|
||||||
depURNs = append(depURNs, depURN)
|
|
||||||
depset[depURN] = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(deps) > 0 {
|
deps.union(allDeps)
|
||||||
pdeps[pname] = deps
|
|
||||||
|
if len(allDeps) > 0 {
|
||||||
|
pdeps[pname] = allDeps.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !v.IsNull() || len(deps) > 0 {
|
if !v.IsNull() || len(deps) > 0 {
|
||||||
|
@ -110,7 +182,7 @@ func marshalInputs(props Input) (resource.PropertyMap, map[string][]URN, []URN,
|
||||||
pv := reflect.ValueOf(props)
|
pv := reflect.ValueOf(props)
|
||||||
if pv.Kind() == reflect.Ptr {
|
if pv.Kind() == reflect.Ptr {
|
||||||
if pv.IsNil() {
|
if pv.IsNil() {
|
||||||
return pmap, pdeps, depURNs, nil
|
return pmap, pdeps, nil, nil
|
||||||
}
|
}
|
||||||
pv = pv.Elem()
|
pv = pv.Elem()
|
||||||
}
|
}
|
||||||
|
@ -157,7 +229,7 @@ func marshalInputs(props Input) (resource.PropertyMap, map[string][]URN, []URN,
|
||||||
return nil, nil, nil, fmt.Errorf("cannot marshal Input that is not a struct or map, saw type %s", pt.String())
|
return nil, nil, nil, fmt.Errorf("cannot marshal Input that is not a struct or map, saw type %s", pt.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return pmap, pdeps, depURNs, nil
|
return pmap, pdeps, deps.values(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// `gosec` thinks these are credentials, but they are not.
|
// `gosec` thinks these are credentials, but they are not.
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
|
structpb "github.com/golang/protobuf/ptypes/struct"
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
"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/resource/plugin"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -829,3 +830,56 @@ func TestInvalidArchive(t *testing.T) {
|
||||||
_, _, err = marshalInput(d, archiveType, true)
|
_, _, err = marshalInput(d, archiveType, true)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDependsOnComponent(t *testing.T) {
|
||||||
|
ctx, err := NewContext(context.Background(), RunInfo{})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
registerResource := func(name string, res Resource, opts *resourceOptions) (Resource, []string) {
|
||||||
|
state := ctx.makeResourceState("", "", res, nil, nil, "", nil, nil)
|
||||||
|
state.resolve(ctx, nil, nil, name, "", &structpb.Struct{}, nil)
|
||||||
|
|
||||||
|
if opts == nil {
|
||||||
|
opts = &resourceOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs, err := ctx.prepareResourceInputs(res, Map{}, "", opts, state, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return res, inputs.deps
|
||||||
|
}
|
||||||
|
|
||||||
|
newResource := func(name string, opts *resourceOptions) (Resource, []string) {
|
||||||
|
var res testResource
|
||||||
|
return registerResource(name, &res, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
newComponent := func(name string, opts *resourceOptions) (Resource, []string) {
|
||||||
|
var res simpleComponentResource
|
||||||
|
return registerResource(name, &res, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
resA, _ := newResource("resA", nil)
|
||||||
|
comp1, _ := newComponent("comp1", nil)
|
||||||
|
resB, _ := newResource("resB", &resourceOptions{Parent: comp1})
|
||||||
|
newResource("resC", &resourceOptions{Parent: resB})
|
||||||
|
comp2, _ := newComponent("comp2", &resourceOptions{Parent: comp1})
|
||||||
|
|
||||||
|
resD, deps := newResource("resD", &resourceOptions{DependsOn: []Resource{resA}, Parent: comp2})
|
||||||
|
assert.Equal(t, []string{"resA"}, deps)
|
||||||
|
|
||||||
|
_, deps = newResource("resE", &resourceOptions{DependsOn: []Resource{resD}, Parent: comp2})
|
||||||
|
assert.Equal(t, []string{"resD"}, deps)
|
||||||
|
|
||||||
|
_, deps = newResource("resF", &resourceOptions{DependsOn: []Resource{resA}})
|
||||||
|
assert.Equal(t, []string{"resA"}, deps)
|
||||||
|
|
||||||
|
resG, deps := newResource("resG", &resourceOptions{DependsOn: []Resource{comp1}})
|
||||||
|
assert.Equal(t, []string{"resB", "resD", "resE"}, deps)
|
||||||
|
|
||||||
|
_, deps = newResource("resH", &resourceOptions{DependsOn: []Resource{comp2}})
|
||||||
|
assert.Equal(t, []string{"resD", "resE"}, deps)
|
||||||
|
|
||||||
|
_, deps = newResource("resI", &resourceOptions{DependsOn: []Resource{resG}})
|
||||||
|
assert.Equal(t, []string{"resG"}, deps)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue