Update @pulumi/pulumi to version 0.17.0 (#2510)

This update includes several changes to core `@pulumi/pulumi` constructs that will not play nicely
in side-by-side applications that pull in prior versions of this package.  As such, we are rev'ing
the minor version of the package from 0.16 to 0.17.  Recent version of `pulumi` will now detect,
and warn, if different versions of `@pulumi/pulumi` are loaded into the same application.  If you
encounter this warning, it is recommended you move to versions of the `@pulumi/...` packages that
are compatible.  i.e. keep everything on 0.16.x until you are ready to move everything to 0.17.x.

### Improvements

- `Output<T>` now 'lifts' property members from the value it wraps, simplifying common coding patterns.  Note: this wrapping only happens for POJO values, not `Output<Resource>`s. 

- Depending on a **Component** Resource will now depend on all other Resources parented by that
  Resource. This will help out the programming model for Component Resources as your consumers can
  just depend on a Component and have that automatically depend on all the child Resources created
  by that Component.  Note: this does not apply to a **Custom** resource.  Depending on a
  CustomResource will still only wait on that single resource being created, not any other Resources
  that consider that CustomResource to be a parent.
This commit is contained in:
CyrusNajmabadi 2019-03-05 17:06:57 -08:00 committed by GitHub
parent 905e7353e4
commit 7f5e089f04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 775 additions and 74 deletions

View file

@ -1,7 +1,27 @@
## 0.16.19 (Unreleased)
## 0.17.1 (unreleased)
### Improvements
## 0.17.0 (Released March 5, 2019)
This update includes several changes to core `@pulumi/pulumi` constructs that will not play nicely
in side-by-side applications that pull in prior versions of this package. As such, we are rev'ing
the minor version of the package from 0.16 to 0.17. Recent version of `pulumi` will now detect,
and warn, if different versions of `@pulumi/pulumi` are loaded into the same application. If you
encounter this warning, it is recommended you move to versions of the `@pulumi/...` packages that
are compatible. i.e. keep everything on 0.16.x until you are ready to move everything to 0.17.x.
### Improvements
- `Output<T>` now 'lifts' property members from the value it wraps, simplifying common coding patterns.
- Depending on a **Component** Resource will now depend on all other Resources parented by that
Resource. This will help out the programming model for Component Resources as your consumers can
just depend on a Component and have that automatically depend on all the child Resources created
by that Component. Note: this does not apply to a **Custom** resource. Depending on a
CustomResource will still only wait on that single resource being created, not any other Resources
that consider that CustomResource to be a parent.
## 0.16.18 (Released March 1, 2019)
- Fix an issue where the Pulumi CLI would load the newest plugin for a resource provider instead of the version that was

View file

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import * as log from "./log";
import { Resource } from "./resource";
import * as runtime from "./runtime";
import * as utils from "./utils";
@ -23,7 +24,7 @@ import * as utils from "./utils";
* 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.
*/
export class Output<T> {
class OutputImpl<T> implements OutputInstance<T> {
/**
* A private field to help with RTTI that works in SxS scenarios.
*
@ -56,43 +57,7 @@ export class Output<T> {
*/
/* @internal */ public readonly resources: () => Set<Resource>;
/**
* Transforms the data of the output with the provided func. The result remains a
* Output so that dependent resources can be properly tracked.
*
* 'func' is not allowed to make resources.
*
* 'func' can return other Outputs. This can be handy if you have a 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 *not* 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 only be called execution of a 'pulumi update' request. It will not run
* during 'pulumi preview' (as the values of resources are of course not known then). 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()`.
*/
public readonly apply: <U>(func: (t: T) => Input<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 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.
*/
public readonly get: () => T;
/**
@ -251,6 +216,70 @@ This function may throw in a future version of @pulumi/pulumi.`;
throw new Error(`Cannot call '.get' during update or preview.
To manipulate the value of this Output, use '.apply' instead.`);
};
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`.
return obj.apply(ob => {
if (ob === undefined || ob === null) {
return undefined;
}
return ob[prop];
});
},
});
}
}
@ -376,14 +405,15 @@ function getResourcesAndIsKnown<T>(allOutputs: Output<Unwrap<T>>[]): [Resource[]
}
/**
* Input is a property input for a resource. It may be a promptly available T, a promise
* for one, or the output from a existing Resource.
* [Input] is a property input for a resource. It may be a promptly available T, a promise for one,
* or the output from a existing Resource.
*/
export type Input<T> = T | Promise<T> | Output<T>;
// 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>;
/**
* Inputs is a map of property name to property input, one for each resource
* property value.
* [Inputs] is a map of property name to property input, one for each resource property value.
*/
export type Inputs = Record<string, Input<any>>;
@ -416,7 +446,7 @@ export type Unwrap<T> =
// 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 Output<infer U2> ? UnwrapSimple<U2> :
T extends OutputInstance<infer U2> ? UnwrapSimple<U2> :
UnwrapSimple<T>;
type primitive = Function | string | number | boolean | undefined | null;
@ -445,6 +475,178 @@ export type UnwrappedObject<T> = {
[P in keyof T]: Unwrap<T[P]>;
};
/**
* Instance side of the [Output<T>] type. Exposes the deployment-time and run-time mechanisms
* for working with the underlying value of an [Output<T>].
*/
export interface OutputInstance<T> {
/* @internal */ readonly isKnown: Promise<boolean>;
/* @internal */ promise(): Promise<T>;
/* @internal */ resources(): Set<Resource>;
/**
* Transforms the data of the output with the provided func. The result remains a
* Output so that dependent resources can be properly tracked.
*
* 'func' is not allowed to make resources.
*
* 'func' can return other Outputs. This can be handy if you have a 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 *not* 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 only be called execution of a 'pulumi update' request. It will not run
* during 'pulumi preview' (as the values of resources are of course not known then). 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) => Input<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 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 [Output<T>] type. Can be used to [create] Outputs as well as test
* arbitrary values to see if they are [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>): Output<T>;
}
/**
* [Output] helps encode the relationship between Resources in a Pulumi application. Specifically an
* [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
* [Output.apply(func)]. This allows the value to be accessed and manipulated, while still
* resulting in an [Output] that is keeping track of [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 [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 [Output]s 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>;
// tslint:disable-next-line:variable-name
export const Output: OutputConstructor = <any>OutputImpl;
/**
* The [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 [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> }> }`
*
* [Lifted] is somewhat the opposite of [Unwrap]. It's 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 [.apply]).
*
* This lifting only happens through simple pojo 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> =
// Output<T> is an intersection type with 'Lifted<T>'. So, when we don't want to add any
// members to Output<T>, we just return `{}` which will leave it untouched.
T extends Resource ? {} :
// 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> :
LiftedObject<T, NonFunctionPropertyNames<T>>;
// The set of property names in T that are *not* functions.
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]: 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>;
};
/**
* [concat] takes a sequence of [Inputs], stringifies each, and concatenates all values into one
* final string. Individual inputs can be any sort of [Input] value. i.e. they can be [Promise]s,

View file

@ -30,6 +30,52 @@ export abstract class Resource {
// tslint:disable-next-line:variable-name
/* @internal */ public readonly __pulumiResource: boolean = true;
/**
* The optional parent of this resource.
*/
// tslint:disable-next-line:variable-name
/* @internal */ public readonly __parentResource: Resource | undefined;
/**
* The child resources of this resource. We use these (only from a ComponentResource) to allow
* code to dependOn a ComponentResource and have that effectively mean that it is depending on
* all the CustomResource children of that component.
*
* Important! We only walk through ComponentResources. They're the only resources that serve
* as an aggregation of other primitive (i.e. custom) resources. While a custom resource can be
* a parent of other resources, we don't want to ever depend on those child resource. If we do,
* it's simple to end up in a situation where we end up depending on a child resource that has a
* data cycle dependency due to the data passed into it.
*
* An example of how this would be bad is:
*
* ```ts
* var c1 = new CustomResource("c1");
* var c2 = new CustomResource("c2", { parentId: c1.id }, { parent: c1 });
* var c3 = new CustomResource("c3", { parentId: c1.id }, { parent: c1 });
* ```
*
* The problem here is that 'c2' has a data dependency on 'c1'. If it tries to wait on 'c1' it
* will walk to the children and wait on them. This will mean it will wait on 'c3'. But 'c3'
* will be waiting in the same manner on 'c2', and a cycle forms.
*
* This normally does not happen with ComponentResources as they do not have any data flowing
* into them. The only way you would be able to have a problem is if you had this sort of coding
* pattern:
*
* ```ts
* var c1 = new ComponentResource("c1");
* var c2 = new CustomResource("c2", { parentId: c1.urn }, { parent: c1 });
* var c3 = new CustomResource("c3", { parentId: c1.urn }, { parent: c1 });
* ```
*
* However, this would be pretty nonsensical as there is zero need for a custom resource to ever
* need to reference the urn of a component resource. So it's acceptable if that sort of
* pattern failed in practice.
*/
// tslint:disable-next-line:variable-name
/* @internal */ public __childResources: Set<Resource> | undefined;
/**
* urn is the stable logical URN used to distinctly address a resource, both before and after
* deployments.
@ -90,6 +136,10 @@ export abstract class Resource {
throw new RunError(`Resource parent is not a valid Resource: ${opts.parent}`);
}
this.__parentResource = opts.parent;
this.__parentResource.__childResources = this.__parentResource.__childResources || new Set();
this.__parentResource.__childResources.add(this);
if (opts.protect === undefined) {
opts.protect = opts.parent.__protect;
}
@ -263,6 +313,20 @@ export abstract class ProviderResource extends CustomResource {
* operations for provisioning.
*/
export class ComponentResource extends Resource {
/**
* A private field to help with RTTI that works in SxS scenarios.
*/
// tslint:disable-next-line:variable-name
/* @internal */ public readonly __pulumiComponentResource: boolean;
/**
* Returns true if the given object is an instance of CustomResource. This is designed to work even when
* multiple copies of the Pulumi SDK have been loaded into the same process.
*/
public static isInstance(obj: any): obj is ComponentResource {
return utils.isInstance<ComponentResource>(obj, "__pulumiComponentResource");
}
/**
* Creates and registers a new component resource. [type] is the fully qualified type token and
* [name] is the "name" part to use in creating a stable and globally unique URN for the object.
@ -290,6 +354,7 @@ export class ComponentResource extends Resource {
// not correspond to a real piece of cloud infrastructure. As such, changes to it *itself*
// do not have any effect on the cloud side of things at all.
super(type, name, /*custom:*/ false, /*props:*/ {}, opts);
this.__pulumiComponentResource = true;
}
// registerOutputs registers synthetic outputs that a component has initialized, usually by

View file

@ -15,7 +15,7 @@
import * as grpc from "grpc";
import * as log from "../log";
import { Input, Inputs, Output } from "../output";
import { CustomResourceOptions, ID, Resource, ResourceOptions, URN } from "../resource";
import { ComponentResource, CustomResource, CustomResourceOptions, ID, Resource, ResourceOptions, URN } from "../resource";
import { debuggablePromise } from "./debuggable";
import {
@ -47,10 +47,12 @@ interface ResourceResolverOperation {
providerRef: string | undefined;
// All serialized properties, fully awaited, serialized, and ready to go.
serializedProps: Record<string, any>;
// A set of URNs that this resource is directly dependent upon.
// A set of URNs that this resource is directly dependent upon. These will all be URNs of
// custom resources, not component resources.
allDirectDependencyURNs: Set<URN>;
// Set of URNs that this resource is directly dependent upon, keyed by the property that
// causes the dependency. All urns in this map must exist in [allDirectDependencyURNs]
// Set of URNs that this resource is directly dependent upon, keyed by the property that causes
// the dependency. All urns in this map must exist in [allDirectDependencyURNs]. These will
// all be URNs of custom resources, not component resources.
propertyToDirectDependencyURNs: Map<string, Set<URN>>;
}
@ -258,13 +260,13 @@ async function prepareResource(label: string, res: Resource, custom: boolean,
// The list of all dependencies (implicit or explicit).
const allDirectDependencies = new Set<Resource>(explicitDirectDependencies);
const allDirectDependencyURNs = await getResourceURNs(explicitDirectDependencies);
const allDirectDependencyURNs = await getAllTransitivelyReferencedCustomResourceURNs(explicitDirectDependencies);
const propertyToDirectDependencyURNs = new Map<string, Set<URN>>();
for (const [propertyName, directDependencies] of propertyToDirectDependencies) {
addAll(allDirectDependencies, directDependencies);
const urns = await getResourceURNs(directDependencies);
const urns = await getAllTransitivelyReferencedCustomResourceURNs(directDependencies);
addAll(allDirectDependencyURNs, urns);
propertyToDirectDependencyURNs.set(propertyName, urns);
}
@ -287,15 +289,60 @@ function addAll<T>(to: Set<T>, from: Set<T>) {
}
}
async function getResourceURNs(resources: Set<Resource>) {
const result = new Set<URN>();
for (const resource of resources) {
result.add(await resource.urn.promise());
}
async function getAllTransitivelyReferencedCustomResourceURNs(resources: Set<Resource>) {
// Go through 'resources', but transitively walk through **Component** resources, collecting any
// of their child resources. This way, a Component acts as an aggregation really of all the
// reachable custom resources it parents. This walking will transitively walk through other
// child ComponentResources, but will stop when it hits custom resources. in other words, if we
// had:
//
// Comp1
// / \
// Cust1 Comp2
// / \
// Cust2 Cust3
// /
// Cust4
//
// Then the transitively reachable custom resources of Comp1 will be [Cust1, Cust2, Cust3]. It
// will *not* include `Cust4`.
// To do this, first we just get the transitively reachable set of resources (not diving
// into custom resources). In the above picture, if we start with 'Comp1', this will be
// [Comp1, Cust1, Comp2, Cust2, Cust3]
const transitivelyReachableResources = getTransitivelyReferencedChildResourcesOfComponentResources(resources);
const transitivelyReacableCustomResources = [...transitivelyReachableResources].filter(r => CustomResource.isInstance(r));
const promises = transitivelyReacableCustomResources.map(r => r.urn.promise());
const urns = await Promise.all(promises);
return new Set<string>(urns);
}
/**
* Recursively walk the resources passed in, returning them and all resources reachable from
* [Resource.__childResources] through any **Component** resources we encounter.
*/
function getTransitivelyReferencedChildResourcesOfComponentResources(resources: Set<Resource>) {
// Recursively walk the dependent resources through their children, adding them to the result set.
const result = new Set<Resource>();
addTransitivelyReferencedChildResourcesOfComponentResources(resources, result);
return result;
}
function addTransitivelyReferencedChildResourcesOfComponentResources(resources: Set<Resource> | undefined, result: Set<Resource>) {
if (resources) {
for (const resource of resources) {
if (!result.has(resource)) {
result.add(resource);
if (ComponentResource.isInstance(resource)) {
addTransitivelyReferencedChildResourcesOfComponentResources(resource.__childResources, result);
}
}
}
}
}
/**
* Gathers explicit dependent Resources from a list of Resources (possibly Promises and/or Outputs).
*/

View file

@ -19,6 +19,25 @@ import { Output, concat, interpolate, output } from "../output";
import * as runtime from "../runtime";
import { asyncTest } from "./util";
interface Widget {
type: string; // metric | text
x?: number;
y?: number;
properties: Object;
}
// This ensures that the optionality of 'x' and 'y' are preserved when describing an Output<Widget>.
// Subtle changes in the definitions of Lifted<T> can make TS think these are required, which can
// break downstream consumers.
function mustCompile(): Output<Widget> {
return output({
type: "foo",
properties: {
whatever: 1,
}
})
}
describe("output", () => {
it("propagates true isKnown bit from inner Output", asyncTest(async () => {
runtime.setIsDryRun(true);
@ -128,4 +147,45 @@ describe("output", () => {
assert.equal(await result.promise(), "http://a:80/");
}));
});
describe("lifted operations", () => {
it("lifts properties from inner object", asyncTest(async () => {
const output1 = output({ a: 1, b: true, c: "str", d: [2], e: { f: 3 }, g: undefined, h: null });
assert.equal(await output1.a.promise(), 1);
assert.equal(await output1.b.promise(), true);
assert.equal(await output1.c.promise(), "str");
// Can lift both outer arrays as well as array accesses
assert.deepEqual(await output1.d.promise(), [2]);
assert.equal(await output1.d[0].promise(), 2);
// Can lift nested objects as well as their properties.
assert.deepEqual(await output1.e.promise(), { f: 3 });
assert.equal(await output1.e.f.promise(), 3);
assert.strictEqual(await output1.g.promise(), undefined);
assert.strictEqual(await output1.h.promise(), null);
// Unspecified things can be lifted, but produce 'undefined'.
assert.notEqual((<any>output1).z, undefined);
assert.equal(await (<any>output1).z.promise(), undefined);
}));
it("prefers Output members over lifted members", asyncTest(async () => {
const output1 = output({ apply: 1, promise: 2 });
assert.ok(output1.apply instanceof Function);
assert.ok(output1.isKnown instanceof Promise);
}));
it("does not lift symbols", asyncTest(async () => {
const output1 = output({ apply: 1, promise: 2 });
assert.strictEqual((<any>output1)[Symbol.toPrimitive], undefined);
}));
it("does not lift __ properties", asyncTest(async () => {
const output1 = output({ a: 1, b: 2 });
assert.strictEqual((<any>output1).__pulumiResource, undefined);
}));
});
});

View file

@ -1,5 +1,3 @@
// Test the ability to invoke provider functions via RPC.
let assert = require("assert");
let pulumi = require("../../../../../");
@ -9,5 +7,9 @@ class MyResource extends pulumi.CustomResource {
}
}
let resA = new MyResource("resA", {});
let resB = new MyResource("resB", { parentId: resA.id }, { parent: resA });
// cust1
// \
// cust2
let cust1 = new MyResource("cust1", {});
let cust2 = new MyResource("cust2", { parentId: cust1.id }, { parent: cust1 });

View file

@ -1,5 +1,3 @@
// Test the ability to invoke provider functions via RPC.
let assert = require("assert");
let pulumi = require("../../../../../");
@ -9,6 +7,10 @@ class MyResource extends pulumi.CustomResource {
}
}
let resA = new MyResource("resA");
let resB = new MyResource("resB", { parentId: resA.id }, { parent: resA });
let resC = new MyResource("resC", { parentId: resA.id }, { parent: resA });
// cust1
// / \
// cust2 cust3
let cust1 = new MyResource("cust1");
let cust2 = new MyResource("cust2", { parentId: cust1.id }, { parent: cust1 });
let cust3 = new MyResource("cust3", { parentId: cust1.id }, { parent: cust1 });

View file

@ -0,0 +1,28 @@
let assert = require("assert");
let pulumi = require("../../../../../");
class MyCustomResource extends pulumi.CustomResource {
constructor(name, args, opts) {
super("test:index:MyCustomResource", name, args, opts);
}
}
class MyComponentResource extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("test:index:MyComponentResource", name, args, opts);
}
}
// comp1
// / \
// cust1 cust2
let comp1 = new MyComponentResource("comp1");
// this represents a nonsensical program where a custom resource references the urn
// of a component. There is no good need for the urn to be used there. To represent
// a component dependency, 'dependsOn' should be used instead.
//
// This test just documents our behavior here (which is that we deadlock).
let cust1 = new MyCustomResource("cust1", { parentId: comp1.urn }, { parent: comp1 });
let cust2 = new MyCustomResource("cust2", { parentId: comp1.urn }, { parent: comp1 });

View file

@ -0,0 +1,22 @@
let assert = require("assert");
let pulumi = require("../../../../../");
class MyCustomResource extends pulumi.CustomResource {
constructor(name, args, opts) {
super("test:index:MyCustomResource", name, args, opts);
}
}
class MyComponentResource extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("test:index:MyComponentResource", name, args, opts);
}
}
// comp1
// / \
// cust1 cust2
let comp1 = new MyComponentResource("comp1");
let cust1 = new MyCustomResource("cust1", { }, { parent: comp1 });
let cust2 = new MyCustomResource("cust2", { }, { parent: comp1 });

