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)); }
/// <summary> /// Calls <see cref="ReadOrRegisterResourceAsync"/> then completes all the /// <see cref="IOutputCompletionSource"/> sources on the <paramref name="resource"/> with /// the results of it. /// </summary> private async Task CompleteResourceAsync( Resource resource, bool remote, Func<string, Resource> newDependency, ResourceArgs args, ResourceOptions options, ImmutableDictionary<string, IOutputCompletionSource> completionSources) { // Run in a try/catch/finally so that we always resolve all the outputs of the resource // regardless of whether we encounter an errors computing the action. try { var response = await ReadOrRegisterResourceAsync( resource, remote, newDependency, args, options).ConfigureAwait(false); completionSources[Constants.UrnPropertyName].SetStringValue(response.urn, isKnown: true); if (resource is CustomResource) { // ReSharper disable once ConstantNullCoalescingCondition var id = response.id ?? ""; completionSources[Constants.IdPropertyName].SetStringValue(id, isKnown: id != ""); } // Go through all our output fields and lookup a corresponding value in the response // object. Allow the output field to deserialize the response. foreach (var (fieldName, completionSource) in completionSources) { if (fieldName == Constants.UrnPropertyName || fieldName == Constants.IdPropertyName) { // Already handled specially above. continue; } // We process and deserialize each field (instead of bulk processing // 'response.data' so that each field can have independent isKnown/isSecret // values. We do not want to bubble up isKnown/isSecret from one field to the // rest. if (response.data.Fields.TryGetValue(fieldName, out var value)) { if (!response.dependencies.TryGetValue(fieldName, out var dependencies)) { dependencies = ImmutableHashSet<Resource>.Empty; } var converted = Converter.ConvertValue(err => Log.Warn(err, resource), $"{resource.GetType().FullName}.{fieldName}", value, completionSource.TargetType, dependencies); completionSource.SetValue(converted); } } } catch (Exception e) { // Mark any unresolved output properties with this exception. That way we don't // leave any outstanding tasks sitting around which might cause hangs. foreach (var source in completionSources.Values) { source.TrySetException(e); } throw; } finally { // ensure that we've at least resolved all our completion sources. That way we // don't leave any outstanding tasks sitting around which might cause hangs. foreach (var source in completionSources.Values) { // Didn't get a value for this field. Resolve it with a default value. // If we're in preview, we'll consider this unknown and in a normal // update we'll consider it known. source.TrySetDefaultResult(isKnown: !_isDryRun); } } }