Deprecate deps maps when using output values

When output values are being sent to a provider, there's no need to also send the dependencies map, because the output values themselves contain the dependencies. This allows for a simpler implementation in the provider.
This commit is contained in:
Justin Van Patten 2021-10-20 07:08:02 -07:00
parent 7888623aca
commit 7487e99d94
2 changed files with 106 additions and 26 deletions

View file

@ -631,13 +631,16 @@ func (rm *resmon) Call(ctx context.Context, req *pulumirpc.CallRequest) (*pulumi
return nil, errors.Wrapf(err, "failed to unmarshal %v args", tok)
}
argDependencies := map[resource.PropertyKey][]resource.URN{}
for name, deps := range req.GetArgDependencies() {
urns := make([]resource.URN, len(deps.Urns))
for i, urn := range deps.Urns {
urns[i] = resource.URN(urn)
var argDependencies map[resource.PropertyKey][]resource.URN
if len(req.GetArgDependencies()) > 0 {
argDependencies = map[resource.PropertyKey][]resource.URN{}
for name, deps := range req.GetArgDependencies() {
urns := make([]resource.URN, len(deps.Urns))
for i, urn := range deps.Urns {
urns[i] = resource.URN(urn)
}
argDependencies[resource.PropertyKey(name)] = urns
}
argDependencies[resource.PropertyKey(name)] = urns
}
info := plugin.CallInfo{
@ -853,6 +856,7 @@ func (rm *resmon) RegisterResource(ctx context.Context,
replaceOnChanges := req.GetReplaceOnChanges()
id := resource.ID(req.GetImportId())
customTimeouts := req.GetCustomTimeouts()
hasOutputs := req.GetHasOutputs()
// Custom resources must have a three-part type so that we can 1) identify if they are providers and 2) retrieve the
// provider responsible for managing a particular resource (based on the type's Package).
@ -913,7 +917,7 @@ func (rm *resmon) RegisterResource(ctx context.Context,
KeepResources: true,
// To initially scope the use of this new feature, we only keep output values when unmarshaling
// properties for RegisterResource (when remote is true for multi-lang components) and Call.
KeepOutputValues: remote,
KeepOutputValues: remote || hasOutputs,
})
if err != nil {
return nil, err
@ -993,11 +997,14 @@ func (rm *resmon) RegisterResource(ctx context.Context,
// Invoke the provider's Construct RPC method.
options := plugin.ConstructOptions{
Aliases: aliases,
Dependencies: dependencies,
Protect: protect,
PropertyDependencies: propertyDependencies,
Providers: providerRefs,
Aliases: aliases,
Dependencies: dependencies,
Protect: protect,
Providers: providerRefs,
}
if !hasOutputs {
// Only include property dependencies when we don't have output values in the properties.
options.PropertyDependencies = propertyDependencies
}
constructResult, err := provider.Construct(rm.constructInfo, t, name, parent, props, options)
if err != nil {

View file

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
"github.com/blang/semver"
@ -1125,14 +1126,24 @@ func (p *provider) Construct(info ConstructInfo, typ tokens.Type, name tokens.QN
dependencies[i] = string(dep)
}
// Marshal the property dependencies.
inputDependencies := map[string]*pulumirpc.ConstructRequest_PropertyDependencies{}
for name, dependencies := range options.PropertyDependencies {
urns := make([]string, len(dependencies))
for i, urn := range dependencies {
urns[i] = string(urn)
// If the provider accepts outputs, the marshaled inputs will have output values with dependencies,
// so there's no need to specify the dependencies map.
var inputDependencies map[string]*pulumirpc.ConstructRequest_PropertyDependencies
if !p.acceptOutputs {
// If the provider doesn't accept outputs, pass along the dependencies map.
dependenciesMap := options.PropertyDependencies
if len(dependenciesMap) == 0 {
// If the dependencies map is empty, "polyfill" it based on dependencies gathered from the inputs.
dependenciesMap = gatherDependenciesMap(inputs)
}
inputDependencies = make(map[string]*pulumirpc.ConstructRequest_PropertyDependencies, len(dependenciesMap))
for name, dependencies := range dependenciesMap {
urns := make([]string, len(dependencies))
for i, urn := range dependencies {
urns[i] = string(urn)
}
inputDependencies[string(name)] = &pulumirpc.ConstructRequest_PropertyDependencies{Urns: urns}
}
inputDependencies[string(name)] = &pulumirpc.ConstructRequest_PropertyDependencies{Urns: urns}
}
// Marshal the config.
@ -1367,14 +1378,24 @@ func (p *provider) Call(tok tokens.ModuleMember, args resource.PropertyMap, info
return CallResult{}, err
}
// Marshal the arg dependencies.
argDependencies := map[string]*pulumirpc.CallRequest_ArgumentDependencies{}
for name, dependencies := range options.ArgDependencies {
urns := make([]string, len(dependencies))
for i, urn := range dependencies {
urns[i] = string(urn)
// If the provider accepts outputs, the marshaled args will have output values with dependencies,
// so there's no need to specify the dependencies map.
var argDependencies map[string]*pulumirpc.CallRequest_ArgumentDependencies
if !p.acceptOutputs {
// If the provider doesn't accept outputs, pass along the dependencies map.
dependenciesMap := options.ArgDependencies
if len(dependenciesMap) == 0 {
// If the dependencies map is empty, "polyfill" it based on dependencies gathered from the args.
dependenciesMap = gatherDependenciesMap(args)
}
argDependencies = make(map[string]*pulumirpc.CallRequest_ArgumentDependencies, len(dependenciesMap))
for name, dependencies := range dependenciesMap {
urns := make([]string, len(dependencies))
for i, urn := range dependencies {
urns[i] = string(urn)
}
argDependencies[string(name)] = &pulumirpc.CallRequest_ArgumentDependencies{Urns: urns}
}
argDependencies[string(name)] = &pulumirpc.CallRequest_ArgumentDependencies{Urns: urns}
}
// Marshal the config.
@ -1613,3 +1634,55 @@ func decorateProviderSpans(span opentracing.Span, method string, req, resp inter
span.SetTag("pulumi-decorator", req.(*pulumirpc.InvokeRequest).Tok)
}
}
// gatherDependenciesMap deeply gathers dependencies in the input's output values and resource
// references to "polyfill" a dependencies map for providers that don't accept output values.
func gatherDependenciesMap(inputs resource.PropertyMap) map[resource.PropertyKey][]resource.URN {
type urnSet = map[resource.URN]struct{}
add := func(s urnSet, urn resource.URN) {
s[urn] = struct{}{}
}
sortedValues := func(s urnSet) []resource.URN {
sorted := make([]resource.URN, 0, len(s))
for k := range s {
sorted = append(sorted, k)
}
sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
return sorted
}
var gatherDeps func(v resource.PropertyValue, deps urnSet)
gatherDeps = func(v resource.PropertyValue, deps urnSet) {
switch {
case v.IsSecret():
gatherDeps(v.SecretValue().Element, deps)
case v.IsComputed():
gatherDeps(v.Input().Element, deps)
case v.IsOutput():
for _, urn := range v.OutputValue().Dependencies {
add(deps, urn)
}
gatherDeps(v.OutputValue().Element, deps)
case v.IsResourceReference():
add(deps, v.ResourceReferenceValue().URN)
case v.IsArray():
for _, e := range v.ArrayValue() {
gatherDeps(e, deps)
}
case v.IsObject():
for _, e := range v.ObjectValue() {
gatherDeps(e, deps)
}
}
}
result := make(map[resource.PropertyKey][]resource.URN, len(inputs))
for k, v := range inputs {
deps := urnSet{}
gatherDeps(v, deps)
result[k] = sortedValues(deps)
}
return result
}