-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Deployment_Prepare.cs
174 lines (152 loc) · 8.47 KB
/
Deployment_Prepare.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// 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();
Log.Debug($"Gathering explicit dependencies: t={type}, name={name}, custom={custom}");
var explicitDirectDependencies = new HashSet<Resource>(
await GatherExplicitDependenciesAsync(options.DependsOn).ConfigureAwait(false));
Log.Debug($"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'.
Log.Debug($"Serializing properties: t={type}, name={name}, custom={custom}");
var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false);
var (serializedProps, propertyToDirectDependencies) =
await SerializeResourcePropertiesAsync(label, dictionary).ConfigureAwait(false);
Log.Debug($"Serialized properties: t={type}, name={name}, custom={custom}");
// Wait for the parent to complete.
// If no parent was provided, parent to the root resource.
Log.Debug($"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);
Log.Debug($"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);
}
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)
{
AddTransitivelyReferencedChildResourcesOfComponentResources(resource.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;
}
}
}
}