示例#1
0
        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)
示例#2
0
        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,
            });
        }
示例#3
0
        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);
        }
示例#4
0
        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);
        }
示例#6
0
 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,
 };
示例#7
0
 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
 };
示例#8
0
            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));
        }
示例#11
0
        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());
        }
示例#12
0
        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);
        }
示例#13
0
        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>(),
            });
        }
示例#14
0
        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));
        }
示例#15
0
 internal static ResourceOptions CreateResourceOptionsCopy(ResourceOptions options)
 => CreateCopy <ResourceOptions>(options);
示例#16
0
        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);
                }
            }
        }
示例#17
0
        /// <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);
                }
            }
        }