pulumi/sdk/dotnet/Pulumi/Deployment/Deployment_Prepare.cs
Pat Gavlin 3d2e31289a
Add support for serialized resource references. (#5041)
Resources are serialized as their URN, ID, and package version. Each
Pulumi package is expected to register itself with the SDK. The package
will be invoked to construct appropriate instances of rehydrated
resources. Packages are distinguished by their name and their version.

This is the foundation of cross-process resources.

Related to #2430.

Co-authored-by: Mikhail Shilkov <github@mikhail.io>
Co-authored-by: Luke Hoban <luke@pulumi.com>
Co-authored-by: Levi Blackstone <levi@pulumi.com>
2020-10-27 10:12:12 -07:00

189 lines
9 KiB
C#

// Copyright 2016-2019, Pulumi Corporation
using System;
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<PrepareResult> PrepareResourceAsync(
string label, Resource res, bool custom,
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}");
var explicitDirectDependencies = new HashSet<Resource>(
await GatherExplicitDependenciesAsync(options.DependsOn).ConfigureAwait(false));
LogExcessive($"Gathered explicit dependencies: t={type}, name={name}, custom={custom}");
// 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}");
var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false);
var (serializedProps, propertyToDirectDependencies) =
await SerializeResourcePropertiesAsync(
label,
dictionary,
await this.MonitorSupportsResourceReferences().ConfigureAwait(false)).ConfigureAwait(false);
LogExcessive($"Serialized properties: t={type}, name={name}, custom={custom}");
// 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}");
var parentURN = options.Parent != null
? await options.Parent.Urn.GetValueAsync().ConfigureAwait(false)
: await GetRootResourceAsync(type).ConfigureAwait(false);
LogExcessive($"Got parent urn: t={type}, name={name}, custom={custom}");
string? providerRef = null;
if (custom)
{
var customOpts = options as CustomResourceOptions;
providerRef = await ProviderResource.RegisterAsync(customOpts?.Provider).ConfigureAwait(false);
}
// 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<Resource>(explicitDirectDependencies);
var allDirectDependencyURNs = await GetAllTransitivelyReferencedCustomResourceURNsAsync(explicitDirectDependencies).ConfigureAwait(false);
var propertyToDirectDependencyURNs = new Dictionary<string, HashSet<string>>();
foreach (var (propertyName, directDependencies) in propertyToDirectDependencies)
{
allDirectDependencies.AddRange(directDependencies);
var urns = await GetAllTransitivelyReferencedCustomResourceURNsAsync(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<string>();
var uniqueAliases = new HashSet<string>();
foreach (var alias in res._aliases)
{
var aliasVal = await alias.ToOutput().GetValueAsync().ConfigureAwait(false);
if (uniqueAliases.Add(aliasVal))
{
aliases.Add(aliasVal);
}
}
return new PrepareResult(
serializedProps,
parentURN ?? "",
providerRef ?? "",
allDirectDependencyURNs,
propertyToDirectDependencyURNs,
aliases);
void LogExcessive(string message)
{
if (_excessiveDebugOutput)
Log.Debug(message);
}
}
private static Task<ImmutableArray<Resource>> GatherExplicitDependenciesAsync(InputList<Resource> resources)
=> resources.ToOutput().GetValueAsync();
private static async Task<HashSet<string>> GetAllTransitivelyReferencedCustomResourceURNsAsync(
HashSet<Resource> 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 custom resources it parents. This walking
// will transitively walk through other child ComponentResources, but will stop when it
// hits custom resources. in other words, if we had:
//
// Comp1
// / \
// Cust1 Comp2
// / \
// Cust2 Cust3
// /
// Cust4
//
// Then the transitively reachable custom resources of Comp1 will be [Cust1, Cust2,
// Cust3]. It will *not* include 'Cust4'.
// To do this, first we just get the transitively reachable set of resources (not diving
// into custom resources). In the above picture, if we start with 'Comp1', this will be
// [Comp1, Cust1, Comp2, Cust2, Cust3]
var transitivelyReachableResources = GetTransitivelyReferencedChildResourcesOfComponentResources(resources);
var transitivelyReachableCustomResources = transitivelyReachableResources.OfType<CustomResource>();
var tasks = transitivelyReachableCustomResources.Select(r => r.Urn.GetValueAsync());
var urns = await Task.WhenAll(tasks).ConfigureAwait(false);
return new HashSet<string>(urns);
}
/// <summary>
/// Recursively walk the resources passed in, returning them and all resources reachable from
/// <see cref="Resource.ChildResources"/> through any **Component** resources we encounter.
/// </summary>
private static HashSet<Resource> GetTransitivelyReferencedChildResourcesOfComponentResources(HashSet<Resource> resources)
{
// Recursively walk the dependent resources through their children, adding them to the result set.
var result = new HashSet<Resource>();
AddTransitivelyReferencedChildResourcesOfComponentResources(resources, result);
return result;
}
private static void AddTransitivelyReferencedChildResourcesOfComponentResources(HashSet<Resource> resources, HashSet<Resource> result)
{
foreach (var resource in resources)
{
if (result.Add(resource))
{
if (resource is ComponentResource)
{
HashSet<Resource> childResources;
lock (resource.ChildResources)
{
childResources = new HashSet<Resource>(resource.ChildResources);
}
AddTransitivelyReferencedChildResourcesOfComponentResources(childResources, result);
}
}
}
}
private struct PrepareResult
{
public readonly Struct SerializedProps;
public readonly string ParentUrn;
public readonly string ProviderRef;
public readonly HashSet<string> AllDirectDependencyURNs;
public readonly Dictionary<string, HashSet<string>> PropertyToDirectDependencyURNs;
public readonly List<string> Aliases;
public PrepareResult(Struct serializedProps, string parentUrn, string providerRef, HashSet<string> allDirectDependencyURNs, Dictionary<string, HashSet<string>> propertyToDirectDependencyURNs, List<string> aliases)
{
SerializedProps = serializedProps;
ParentUrn = parentUrn;
ProviderRef = providerRef;
AllDirectDependencyURNs = allDirectDependencyURNs;
PropertyToDirectDependencyURNs = propertyToDirectDependencyURNs;
Aliases = aliases;
}
}
}
}