private async Task <PrepareResult> PrepareResourceAsync( string label, Resource res, bool custom, bool remote, ResourceArgs args, ResourceOptions options) { // 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 <Resource>( 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)
internal static CustomResourceOptions CreateCustomResourceOptionsCopy(ResourceOptions options) { var customOptions = options as CustomResourceOptions; var copied = CreateResourceOptionsCopy(options); return(new CustomResourceOptions { // Base properties Aliases = copied.Aliases, CustomTimeouts = copied.CustomTimeouts, DependsOn = copied.DependsOn, Id = copied.Id, Parent = copied.Parent, IgnoreChanges = copied.IgnoreChanges, Protect = copied.Protect, Provider = copied.Provider, ResourceTransformations = copied.ResourceTransformations, Version = copied.Version, // Our properties AdditionalSecretOutputs = customOptions?.AdditionalSecretOutputs.ToList() ?? new List <string>(), DeleteBeforeReplace = customOptions?.DeleteBeforeReplace, ImportId = customOptions?.ImportId, }); }
private static RegisterResourceRequest CreateRegisterResourceRequest( string type, string name, bool custom, bool remote, ResourceOptions options) { var customOpts = options as CustomResourceOptions; var deleteBeforeReplace = customOpts?.DeleteBeforeReplace; var request = new RegisterResourceRequest() { Type = type, Name = name, Custom = custom, Protect = options.Protect ?? false, Version = options.Version ?? "", ImportId = customOpts?.ImportId ?? "", AcceptSecrets = true, DeleteBeforeReplace = deleteBeforeReplace ?? false, DeleteBeforeReplaceDefined = deleteBeforeReplace != null, CustomTimeouts = new RegisterResourceRequest.Types.CustomTimeouts { Create = TimeoutString(options.CustomTimeouts?.Create), Delete = TimeoutString(options.CustomTimeouts?.Delete), Update = TimeoutString(options.CustomTimeouts?.Update), }, Remote = remote, }; if (customOpts != null) { request.AdditionalSecretOutputs.AddRange(customOpts.AdditionalSecretOutputs); } request.IgnoreChanges.AddRange(options.IgnoreChanges); return(request); }
private async Task <(string urn, string id, Struct data)> ReadOrRegisterResourceAsync( Resource resource, ResourceArgs args, ResourceOptions options) { if (options.Id != null) { var id = await options.Id.ToOutput().GetValueAsync().ConfigureAwait(false); if (!string.IsNullOrEmpty(id)) { if (!(resource is CustomResource)) { throw new ArgumentException($"{nameof(ResourceOptions)}.{nameof(ResourceOptions.Id)} is only valid for a {nameof(CustomResource)}"); } // If this resource already exists, read its state rather than registering it anew. return(await ReadResourceAsync(resource, id, args, options).ConfigureAwait(false)); } } // Kick off the resource registration. If we are actually performing a deployment, this // resource's properties will be resolved asynchronously after the operation completes, // so that dependent computations resolve normally. If we are just planning, on the // other hand, values will never resolve. return(await RegisterResourceAsync(resource, args, options).ConfigureAwait(false)); }
private async Task <(string urn, string id, Struct data)> RegisterResourceAsync( Resource resource, ResourceArgs args, ResourceOptions options) { var name = resource.GetResourceName(); var type = resource.GetResourceType(); var custom = resource is CustomResource; var label = $"resource:{name}[{type}]"; Log.Debug($"Registering resource start: t={type}, name={name}, custom={custom}"); var request = CreateRegisterResourceRequest(type, name, custom, options); Log.Debug($"Preparing resource: t={type}, name={name}, custom={custom}"); var prepareResult = await PrepareResourceAsync(label, resource, custom, args, options).ConfigureAwait(false); Log.Debug($"Prepared resource: t={type}, name={name}, custom={custom}"); PopulateRequest(request, prepareResult); Log.Debug($"Registering resource monitor start: t={type}, name={name}, custom={custom}"); var result = await this.Monitor.RegisterResourceAsync(request); Log.Debug($"Registering resource monitor end: t={type}, name={name}, custom={custom}"); return(result.Urn, result.Id, result.Object); }
internal static ResourceOptions CreateResourceOptionsCopy(ResourceOptions options) => new ResourceOptions { Aliases = options.Aliases.ToList(), CustomTimeouts = CustomTimeouts.Clone(options.CustomTimeouts), DependsOn = options.DependsOn.Clone(), Id = options.Id, Parent = options.Parent, IgnoreChanges = options.IgnoreChanges.ToList(), Protect = options.Protect, Provider = options.Provider, ResourceTransformations = options.ResourceTransformations.ToList(), Version = options.Version, };
internal static TResourceOptions CreateCopy <TResourceOptions>(ResourceOptions options) where TResourceOptions : ResourceOptions, new() => new TResourceOptions { Aliases = options.Aliases.ToList(), CustomTimeouts = CustomTimeouts.Clone(options.CustomTimeouts), DependsOn = options.DependsOn.Clone(), Id = options.Id, Parent = options.Parent, IgnoreChanges = options.IgnoreChanges.ToList(), Protect = options.Protect, Provider = options.Provider, ResourceTransformations = options.ResourceTransformations.ToList(), Urn = options.Urn, Version = options.Version };
static ResourceOptions MergeNormalOptions(ResourceOptions options1, ResourceOptions options2) { options1.Id = options2.Id ?? options1.Id; options1.Parent = options2.Parent ?? options1.Parent; options1.Protect = options2.Protect ?? options1.Protect; options1.Version = options2.Version ?? options1.Version; options1.Provider = options2.Provider ?? options1.Provider; options1.CustomTimeouts = options2.CustomTimeouts ?? options1.CustomTimeouts; options1.IgnoreChanges.AddRange(options2.IgnoreChanges); options1.ResourceTransformations.AddRange(options2.ResourceTransformations); options1.Aliases.AddRange(options2.Aliases); options1.DependsOn = options1.DependsOn.Concat(options2.DependsOn); return(options1); }
void IDeploymentInternal.ReadOrRegisterResource( Resource resource, bool remote, Func<string, Resource> newDependency, ResourceArgs args, ResourceOptions options) { // ReadOrRegisterResource is called in a fire-and-forget manner. Make sure we keep // track of this task so that the application will not quit until this async work // completes. // // Also, we can only do our work once the constructor for the resource has actually // finished. Otherwise, we might actually read and get the result back *prior* to the // object finishing initializing. Note: this is not a speculative concern. This is // something that does happen and has to be accounted for. _runner.RegisterTask( $"{nameof(IDeploymentInternal.ReadOrRegisterResource)}: {resource.GetResourceType()}-{resource.GetResourceName()}", CompleteResourceAsync(resource, remote, newDependency, args, options, resource.CompletionSources)); }
private async Task <(string urn, string id, Struct data, ImmutableDictionary <string, ImmutableHashSet <Resource> > dependencies)> ReadOrRegisterResourceAsync( Resource resource, bool remote, Func <string, Resource> newDependency, ResourceArgs args, ResourceOptions options) { if (options.Urn != null) { // This is a resource that already exists. Read its state from the engine. var invokeResult = await InvokeRawAsync( "pulumi:pulumi:getResource", new GetResourceInvokeArgs { Urn = options.Urn }, new InvokeOptions()).ConfigureAwait(false); var result = invokeResult.Serialized; var urn = result.Fields["urn"].StringValue; var id = result.Fields["id"].StringValue; var state = result.Fields["state"].StructValue; return(urn, id, state, ImmutableDictionary <string, ImmutableHashSet <Resource> > .Empty); } if (options.Id != null) { var id = await options.Id.ToOutput().GetValueAsync(whenUnknown: "").ConfigureAwait(false); if (!string.IsNullOrEmpty(id)) { if (!(resource is CustomResource)) { throw new ArgumentException($"{nameof(ResourceOptions)}.{nameof(ResourceOptions.Id)} is only valid for a {nameof(CustomResource)}"); } // If this resource already exists, read its state rather than registering it anew. return(await ReadResourceAsync(resource, id, args, options).ConfigureAwait(false)); } } // Kick off the resource registration. If we are actually performing a deployment, this // resource's properties will be resolved asynchronously after the operation completes, // so that dependent computations resolve normally. If we are just planning, on the // other hand, values will never resolve. return(await RegisterResourceAsync(resource, remote, newDependency, args, options).ConfigureAwait(false)); }
private async Task <(string urn, string id, Struct data, ImmutableDictionary <string, ImmutableHashSet <Resource> > dependencies)> RegisterResourceAsync( Resource resource, bool remote, Func <string, Resource> newDependency, ResourceArgs args, ResourceOptions options) { var name = resource.GetResourceName(); var type = resource.GetResourceType(); var custom = resource is CustomResource; var label = $"resource:{name}[{type}]"; Log.Debug($"Registering resource start: t={type}, name={name}, custom={custom}, remote={remote}"); var request = CreateRegisterResourceRequest(type, name, custom, remote, options); Log.Debug($"Preparing resource: t={type}, name={name}, custom={custom}, remote={remote}"); var prepareResult = await PrepareResourceAsync(label, resource, custom, remote, args, options).ConfigureAwait(false); Log.Debug($"Prepared resource: t={type}, name={name}, custom={custom}, remote={remote}"); PopulateRequest(request, prepareResult); Log.Debug($"Registering resource monitor start: t={type}, name={name}, custom={custom}, remote={remote}"); var result = await this.Monitor.RegisterResourceAsync(resource, request); Log.Debug($"Registering resource monitor end: t={type}, name={name}, custom={custom}, remote={remote}"); var dependencies = ImmutableDictionary.CreateBuilder <string, ImmutableHashSet <Resource> >(); foreach (var(key, propertyDependencies) in result.PropertyDependencies) { var urns = ImmutableHashSet.CreateBuilder <Resource>(); foreach (var urn in propertyDependencies.Urns) { urns.Add(newDependency(urn)); } dependencies[key] = urns.ToImmutable(); } return(result.Urn, result.Id, result.Object, dependencies.ToImmutable()); }
private async Task <(string urn, string id, Struct data, ImmutableDictionary <string, ImmutableHashSet <Resource> > dependencies)> ReadResourceAsync( Resource resource, string id, ResourceArgs args, ResourceOptions options) { var name = resource.GetResourceName(); var type = resource.GetResourceType(); var label = $"resource:{name}[{type}]#..."; Log.Debug($"Reading resource: id={id}, t=${type}, name=${name}"); var prepareResult = await this.PrepareResourceAsync( label, resource, custom : true, remote : false, args, options).ConfigureAwait(false); var serializer = new Serializer(_excessiveDebugOutput); Log.Debug($"ReadResource RPC prepared: id={id}, t={type}, name={name}" + (_excessiveDebugOutput ? $", obj={prepareResult.SerializedProps}" : "")); // Create a resource request and do the RPC. var request = new ReadResourceRequest { Type = type, Name = name, Id = id, Parent = prepareResult.ParentUrn, Provider = prepareResult.ProviderRef, Properties = prepareResult.SerializedProps, Version = options?.Version ?? "", AcceptSecrets = true, AcceptResources = !_disableResourceReferences, }; request.Dependencies.AddRange(prepareResult.AllDirectDependencyURNs); // Now run the operation, serializing the invocation if necessary. var response = await this.Monitor.ReadResourceAsync(resource, request); return(response.Urn, id, response.Properties, ImmutableDictionary <string, ImmutableHashSet <Resource> > .Empty); }
internal static ComponentResourceOptions CreateComponentResourceOptionsCopy(ResourceOptions options) { var componentOptions = options as ComponentResourceOptions; var cloned = CreateResourceOptionsCopy(options); return(new ComponentResourceOptions { // Base properties Aliases = cloned.Aliases, CustomTimeouts = cloned.CustomTimeouts, DependsOn = cloned.DependsOn, Id = cloned.Id, Parent = cloned.Parent, IgnoreChanges = cloned.IgnoreChanges, Protect = cloned.Protect, Provider = cloned.Provider, ResourceTransformations = cloned.ResourceTransformations, Version = cloned.Version, // Our properties Providers = componentOptions?.Providers.ToList() ?? new List <ProviderResource>(), }); }
void IDeploymentInternal.ReadOrRegisterResource( Resource resource, ResourceArgs args, ResourceOptions options) { // ReadOrRegisterResource is called in a fire-and-forget manner. Make sure we keep // track of this task so that the application will not quit until this async work // completes. // // Also, we can only do our work once the constructor for the resource has actually // finished. Otherwise, we might actually read and get the result back *prior* to the // object finishing initializing. Note: this is not a speculative concern. This is // something that does happen and has to be accounted for. // // IMPORTANT! We have to make sure we run 'OutputCompletionSource.InitializeOutputs' // synchronously directly when `resource`'s constructor runs since this will set all of // the `[Output(...)] Output<T>` properties. We need those properties assigned by the // time the base 'Resource' constructor finishes so that both derived classes and // external consumers can use the Output properties of `resource`. var completionSources = OutputCompletionSource.InitializeOutputs(resource); _runner.RegisterTask( $"{nameof(IDeploymentInternal.ReadOrRegisterResource)}: {resource.GetResourceType()}-{resource.GetResourceName()}", CompleteResourceAsync(resource, args, options, completionSources)); }
internal static ResourceOptions CreateResourceOptionsCopy(ResourceOptions options) => CreateCopy <ResourceOptions>(options);
private async Task <PrepareResult> 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 <Resource>( 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)).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().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 <string, string>(); 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 <Resource>(explicitDirectDependencies); var allDirectDependencyUrns = await GetAllTransitivelyReferencedResourceUrnsAsync(explicitDirectDependencies).ConfigureAwait(false); var propertyToDirectDependencyUrns = new Dictionary <string, HashSet <string> >(); 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 <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 ?? "", providerRefs, allDirectDependencyUrns, propertyToDirectDependencyUrns, aliases)); void LogExcessive(string message) { if (_excessiveDebugOutput) { Log.Debug(message); } } }
/// <summary> /// Creates and registers a new resource object. <paramref name="type"/> is the fully /// qualified type token and <paramref name="name"/> is the "name" part to use in creating a /// stable and globally unique URN for the object. dependsOn is an optional list of other /// resources that this resource depends on, controlling the order in which we perform /// resource operations. /// </summary> /// <param name="type">The type of the resource.</param> /// <param name="name">The unique name of the resource.</param> /// <param name="custom">True to indicate that this is a custom resource, managed by a plugin.</param> /// <param name="args">The arguments to use to populate the new resource.</param> /// <param name="options">A bag of options that control this resource's behavior.</param> /// <param name="remote">True if this is a remote component resource.</param> /// <param name="dependency">True if this is a synthetic resource used internally for dependency tracking.</param> private protected Resource( string type, string name, bool custom, ResourceArgs args, ResourceOptions options, bool remote = false, bool dependency = false) { if (dependency) { _type = ""; _name = ""; _protect = false; _providers = ImmutableDictionary <string, ProviderResource> .Empty; return; } if (string.IsNullOrEmpty(type)) { throw new ArgumentException("'type' cannot be null or empty.", nameof(type)); } if (string.IsNullOrEmpty(name)) { throw new ArgumentException("'name' cannot be null or empty.", nameof(name)); } // Before anything else - if there are transformations registered, invoke them in order // to transform the properties and options assigned to this resource. var parent = type == Stack._rootPulumiStackTypeName ? null : (options.Parent ?? Deployment.InternalInstance.Stack); _type = type; _name = name; var transformations = ImmutableArray.CreateBuilder <ResourceTransformation>(); transformations.AddRange(options.ResourceTransformations); if (parent != null) { transformations.AddRange(parent._transformations); } this._transformations = transformations.ToImmutable(); foreach (var transformation in this._transformations) { var tres = transformation(new ResourceTransformationArgs(this, args, options)); if (tres != null) { if (tres.Value.Options.Parent != options.Parent) { // This is currently not allowed because the parent tree is needed to // establish what transformation to apply in the first place, and to compute // inheritance of other resource options in the Resource constructor before // transformations are run (so modifying it here would only even partially // take affect). It's theoretically possible this restriction could be // lifted in the future, but for now just disallow re-parenting resources in // transformations to be safe. throw new ArgumentException("Transformations cannot currently be used to change the 'parent' of a resource."); } args = tres.Value.Args; options = tres.Value.Options; } } // Make a shallow clone of options to ensure we don't modify the value passed in. options = options.Clone(); var componentOpts = options as ComponentResourceOptions; var customOpts = options as CustomResourceOptions; if (options.Provider != null && componentOpts?.Providers.Count > 0) { throw new ResourceException("Do not supply both 'provider' and 'providers' options to a ComponentResource.", options.Parent); } // Check the parent type if one exists and fill in any default options. this._providers = ImmutableDictionary <string, ProviderResource> .Empty; if (options.Parent != null) { var parentResource = options.Parent; lock (parentResource.ChildResources) { parentResource.ChildResources.Add(this); } options.Protect ??= options.Parent._protect; // Make a copy of the aliases array, and add to it any implicit aliases inherited from its parent options.Aliases = options.Aliases.ToList(); foreach (var parentAlias in options.Parent._aliases) { options.Aliases.Add(Pulumi.Urn.InheritedChildAlias(name, options.Parent.GetResourceName(), parentAlias, type)); } this._providers = options.Parent._providers; } if (custom) { var provider = customOpts?.Provider; if (provider == null) { if (options.Parent != null) { // If no provider was given, but we have a parent, then inherit the // provider from our parent. options.Provider = options.Parent.GetProvider(type); } } else { // If a provider was specified, add it to the providers map under this type's package so that // any children of this resource inherit its provider. var typeComponents = type.Split(":"); if (typeComponents.Length == 3) { var pkg = typeComponents[0]; this._providers = this._providers.SetItem(pkg, provider); } } } else { // Note: we checked above that at most one of options.provider or options.providers // is set. // If options.provider is set, treat that as if we were given a array of provider // with that single value in it. Otherwise, take the array of providers, convert it // to a map and combine with any providers we've already set from our parent. var providerList = options.Provider != null ? new List <ProviderResource> { options.Provider } : componentOpts?.Providers; this._providers = this._providers.AddRange(ConvertToProvidersMap(providerList)); } this._protect = options.Protect == true; this._provider = custom ? options.Provider : null; this._version = options.Version; // Collapse any 'Alias'es down to URNs. We have to wait until this point to do so // because we do not know the default 'name' and 'type' to apply until we are inside the // resource constructor. var aliases = ImmutableArray.CreateBuilder <Input <string> >(); foreach (var alias in options.Aliases) { aliases.Add(CollapseAliasToUrn(alias, name, type, options.Parent)); } this._aliases = aliases.ToImmutable(); Deployment.InternalInstance.ReadOrRegisterResource(this, remote, urn => new DependencyResource(urn), args, options); }
/// <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($"{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); } } }