View file

@ -0,0 +1,24 @@
let assert = require("assert");
let pulumi = require("../../../../../");
class MyCustomResource extends pulumi.CustomResource {
constructor(name, args, opts) {
super("test:index:MyCustomResource", name, args, opts);
}
}
class MyComponentResource extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("test:index:MyComponentResource", name, args, opts);
}
}
// comp1
// / \
// cust1 cust2
let comp1 = new MyComponentResource("comp1");
let cust1 = new MyCustomResource("cust1", { }, { parent: comp1 });
let cust2 = new MyCustomResource("cust2", { }, { parent: comp1 });
let res1 = new MyCustomResource("res1", { }, { dependsOn: comp1 });

View file

@ -0,0 +1,29 @@
let assert = require("assert");
let pulumi = require("../../../../../");
class MyCustomResource extends pulumi.CustomResource {
constructor(name, args, opts) {
super("test:index:MyCustomResource", name, args, opts);
}
}
class MyComponentResource extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("test:index:MyComponentResource", name, args, opts);
}
}
// comp1
// / \
// cust1 comp2
// / \
// cust2 cust3
let comp1 = new MyComponentResource("comp1");
let cust1 = new MyCustomResource("cust1", { }, { parent: comp1 });
let comp2 = new MyComponentResource("comp2", { }, { parent: comp1 });
let cust2 = new MyCustomResource("cust2", { }, { parent: comp2 });
let cust3 = new MyCustomResource("cust3", { }, { parent: comp2 });
let res1 = new MyCustomResource("res1", { }, { dependsOn: comp1 });

