pulumi/sdk/nodejs/output.ts

1180 lines
48 KiB
TypeScript
Raw Permalink Normal View History

// Copyright 2016-2018, Pulumi Corporation.
//
// 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.
import { Resource } from "./resource";
import * as settings from "./runtime/settings";
import * as utils from "./utils";
/* eslint-disable no-shadow, @typescript-eslint/no-shadow */
/**
* {@link Output} helps encode the relationship between {@link Resource}s in a
* Pulumi application. Specifically, an {@link Output} holds onto a piece of
* data and the resource it was generated from. An output value can then be
* provided when constructing new resources, allowing that new resource to know
* both the value as well as the resource the value came from. This allows for
* a precise resource dependency graph to be created, which properly tracks the
* relationship between resources.
*/
class OutputImpl<T> implements OutputInstance<T> {
/**
* A private field to help with RTTI that works in SxS scenarios.
*
* This is internal instead of being truly private, to support mixins and our serialization model.
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/naming-convention,no-underscore-dangle,id-blacklist,id-match
public readonly __pulumiOutput: boolean = true;
2019-05-10 00:58:51 +00:00
/**
* Whether or not this output wraps a secret value. Values which are
* marked as secret are stored in an encrypted format when they are
* persisted as part of a state file. When, `true` this "taints" any
* additional resources created from it via an [all] or [apply], such that
* they are also treated as secrets.
*
* @internal
2019-05-10 00:58:51 +00:00
*/
public readonly isSecret: Promise<boolean>;
/**
* Whether or not this output should actually perform `.apply` calls. During
* a preview, an output value may not be known (because it would have to
* actually be computed by doing an update). In that case, we don't want to
* perform any `.apply` calls as the callbacks may not expect an undefined
* value. So, instead, we just transition to another output value that
* itself knows it should not perform `.apply` calls.
*
* @internal
*/
public readonly isKnown: Promise<boolean>;
/**
* A method that actually produces the concrete value of this output, as
* well as the total deployment-time set of resources this output depends
* on. If the value of the output is not known (i.e. {@link isKnown}
* resolves to `false`), this promise should resolve to `undefined` unless
* the `withUnknowns` flag is passed, in which case it will resolve to
* `unknown`.
*
* Only callable on the outside.
*
* @internal
*/
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
public readonly promise: (withUnknowns?: boolean) => Promise<T>;
/**
* The list of resources that this output value depends on.
*
* This only returns the set of dependent resources that were known at
* Output construction time. It represents the `@pulumi/pulumi` API prior to
* the addition of "async resource" dependencies. Code inside
* `@pulumi/pulumi` should use `.allResources` instead.
*
* Only callable on the outside.
*
* @internal
*/
public readonly resources: () => Set<Resource>;
/**
* The entire list of resources that this output depends on.
*
* This includes both the dependent resources that were known when the
* output was explicitly instantiated, along with any dependent resources
* produced asynchronously and returned from the function passed to
* {@link Output.apply}.
*
* This should be used whenever available inside this package. However,
* code that uses this should be resilient to it being absent and should
* fall back to using `.resources()` instead.
*
* Note: it is fine to use this property if it is guaranteed that it is on
* an output produced by this SDK (and not another sxs version).
*
* @internal
*/
// Marked as optional for sxs scenarios.
public readonly allResources?: () => Promise<Set<Resource>>;
/**
* {@link toString} on an {@link Output} is not supported. This is because
* the value an {@link Output} points to is asynchronously computed (and
* thus, this is akin to calling {@link toString} on a {@link Promise}).
*
* Calling this will simply return useful text about the issue, and will log
* a warning. In a future version of `@pulumi/pulumi` this will be changed
* to throw an error when this occurs.
*
* To get the value of an {@link Output} as an `Output<string>` consider
* either:
* 1. `o.apply(v => ``prefix${v}suffix``)` or
* 2. `pulumi.interpolate ``prefix${v}suffix`` `
*
* This will return an {@link Output} with the inner computed value and all
* resources still tracked.
*
* @see https://www.pulumi.com/docs/concepts/inputs-outputs
*
* @internal
*/
public toString: () => string;
/**
* {@link toJSON} on an {@link Output} is not supported. This is because the
* value an {@link Output} points to is asynchronously computed (and thus,
* this is akin to calling {@link toJSON} on a {@link Promise}).
*
* Calling this will simply return useful text about the issue, and will log
* a warning. In a future version of `@pulumi/pulumi` this will be changed
* to throw an error when this occurs.
*
* To get the value of an {@link Output} as a JSON value or JSON string consider
* either:
* 1. `o.apply(v => v.toJSON())` or
* 2. `o.apply(v => JSON.stringify(v))`
*
* This will return an {@link Output} with the inner computed value and all
* resources still tracked.
*
* @see https://www.pulumi.com/docs/concepts/inputs-outputs for more details
*
* @internal
*/
public toJSON: () => any;
// Statics
/**
* Create takes any {@link Input} value and converts it into an
* {@link Output}, deeply unwrapping nested {@link Input} values as
* necessary.
*/
public static create<T>(val: Input<T>): Output<Unwrap<T>>;
public static create<T>(val: Input<T> | undefined): Output<Unwrap<T | undefined>>;
public static create<T>(val: Input<T | undefined>): Output<Unwrap<T | undefined>> {
return output(val);
}
/**
* Returns true if the given object is an {@link Output}. This is designed
* to work even when multiple copies of the Pulumi SDK have been loaded into
* the same process.
*/
public static isInstance<T>(obj: any): obj is Output<T> {
return utils.isInstance(obj, "__pulumiOutput");
}
/**
* @internal
*/
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
static async getPromisedValue<T>(promise: Promise<T>, withUnknowns?: boolean): Promise<T> {
// If the caller did not explicitly ask to see unknown values and val contains unknowns, return undefined. This
// preserves compatibility with earlier versions of the Pulumi SDK.
const val = await promise;
if (!withUnknowns && containsUnknowns(val)) {
return <T>(<any>undefined);
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
}
return val;
}
/**
* @internal
*/
public constructor(
resources: Set<Resource> | Resource[] | Resource,
promise: Promise<T>,
isKnown: Promise<boolean>,
isSecret: Promise<boolean>,
allResources: Promise<Set<Resource> | Resource[] | Resource> | undefined,
) {
// Always create a copy so that no one accidentally modifies our Resource list.
const resourcesCopy = copyResources(resources);
// Create a copy of the async resources. Populate this with the sync-resources if that's
// all we have. That way this is always ensured to be a superset of the list of sync resources.
allResources = allResources || Promise.resolve([]);
const allResourcesCopy = allResources.then((r) => utils.union(copyResources(r), resourcesCopy));
// We are only known if we are not explicitly unknown and the resolved value of the output
// contains no distinguished unknown values.
isKnown = Promise.all([isKnown, promise]).then(([known, val]) => known && !containsUnknowns(val));
const lifted = Promise.all([allResourcesCopy, promise, isKnown, isSecret]).then(
([liftedResources, value, liftedIsKnown, liftedIsSecret]) =>
liftInnerOutput(liftedResources, value, liftedIsKnown, liftedIsSecret),
);
this.resources = () => resourcesCopy;
this.allResources = () => lifted.then((l) => l.allResources);
this.isKnown = lifted.then((l) => l.isKnown);
this.isSecret = lifted.then((l) => l.isSecret);
this.promise = (withUnknowns?: boolean) =>
OutputImpl.getPromisedValue(
lifted.then((l) => l.value),
withUnknowns,
);
this.toString = () => {
Add option to throw on output toString and toJSON calls (#14811) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description This adds an environment variable that changes output's `.toString` and `.toJSON` methods to throw an exception instead of returning a string containing a warning. This feature mitigates the issue described in #4261 by allowing users to opt in to having accidental toString calls (e.g. in a JS backtick string that is missing `pulumi.interpolate`) throw an exception. The behaviour ends up similar to the `get()` method of outputs, which throws an exception. I don't know how to write tests for this change, or even test it manually. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Fraser Waters <frassle@gmail.com>
2023-12-20 11:43:07 +00:00
let message = `Calling [toString] on an [Output<T>] is not supported.
To get the value of an Output<T> as an Output<string> consider either:
1: o.apply(v => \`prefix\${v}suffix\`)
2: pulumi.interpolate \`prefix\${v}suffix\`
See https://www.pulumi.com/docs/concepts/inputs-outputs for more details.
Or use ESLint with https://github.com/pulumi/eslint-plugin-pulumi to warn or
error lint on using Output<T> in template literals.`;
Add option to throw on output toString and toJSON calls (#14811) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description This adds an environment variable that changes output's `.toString` and `.toJSON` methods to throw an exception instead of returning a string containing a warning. This feature mitigates the issue described in #4261 by allowing users to opt in to having accidental toString calls (e.g. in a JS backtick string that is missing `pulumi.interpolate`) throw an exception. The behaviour ends up similar to the `get()` method of outputs, which throws an exception. I don't know how to write tests for this change, or even test it manually. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Fraser Waters <frassle@gmail.com>
2023-12-20 11:43:07 +00:00
if (utils.errorOutputString) {
throw new Error(message);
}
message += `\nThis function may throw in a future version of @pulumi/pulumi.`;
return message;
};
this.toJSON = () => {
Add option to throw on output toString and toJSON calls (#14811) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description This adds an environment variable that changes output's `.toString` and `.toJSON` methods to throw an exception instead of returning a string containing a warning. This feature mitigates the issue described in #4261 by allowing users to opt in to having accidental toString calls (e.g. in a JS backtick string that is missing `pulumi.interpolate`) throw an exception. The behaviour ends up similar to the `get()` method of outputs, which throws an exception. I don't know how to write tests for this change, or even test it manually. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Fraser Waters <frassle@gmail.com>
2023-12-20 11:43:07 +00:00
let message = `Calling [toJSON] on an [Output<T>] is not supported.
To get the value of an Output as a JSON value or JSON string consider either:
1: o.apply(v => v.toJSON())
2: o.apply(v => JSON.stringify(v))
Add option to throw on output toString and toJSON calls (#14811) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description This adds an environment variable that changes output's `.toString` and `.toJSON` methods to throw an exception instead of returning a string containing a warning. This feature mitigates the issue described in #4261 by allowing users to opt in to having accidental toString calls (e.g. in a JS backtick string that is missing `pulumi.interpolate`) throw an exception. The behaviour ends up similar to the `get()` method of outputs, which throws an exception. I don't know how to write tests for this change, or even test it manually. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Fraser Waters <frassle@gmail.com>
2023-12-20 11:43:07 +00:00
See https://www.pulumi.com/docs/concepts/inputs-outputs for more details.`;
if (utils.errorOutputString) {
throw new Error(message);
}
message += `\nThis function may throw in a future version of @pulumi/pulumi.`;
return message;
};
return new Proxy(this, {
get: (obj, prop: keyof T) => {
// Recreate the prototype walk to ensure we find any actual members defined directly
// on `Output<T>`.
for (let o = obj; o; o = Object.getPrototypeOf(o)) {
if (o.hasOwnProperty(prop)) {
return (<any>o)[prop];
}
}
// Always explicitly fail on a member called 'then'. It is used by other systems to
// determine if this is a Promise, and we do not want to indicate that that's what
// we are.
if (prop === "then") {
return undefined;
}
// Do not lift members that start with __. Technically, if all libraries were
// using this version of pulumi/pulumi we would not need this. However, this is
// so that downstream consumers can use this version of pulumi/pulumi while also
// passing these new Outputs to older versions of pulumi/pulumi. The reason this
// can be a problem is that older versions do an RTTI check that simply asks questions
// like:
//
// Is there a member on this object called '__pulumiResource'
//
// If we automatically lift such a member (even if it eventually points to 'undefined'),
// then those RTTI checks will succeed.
//
// Note: this should be safe to not lift as, in general, properties with this prefix
// are not at all common (and in general are used to represent private things anyway
// that likely should not be exposed).
//
// Similarly, do not respond to the 'doNotCapture' member name. It serves a similar
// RTTI purpose.
if (typeof prop === "string") {
if (prop.startsWith("__") || prop === "doNotCapture" || prop === "deploymentOnlyModule") {
return undefined;
}
}
// Fail out if we are being accessed using a symbol. Many APIs will access with a
// well known symbol (like 'Symbol.toPrimitive') to check for the presence of something.
// They will only check for the existence of that member, and we don't want to make it
// appear that have those.
//
// Another way of putting this is that we only forward 'string/number' members to our
// underlying value.
if (typeof prop === "symbol") {
return undefined;
}
// Else for *any other* property lookup, succeed the lookup and return a lifted
// `apply` on the underlying `Output`.
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
return (<any>obj.apply)((ob: any) => {
if (ob === undefined || ob === null) {
return undefined;
} else if (isUnknown(ob)) {
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 of this output is unknown, the result of the access should also be unknown.
// This is conceptually consistent, and also prevents us from returning a "known undefined"
// value from the `ob[prop]` expression below.
return unknown;
}
return ob[prop];
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
}, /*runWithUnknowns:*/ true);
},
});
}
2019-10-28 18:39:52 +00:00
public get(): T {
throw new Error(`Cannot call '.get' during update or preview.
To manipulate the value of this Output, use '.apply' instead.`);
}
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
// runWithUnknowns requests that `func` is run even if `isKnown` resolves to `false`. This is used to allow
// callers to process fully- or partially-unknown values and return a known result. the output proxy takes
// advantage of this to allow proxied property accesses to return known values even if other properties of
// the containing object are unknown.
public apply<U>(func: (t: T) => Input<U>, runWithUnknowns?: boolean): Output<U> {
// we're inside the modern `output` code, so it's safe to call `.allResources!` here.
const applied = Promise.all([
this.allResources!(),
this.promise(/*withUnknowns*/ true),
this.isKnown,
this.isSecret,
]).then(([allResources, value, isKnown, isSecret]) =>
applyHelperAsync<T, U>(allResources, value, isKnown, isSecret, func, !!runWithUnknowns),
);
2019-10-28 18:39:52 +00:00
const result = new OutputImpl<U>(
this.resources(),
applied.then((a) => a.value),
applied.then((a) => a.isKnown),
applied.then((a) => a.isSecret),
applied.then((a) => a.allResources),
);
return <Output<U>>(<any>result);
2019-10-28 18:39:52 +00:00
}
}
/**
* @Internal
*/
export function getAllResources<T>(op: OutputInstance<T>): Promise<Set<Resource>> {
return op.allResources instanceof Function ? op.allResources() : Promise.resolve(op.resources());
}
function copyResources(resources: Set<Resource> | Resource[] | Resource) {
const copy = Array.isArray(resources)
? new Set(resources)
: resources instanceof Set
? new Set(resources)
: new Set([resources]);
return copy;
}
async function liftInnerOutput(allResources: Set<Resource>, value: any, isKnown: boolean, isSecret: boolean) {
if (!Output.isInstance(value)) {
// 'value' itself wasn't an output, no need to transform any of the data we got.
return { allResources, value, isKnown, isSecret };
}
// 'value' was an Output. So we unwrap that to get the inner value/isKnown/isSecret/resources
// returned by that Output and merge with the state passed in to get the state of the final Output.
// Note: we intentionally await all the promises of the inner output. This way we properly
// propagate any rejections of any of these promises through the outer output as well.
const innerValue = await value.promise(/*withUnknowns*/ true);
const innerIsKnown = await value.isKnown;
const innerIsSecret = await (value.isSecret || Promise.resolve(false));
// If we're working with a new-style output, grab all its resources and merge into ours.
// Otherwise, if this is an old-style output, just grab the resources it was known to have
// at construction time.
const innerResources = await getAllResources(value);
const totalResources = utils.union(allResources, innerResources);
return {
allResources: totalResources,
value: innerValue,
isKnown: innerIsKnown,
isSecret: isSecret || innerIsSecret,
};
}
/* eslint-disable max-len */
async function applyHelperAsync<T, U>(
allResources: Set<Resource>,
value: T,
isKnown: boolean,
isSecret: boolean,
func: (t: T) => Input<U>,
runWithUnknowns: boolean,
) {
// Only perform the apply if the engine was able to give us an actual value
// for this Output.
const doApply = isKnown || runWithUnknowns;
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 (!doApply) {
// We didn't actually run the function, our new Output is definitely **not** known.
return {
allResources,
value: <U>(<any>undefined),
isKnown: false,
isSecret,
};
}
// If we are running with unknown values and the value is explicitly unknown but does not actually
// contain any unknown values, collapse its value to the unknown value. This ensures that callbacks
// that expect to see unknowns during preview in outputs that are not known will always do so.
if (!isKnown && runWithUnknowns && !containsUnknowns(value)) {
value = <T>(<any>unknown);
2019-10-28 18:39:52 +00:00
}
const transformed = await func(value);
// We successfully ran the inner function. Our new Output should be considered known. We
2019-10-28 18:39:52 +00:00
// preserve secretness from our original Output to the new one we're creating.
return liftInnerOutput(allResources, transformed, /*isKnown*/ true, isSecret);
}
/**
* Returns a promise denoting if the output is a secret or not. This is not the
* same as just calling `.isSecret` because in cases where the output does not
* have a `isSecret` property and it is a Proxy, we need to ignore the `isSecret`
* member that the proxy reports back.
*
* This calls the public implementation so that we only make any calculations in
* a single place.
*
* @internal
*/
export function isSecretOutput<T>(o: Output<T>): Promise<boolean> {
return isSecret(o);
}
/**
* Helper function for {@link output}. This function trivially recurses through
* an object, copying it, while also lifting any inner {@link Outputs} (with all
* their respective state) to a top-level output at the end. If there are no
* inner outputs, this will not affect the data (except by producing a new copy
* of it).
*
* Importantly:
*
* 1. Resources encountered while recursing are not touched. This helps ensure
* they stay Resources (with an appropriate prototype chain).
*
* 2. Primitive values (string, number, etc.) are returned as is.
*
* 3. Arrays and records are recursed into. An `Array<...>` that contains any
* `Outputs` wil become an `Output<Array<Unwrapped>>`. A `Record<string, ...>`
* that contains any output values will be an `Output<Record<string Unwrap<...>>`.
* In both cases of recursion, the outer output's known/secret/resources
* will be computed from the nested Outputs.
*/
function outputRec(val: any): any {
if (val === null || typeof val !== "object") {
// strings, numbers, booleans, functions, symbols, undefineds, nulls are all returned as
// themselves. They are always 'known' (i.e. we can safely 'apply' off of them even during
// preview).
return val;
} else if (Resource.isInstance(val)) {
// Don't unwrap Resources, there are existing codepaths that return Resources through
// Outputs and we want to preserve them as is when flattening.
return val;
} else if (isUnknown(val)) {
return val;
} else if (val instanceof Promise) {
// Recurse into the value the Promise points to. This may end up producing a
// Promise<Output>. Wrap this in another Output as the final result. This Output's
// construction will be able to merge the inner Output's data with its own. See
// liftInnerOutput for more details.
return createSimpleOutput(val.then((v) => outputRec(v)));
} else if (Output.isInstance(val)) {
// We create a new output here from the raw pieces of the original output in order to
// accommodate outputs from downlevel SxS SDKs. This ensures that within this package it is
// safe to assume the implementation of any Output returned by the `output` function.
//
// This includes:
// 1. that first-class unknowns are properly represented in the system: if this was a
// downlevel output where val.isKnown resolves to false, this guarantees that the
// returned output's promise resolves to unknown.
// 2. That the `isSecret` property is available.
// 3. That the `.allResources` is available.
const allResources = getAllResources(val);
const newOutput = new OutputImpl(
val.resources(),
val.promise(/*withUnknowns*/ true),
val.isKnown,
val.isSecret,
allResources,
);
return newOutput.apply(outputRec, /*runWithUnknowns*/ true);
} else if (val instanceof Array) {
const allValues = [];
let hasOutputs = false;
for (const v of val) {
const ev = outputRec(v);
allValues.push(ev);
if (Output.isInstance(ev)) {
hasOutputs = true;
}
}
// If we didn't encounter any nested Outputs, we don't need to do anything. We can just
// return this value as is.
if (!hasOutputs) {
// Note: we intentionally return 'allValues' here and not 'val'. This ensures we get a
// copy. This has been behavior we've had since the beginning and there may be subtle
// logic out there that depends on this that we would not want ot break.
return allValues;
}
// Otherwise, combine the data from all the outputs/non-outputs to one final output.
const promisedArray = Promise.all(allValues.map((v) => getAwaitableValue(v)));
const [syncResources, isKnown, isSecret, allResources] = getResourcesAndDetails(allValues);
return new Output(syncResources, promisedArray, isKnown, isSecret, allResources);
} else {
const promisedValues: { key: string; value: any }[] = [];
let hasOutputs = false;
for (const k of Object.keys(val)) {
const ev = outputRec(val[k]);
promisedValues.push({ key: k, value: ev });
if (Output.isInstance(ev)) {
hasOutputs = true;
}
}
if (!hasOutputs) {
// Note: we intentionally return a new value here and not 'val'. This ensures we get a
// copy. This has been behavior we've had since the beginning and there may be subtle
// logic out there that depends on this that we would not want ot break.
return promisedValues.reduce(
(o, kvp) => {
o[kvp.key] = kvp.value;
return o;
},
<any>{},
);
}
const promisedObject = getPromisedObject(promisedValues);
const [syncResources, isKnown, isSecret, allResources] = getResourcesAndDetails(
promisedValues.map((kvp) => kvp.value),
);
return new Output(syncResources, promisedObject, isKnown, isSecret, allResources);
}
}
/**
* {@link output} takes any {@link Input} value and converts it into an
* {@link Output}, deeply unwrapping nested {@link Input} values as necessary.
*
* The expected way to use this function is like so:
*
* ```ts
* var transformed = pulumi.output(someVal).apply(unwrapped => {
* // Do whatever you want now. 'unwrapped' will contain no outputs/promises inside
* // here, so you can easily do whatever sort of transformation is most convenient.
* });
*
* // the result can be passed to another Resource. The dependency information will be
* // properly maintained.
* var someResource = new SomeResource(name, { data: transformed ... });
* ```
*/
export function output<T>(val: Input<T>): Output<Unwrap<T>>;
export function output<T>(val: Input<T> | undefined): Output<Unwrap<T | undefined>>;
export function output<T>(val: Input<T | undefined>): Output<Unwrap<T | undefined>> {
const ov = outputRec(val);
return Output.isInstance<Unwrap<T>>(ov) ? ov : createSimpleOutput(ov);
}
/**
* {@link secret} behaves the same as {@link output} except the returned output
* is marked as containing sensitive data.
*/
export function secret<T>(val: Input<T>): Output<Unwrap<T>>;
export function secret<T>(val: Input<T> | undefined): Output<Unwrap<T | undefined>>;
export function secret<T>(val: Input<T | undefined>): Output<Unwrap<T | undefined>> {
const o = output(val);
// we called `output` right above this, so it's safe to call `.allResources` on the result.
return new Output(
o.resources(),
o.promise(/*withUnknowns*/ true),
o.isKnown,
Promise.resolve(true),
o.allResources!(),
);
}
/**
* {@link unsecret} behaves the same as {@link output} except the returned
* output takes the existing output and unwraps the secret.
*/
export function unsecret<T>(val: Output<T>): Output<T> {
return new Output(
val.resources(),
val.promise(/*withUnknowns*/ true),
val.isKnown,
Promise.resolve(false),
val.allResources!(),
);
}
/**
* {@link isSecret} returns `true` if and only if the provided {@link Output} is
* a secret.
*/
export function isSecret<T>(val: Output<T>): Promise<boolean> {
return Output.isInstance(val.isSecret) ? Promise.resolve(false) : val.isSecret;
}
function createSimpleOutput(val: any) {
return new Output(
new Set(),
val instanceof Promise ? val : Promise.resolve(val),
/*isKnown*/ Promise.resolve(true),
/*isSecret */ Promise.resolve(false),
Promise.resolve(new Set()),
);
}
/**
* Allows for multiple {@link Output} objects to be combined into a single
* {@link Output} object. The single {@link Output} will depend on the union of
* {@link Resources} that the individual dependencies depend on.
*
* This can be used in the following manner:
*
* ```ts
* var d1: Output<string>;
* var d2: Output<number>;
*
* var d3: Output<ResultType> = Output.all([d1, d2]).apply(([s, n]) => ...);
* ```
*
* In this example, taking a dependency on `d3` means a resource will depend on
* all the resources of `d1` and `d2`.
*/
/* eslint-disable max-len */
export function all<T>(val: Record<string, Input<T>>): Output<Record<string, Unwrap<T>>>;
export function all<T1, T2, T3, T4, T5, T6, T7, T8>(
values: [Input<T1>, Input<T2>, Input<T3>, Input<T4>, Input<T5>, Input<T6>, Input<T7>, Input<T8>],
): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>, Unwrap<T5>, Unwrap<T6>, Unwrap<T7>, Unwrap<T8>]>;
export function all<T1, T2, T3, T4, T5, T6, T7>(
values: [Input<T1>, Input<T2>, Input<T3>, Input<T4>, Input<T5>, Input<T6>, Input<T7>],
): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>, Unwrap<T5>, Unwrap<T6>, Unwrap<T7>]>;
export function all<T1, T2, T3, T4, T5, T6>(
values: [Input<T1>, Input<T2>, Input<T3>, Input<T4>, Input<T5>, Input<T6>],
): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>, Unwrap<T5>, Unwrap<T6>]>;
export function all<T1, T2, T3, T4, T5>(
values: [Input<T1>, Input<T2>, Input<T3>, Input<T4>, Input<T5>],
): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>, Unwrap<T5>]>;
export function all<T1, T2, T3, T4>(
values: [Input<T1>, Input<T2>, Input<T3>, Input<T4>],
): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>]>;
export function all<T1, T2, T3>(
values: [Input<T1>, Input<T2>, Input<T3>],
): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>]>;
export function all<T1, T2>(values: [Input<T1>, Input<T2>]): Output<[Unwrap<T1>, Unwrap<T2>]>;
export function all<T>(ds: Input<T>[]): Output<Unwrap<T>[]>;
export function all<T>(val: Input<T>[] | Record<string, Input<T>>): Output<any> {
// Our recursive `output` helper already does exactly what `all` needs to do in terms of the
// implementation. Why have both `output` and `all` then? Currently, to the best of our
// abilities, we haven't been able to make a single signature for both that can unify tuples and
// arrays for TypeScript. So `all` is much better when dealing with a tuple of heterogenous
// values, while `output` is good for everything else.
//
// Specifically ``all` can take an `[Output<string>, Output<number>]` and produce an
// `Output<[string, number]>` However, `output` for that same type will produce an
// `Output<(string|number)[]>` which is definitely suboptimal.
return output(val);
}
function getAwaitableValue(v: any): any {
if (Output.isInstance(v)) {
return v.promise(/* withUnknowns */ true);
} else {
return v;
}
}
async function getPromisedObject<T>(keysAndOutputs: { key: string; value: any }[]): Promise<Record<string, Unwrap<T>>> {
const result: Record<string, Unwrap<T>> = {};
for (const kvp of keysAndOutputs) {
result[kvp.key] = await getAwaitableValue(kvp.value);
}
return result;
}
function getResourcesAndDetails(
allValues: any[],
): [Set<Resource>, Promise<boolean>, Promise<boolean>, Promise<Set<Resource>>] {
const syncResources = new Set<Resource>();
const allOutputs = [];
for (const v of allValues) {
if (Output.isInstance(v)) {
allOutputs.push(v);
for (const res of v.resources()) {
syncResources.add(res);
}
}
}
// All the outputs were generated in `function all` using `output(v)`. So it's safe
// to call `.allResources!` here.
const allResources = Promise.all(allOutputs.map((o) => o.allResources!())).then((arr) => {
const result = new Set<Resource>();
for (const set of arr) {
for (const res of set) {
result.add(res);
}
}
return result;
});
// A merged output is known if all of its inputs are known.
const isKnown = Promise.all(allOutputs.map((o) => o.isKnown)).then((ps) => ps.every((b) => b));
// A merged output is secret if any of its inputs are secret.
const isSecret = Promise.all(allOutputs.map((o) => isSecretOutput(o))).then((ps) => ps.some((b) => b));
return [syncResources, isKnown, isSecret, allResources];
}
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
/**
* Unknown represents a value that is unknown. These values correspond to
* unknown property values received from the Pulumi engine as part of the result
* of a resource registration (see `runtime/rpc.ts`). User code is not typically
* exposed to these values: any {@link Output} that contains an {@link Unknown}
* will itself be unknown, so any user callbacks passed to `apply` will not be
* run. Internal callers of `apply` can request that they are run even with
* unknown values; the output proxy takes advantage of this to allow proxied
* property accesses to return known values even if other properties of the
* containing object are unknown.
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
*/
class Unknown {
/**
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
* A private field to help with RTTI that works in SxS scenarios.
*
* This is internal instead of being truly private, to support mixins and our serialization model.
*
* @internal
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
*/
// eslint-disable-next-line @typescript-eslint/naming-convention,no-underscore-dangle,id-blacklist,id-match
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
public readonly __pulumiUnknown: boolean = true;
/**
* Returns true if the given object is an {@link Unknown}. This is designed
* to work even when multiple copies of the Pulumi SDK have been loaded into
* the same process.
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
*/
public static isInstance(obj: any): obj is Unknown {
return utils.isInstance<Unknown>(obj, "__pulumiUnknown");
}
}
/**
* {@link unknown} is the singleton {@link Unknown} value.
*
* @internal
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
*/
export const unknown = new Unknown();
/**
* Returns true if the given value is unknown.
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
*/
export function isUnknown(val: any): boolean {
return Unknown.isInstance(val);
}
/**
* Returns true if the given value is or contains unknown values.
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
*/
export function containsUnknowns(value: any): boolean {
return impl(value, new Set<any>());
function impl(val: any, seen: Set<any>): boolean {
if (val === null || typeof val !== "object") {
return false;
} else if (isUnknown(val)) {
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
return true;
} else if (seen.has(val)) {
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
return false;
}
seen.add(val);
if (val instanceof Array) {
return val.some((e) => impl(e, seen));
} else {
return Object.keys(val).some((k) => impl(val[k], seen));
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
}
}
}
/**
* {@link Input} is a property input for a resource. It may be a promptly
* available `T`, a promise for one, or the {@link Output} from a existing
* {@link Resource}.
*/
// Note: we accept an OutputInstance (and not an Output) here to be *more* flexible in terms of
// what an Input is. OutputInstance has *less* members than Output (because it doesn't lift anything).
export type Input<T> = T | Promise<T> | OutputInstance<T>;
/**
* {@link Inputs} is a map of property name to property input, one for each
* resource property value.
*/
export type Inputs = Record<string, Input<any>>;
/**
* The {@link Unwrap} type allows us to express the operation of taking a type,
* with potentially deeply nested {@link Promise}s and {@link Output}s and to
* then get that same type with all the promises and outputs replaced with their
* wrapped type. Note that this unwrapping is "deep". So if you had:
*
* `type X = { A: Promise<{ B: Output<{ c: Input<boolean> }> }> }`
*
* Then `Unwrap<X>` would be equivalent to:
*
* `... = { A: { B: { C: boolean } } }`
*
* Unwrapping sees through promises, outputs, arrays and objects.
*
* Note: due to TypeScript limitations there are some things that cannot be
* expressed. Specifically, if you had a `Promise<Output<T>>` then the {@link
* Unwrap} type would not be able to undo both of those wraps. In practice that
* should be OK. Values in an object graph should not wrap outputs in promises.
* Instead, any code that needs to work Outputs and also be async should either
* create the output with the promise (which will collapse into just an output).
* Or, it should start with an output and call `apply` on it, passing in an
* `async` function. This will also collapse and just produce an output.
*
* In other words, this should not be used as the shape of an object: `{ a:
* Promise<Output<...>> }`. It should always either be `{ a: Promise<NonOutput>
* }` or just `{ a: Output<...> }`.
*/
export type Unwrap<T> =
// 1. If we have a promise, just get the type it itself is wrapping and recursively unwrap that.
// 2. Otherwise, if we have an output, do the same as a promise and just unwrap the inner type.
// 3. Otherwise, we have a basic type. Just unwrap that.
T extends Promise<infer U1>
? UnwrapSimple<U1>
: T extends OutputInstance<infer U2>
? UnwrapSimple<U2>
: UnwrapSimple<T>;
type primitive = Function | string | number | boolean | undefined | null;
/**
* Handles encountering basic types when unwrapping.
*/
export type UnwrapSimple<T> =
// 1. Any of the primitive types just unwrap to themselves.
// 2. An array of some types unwraps to an array of that type itself unwrapped. Note, due to a
// TS limitation we cannot express that as Array<Unwrap<U>> due to how it handles recursive
// types. We work around that by introducing an structurally equivalent interface that then
// helps make typescript defer type-evaluation instead of doing it eagerly.
// 3. An object unwraps to an object with properties of the same name, but where the property
// types have been unwrapped.
// 4. return 'never' at the end so that if we've missed something we'll discover it.
T extends primitive
? T
: T extends Resource
? T
: T extends Array<infer U>
? UnwrappedArray<U>
: T extends object
? UnwrappedObject<T>
: never;
export type UnwrappedArray<T> = Array<Unwrap<T>>;
export type UnwrappedObject<T> = {
[P in keyof T]: Unwrap<T[P]>;
};
/**
* Instance side of the {@link Output} type. Exposes the deployment-time and
* run-time mechanisms for working with the underlying value of an {@link Output}.
*/
export interface OutputInstance<T> {
/** @internal */ allResources?: () => Promise<Set<Resource>>;
/** @internal */ readonly isKnown: Promise<boolean>;
/** @internal */ readonly isSecret: Promise<boolean>;
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
/** @internal */ promise(withUnknowns?: boolean): Promise<T>;
/** @internal */ resources(): Set<Resource>;
/**
* Transforms the data of the output with the provided `func`. The result
* remains an {@link Output} so that dependent resources can be properly
* tracked.
*
* `func` is not allowed to make resources.
*
* `func` can return other {@link Output}s. This can be handy if you have an
* `Output<SomeVal>` and you want to get a transitive dependency of it,
* i.e.
*
* ```ts
* var d1: Output<SomeVal>;
* var d2 = d1.apply(v => v.x.y.OtherOutput); // getting an output off of 'v'.
* ```
*
* In this example, taking a dependency on `d2` means a resource will depend
* on all the resources of `d1`. It will *also* depend on the resources of
* `v.x.y.OtherDep`.
*
* Importantly, the resources that `d2` feels like it will depend on are the
* same resources as `d1`. If you need have multiple outputs and a single
* output is needed that combines both set of resources, then `pulumi.all`
* should be used instead.
*
* This function will be called during the execution of a `pulumi up` or
* `pulumi preview` operation, but it will not run when the values of the
* output are unknown. It is not available for functions that end up
* executing in the cloud during runtime. To get the value of the Output
* during cloud runtime execution, use `get()`.
*/
apply<U>(func: (t: T) => Promise<U>): Output<U>;
apply<U>(func: (t: T) => OutputInstance<U>): Output<U>;
apply<U>(func: (t: T) => U): Output<U>;
/**
* Retrieves the underlying value of this dependency.
*
* This function is only callable in code that runs in the cloud
* post-deployment. At this point all {@link Output} values will be known
* and can be safely retrieved. During Pulumi deployment or preview
* execution this must not be called (and will throw). This is because doing
* so would allow output values to flow into resources while losing the data
* that would allow the dependency graph to be changed.
*/
get(): T;
}
/**
* Static side of the {@link Output} type. Can be used to create outputs as well
* as test arbitrary values to see if they are {@link Output}s.
*/
export interface OutputConstructor {
create<T>(val: Input<T>): Output<Unwrap<T>>;
create<T>(val: Input<T> | undefined): Output<Unwrap<T | undefined>>;
isInstance<T>(obj: any): obj is Output<T>;
/** @internal */ new <T>(
resources: Set<Resource> | Resource[] | Resource,
promise: Promise<T>,
isKnown: Promise<boolean>,
isSecret: Promise<boolean>,
allResources: Promise<Set<Resource> | Resource[] | Resource>,
): Output<T>;
}
/**
* {@link Output} helps encode the relationship between {@link Resource}s in a
* Pulumi application. Specifically, an {@link Output} holds onto a piece of
* data and the resource it was generated from. An output value can then be
* provided when constructing new resources, allowing that new resource to know
* both the value as well as the resource the value came from. This allows for
* a precise resource dependency graph to be created, which properly tracks the
* relationship between resources.
*
* An output is used in a Pulumi program differently depending on if the
* application is executing at "deployment time" (i.e. when actually running the
* `pulumi` executable), or at "run time" (i.e. a piece of code running in some
* cloud).
*
* At "deployment time", the correct way to work with the underlying value is to
* call {@link Output.apply}. This allows the value to be accessed and
* manipulated, while still resulting in an output that is keeping track of
* {@link Resource}s appropriately. At deployment time the underlying value may
* or may not exist (for example, if a preview is being performed). In this
* case, the `func` callback will not be executed, and calling `.apply` will
* immediately return an output that points to the `undefined` value. During a
* normal update though, the `func` callbacks should always be executed.
*
* At "run time", the correct way to work with the underlying value is to simply
* call {@link Output.get} which will be promptly return the entire value. This
* will be a simple JavaScript object that can be manipulated as necessary.
*
* To ease with using outputs at deployment time, Pulumi will "lift" simple data
* properties of an underlying value to the output itself. For example:
*
* ```ts
* const o: Output<{ name: string, age: number, orders: Order[] }> = ...;
* const name : Output<string> = o.name;
* const age : Output<number> = o.age;
* const first: Output<Order> = o.orders[0];
* ```
*
* Instead of having to write:
*
* ```ts
* const o: Output<{ name: string, age: number, orders: Order[] }> = ...;
* const name : Output<string> = o.apply(v => v.name);
* const age : Output<number> = o.apply(v => v.age);
* const first: Output<Order> = o.apply(v => v.orders[0]);
* ```
*/
export type Output<T> = OutputInstance<T> & Lifted<T>;
// eslint-disable-next-line @typescript-eslint/naming-convention,@typescript-eslint/no-redeclare,no-underscore-dangle,id-blacklist,id-match
export const Output: OutputConstructor = <any>OutputImpl;
/**
* The {@link Lifted} type allows us to express the operation of taking a type,
* with potentially deeply nested objects and arrays and to then get a type with
* the same properties, except whose property types are now {@link Output}s of the
* original property type.
*
* For example:
*
*
* `type X = { A: string, B: { c: boolean } }`
*
* Then `Lifted<X>` would be equivalent to:
*
* `... = { A: Output<string>, B: Output<{ C: Output<boolean> }> }`
*
* {@link Lifted} is somewhat the opposite of {@link Unwrap}. Its primary
* purpose is to allow an instance of `Output<SomeType>` to provide simple
* access to the properties of `SomeType` directly on the instance itself
* (instead of haveing to use {@link Output.apply}).
*
* This lifting only happens through simple objects and arrays. Functions, for
* example, are not lifted. So you cannot do:
*
* ```ts
* const o: Output<string> = ...;
* const c: Output<number> = o.charCodeAt(0);
* ```
*
* Instead, you still need to write;
*
* ```ts
* const o: Output<string> = ...;
* const c: Output<number> = o.apply(v => v.charCodeAt(0));
* ```
*/
export type Lifted<T> =
// Specially handle 'string' since TS doesn't map the 'String.Length' property to it.
T extends string
? LiftedObject<String, NonFunctionPropertyNames<String>>
: T extends Array<infer U>
? LiftedArray<U>
: T extends object
? LiftedObject<T, NonFunctionPropertyNames<T>>
: // fallback to lifting no properties. Note that `Lifted` is used in
// Output<T> = OutputInstance<T> & Lifted<T>
// so returning an empty object just means that we're adding nothing to Output<T>.
// This is needed for cases like `Output<any>`.
{};
// The set of property names in T that are *not* functions.
Add option to throw on output toString and toJSON calls (#14811) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description This adds an environment variable that changes output's `.toString` and `.toJSON` methods to throw an exception instead of returning a string containing a warning. This feature mitigates the issue described in #4261 by allowing users to opt in to having accidental toString calls (e.g. in a JS backtick string that is missing `pulumi.interpolate`) throw an exception. The behaviour ends up similar to the `get()` method of outputs, which throws an exception. I don't know how to write tests for this change, or even test it manually. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Fraser Waters <frassle@gmail.com>
2023-12-20 11:43:07 +00:00
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
// Lift up all the non-function properties. If it was optional before, keep it optional after.
// If it's require before, keep it required afterwards.
export type LiftedObject<T, K extends keyof T> = {
[P in K]: T[P] extends OutputInstance<infer T1>
? Output<T1>
: T[P] extends Promise<infer T2>
? Output<T2>
: Output<T[P]>;
};
export type LiftedArray<T> = {
/**
* Gets the length of the array. This is a number one higher than the highest element defined
* in an array.
*/
readonly length: Output<number>;
readonly [n: number]: Output<T>;
};
/**
* {@link concat} takes a sequence of {@link Input}s, stringifies each one, and
* concatenates all values into one final string. Individual inputs can be any
* sort of input value: they can be promises, outputs, or just plain JavaScript
* values. Use this function like so:
*
* ```ts
* // 'server' and 'loadBalancer' are both resources that expose [Output] properties.
* let val: Output<string> = pulumi.concat("http://", server.hostname, ":", loadBalancer.port);
* ```
*
*/
export function concat(...params: Input<any>[]): Output<string> {
return output(params).apply((array) => array.join(""));
}
/**
* {@link interpolate} is similar to {@link concat} but is designed to be used
* as a tagged template expression, e.g.:
*
* ```ts
* // 'server' and 'loadBalancer' are both resources that expose [Output] properties.
* let val: Output<string> = pulumi.interpolate `http://${server.hostname}:${loadBalancer.port}`
* ```
*
* As with {@link concat}, the placeholders between `${}` can be any
* {@link Input}s: promises, outputs, or just plain JavaScript values.
*/
export function interpolate(literals: TemplateStringsArray, ...placeholders: Input<any>[]): Output<string> {
return output(placeholders).apply((unwrapped) => {
let result = "";
// interleave the literals with the placeholders
for (let i = 0; i < unwrapped.length; i++) {
result += literals[i];
result += unwrapped[i];
}
// add the last literal
result += literals[literals.length - 1];
return result;
});
}
/**
* {@link jsonStringify} uses {@link JSON.stringify} to serialize the given
* {@link Input} value into a JSON string.
*/
export function jsonStringify(
obj: Input<any>,
replacer?: (this: any, key: string, value: any) => any | (number | string)[],
space?: string | number,
): Output<string> {
return output(obj).apply((o) => {
return JSON.stringify(o, replacer, space);
});
}
2022-12-26 22:09:32 +00:00
/**
* {@link jsonParse} Uses {@link JSON.parse} to deserialize the given {@link
* Input} JSON string into a value.
2022-12-26 22:09:32 +00:00
*/
export function jsonParse(text: Input<string>, reviver?: (this: any, key: string, value: any) => any): Output<any> {
return output(text).apply((t) => {
2022-12-26 22:09:32 +00:00
return JSON.parse(t, reviver);
});
}