2024-03-07 08:52:34 +00:00
|
|
|
// Copyright 2016-2024, Pulumi Corporation.
|
2018-05-22 19:43:36 +00:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2017-09-27 19:34:44 +00:00
|
|
|
|
|
|
|
import * as asset from "../asset";
|
2020-09-24 02:06:26 +00:00
|
|
|
import { isGrpcError } from "../errors";
|
2017-10-08 19:10:46 +00:00
|
|
|
import * as log from "../log";
|
2019-12-18 03:04:09 +00:00
|
|
|
import { getAllResources, Input, Inputs, isUnknown, Output, unknown } from "../output";
|
2022-05-30 08:31:28 +00:00
|
|
|
import { ComponentResource, CustomResource, DependencyResource, ProviderResource, Resource } from "../resource";
|
2023-12-19 14:35:23 +00:00
|
|
|
import { debuggablePromise, debugPromiseLeaks, errorString } from "./debuggable";
|
2021-09-16 01:25:26 +00:00
|
|
|
import { getAllTransitivelyReferencedResourceURNs } from "./resource";
|
2023-12-19 14:35:23 +00:00
|
|
|
import { excessiveDebugOutput, isDryRun } from "./settings";
|
|
|
|
import { getStore } from "./state";
|
2017-09-27 19:34:44 +00:00
|
|
|
|
2024-03-02 00:00:57 +00:00
|
|
|
import * as gstruct from "google-protobuf/google/protobuf/struct_pb";
|
2024-03-07 08:52:34 +00:00
|
|
|
import * as semver from "semver";
|
2020-12-01 18:58:15 +00:00
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
export type OutputResolvers = Record<
|
|
|
|
string,
|
|
|
|
(value: any, isStable: boolean, isSecret: boolean, deps?: Resource[], err?: Error) => void
|
|
|
|
>;
|
2018-04-05 16:48:09 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Mutates the `onto` resource so that it has Promise-valued properties for all
|
|
|
|
* the `props` input/output props. *Importantly* all these promises are
|
|
|
|
* completely unresolved. This is because we don't want anyone to observe the
|
|
|
|
* values of these properties until the rpc call to registerResource actually
|
|
|
|
* returns. This is because the registerResource call may actually override
|
|
|
|
* input values, and we only want people to see the final value.
|
2018-01-25 21:34:21 +00:00
|
|
|
*
|
2024-07-15 11:27:47 +00:00
|
|
|
* The result of this call (beyond the stateful changes to `onto`) is the set of
|
|
|
|
* {@link Promise} resolvers that will be called post-RPC call. When the
|
|
|
|
* {@link registerResource} RPC call comes back, the values that the engine
|
|
|
|
* actualy produced will be used to resolve all the unresolved promised placed
|
|
|
|
* on `onto`.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
2018-04-05 16:48:09 +00:00
|
|
|
export function transferProperties(onto: Resource, label: string, props: Inputs): OutputResolvers {
|
|
|
|
const resolvers: OutputResolvers = {};
|
2018-01-25 21:34:21 +00:00
|
|
|
for (const k of Object.keys(props)) {
|
|
|
|
// Skip "id" and "urn", since we handle those specially.
|
|
|
|
if (k === "id" || k === "urn") {
|
|
|
|
continue;
|
|
|
|
}
|
2017-09-27 19:34:44 +00:00
|
|
|
|
2018-01-25 21:34:21 +00:00
|
|
|
// Create a property to wrap the value and store it on the resource.
|
|
|
|
if (onto.hasOwnProperty(k)) {
|
|
|
|
throw new Error(`Property '${k}' is already initialized on target '${label}`);
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2018-03-18 07:15:22 +00:00
|
|
|
|
|
|
|
let resolveValue: (v: any) => void;
|
2021-03-31 03:16:25 +00:00
|
|
|
let rejectValue: (err: Error) => void;
|
2018-05-23 21:47:40 +00:00
|
|
|
let resolveIsKnown: (v: boolean) => void;
|
2021-03-31 03:16:25 +00:00
|
|
|
let rejectIsKnown: (err: Error) => void;
|
2019-04-12 23:59:18 +00:00
|
|
|
let resolveIsSecret: (v: boolean) => void;
|
2021-03-31 03:16:25 +00:00
|
|
|
let rejectIsSecret: (err: Error) => void;
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
let resolveDeps: (v: Resource[]) => void;
|
2021-03-31 03:16:25 +00:00
|
|
|
let rejectDeps: (err: Error) => void;
|
2018-03-18 07:15:22 +00:00
|
|
|
|
2020-09-24 02:06:26 +00:00
|
|
|
resolvers[k] = (v: any, isKnown: boolean, isSecret: boolean, deps: Resource[] = [], err?: Error) => {
|
2023-04-29 02:16:01 +00:00
|
|
|
if (err) {
|
2023-06-09 19:04:35 +00:00
|
|
|
if (isGrpcError(err)) {
|
2023-06-15 14:43:05 +00:00
|
|
|
if (debugPromiseLeaks) {
|
2023-06-09 19:04:35 +00:00
|
|
|
console.error("info: skipped rejection in transferProperties");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2021-03-31 03:16:25 +00:00
|
|
|
rejectValue(err);
|
|
|
|
rejectIsKnown(err);
|
|
|
|
rejectIsSecret(err);
|
|
|
|
rejectDeps(err);
|
|
|
|
} else {
|
|
|
|
resolveValue(v);
|
|
|
|
resolveIsKnown(isKnown);
|
|
|
|
resolveIsSecret(isSecret);
|
|
|
|
resolveDeps(deps);
|
|
|
|
}
|
2018-03-18 07:15:22 +00:00
|
|
|
};
|
|
|
|
|
2019-02-28 00:00:54 +00:00
|
|
|
const propString = Output.isInstance(props[k]) ? "Output<T>" : `${props[k]}`;
|
2018-10-27 03:49:16 +00:00
|
|
|
(<any>onto)[k] = new Output(
|
2018-02-05 22:44:23 +00:00
|
|
|
onto,
|
2018-01-25 21:34:21 +00:00
|
|
|
debuggablePromise(
|
2021-03-31 03:16:25 +00:00
|
|
|
new Promise<any>((resolve, reject) => {
|
|
|
|
resolveValue = resolve;
|
|
|
|
rejectValue = reject;
|
|
|
|
}),
|
2023-04-28 22:27:10 +00:00
|
|
|
`transferProperty(${label}, ${k}, ${propString})`,
|
|
|
|
),
|
2018-03-18 07:15:22 +00:00
|
|
|
debuggablePromise(
|
2021-03-31 03:16:25 +00:00
|
|
|
new Promise<boolean>((resolve, reject) => {
|
|
|
|
resolveIsKnown = resolve;
|
|
|
|
rejectIsKnown = reject;
|
|
|
|
}),
|
2023-04-28 22:27:10 +00:00
|
|
|
`transferIsStable(${label}, ${k}, ${propString})`,
|
|
|
|
),
|
2019-04-12 23:59:18 +00:00
|
|
|
debuggablePromise(
|
2021-03-31 03:16:25 +00:00
|
|
|
new Promise<boolean>((resolve, reject) => {
|
|
|
|
resolveIsSecret = resolve;
|
|
|
|
rejectIsSecret = reject;
|
|
|
|
}),
|
2023-04-28 22:27:10 +00:00
|
|
|
`transferIsSecret(${label}, ${k}, ${propString})`,
|
|
|
|
),
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
debuggablePromise(
|
2021-03-31 03:16:25 +00:00
|
|
|
new Promise<Resource[]>((resolve, reject) => {
|
|
|
|
resolveDeps = resolve;
|
|
|
|
rejectDeps = reject;
|
|
|
|
}),
|
2023-04-28 22:27:10 +00:00
|
|
|
`transferDeps(${label}, ${k}, ${propString})`,
|
|
|
|
),
|
|
|
|
);
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
|
2018-01-25 21:34:21 +00:00
|
|
|
return resolvers;
|
|
|
|
}
|
2017-09-27 19:34:44 +00:00
|
|
|
|
2021-09-16 01:25:26 +00:00
|
|
|
/**
|
|
|
|
* Controls the serialization of RPC structures.
|
|
|
|
*/
|
|
|
|
export interface SerializationOptions {
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* True if we are keeping output values. If the monitor does not support
|
|
|
|
* output values, they will not be kept, even when this is set to true.
|
2021-09-16 01:25:26 +00:00
|
|
|
*/
|
|
|
|
keepOutputValues?: boolean;
|
|
|
|
}
|
|
|
|
|
2018-01-25 21:34:21 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Walks the props object passed in, awaiting all interior promises for
|
|
|
|
* properties with keys that match the provided filter, creating a reasonable
|
|
|
|
* POJO object that can be remoted over to {@link registerResource}.
|
2018-01-25 21:34:21 +00:00
|
|
|
*/
|
2018-05-01 22:05:42 +00:00
|
|
|
async function serializeFilteredProperties(
|
2021-08-10 18:31:59 +00:00
|
|
|
label: string,
|
|
|
|
props: Inputs,
|
|
|
|
acceptKey: (k: string) => boolean,
|
2021-09-16 01:25:26 +00:00
|
|
|
opts?: SerializationOptions,
|
2021-08-10 18:31:59 +00:00
|
|
|
): Promise<[Record<string, any>, Map<string, Set<Resource>>]> {
|
2019-02-22 04:18:29 +00:00
|
|
|
const propertyToDependentResources = new Map<string, Set<Resource>>();
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 17:46:30 +00:00
|
|
|
|
2018-01-25 21:34:21 +00:00
|
|
|
const result: Record<string, any> = {};
|
|
|
|
for (const k of Object.keys(props)) {
|
2018-05-23 21:47:40 +00:00
|
|
|
if (acceptKey(k)) {
|
|
|
|
// We treat properties with undefined values as if they do not exist.
|
2019-02-22 04:18:29 +00:00
|
|
|
const dependentResources = new Set<Resource>();
|
2021-09-16 01:25:26 +00:00
|
|
|
const v = await serializeProperty(`${label}.${k}`, props[k], dependentResources, opts);
|
2018-05-23 21:47:40 +00:00
|
|
|
if (v !== undefined) {
|
|
|
|
result[k] = v;
|
2019-02-22 04:18:29 +00:00
|
|
|
propertyToDependentResources.set(k, dependentResources);
|
2018-05-23 21:47:40 +00:00
|
|
|
}
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-22 04:18:29 +00:00
|
|
|
return [result, propertyToDependentResources];
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
|
2018-05-01 22:05:42 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Walks the props object passed in, awaiting all interior promises besides
|
|
|
|
* those for `id` and `urn`, creating a reasonable POJO object that can be
|
|
|
|
* remoted over to {@link registerResource}.
|
2018-05-01 22:05:42 +00:00
|
|
|
*/
|
2021-09-16 01:25:26 +00:00
|
|
|
export async function serializeResourceProperties(label: string, props: Inputs, opts?: SerializationOptions) {
|
2023-04-28 22:27:10 +00:00
|
|
|
return serializeFilteredProperties(label, props, (key) => key !== "id" && key !== "urn", opts);
|
2018-05-01 22:05:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Walks the props object passed in, awaiting all interior promises, creating a
|
|
|
|
* reasonable POJO object that can be remoted over to {@link registerResource}.
|
2018-05-01 22:05:42 +00:00
|
|
|
*/
|
2021-09-16 01:25:26 +00:00
|
|
|
export async function serializeProperties(label: string, props: Inputs, opts?: SerializationOptions) {
|
2023-04-28 22:27:10 +00:00
|
|
|
const [result] = await serializeFilteredProperties(label, props, (_) => true, opts);
|
2019-02-22 04:18:29 +00:00
|
|
|
return result;
|
2018-05-01 22:05:42 +00:00
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2021-09-16 01:25:26 +00:00
|
|
|
export async function serializePropertiesReturnDeps(label: string, props: Inputs, opts?: SerializationOptions) {
|
2023-04-28 22:27:10 +00:00
|
|
|
return serializeFilteredProperties(label, props, (_) => true, opts);
|
2021-07-07 23:03:56 +00:00
|
|
|
}
|
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Fetches the raw outputs and deserializes them from a gRPC call result.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
2024-04-22 11:12:45 +00:00
|
|
|
export function deserializeProperties(outputsStruct: gstruct.Struct, keepUnknowns?: boolean): Inputs {
|
2024-03-02 00:00:57 +00:00
|
|
|
const props: Inputs = {};
|
|
|
|
const outputs = outputsStruct.toJavaScript();
|
2017-10-18 22:03:56 +00:00
|
|
|
for (const k of Object.keys(outputs)) {
|
2018-05-23 21:47:40 +00:00
|
|
|
// We treat properties with undefined values as if they do not exist.
|
|
|
|
if (outputs[k] !== undefined) {
|
2024-04-22 11:12:45 +00:00
|
|
|
props[k] = deserializeProperty(outputs[k], keepUnknowns);
|
2018-05-23 21:47:40 +00:00
|
|
|
}
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Takes as input a gRPC serialized `proto.google.protobuf.Struct` and resolves
|
|
|
|
* all of the resource's matching properties to the values inside.
|
2018-05-23 21:47:40 +00:00
|
|
|
*
|
2024-07-15 11:27:47 +00:00
|
|
|
* NOTE: it is imperative that the properties in `allProps` were produced by
|
|
|
|
* `deserializeProperties` in order for output properties to work correctly
|
|
|
|
* w.r.t. knowns/unknowns: this function assumes that any undefined value in
|
|
|
|
* `allProps`represents an unknown value that was returned by an engine
|
|
|
|
* operation.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
2018-01-25 21:34:21 +00:00
|
|
|
export function resolveProperties(
|
2023-04-28 22:27:10 +00:00
|
|
|
res: Resource,
|
|
|
|
resolvers: Record<string, (v: any, isKnown: boolean, isSecret: boolean, deps?: Resource[], err?: Error) => void>,
|
|
|
|
t: string,
|
|
|
|
name: string,
|
|
|
|
allProps: any,
|
|
|
|
deps: Record<string, Resource[]>,
|
|
|
|
err?: Error,
|
2024-04-22 11:12:45 +00:00
|
|
|
keepUnknowns?: boolean,
|
2023-04-28 22:27:10 +00:00
|
|
|
): void {
|
2020-09-24 02:06:26 +00:00
|
|
|
// If there is an error, just reject everything.
|
|
|
|
if (err) {
|
|
|
|
for (const k of Object.keys(resolvers)) {
|
|
|
|
const resolve = resolvers[k];
|
|
|
|
resolve(undefined, true, false, [], err);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2017-09-27 19:34:44 +00:00
|
|
|
|
|
|
|
// Now go ahead and resolve all properties present in the inputs and outputs set.
|
2018-02-05 22:44:23 +00:00
|
|
|
for (const k of Object.keys(allProps)) {
|
2017-09-27 19:34:44 +00:00
|
|
|
// Skip "id" and "urn", since we handle those specially.
|
|
|
|
if (k === "id" || k === "urn") {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, unmarshal the value, and store it on the resource object.
|
2018-11-08 04:24:16 +00:00
|
|
|
const resolve = resolvers[k];
|
2018-03-18 07:15:22 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
if (resolve === undefined) {
|
2018-11-08 04:24:16 +00:00
|
|
|
// engine returned a property that was not in our initial property-map. This can happen
|
|
|
|
// for outputs that were registered through direct calls to 'registerOutputs'. We do
|
|
|
|
// *not* want to do anything with these returned properties. First, the component
|
|
|
|
// resources that were calling 'registerOutputs' will have already assigned these fields
|
|
|
|
// directly on them themselves. Second, if we were to try to assign here we would have
|
|
|
|
// an incredibly bad race condition for two reasons:
|
|
|
|
//
|
|
|
|
// 1. This call to 'resolveProperties' happens asynchronously at some point far after
|
|
|
|
// the resource was constructed. So the user will have been able to observe the
|
|
|
|
// initial value up until we get to this point.
|
|
|
|
//
|
|
|
|
// 2. The component resource will have often assigned a value of some arbitrary type
|
|
|
|
// (say, a 'string'). If we overwrite this with an `Output<string>` we'll be changing
|
|
|
|
// the type at some non-deterministic point in the future.
|
|
|
|
continue;
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2018-03-18 07:15:22 +00:00
|
|
|
|
2019-04-12 23:59:18 +00:00
|
|
|
// If this value is a secret, unwrap its inner value.
|
|
|
|
let value = allProps[k];
|
2019-07-02 23:31:14 +00:00
|
|
|
const isSecret = isRpcSecret(value);
|
|
|
|
value = unwrapRpcSecret(value);
|
2019-04-12 23:59:18 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
try {
|
Propagate inputs to outputs during preview. (#3327)
These changes restore a more-correct version of the behavior that was
disabled with #3014. The original implementation of this behavior was
done in the SDKs, which do not have access to the complete inputs for a
resource (in particular, default values filled in by the provider during
`Check` are not exposed to the SDK). This lack of information meant that
the resolved output values could disagree with the typings present in
a provider SDK. Exacerbating this problem was the fact that unknown
values were dropped entirely, causing `undefined` values to appear in
unexpected places.
By doing this in the engine and allowing unknown values to be
represented in a first-class manner in the SDK, we can attack both of
these issues.
Although this behavior is not _strictly_ consistent with respect to the
resource model--in an update, a resource's output properties will come
from its provider and may differ from its input properties--this
behavior was present in the product for a fairly long time without
significant issues. In the future, we may be able to improve the
accuracy of resource outputs during a preview by allowing the provider
to dry-run CRUD operations and return partially-known values where
possible.
These changes also introduce new APIs in the Node and Python SDKs
that work with unknown values in a first-class fashion:
- A new parameter to the `apply` function that indicates that the
callback should be run even if the result of the apply contains
unknown values
- `containsUnknowns` and `isUnknown`, which return true if a value
either contains nested unknown values or is exactly an unknown value
- The `Unknown` type, which represents unknown values
The primary use case for these APIs is to allow nested, properties with
known values to be accessed via the lifted property accessor even when
the containing property is not fully know. A common example of this
pattern is the `metadata.name` property of a Kubernetes `Namespace`
object: while other properties of the `metadata` bag may be unknown,
`name` is often known. These APIs allow `ns.metadata.name` to return a
known value in this case.
In order to avoid exposing downlevel SDKs to unknown values--a change
which could break user code by exposing it to unexpected values--a
language SDK must indicate whether or not it supports first-class
unknown values as part of each `RegisterResourceRequest`.
These changes also allow us to avoid breaking user code with the new
behavior introduced by the prior commit.
Fixes #3190.
2019-11-11 20:09:34 +00:00
|
|
|
// If the value the engine handed back is or contains an unknown value, the resolver will mark its value as
|
|
|
|
// unknown automatically, so we just pass true for isKnown here. Note that unknown values will only be
|
|
|
|
// present during previews (i.e. isDryRun() will be true).
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
resolve(value, /*isKnown*/ true, isSecret, deps[k]);
|
2023-04-28 22:27:10 +00:00
|
|
|
} catch (resolveError) {
|
2017-09-27 19:34:44 +00:00
|
|
|
throw new Error(
|
2023-04-28 22:27:10 +00:00
|
|
|
`Unable to set property '${k}' on resource '${name}' [${t}]; error: ${errorString(resolveError)}`,
|
|
|
|
);
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-24 18:22:08 +00:00
|
|
|
// `allProps` may not have contained a value for every resolver: for example, optional outputs may not be present.
|
|
|
|
// We will resolve all of these values as `undefined`, and will mark the value as known if we are not running a
|
2024-04-22 11:12:45 +00:00
|
|
|
// preview. For updates when the update of the resource was either skipped or failed we'll mark them as `unknown`.
|
2018-01-25 21:34:21 +00:00
|
|
|
for (const k of Object.keys(resolvers)) {
|
2018-02-05 22:44:23 +00:00
|
|
|
if (!allProps.hasOwnProperty(k)) {
|
2018-03-18 07:15:22 +00:00
|
|
|
const resolve = resolvers[k];
|
2024-04-22 11:12:45 +00:00
|
|
|
if (!isDryRun && keepUnknowns) {
|
|
|
|
resolve(unknown, true, false);
|
|
|
|
} else {
|
|
|
|
resolve(undefined, !isDryRun() && !keepUnknowns, false);
|
|
|
|
}
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-23 21:47:40 +00:00
|
|
|
* Unknown values are encoded as a distinguished string value.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
2018-05-23 21:47:40 +00:00
|
|
|
export const unknownValue = "04da6b54-80e4-46f7-96ec-b56ff0331ba9";
|
2024-07-15 11:27:47 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* {@link specialSigKey} is sometimes used to encode type identity inside of a
|
|
|
|
* map.
|
|
|
|
*
|
|
|
|
* @see sdk/go/common/resource/properties.go.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
|
|
|
export const specialSigKey = "4dabf18193072939515e22adb298388d";
|
2024-07-15 11:27:47 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* {@link specialAssetSig} is a randomly assigned hash used to identify assets
|
|
|
|
* in maps.
|
|
|
|
*
|
|
|
|
* @see sdk/go/common/resource/asset.go.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
|
|
|
export const specialAssetSig = "c44067f5952c0a294b673a41bacd8c17";
|
2024-07-15 11:27:47 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* {@link specialArchiveSig} is a randomly assigned hash used to identify
|
|
|
|
* archives in maps.
|
|
|
|
*
|
|
|
|
* @see sdk/go/common/resource/asset.go.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
|
|
|
export const specialArchiveSig = "0def7320c3a5731c473e5ecbe6d01bc7";
|
2024-07-15 11:27:47 +00:00
|
|
|
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 17:46:30 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* {@link specialSecretSig} is a randomly assigned hash used to identify secrets
|
|
|
|
* in maps.
|
|
|
|
*
|
|
|
|
* @see sdk/go/common/resource/properties.go.
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 17:46:30 +00:00
|
|
|
*/
|
|
|
|
export const specialSecretSig = "1b47061264138c4ac30d75fd1eb44270";
|
2024-07-15 11:27:47 +00:00
|
|
|
|
2020-10-27 17:12:12 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* {@link specialResourceSig} is a randomly assigned hash used to identify
|
|
|
|
* resources in maps.
|
|
|
|
*
|
|
|
|
* @see sdk/go/common/resource/properties.go.
|
2020-10-27 17:12:12 +00:00
|
|
|
*/
|
|
|
|
export const specialResourceSig = "5cf8f73096256a8f31e491e813e4eb8e";
|
2024-07-15 11:27:47 +00:00
|
|
|
|
2021-09-16 01:25:26 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* {@link specialOutputValueSig} is a randomly assigned hash used to identify
|
|
|
|
* outputs in maps.
|
|
|
|
*
|
|
|
|
* @see sdk/go/common/resource/properties.go.
|
2021-09-16 01:25:26 +00:00
|
|
|
*/
|
|
|
|
export const specialOutputValueSig = "d0e6a833031e9bbcd3f4e8bde6ca49a4";
|
2017-09-27 19:34:44 +00:00
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Serializes properties deeply. This understands how to wait on any unresolved
|
|
|
|
* promises, as appropriate, in addition to translating certain "special" values
|
|
|
|
* so that they are ready to go on the wire.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
2021-09-16 01:25:26 +00:00
|
|
|
export async function serializeProperty(
|
2023-04-28 22:27:10 +00:00
|
|
|
ctx: string,
|
|
|
|
prop: Input<any>,
|
|
|
|
dependentResources: Set<Resource>,
|
|
|
|
opts?: SerializationOptions,
|
|
|
|
): Promise<any> {
|
2019-10-15 05:08:06 +00:00
|
|
|
// IMPORTANT:
|
2020-10-27 17:12:12 +00:00
|
|
|
// IMPORTANT: Keep this in sync with serializePropertiesSync in invoke.ts
|
2019-10-15 05:08:06 +00:00
|
|
|
// IMPORTANT:
|
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
if (
|
|
|
|
prop === undefined ||
|
2018-05-23 21:47:40 +00:00
|
|
|
prop === null ||
|
|
|
|
typeof prop === "boolean" ||
|
|
|
|
typeof prop === "number" ||
|
2023-04-28 22:27:10 +00:00
|
|
|
typeof prop === "string"
|
|
|
|
) {
|
2017-09-27 19:34:44 +00:00
|
|
|
if (excessiveDebugOutput) {
|
2017-10-08 19:10:46 +00:00
|
|
|
log.debug(`Serialize property [${ctx}]: primitive=${prop}`);
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
return prop;
|
|
|
|
}
|
2018-02-05 22:44:23 +00:00
|
|
|
|
2018-12-10 22:31:54 +00:00
|
|
|
if (asset.Asset.isInstance(prop) || asset.Archive.isInstance(prop)) {
|
2017-09-27 19:34:44 +00:00
|
|
|
// Serializing an asset or archive requires the use of a magical signature key, since otherwise it would look
|
|
|
|
// like any old weakly typed object/map when received by the other side of the RPC boundary.
|
2017-10-18 22:03:56 +00:00
|
|
|
const obj: any = {
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 21:03:37 +00:00
|
|
|
[specialSigKey]: asset.Asset.isInstance(prop) ? specialAssetSig : specialArchiveSig,
|
2017-09-27 19:34:44 +00:00
|
|
|
};
|
2018-01-25 21:34:21 +00:00
|
|
|
|
2021-09-16 01:25:26 +00:00
|
|
|
return await serializeAllKeys(prop, obj, { keepOutputValues: false });
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2018-12-10 22:31:54 +00:00
|
|
|
|
|
|
|
if (prop instanceof Promise) {
|
2017-09-27 19:34:44 +00:00
|
|
|
// For a promise input, await the property and then serialize the result.
|
|
|
|
if (excessiveDebugOutput) {
|
2018-02-05 22:44:23 +00:00
|
|
|
log.debug(`Serialize property [${ctx}]: Promise<T>`);
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2018-12-10 22:31:54 +00:00
|
|
|
|
2018-02-05 22:44:23 +00:00
|
|
|
const subctx = `Promise<${ctx}>`;
|
2023-04-28 22:27:10 +00:00
|
|
|
return serializeProperty(
|
|
|
|
subctx,
|
|
|
|
await debuggablePromise(prop, `serializeProperty.await(${subctx})`),
|
|
|
|
dependentResources,
|
|
|
|
opts,
|
|
|
|
);
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2018-12-10 22:31:54 +00:00
|
|
|
|
|
|
|
if (Output.isInstance(prop)) {
|
2018-02-05 22:44:23 +00:00
|
|
|
if (excessiveDebugOutput) {
|
2018-05-23 21:47:40 +00:00
|
|
|
log.debug(`Serialize property [${ctx}]: Output<T>`);
|
2018-02-05 22:44:23 +00:00
|
|
|
}
|
|
|
|
|
2019-12-18 03:04:09 +00:00
|
|
|
// handle serializing both old-style outputs (with sync resources) and new-style outputs
|
|
|
|
// (with async resources).
|
|
|
|
|
|
|
|
const propResources = await getAllResources(prop);
|
|
|
|
for (const resource of propResources) {
|
2019-02-22 04:18:29 +00:00
|
|
|
dependentResources.add(resource);
|
|
|
|
}
|
2018-05-23 21:47:40 +00:00
|
|
|
|
|
|
|
// When serializing an Output, we will either serialize it as its resolved value or the "unknown value"
|
|
|
|
// sentinel. We will do the former for all outputs created directly by user code (such outputs always
|
|
|
|
// resolve isKnown to true) and for any resource outputs that were resolved with known values.
|
|
|
|
const isKnown = await prop.isKnown;
|
2019-05-13 22:12:20 +00:00
|
|
|
|
|
|
|
// You might think that doing an explict `=== true` here is not needed, but it is for a subtle reason. If the
|
|
|
|
// output we are serializing is a proxy itself, and it comes from a version of the SDK that did not have the
|
|
|
|
// `isSecret` member on `OutputImpl` then the call to `prop.isSecret` here will return an Output itself,
|
|
|
|
// which will wrap undefined, if it were to be resolved (since `Output` has no member named .isSecret).
|
|
|
|
// so we must compare to the literal true instead of just doing await prop.isSecret.
|
2023-04-28 22:27:10 +00:00
|
|
|
const isSecret = (await prop.isSecret) === true;
|
2021-09-16 01:25:26 +00:00
|
|
|
const promiseDeps = new Set<Resource>();
|
|
|
|
const value = await serializeProperty(`${ctx}.id`, prop.promise(), promiseDeps, {
|
|
|
|
keepOutputValues: false,
|
|
|
|
});
|
|
|
|
for (const resource of promiseDeps) {
|
|
|
|
propResources.add(resource);
|
|
|
|
dependentResources.add(resource);
|
|
|
|
}
|
|
|
|
|
2023-12-19 14:35:23 +00:00
|
|
|
if (opts?.keepOutputValues && getStore().supportsOutputValues) {
|
2021-09-16 01:25:26 +00:00
|
|
|
const urnDeps = new Set<Resource>();
|
|
|
|
for (const resource of propResources) {
|
|
|
|
await serializeProperty(`${ctx} dependency`, resource.urn, urnDeps, {
|
|
|
|
keepOutputValues: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
for (const resource of urnDeps) {
|
|
|
|
propResources.add(resource);
|
|
|
|
dependentResources.add(resource);
|
|
|
|
}
|
|
|
|
|
2022-05-30 18:37:15 +00:00
|
|
|
const dependencies = await getAllTransitivelyReferencedResourceURNs(propResources, new Set<Resource>());
|
2021-09-16 01:25:26 +00:00
|
|
|
|
|
|
|
const obj: any = {
|
|
|
|
[specialSigKey]: specialOutputValueSig,
|
|
|
|
};
|
|
|
|
if (isKnown) {
|
|
|
|
// coerce 'undefined' to 'null' as required by the protobuf system.
|
|
|
|
obj["value"] = value === undefined ? null : value;
|
|
|
|
}
|
|
|
|
if (isSecret) {
|
|
|
|
obj["secret"] = isSecret;
|
|
|
|
}
|
|
|
|
if (dependencies.size > 0) {
|
|
|
|
obj["dependencies"] = Array.from(dependencies);
|
|
|
|
}
|
|
|
|
return obj;
|
|
|
|
}
|
2019-08-12 23:00:20 +00:00
|
|
|
|
2019-04-12 23:59:18 +00:00
|
|
|
if (!isKnown) {
|
|
|
|
return unknownValue;
|
|
|
|
}
|
2023-12-19 14:35:23 +00:00
|
|
|
if (isSecret && getStore().supportsSecrets) {
|
2019-04-12 23:59:18 +00:00
|
|
|
return {
|
|
|
|
[specialSigKey]: specialSecretSig,
|
2019-08-12 23:00:20 +00:00
|
|
|
// coerce 'undefined' to 'null' as required by the protobuf system.
|
|
|
|
value: value === undefined ? null : value,
|
2019-04-12 23:59:18 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
return value;
|
2018-01-25 21:34:21 +00:00
|
|
|
}
|
|
|
|
|
Propagate inputs to outputs during preview. (#3327)
These changes restore a more-correct version of the behavior that was
disabled with #3014. The original implementation of this behavior was
done in the SDKs, which do not have access to the complete inputs for a
resource (in particular, default values filled in by the provider during
`Check` are not exposed to the SDK). This lack of information meant that
the resolved output values could disagree with the typings present in
a provider SDK. Exacerbating this problem was the fact that unknown
values were dropped entirely, causing `undefined` values to appear in
unexpected places.
By doing this in the engine and allowing unknown values to be
represented in a first-class manner in the SDK, we can attack both of
these issues.
Although this behavior is not _strictly_ consistent with respect to the
resource model--in an update, a resource's output properties will come
from its provider and may differ from its input properties--this
behavior was present in the product for a fairly long time without
significant issues. In the future, we may be able to improve the
accuracy of resource outputs during a preview by allowing the provider
to dry-run CRUD operations and return partially-known values where
possible.
These changes also introduce new APIs in the Node and Python SDKs
that work with unknown values in a first-class fashion:
- A new parameter to the `apply` function that indicates that the
callback should be run even if the result of the apply contains
unknown values
- `containsUnknowns` and `isUnknown`, which return true if a value
either contains nested unknown values or is exactly an unknown value
- The `Unknown` type, which represents unknown values
The primary use case for these APIs is to allow nested, properties with
known values to be accessed via the lifted property accessor even when
the containing property is not fully know. A common example of this
pattern is the `metadata.name` property of a Kubernetes `Namespace`
object: while other properties of the `metadata` bag may be unknown,
`name` is often known. These APIs allow `ns.metadata.name` to return a
known value in this case.
In order to avoid exposing downlevel SDKs to unknown values--a change
which could break user code by exposing it to unexpected values--a
language SDK must indicate whether or not it supports first-class
unknown values as part of each `RegisterResourceRequest`.
These changes also allow us to avoid breaking user code with the new
behavior introduced by the prior commit.
Fixes #3190.
2019-11-11 20:09:34 +00:00
|
|
|
if (isUnknown(prop)) {
|
|
|
|
return unknownValue;
|
|
|
|
}
|
|
|
|
|
2018-12-10 22:31:54 +00:00
|
|
|
if (CustomResource.isInstance(prop)) {
|
|
|
|
if (excessiveDebugOutput) {
|
2020-10-27 17:12:12 +00:00
|
|
|
log.debug(`Serialize property [${ctx}]: custom resource urn`);
|
2018-12-10 22:31:54 +00:00
|
|
|
}
|
|
|
|
|
2019-02-22 04:18:29 +00:00
|
|
|
dependentResources.add(prop);
|
2021-09-16 01:25:26 +00:00
|
|
|
const id = await serializeProperty(`${ctx}.id`, prop.id, dependentResources, {
|
|
|
|
keepOutputValues: false,
|
|
|
|
});
|
2020-10-27 17:12:12 +00:00
|
|
|
|
2023-12-19 14:35:23 +00:00
|
|
|
if (getStore().supportsResourceReferences) {
|
2020-10-27 17:12:12 +00:00
|
|
|
// If we are keeping resources, emit a stronly typed wrapper over the URN
|
2021-09-16 01:25:26 +00:00
|
|
|
const urn = await serializeProperty(`${ctx}.urn`, prop.urn, dependentResources, {
|
|
|
|
keepOutputValues: false,
|
|
|
|
});
|
2020-10-27 17:12:12 +00:00
|
|
|
return {
|
|
|
|
[specialSigKey]: specialResourceSig,
|
|
|
|
urn: urn,
|
|
|
|
id: id,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
// Else, return the id for backward compatibility.
|
|
|
|
return id;
|
2018-12-10 22:31:54 +00:00
|
|
|
}
|
|
|
|
|
2018-12-11 21:42:49 +00:00
|
|
|
if (ComponentResource.isInstance(prop)) {
|
|
|
|
// Component resources often can contain cycles in them. For example, an awsinfra
|
|
|
|
// SecurityGroupRule can point a the awsinfra SecurityGroup, which in turn can point back to
|
|
|
|
// its rules through its `egressRules` and `ingressRules` properties. If serializing out
|
|
|
|
// the `SecurityGroup` resource ends up trying to serialize out those properties, a deadlock
|
|
|
|
// will happen, due to waiting on the child, which is waiting on the parent.
|
|
|
|
//
|
|
|
|
// Practically, there is no need to actually serialize out a component. It doesn't represent
|
|
|
|
// a real resource, nor does it have normal properties that need to be tracked for differences
|
|
|
|
// (since changes to its properties don't represent changes to resources in the real world).
|
|
|
|
//
|
|
|
|
// So, to avoid these problems, while allowing a flexible and simple programming model, we
|
|
|
|
// just serialize out the component as its urn. This allows the component to be identified
|
|
|
|
// and tracked in a reasonable manner, while not causing us to compute or embed information
|
|
|
|
// about it that is not needed, and which can lead to deadlocks.
|
|
|
|
if (excessiveDebugOutput) {
|
2020-10-27 17:12:12 +00:00
|
|
|
log.debug(`Serialize property [${ctx}]: component resource urn`);
|
2018-12-11 21:42:49 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 14:35:23 +00:00
|
|
|
if (getStore().supportsResourceReferences) {
|
2020-10-27 17:12:12 +00:00
|
|
|
// If we are keeping resources, emit a strongly typed wrapper over the URN
|
2021-09-16 01:25:26 +00:00
|
|
|
const urn = await serializeProperty(`${ctx}.urn`, prop.urn, dependentResources, {
|
|
|
|
keepOutputValues: false,
|
|
|
|
});
|
2020-10-27 17:12:12 +00:00
|
|
|
return {
|
|
|
|
[specialSigKey]: specialResourceSig,
|
|
|
|
urn: urn,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
// Else, return the urn for backward compatibility.
|
2021-09-16 01:25:26 +00:00
|
|
|
return serializeProperty(`${ctx}.urn`, prop.urn, dependentResources, {
|
|
|
|
keepOutputValues: false,
|
|
|
|
});
|
2018-12-11 21:42:49 +00:00
|
|
|
}
|
|
|
|
|
2018-12-10 22:31:54 +00:00
|
|
|
if (prop instanceof Array) {
|
|
|
|
const result: any[] = [];
|
|
|
|
for (let i = 0; i < prop.length; i++) {
|
|
|
|
if (excessiveDebugOutput) {
|
|
|
|
log.debug(`Serialize property [${ctx}]: array[${i}] element`);
|
|
|
|
}
|
2018-12-16 05:04:53 +00:00
|
|
|
// When serializing arrays, we serialize any undefined values as `null`. This matches JSON semantics.
|
2021-09-16 01:25:26 +00:00
|
|
|
const elem = await serializeProperty(`${ctx}[${i}]`, prop[i], dependentResources, opts);
|
2018-12-10 22:31:54 +00:00
|
|
|
result.push(elem === undefined ? null : elem);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-09-16 01:25:26 +00:00
|
|
|
return await serializeAllKeys(prop, {}, opts);
|
2018-12-10 22:31:54 +00:00
|
|
|
|
2021-09-16 01:25:26 +00:00
|
|
|
async function serializeAllKeys(innerProp: any, obj: any, innerOpts?: SerializationOptions) {
|
2018-01-25 21:34:21 +00:00
|
|
|
for (const k of Object.keys(innerProp)) {
|
2017-09-27 19:34:44 +00:00
|
|
|
if (excessiveDebugOutput) {
|
2017-10-08 19:10:46 +00:00
|
|
|
log.debug(`Serialize property [${ctx}]: object.${k}`);
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2018-05-23 21:47:40 +00:00
|
|
|
|
|
|
|
// When serializing an object, we omit any keys with undefined values. This matches JSON semantics.
|
2021-09-16 01:25:26 +00:00
|
|
|
const v = await serializeProperty(`${ctx}.${k}`, innerProp[k], dependentResources, innerOpts);
|
2018-05-23 21:47:40 +00:00
|
|
|
if (v !== undefined) {
|
|
|
|
obj[k] = v;
|
|
|
|
}
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2018-01-25 21:34:21 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-02 23:31:14 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns true if the given object is a wrapped secret value (i.e. it's an
|
|
|
|
* object with the special key set).
|
2019-07-02 23:31:14 +00:00
|
|
|
*/
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
export function isRpcSecret(obj: any): boolean {
|
2019-07-02 23:31:14 +00:00
|
|
|
return obj && obj[specialSigKey] === specialSecretSig;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns the underlying value for a secret, or the value itself if it was not
|
|
|
|
* a secret.
|
2019-07-02 23:31:14 +00:00
|
|
|
*/
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
export function unwrapRpcSecret(obj: any): any {
|
2019-07-02 23:31:14 +00:00
|
|
|
if (!isRpcSecret(obj)) {
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
return obj.value;
|
|
|
|
}
|
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Unpacks some special types, reversing the process undertaken by
|
|
|
|
* {@link serializeProperty}.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
2024-04-22 11:12:45 +00:00
|
|
|
export function deserializeProperty(prop: any, keepUnknowns?: boolean): any {
|
2018-05-23 21:47:40 +00:00
|
|
|
if (prop === undefined) {
|
|
|
|
throw new Error("unexpected undefined property value during deserialization");
|
2023-04-28 22:27:10 +00:00
|
|
|
} else if (prop === unknownValue) {
|
2024-04-22 11:12:45 +00:00
|
|
|
return isDryRun() || keepUnknowns ? unknown : undefined;
|
2023-04-28 22:27:10 +00:00
|
|
|
} else if (prop === null || typeof prop === "boolean" || typeof prop === "number" || typeof prop === "string") {
|
2017-09-27 19:34:44 +00:00
|
|
|
return prop;
|
2023-04-28 22:27:10 +00:00
|
|
|
} else if (prop instanceof Array) {
|
2019-10-25 23:59:50 +00:00
|
|
|
// We can just deserialize all the elements of the underlying array and return it.
|
2019-07-02 23:31:14 +00:00
|
|
|
// However, we want to push secretness up to the top level (since we can't set sub-properties to secret)
|
|
|
|
// values since they are not typed as Output<T>.
|
|
|
|
let hadSecret = false;
|
2017-10-18 22:03:56 +00:00
|
|
|
const elems: any[] = [];
|
|
|
|
for (const e of prop) {
|
2024-04-22 11:12:45 +00:00
|
|
|
prop = deserializeProperty(e, keepUnknowns);
|
2019-07-02 23:31:14 +00:00
|
|
|
hadSecret = hadSecret || isRpcSecret(prop);
|
|
|
|
elems.push(unwrapRpcSecret(prop));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hadSecret) {
|
|
|
|
return {
|
|
|
|
[specialSigKey]: specialSecretSig,
|
|
|
|
value: elems,
|
|
|
|
};
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2019-07-02 23:31:14 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
return elems;
|
2023-04-28 22:27:10 +00:00
|
|
|
} else {
|
2017-09-27 19:34:44 +00:00
|
|
|
// We need to recognize assets and archives specially, so we can produce the right runtime objects.
|
2017-10-18 22:03:56 +00:00
|
|
|
const sig: any = prop[specialSigKey];
|
2017-09-27 19:34:44 +00:00
|
|
|
if (sig) {
|
|
|
|
switch (sig) {
|
2023-04-28 22:27:10 +00:00
|
|
|
case specialAssetSig:
|
|
|
|
if (prop["path"]) {
|
|
|
|
return new asset.FileAsset(<string>prop["path"]);
|
|
|
|
} else if (prop["text"]) {
|
|
|
|
return new asset.StringAsset(<string>prop["text"]);
|
|
|
|
} else if (prop["uri"]) {
|
|
|
|
return new asset.RemoteAsset(<string>prop["uri"]);
|
|
|
|
} else {
|
|
|
|
throw new Error("Invalid asset encountered when unmarshaling resource property");
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2023-04-28 22:27:10 +00:00
|
|
|
case specialArchiveSig:
|
|
|
|
if (prop["assets"]) {
|
|
|
|
const assets: asset.AssetMap = {};
|
|
|
|
for (const name of Object.keys(prop["assets"])) {
|
2024-04-22 11:12:45 +00:00
|
|
|
const a = deserializeProperty(prop["assets"][name], keepUnknowns);
|
2023-04-28 22:27:10 +00:00
|
|
|
if (!asset.Asset.isInstance(a) && !asset.Archive.isInstance(a)) {
|
|
|
|
throw new Error(
|
|
|
|
"Expected an AssetArchive's assets to be unmarshaled Asset or Archive objects",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
assets[name] = a;
|
|
|
|
}
|
|
|
|
return new asset.AssetArchive(assets);
|
|
|
|
} else if (prop["path"]) {
|
|
|
|
return new asset.FileArchive(<string>prop["path"]);
|
|
|
|
} else if (prop["uri"]) {
|
|
|
|
return new asset.RemoteArchive(<string>prop["uri"]);
|
|
|
|
} else {
|
|
|
|
throw new Error("Invalid archive encountered when unmarshaling resource property");
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
2023-04-28 22:27:10 +00:00
|
|
|
case specialSecretSig:
|
|
|
|
return {
|
|
|
|
[specialSigKey]: specialSecretSig,
|
2024-04-22 11:12:45 +00:00
|
|
|
value: deserializeProperty(prop["value"], keepUnknowns),
|
2023-04-28 22:27:10 +00:00
|
|
|
};
|
|
|
|
case specialResourceSig:
|
|
|
|
// Deserialize the resource into a live Resource reference
|
|
|
|
const urn = prop["urn"];
|
|
|
|
const version = prop["packageVersion"];
|
|
|
|
|
|
|
|
const urnParts = urn.split("::");
|
|
|
|
const qualifiedType = urnParts[2];
|
|
|
|
const urnName = urnParts[3];
|
|
|
|
|
|
|
|
const type = qualifiedType.split("$").pop()!;
|
|
|
|
const typeParts = type.split(":");
|
|
|
|
const pkgName = typeParts[0];
|
|
|
|
const modName = typeParts.length > 1 ? typeParts[1] : "";
|
|
|
|
const typName = typeParts.length > 2 ? typeParts[2] : "";
|
|
|
|
const isProvider = pkgName === "pulumi" && modName === "providers";
|
|
|
|
|
|
|
|
if (isProvider) {
|
|
|
|
const resourcePackage = getResourcePackage(typName, version);
|
|
|
|
if (resourcePackage) {
|
|
|
|
return resourcePackage.constructProvider(urnName, type, urn);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const resourceModule = getResourceModule(pkgName, modName, version);
|
|
|
|
if (resourceModule) {
|
|
|
|
return resourceModule.construct(urnName, type, urn);
|
|
|
|
}
|
2020-10-27 17:12:12 +00:00
|
|
|
}
|
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
// If we've made it here, deserialize the reference as either a URN or an ID (if present).
|
|
|
|
if (prop["id"]) {
|
|
|
|
const id = prop["id"];
|
2024-04-22 11:12:45 +00:00
|
|
|
return deserializeProperty(id === "" ? unknownValue : id, keepUnknowns);
|
2023-04-28 22:27:10 +00:00
|
|
|
}
|
|
|
|
return urn;
|
2020-12-01 18:58:15 +00:00
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
case specialOutputValueSig:
|
|
|
|
let value = prop["value"];
|
|
|
|
const isKnown = value !== undefined;
|
|
|
|
if (isKnown) {
|
2024-04-22 11:12:45 +00:00
|
|
|
value = deserializeProperty(value, keepUnknowns);
|
2023-04-28 22:27:10 +00:00
|
|
|
}
|
Enable output values by default (#8014)
* Enable output values by default
Enable output values by default in the resource monitor and change the polarity of the envvar from `PULUMI_ENABLE_OUTPUT_VALUES` to `PULUMI_DISABLE_OUTPUT_VALUES`.
* Marshal unknown as unknown string when `!KeepOutputValues`
Marshal all unknown output values as `resource.MakeComputed(resource.NewStringProperty(""))` when not keeping output values, which is consistent with what the SDKs do.
Otherwise, when `v.OutputValue().Element` is nil, `resource.MakeComputed(v.OutputValue().Element)` will be marshaled as a null value rather than as an unknown sentinel.
* Add MarshalOptions.DontSkipOutputs and use where needed
Before we expanded the meaning of `resource.Output`, `MarshalProperties` always skipped output values:
```go
if v.IsOutput() {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
As part of expanding the meaning of `resource.Output`, I'd adjusted `MarshalProperties` to only skip output values when the value was unknown and when not keeping output values:
```go
if v.IsOutput() && !v.OutputValue().Known && !opts.KeepOutputValues {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
However, this doesn't work the way we want when marshaling properties that include unknown output values to a provider that does not accept outputs. In that case, `opts.KeepOutputValues` will be `false` because we want the marshaler to fall back to returning non-output-values (e.g. unknown sentinel value for unknown output values), but instead of getting the intended fallback values, the unknown output values are skipped (not what we want).
I suspect we may be able to delete the output value skipping in `MarshalProperties` altogether (it's odd that it is skipping `resource.Output` but not `resource.Computed`), but to avoid any unintended side effects of doing that, instead, this commit introduces a new `MarshalOptions.DontSkipOutputs` option that can be set to `true` to opt-in to not skipping output values when marshaling. The check in `MarshalProperties` now looks like this:
```go
if !opts.DontSkipOutputs && v.IsOutput() && !v.OutputValue().Known {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
`opts.DontSkipOutputs` is set to `true` when marshaling properties for calls to a provider's `Construct` and `Call`.
* [sdk/nodejs] Deserialize output values
This commit adds support for deserializing output values, which is needed in some cases when serialized inputs are returned as outputs in the SDK.
* [sdk/python] Deserialize output values
This commit adds support for deserializing output values, which is needed in some cases when serialized inputs are returned as outputs in the SDK.
2021-09-24 15:57:04 +00:00
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
const isSecret = prop["secret"] === true;
|
Enable output values by default (#8014)
* Enable output values by default
Enable output values by default in the resource monitor and change the polarity of the envvar from `PULUMI_ENABLE_OUTPUT_VALUES` to `PULUMI_DISABLE_OUTPUT_VALUES`.
* Marshal unknown as unknown string when `!KeepOutputValues`
Marshal all unknown output values as `resource.MakeComputed(resource.NewStringProperty(""))` when not keeping output values, which is consistent with what the SDKs do.
Otherwise, when `v.OutputValue().Element` is nil, `resource.MakeComputed(v.OutputValue().Element)` will be marshaled as a null value rather than as an unknown sentinel.
* Add MarshalOptions.DontSkipOutputs and use where needed
Before we expanded the meaning of `resource.Output`, `MarshalProperties` always skipped output values:
```go
if v.IsOutput() {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
As part of expanding the meaning of `resource.Output`, I'd adjusted `MarshalProperties` to only skip output values when the value was unknown and when not keeping output values:
```go
if v.IsOutput() && !v.OutputValue().Known && !opts.KeepOutputValues {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
However, this doesn't work the way we want when marshaling properties that include unknown output values to a provider that does not accept outputs. In that case, `opts.KeepOutputValues` will be `false` because we want the marshaler to fall back to returning non-output-values (e.g. unknown sentinel value for unknown output values), but instead of getting the intended fallback values, the unknown output values are skipped (not what we want).
I suspect we may be able to delete the output value skipping in `MarshalProperties` altogether (it's odd that it is skipping `resource.Output` but not `resource.Computed`), but to avoid any unintended side effects of doing that, instead, this commit introduces a new `MarshalOptions.DontSkipOutputs` option that can be set to `true` to opt-in to not skipping output values when marshaling. The check in `MarshalProperties` now looks like this:
```go
if !opts.DontSkipOutputs && v.IsOutput() && !v.OutputValue().Known {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
`opts.DontSkipOutputs` is set to `true` when marshaling properties for calls to a provider's `Construct` and `Call`.
* [sdk/nodejs] Deserialize output values
This commit adds support for deserializing output values, which is needed in some cases when serialized inputs are returned as outputs in the SDK.
* [sdk/python] Deserialize output values
This commit adds support for deserializing output values, which is needed in some cases when serialized inputs are returned as outputs in the SDK.
2021-09-24 15:57:04 +00:00
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
const dependencies = prop["dependencies"];
|
|
|
|
const resources = Array.isArray(dependencies)
|
|
|
|
? dependencies.map((d) => new DependencyResource(d))
|
|
|
|
: [];
|
Enable output values by default (#8014)
* Enable output values by default
Enable output values by default in the resource monitor and change the polarity of the envvar from `PULUMI_ENABLE_OUTPUT_VALUES` to `PULUMI_DISABLE_OUTPUT_VALUES`.
* Marshal unknown as unknown string when `!KeepOutputValues`
Marshal all unknown output values as `resource.MakeComputed(resource.NewStringProperty(""))` when not keeping output values, which is consistent with what the SDKs do.
Otherwise, when `v.OutputValue().Element` is nil, `resource.MakeComputed(v.OutputValue().Element)` will be marshaled as a null value rather than as an unknown sentinel.
* Add MarshalOptions.DontSkipOutputs and use where needed
Before we expanded the meaning of `resource.Output`, `MarshalProperties` always skipped output values:
```go
if v.IsOutput() {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
As part of expanding the meaning of `resource.Output`, I'd adjusted `MarshalProperties` to only skip output values when the value was unknown and when not keeping output values:
```go
if v.IsOutput() && !v.OutputValue().Known && !opts.KeepOutputValues {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
However, this doesn't work the way we want when marshaling properties that include unknown output values to a provider that does not accept outputs. In that case, `opts.KeepOutputValues` will be `false` because we want the marshaler to fall back to returning non-output-values (e.g. unknown sentinel value for unknown output values), but instead of getting the intended fallback values, the unknown output values are skipped (not what we want).
I suspect we may be able to delete the output value skipping in `MarshalProperties` altogether (it's odd that it is skipping `resource.Output` but not `resource.Computed`), but to avoid any unintended side effects of doing that, instead, this commit introduces a new `MarshalOptions.DontSkipOutputs` option that can be set to `true` to opt-in to not skipping output values when marshaling. The check in `MarshalProperties` now looks like this:
```go
if !opts.DontSkipOutputs && v.IsOutput() && !v.OutputValue().Known {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
`opts.DontSkipOutputs` is set to `true` when marshaling properties for calls to a provider's `Construct` and `Call`.
* [sdk/nodejs] Deserialize output values
This commit adds support for deserializing output values, which is needed in some cases when serialized inputs are returned as outputs in the SDK.
* [sdk/python] Deserialize output values
This commit adds support for deserializing output values, which is needed in some cases when serialized inputs are returned as outputs in the SDK.
2021-09-24 15:57:04 +00:00
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
return new Output(
|
|
|
|
resources,
|
|
|
|
Promise.resolve(value),
|
|
|
|
Promise.resolve(isKnown),
|
|
|
|
Promise.resolve(isSecret),
|
|
|
|
Promise.resolve([]),
|
|
|
|
);
|
Enable output values by default (#8014)
* Enable output values by default
Enable output values by default in the resource monitor and change the polarity of the envvar from `PULUMI_ENABLE_OUTPUT_VALUES` to `PULUMI_DISABLE_OUTPUT_VALUES`.
* Marshal unknown as unknown string when `!KeepOutputValues`
Marshal all unknown output values as `resource.MakeComputed(resource.NewStringProperty(""))` when not keeping output values, which is consistent with what the SDKs do.
Otherwise, when `v.OutputValue().Element` is nil, `resource.MakeComputed(v.OutputValue().Element)` will be marshaled as a null value rather than as an unknown sentinel.
* Add MarshalOptions.DontSkipOutputs and use where needed
Before we expanded the meaning of `resource.Output`, `MarshalProperties` always skipped output values:
```go
if v.IsOutput() {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
As part of expanding the meaning of `resource.Output`, I'd adjusted `MarshalProperties` to only skip output values when the value was unknown and when not keeping output values:
```go
if v.IsOutput() && !v.OutputValue().Known && !opts.KeepOutputValues {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
However, this doesn't work the way we want when marshaling properties that include unknown output values to a provider that does not accept outputs. In that case, `opts.KeepOutputValues` will be `false` because we want the marshaler to fall back to returning non-output-values (e.g. unknown sentinel value for unknown output values), but instead of getting the intended fallback values, the unknown output values are skipped (not what we want).
I suspect we may be able to delete the output value skipping in `MarshalProperties` altogether (it's odd that it is skipping `resource.Output` but not `resource.Computed`), but to avoid any unintended side effects of doing that, instead, this commit introduces a new `MarshalOptions.DontSkipOutputs` option that can be set to `true` to opt-in to not skipping output values when marshaling. The check in `MarshalProperties` now looks like this:
```go
if !opts.DontSkipOutputs && v.IsOutput() && !v.OutputValue().Known {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
}
```
`opts.DontSkipOutputs` is set to `true` when marshaling properties for calls to a provider's `Construct` and `Call`.
* [sdk/nodejs] Deserialize output values
This commit adds support for deserializing output values, which is needed in some cases when serialized inputs are returned as outputs in the SDK.
* [sdk/python] Deserialize output values
This commit adds support for deserializing output values, which is needed in some cases when serialized inputs are returned as outputs in the SDK.
2021-09-24 15:57:04 +00:00
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
default:
|
|
|
|
throw new Error(`Unrecognized signature '${sig}' when unmarshaling resource property`);
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there isn't a signature, it's not a special type, and we can simply return the object as a map.
|
2019-05-10 05:13:41 +00:00
|
|
|
// However, we want to push secretness up to the top level (since we can't set sub-properties to secret)
|
|
|
|
// values since they are not typed as Output<T>.
|
2017-10-18 22:03:56 +00:00
|
|
|
const obj: any = {};
|
2019-05-10 05:13:41 +00:00
|
|
|
let hadSecrets = false;
|
|
|
|
|
2017-10-18 22:03:56 +00:00
|
|
|
for (const k of Object.keys(prop)) {
|
2024-04-22 11:12:45 +00:00
|
|
|
const o = deserializeProperty(prop[k], keepUnknowns);
|
2019-07-02 23:31:14 +00:00
|
|
|
hadSecrets = hadSecrets || isRpcSecret(o);
|
|
|
|
obj[k] = unwrapRpcSecret(o);
|
2019-05-10 05:13:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (hadSecrets) {
|
|
|
|
return {
|
2019-07-02 23:31:14 +00:00
|
|
|
[specialSigKey]: specialSecretSig,
|
2019-05-10 05:13:41 +00:00
|
|
|
value: obj,
|
|
|
|
};
|
2017-09-27 19:34:44 +00:00
|
|
|
}
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
}
|
2020-09-24 02:06:26 +00:00
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Silences any unhandled promise rejections that occur due to gRPC errors. The
|
|
|
|
* input promise may still be rejected.
|
2020-09-24 02:06:26 +00:00
|
|
|
*/
|
|
|
|
export function suppressUnhandledGrpcRejections<T>(p: Promise<T>): Promise<T> {
|
2023-04-28 22:27:10 +00:00
|
|
|
p.catch((err) => {
|
2020-09-24 02:06:26 +00:00
|
|
|
if (!isGrpcError(err)) {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return p;
|
|
|
|
}
|
2020-10-27 17:12:12 +00:00
|
|
|
|
2020-12-01 18:58:15 +00:00
|
|
|
function sameVersion(a?: string, b?: string): boolean {
|
|
|
|
// We treat undefined as a wildcard, so it always equals every other version.
|
|
|
|
return a === undefined || b === undefined || semver.eq(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkVersion(want?: semver.SemVer, have?: semver.SemVer): boolean {
|
|
|
|
if (want === undefined || have === undefined) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return have.major === want.major && have.minor >= want.minor && have.patch >= want.patch;
|
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2023-04-28 22:27:10 +00:00
|
|
|
export function register<T extends { readonly version?: string }>(
|
|
|
|
source: Map<string, T[]>,
|
|
|
|
registrationType: string,
|
|
|
|
key: string,
|
|
|
|
item: T,
|
|
|
|
): boolean {
|
2020-12-01 18:58:15 +00:00
|
|
|
let items = source.get(key);
|
|
|
|
if (items) {
|
|
|
|
for (const existing of items) {
|
|
|
|
if (sameVersion(existing.version, item.version)) {
|
2021-02-19 23:06:32 +00:00
|
|
|
// It is possible for the same version of the same provider SDK to be loaded multiple times in Node.js.
|
2021-06-15 17:25:03 +00:00
|
|
|
// In this case, we might legitimately get multiple registrations of the same resource. It should not
|
2021-02-19 23:06:32 +00:00
|
|
|
// matter which we use, so we can just skip re-registering. De-serialized resources will always be
|
|
|
|
// instances of classes from the first registered package.
|
2021-05-26 21:53:44 +00:00
|
|
|
if (excessiveDebugOutput) {
|
|
|
|
log.debug(`skip re-registering already registered ${registrationType} ${key}@${item.version}.`);
|
|
|
|
}
|
2021-02-19 23:06:32 +00:00
|
|
|
return false;
|
2020-12-01 18:58:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
items = [];
|
|
|
|
source.set(key, items);
|
|
|
|
}
|
|
|
|
|
2021-05-26 21:53:44 +00:00
|
|
|
if (excessiveDebugOutput) {
|
|
|
|
log.debug(`registering ${registrationType} ${key}@${item.version}`);
|
|
|
|
}
|
2020-12-01 18:58:15 +00:00
|
|
|
items.push(item);
|
2021-04-01 18:23:47 +00:00
|
|
|
return true;
|
2020-12-01 18:58:15 +00:00
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2023-04-28 22:27:10 +00:00
|
|
|
export function getRegistration<T extends { readonly version?: string }>(
|
|
|
|
source: Map<string, T[]>,
|
|
|
|
key: string,
|
2024-03-07 08:52:34 +00:00
|
|
|
version: string | undefined,
|
2023-04-28 22:27:10 +00:00
|
|
|
): T | undefined {
|
2020-12-01 18:58:15 +00:00
|
|
|
const ver = version ? new semver.SemVer(version) : undefined;
|
|
|
|
|
|
|
|
let bestMatch: T | undefined = undefined;
|
|
|
|
let bestMatchVersion: semver.SemVer | undefined = undefined;
|
|
|
|
for (const existing of source.get(key) ?? []) {
|
|
|
|
const existingVersion = existing.version !== undefined ? new semver.SemVer(existing.version) : undefined;
|
|
|
|
if (!checkVersion(ver, existingVersion)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!bestMatch || (existingVersion && bestMatchVersion && semver.gt(existingVersion, bestMatchVersion))) {
|
|
|
|
bestMatch = existing;
|
|
|
|
bestMatchVersion = existingVersion;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bestMatch;
|
|
|
|
}
|
|
|
|
|
2020-10-27 17:12:12 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* A {@link ResourcePackage} is a type that understands how to construct
|
|
|
|
* resource providers given a name, type, args, and URN.
|
2020-10-27 17:12:12 +00:00
|
|
|
*/
|
2020-11-04 18:24:41 +00:00
|
|
|
export interface ResourcePackage {
|
2020-12-01 18:58:15 +00:00
|
|
|
readonly version?: string;
|
|
|
|
constructProvider(name: string, type: string, urn: string): ProviderResource;
|
2020-11-04 18:24:41 +00:00
|
|
|
}
|
2020-10-27 17:12:12 +00:00
|
|
|
|
2020-12-01 18:58:15 +00:00
|
|
|
const resourcePackages = new Map<string, ResourcePackage[]>();
|
2020-10-27 17:12:12 +00:00
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* Used only for testing purposes.
|
|
|
|
*/
|
2020-12-19 00:27:27 +00:00
|
|
|
export function _resetResourcePackages() {
|
|
|
|
resourcePackages.clear();
|
|
|
|
}
|
|
|
|
|
2020-10-27 17:12:12 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Registers a resource package that will be used to construct providers for any
|
|
|
|
* URNs matching the package name and version that are deserialized by the
|
|
|
|
* current instance of the Pulumi JavaScript SDK.
|
2020-10-27 17:12:12 +00:00
|
|
|
*/
|
2020-12-01 18:58:15 +00:00
|
|
|
export function registerResourcePackage(pkg: string, resourcePackage: ResourcePackage) {
|
|
|
|
register(resourcePackages, "package", pkg, resourcePackage);
|
|
|
|
}
|
|
|
|
|
2024-03-07 08:52:34 +00:00
|
|
|
export function getResourcePackage(pkg: string, version: string | undefined): ResourcePackage | undefined {
|
2020-12-01 18:58:15 +00:00
|
|
|
return getRegistration(resourcePackages, pkg, version);
|
2020-10-27 17:12:12 +00:00
|
|
|
}
|
2020-11-04 18:24:41 +00:00
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* A {@link ResourceModule} is a type that understands how to construct
|
|
|
|
* resources given a name, type, args, and URN.
|
2020-11-04 18:24:41 +00:00
|
|
|
*/
|
|
|
|
export interface ResourceModule {
|
2020-12-01 18:58:15 +00:00
|
|
|
readonly version?: string;
|
|
|
|
construct(name: string, type: string, urn: string): Resource;
|
2020-11-04 18:24:41 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 18:58:15 +00:00
|
|
|
const resourceModules = new Map<string, ResourceModule[]>();
|
2020-11-04 18:24:41 +00:00
|
|
|
|
2020-12-01 18:58:15 +00:00
|
|
|
function moduleKey(pkg: string, mod: string): string {
|
|
|
|
return `${pkg}:${mod}`;
|
2020-11-04 18:24:41 +00:00
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* Used only for testing purposes.
|
|
|
|
*/
|
2020-12-19 00:27:27 +00:00
|
|
|
export function _resetResourceModules() {
|
|
|
|
resourceModules.clear();
|
|
|
|
}
|
|
|
|
|
2020-11-04 18:24:41 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Registers a resource module that will be used to construct resources for any
|
|
|
|
* URNs matching the module name and version that are deserialized by the
|
|
|
|
* current instance of the Pulumi JavaScript SDK.
|
2020-11-04 18:24:41 +00:00
|
|
|
*/
|
2020-12-01 18:58:15 +00:00
|
|
|
export function registerResourceModule(pkg: string, mod: string, module: ResourceModule) {
|
|
|
|
const key = moduleKey(pkg, mod);
|
|
|
|
register(resourceModules, "module", key, module);
|
|
|
|
}
|
|
|
|
|
2024-03-07 08:52:34 +00:00
|
|
|
export function getResourceModule(pkg: string, mod: string, version: string | undefined): ResourceModule | undefined {
|
2020-12-01 18:58:15 +00:00
|
|
|
const key = moduleKey(pkg, mod);
|
|
|
|
return getRegistration(resourceModules, key, version);
|
2020-11-04 18:24:41 +00:00
|
|
|
}
|