View file

@ -0,0 +1,36 @@
let assert = require("assert");
let pulumi = require("../../../../../");
class MyCustomResource extends pulumi.CustomResource {
constructor(name, args, opts) {
super("test:index:MyCustomResource", name, args, opts);
}
}
class MyComponentResource extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("test:index:MyComponentResource", name, args, opts);
}
}
// comp1
// / \
// cust1 comp2
// / \
// cust2 cust3
// /
// cust4
let comp1 = new MyComponentResource("comp1");
let cust1 = new MyCustomResource("cust1", { }, { parent: comp1 });
let comp2 = new MyComponentResource("comp2", { }, { parent: comp1 });
let cust2 = new MyCustomResource("cust2", { }, { parent: comp2 });
let cust3 = new MyCustomResource("cust3", { }, { parent: comp2 });
let cust4 = new MyCustomResource("cust4", { parentId: cust2.id }, { parent: cust2 });
let res1 = new MyCustomResource("res1", { }, { dependsOn: comp1 });
let res2 = new MyCustomResource("res2", { }, { dependsOn: comp2 });
let res3 = new MyCustomResource("res3", { }, { dependsOn: cust2 });
let res4 = new MyCustomResource("res4", { }, { dependsOn: cust4 });

View file

@ -0,0 +1,28 @@
let assert = require("assert");
let pulumi = require("../../../../../");
class MyCustomResource extends pulumi.CustomResource {
constructor(name, args, opts) {
super("test:index:MyCustomResource", name, args, opts);
}
}
class MyComponentResource extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("test:index:MyComponentResource", name, args, opts);
}
}
// comp1
// \
// cust1
// \
// cust2
let comp1 = new MyComponentResource("comp1");
let cust1 = new MyCustomResource("cust1", { }, { parent: comp1 });
let cust2 = new MyCustomResource("cust2", { parentId: cust1.id }, { parent: cust1 });
let res1 = new MyCustomResource("res1", { }, { dependsOn: comp1 });
let res2 = new MyCustomResource("res2", { }, { dependsOn: cust1 });
let res3 = new MyCustomResource("res3", { }, { dependsOn: cust2 });

