// Copyright 2016-2021, Pulumi Corporation using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Google.Protobuf.WellKnownTypes; namespace Pulumi { public partial class Deployment { private async Task PrepareResourceAsync( string label, Resource res, bool custom, bool remote, ResourceArgs args, ResourceOptions options) { /* IMPORTANT! We should never await prior to this line, otherwise the Resource will be partly uninitialized. */ // Before we can proceed, all our dependencies must be finished. var type = res.GetResourceType(); var name = res.GetResourceName(); LogExcessive($"Gathering explicit dependencies: t={type}, name={name}, custom={custom}, remote={remote}"); var explicitDirectDependencies = new HashSet( await GatherExplicitDependenciesAsync(options.DependsOn).ConfigureAwait(false)); LogExcessive($"Gathered explicit dependencies: t={type}, name={name}, custom={custom}, remote={remote}"); // Serialize out all our props to their final values. In doing so, we'll also collect all // the Resources pointed to by any Dependency objects we encounter, adding them to 'propertyDependencies'. LogExcessive($"Serializing properties: t={type}, name={name}, custom={custom}, remote={remote}"); var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false); var (serializedProps, propertyToDirectDependencies) = await SerializeResourcePropertiesAsync( label, dictionary, await this.MonitorSupportsResourceReferences().ConfigureAwait(false), keepOutputValues: remote && await MonitorSupportsOutputValues().ConfigureAwait(false)).ConfigureAwait(false); LogExcessive($"Serialized properties: t={type}, name={name}, custom={custom}, remote={remote}"); // Wait for the parent to complete. // If no parent was provided, parent to the root resource. LogExcessive($"Getting parent urn: t={type}, name={name}, custom={custom}, remote={remote}"); var parentUrn = options.Parent != null ? await options.Parent.Urn.GetValueAsync(whenUnknown: default!).ConfigureAwait(false) : await GetRootResourceAsync(type).ConfigureAwait(false); LogExcessive($"Got parent urn: t={type}, name={name}, custom={custom}, remote={remote}"); string? providerRef = null; if (custom) { var customOpts = options as CustomResourceOptions; providerRef = await ProviderResource.RegisterAsync(customOpts?.Provider).ConfigureAwait(false); } var providerRefs = new Dictionary(); if (remote && options is ComponentResourceOptions componentOpts) { // If only the Provider opt is set, move it to the Providers list for further processing. if (componentOpts.Provider != null && componentOpts.Providers.Count == 0) { componentOpts.Providers.Add(componentOpts.Provider); componentOpts.Provider = null; } foreach (var provider in componentOpts.Providers) { var pref = await ProviderResource.RegisterAsync(provider).ConfigureAwait(false); if (pref != null) { providerRefs.Add(provider.Package, pref); } } } // Collect the URNs for explicit/implicit dependencies for the engine so that it can understand // the dependency graph and optimize operations accordingly. // The list of all dependencies (implicit or explicit). var allDirectDependencies = new HashSet(explicitDirectDependencies); var allDirectDependencyUrns = await GetAllTransitivelyReferencedResourceUrnsAsync(explicitDirectDependencies).ConfigureAwait(false); var propertyToDirectDependencyUrns = new Dictionary>(); foreach (var (propertyName, directDependencies) in propertyToDirectDependencies) { allDirectDependencies.AddRange(directDependencies); var urns = await GetAllTransitivelyReferencedResourceUrnsAsync(directDependencies).ConfigureAwait(false); allDirectDependencyUrns.AddRange(urns); propertyToDirectDependencyUrns[propertyName] = urns; } // Wait for all aliases. Note that we use 'res._aliases' instead of 'options.aliases' as // the former has been processed in the Resource constructor prior to calling // 'registerResource' - both adding new inherited aliases and simplifying aliases down // to URNs. var aliases = new List(); var uniqueAliases = new HashSet(); foreach (var alias in res._aliases) { var aliasVal = await alias.ToOutput().GetValueAsync(whenUnknown: "").ConfigureAwait(false); if (aliasVal != "" && uniqueAliases.Add(aliasVal)) { aliases.Add(aliasVal); } } return new PrepareResult( serializedProps, parentUrn ?? "", providerRef ?? "", providerRefs, allDirectDependencyUrns, propertyToDirectDependencyUrns, aliases); void LogExcessive(string message) { if (_excessiveDebugOutput) Log.Debug(message); } } private static Task> GatherExplicitDependenciesAsync(InputList resources) => resources.ToOutput().GetValueAsync(whenUnknown: ImmutableArray.Empty); internal static async Task> GetAllTransitivelyReferencedResourceUrnsAsync( HashSet resources) { // Go through 'resources', but transitively walk through **Component** resources, collecting any // of their child resources. This way, a Component acts as an aggregation really of all the // reachable resources it parents. This walking will stop when it hits custom resources. // // This function also terminates at remote components, whose children are not known to the Node SDK directly. // Remote components will always wait on all of their children, so ensuring we return the remote component // itself here and waiting on it will accomplish waiting on all of it's children regardless of whether they // are returned explicitly here. // // 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 var transitivelyReachableResources = GetTransitivelyReferencedChildResourcesOfComponentResources(resources); var transitivelyReachableCustomResources = transitivelyReachableResources.Where(res => { switch (res) { case CustomResource _: return true; case ComponentResource component: return component.remote; default: return false; // Unreachable } }); var tasks = transitivelyReachableCustomResources.Select(r => r.Urn.GetValueAsync(whenUnknown: "")); var urns = await Task.WhenAll(tasks).ConfigureAwait(false); return new HashSet(urns.Where(urn => !string.IsNullOrEmpty(urn))); } /// /// Recursively walk the resources passed in, returning them and all resources reachable from /// through any **Component** resources we encounter. /// private static HashSet GetTransitivelyReferencedChildResourcesOfComponentResources(HashSet resources) { // Recursively walk the dependent resources through their children, adding them to the result set. var result = new HashSet(); AddTransitivelyReferencedChildResourcesOfComponentResources(resources, result); return result; } private static void AddTransitivelyReferencedChildResourcesOfComponentResources(HashSet resources, HashSet result) { foreach (var resource in resources) { if (result.Add(resource)) { if (resource is ComponentResource) { HashSet childResources; lock (resource.ChildResources) { childResources = new HashSet(resource.ChildResources); } AddTransitivelyReferencedChildResourcesOfComponentResources(childResources, result); } } } } private readonly struct PrepareResult { public readonly Struct SerializedProps; public readonly string ParentUrn; public readonly string ProviderRef; public readonly Dictionary ProviderRefs; public readonly HashSet AllDirectDependencyUrns; public readonly Dictionary> PropertyToDirectDependencyUrns; public readonly List Aliases; public PrepareResult(Struct serializedProps, string parentUrn, string providerRef, Dictionary providerRefs, HashSet allDirectDependencyUrns, Dictionary> propertyToDirectDependencyUrns, List aliases) { SerializedProps = serializedProps; ParentUrn = parentUrn; ProviderRef = providerRef; ProviderRefs = providerRefs; AllDirectDependencyUrns = allDirectDependencyUrns; PropertyToDirectDependencyUrns = propertyToDirectDependencyUrns; Aliases = aliases; } } } }