View file

@ -0,0 +1,23 @@
let assert = require("assert");
let pulumi = require("../../../../../");
class MyCustomResource extends pulumi.CustomResource {
constructor(name, args, opts) {
super("test:index:MyCustomResource", name, args, opts);
}
}
class MyComponentResource extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("test:index:MyComponentResource", name, args, opts);
}
}
// cust1
// \
// cust2
let cust1 = new MyCustomResource("cust1", { }, { });
let cust2 = new MyCustomResource("cust2", { parentId: cust1.id }, { parent: cust1 });
let res1 = new MyCustomResource("res1", { }, { dependsOn: cust1 });

View file

@ -600,7 +600,12 @@ describe("rpc", () => {
pwd: path.join(base, "021.parent_child_dependencies"),
program: "./index.js",
expectResourceCount: 2,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string) => {
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, deps: string[]) => {
switch (name) {
case "cust1": assert.deepStrictEqual(deps, []); break;
case "cust2": assert.deepStrictEqual(deps, ["test:index:MyResource::cust1"]); break;
default: throw new Error("Didn't check: " + name);
}
return { urn: makeUrn(t, name), id: undefined, props: undefined };
},
},
@ -608,14 +613,124 @@ describe("rpc", () => {
pwd: path.join(base, "022.parent_child_dependencies_2"),
program: "./index.js",
expectResourceCount: 3,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string) => {
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, deps: string[]) => {
switch (name) {
case "cust1": assert.deepStrictEqual(deps, []); break;
case "cust2": assert.deepStrictEqual(deps, ["test:index:MyResource::cust1"]); break;
case "cust3": assert.deepStrictEqual(deps, ["test:index:MyResource::cust1"]); break;
default: throw new Error("Didn't check: " + name);
}
return { urn: makeUrn(t, name), id: undefined, props: undefined };
},
},
"parent_child_dependencies_3": {
pwd: path.join(base, "023.parent_child_dependencies_3"),
program: "./index.js",
expectResourceCount: 1,
expectError: "Program exited with non-zero exit code: 1",
},
"parent_child_dependencies_4": {
pwd: path.join(base, "024.parent_child_dependencies_4"),
program: "./index.js",
expectResourceCount: 3,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, deps: string[]) => {
switch (name) {
case "cust1": assert.deepStrictEqual(deps, []); break;
case "cust2": assert.deepStrictEqual(deps, []); break;
case "comp1": assert.deepStrictEqual(deps, []); break;
default: throw new Error("Didn't check: " + name);
}
return { urn: makeUrn(t, name), id: undefined, props: undefined };
},
},
"parent_child_dependencies_5": {
pwd: path.join(base, "025.parent_child_dependencies_5"),
program: "./index.js",
expectResourceCount: 4,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, deps: string[]) => {
switch (name) {
case "cust1": assert.deepStrictEqual(deps, []); break;
case "cust2": assert.deepStrictEqual(deps, []); break;
case "comp1": assert.deepStrictEqual(deps, []); break;
case "res1": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1", "test:index:MyCustomResource::cust2"]); break;
default: throw new Error("Didn't check: " + name);
}
return { urn: makeUrn(t, name), id: undefined, props: undefined };
},
},
"parent_child_dependencies_6": {
pwd: path.join(base, "026.parent_child_dependencies_6"),
program: "./index.js",
expectResourceCount: 6,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, deps: string[]) => {
switch (name) {
case "comp1": assert.deepStrictEqual(deps, []); break;
case "cust1": assert.deepStrictEqual(deps, []); break;
case "comp2": assert.deepStrictEqual(deps, []); break;
case "cust2": assert.deepStrictEqual(deps, []); break;
case "cust3": assert.deepStrictEqual(deps, []); break;
case "res1": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1", "test:index:MyCustomResource::cust2", "test:index:MyCustomResource::cust3"]); break;
default: throw new Error("Didn't check: " + name);
}
return { urn: makeUrn(t, name), id: undefined, props: undefined };
},
},
"parent_child_dependencies_7": {
pwd: path.join(base, "027.parent_child_dependencies_7"),
program: "./index.js",
expectResourceCount: 10,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, deps: string[]) => {
switch (name) {
case "comp1": assert.deepStrictEqual(deps, []); break;
case "cust1": assert.deepStrictEqual(deps, []); break;
case "comp2": assert.deepStrictEqual(deps, []); break;
case "cust2": assert.deepStrictEqual(deps, []); break;
case "cust3": assert.deepStrictEqual(deps, []); break;
case "cust4": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust2"]); break;
case "res1": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1", "test:index:MyCustomResource::cust2", "test:index:MyCustomResource::cust3"]); break;
case "res2": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust2", "test:index:MyCustomResource::cust3"]); break;
case "res3": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust2"]); break;
case "res4": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust4"]); break;
default: throw new Error("Didn't check: " + name);
}
return { urn: makeUrn(t, name), id: undefined, props: undefined };
},
},
"parent_child_dependencies_8": {
pwd: path.join(base, "028.parent_child_dependencies_8"),
program: "./index.js",
expectResourceCount: 6,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, deps: string[]) => {
switch (name) {
case "comp1": assert.deepStrictEqual(deps, []); break;
case "cust1": assert.deepStrictEqual(deps, []); break;
case "cust2": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]); break;
case "res1": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]); break;
case "res2": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]); break;
case "res3": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust2"]); break;
default: throw new Error("Didn't check: " + name);
}
return { urn: makeUrn(t, name), id: undefined, props: undefined };
},
},
"parent_child_dependencies_9": {
pwd: path.join(base, "029.parent_child_dependencies_9"),
program: "./index.js",
expectResourceCount: 3,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, deps: string[]) => {
switch (name) {
case "cust1": assert.deepStrictEqual(deps, []); break;
case "cust2": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]); break;
case "res1": assert.deepStrictEqual(deps, ["test:index:MyCustomResource::cust1"]); break;
default: throw new Error("Didn't check: " + name);
}
return { urn: makeUrn(t, name), id: undefined, props: undefined };
},
},
};
for (const casename of Object.keys(cases)) {
// if (casename !== "parent_child_dependencies_2") {
// if (casename.indexOf("parent_child_dependencies") < 0) {
// continue;
// }

View file

@ -5358,15 +5358,13 @@ return function () { typescript.parseCommandLine([""]); };
func: function () { console.log(pulumi); },
error: `Error serializing function 'func': tsClosureCases.js(0,0)
function 'func': tsClosureCases.js(0,0): captured
function 'func':(...)
module './bin/index.js' which indirectly referenced
function 'debug':(...)
module './bin/runtime/settings.js' which indirectly referenced
function 'getEngine':(...)
module './bin/proto/engine_grpc_pb.js' which indirectly referenced
(...)
Function code:
(...)
function [Symbol.hasInstance]() { [native code] }
Module './bin/index.js' is a 'deployment only' module. In general these cannot be captured inside a 'run time' function.`
});
}

View file

@ -134,9 +134,9 @@ describe("unwrap", () => {
});
function createOutput<T>(cv: T, ...resources: TestResource[]): Output<T> {
return Output.isInstance(cv)
return Output.isInstance<T>(cv)
? cv
: new Output<any>(<any>new Set(resources), Promise.resolve(cv), Promise.resolve(true))
: new Output(<any>new Set(resources), Promise.resolve(cv), Promise.resolve(true))
}
describe("preserves resources", () => {