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:
parent
905e7353e4
commit
7f5e089f04
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -1,7 +1,27 @@
|
||||||
## 0.16.19 (Unreleased)
|
## 0.17.1 (unreleased)
|
||||||
|
|
||||||
### Improvements
|
### 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)
|
## 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
|
- Fix an issue where the Pulumi CLI would load the newest plugin for a resource provider instead of the version that was
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import * as log from "./log";
|
||||||
import { Resource } from "./resource";
|
import { Resource } from "./resource";
|
||||||
import * as runtime from "./runtime";
|
import * as runtime from "./runtime";
|
||||||
import * as utils from "./utils";
|
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
|
* 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.
|
* 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.
|
* 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>;
|
/* @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>;
|
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;
|
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.
|
throw new Error(`Cannot call '.get' during update or preview.
|
||||||
To manipulate the value of this Output, use '.apply' instead.`);
|
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
|
* [Input] is a property input for a resource. It may be a promptly available T, a promise for one,
|
||||||
* for one, or the output from a existing Resource.
|
* 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
|
* [Inputs] is a map of property name to property input, one for each resource property value.
|
||||||
* property value.
|
|
||||||
*/
|
*/
|
||||||
export type Inputs = Record<string, Input<any>>;
|
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.
|
// 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.
|
// 3. Otherwise, we have a basic type. Just unwrap that.
|
||||||
T extends Promise<infer U1> ? UnwrapSimple<U1> :
|
T extends Promise<infer U1> ? UnwrapSimple<U1> :
|
||||||
T extends Output<infer U2> ? UnwrapSimple<U2> :
|
T extends OutputInstance<infer U2> ? UnwrapSimple<U2> :
|
||||||
UnwrapSimple<T>;
|
UnwrapSimple<T>;
|
||||||
|
|
||||||
type primitive = Function | string | number | boolean | undefined | null;
|
type primitive = Function | string | number | boolean | undefined | null;
|
||||||
|
@ -445,6 +475,178 @@ export type UnwrappedObject<T> = {
|
||||||
[P in keyof T]: Unwrap<T[P]>;
|
[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
|
* [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,
|
* final string. Individual inputs can be any sort of [Input] value. i.e. they can be [Promise]s,
|
||||||
|
|
|
@ -30,6 +30,52 @@ export abstract class Resource {
|
||||||
// tslint:disable-next-line:variable-name
|
// tslint:disable-next-line:variable-name
|
||||||
/* @internal */ public readonly __pulumiResource: boolean = true;
|
/* @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
|
* urn is the stable logical URN used to distinctly address a resource, both before and after
|
||||||
* deployments.
|
* deployments.
|
||||||
|
@ -90,6 +136,10 @@ export abstract class Resource {
|
||||||
throw new RunError(`Resource parent is not a valid Resource: ${opts.parent}`);
|
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) {
|
if (opts.protect === undefined) {
|
||||||
opts.protect = opts.parent.__protect;
|
opts.protect = opts.parent.__protect;
|
||||||
}
|
}
|
||||||
|
@ -263,6 +313,20 @@ export abstract class ProviderResource extends CustomResource {
|
||||||
* operations for provisioning.
|
* operations for provisioning.
|
||||||
*/
|
*/
|
||||||
export class ComponentResource extends Resource {
|
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
|
* 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.
|
* [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*
|
// 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.
|
// do not have any effect on the cloud side of things at all.
|
||||||
super(type, name, /*custom:*/ false, /*props:*/ {}, opts);
|
super(type, name, /*custom:*/ false, /*props:*/ {}, opts);
|
||||||
|
this.__pulumiComponentResource = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerOutputs registers synthetic outputs that a component has initialized, usually by
|
// registerOutputs registers synthetic outputs that a component has initialized, usually by
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
import * as grpc from "grpc";
|
import * as grpc from "grpc";
|
||||||
import * as log from "../log";
|
import * as log from "../log";
|
||||||
import { Input, Inputs, Output } from "../output";
|
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 { debuggablePromise } from "./debuggable";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -47,10 +47,12 @@ interface ResourceResolverOperation {
|
||||||
providerRef: string | undefined;
|
providerRef: string | undefined;
|
||||||
// All serialized properties, fully awaited, serialized, and ready to go.
|
// All serialized properties, fully awaited, serialized, and ready to go.
|
||||||
serializedProps: Record<string, any>;
|
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>;
|
allDirectDependencyURNs: Set<URN>;
|
||||||
// Set of URNs that this resource is directly dependent upon, keyed by the property that
|
// Set of URNs that this resource is directly dependent upon, keyed by the property that causes
|
||||||
// causes the dependency. All urns in this map must exist in [allDirectDependencyURNs]
|
// 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>>;
|
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).
|
// The list of all dependencies (implicit or explicit).
|
||||||
const allDirectDependencies = new Set<Resource>(explicitDirectDependencies);
|
const allDirectDependencies = new Set<Resource>(explicitDirectDependencies);
|
||||||
|
|
||||||
const allDirectDependencyURNs = await getResourceURNs(explicitDirectDependencies);
|
const allDirectDependencyURNs = await getAllTransitivelyReferencedCustomResourceURNs(explicitDirectDependencies);
|
||||||
const propertyToDirectDependencyURNs = new Map<string, Set<URN>>();
|
const propertyToDirectDependencyURNs = new Map<string, Set<URN>>();
|
||||||
|
|
||||||
for (const [propertyName, directDependencies] of propertyToDirectDependencies) {
|
for (const [propertyName, directDependencies] of propertyToDirectDependencies) {
|
||||||
addAll(allDirectDependencies, directDependencies);
|
addAll(allDirectDependencies, directDependencies);
|
||||||
|
|
||||||
const urns = await getResourceURNs(directDependencies);
|
const urns = await getAllTransitivelyReferencedCustomResourceURNs(directDependencies);
|
||||||
addAll(allDirectDependencyURNs, urns);
|
addAll(allDirectDependencyURNs, urns);
|
||||||
propertyToDirectDependencyURNs.set(propertyName, urns);
|
propertyToDirectDependencyURNs.set(propertyName, urns);
|
||||||
}
|
}
|
||||||
|
@ -287,15 +289,60 @@ function addAll<T>(to: Set<T>, from: Set<T>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getResourceURNs(resources: Set<Resource>) {
|
async function getAllTransitivelyReferencedCustomResourceURNs(resources: Set<Resource>) {
|
||||||
const result = new Set<URN>();
|
// Go through 'resources', but transitively walk through **Component** resources, collecting any
|
||||||
for (const resource of resources) {
|
// of their child resources. This way, a Component acts as an aggregation really of all the
|
||||||
result.add(await resource.urn.promise());
|
// 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;
|
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).
|
* Gathers explicit dependent Resources from a list of Resources (possibly Promises and/or Outputs).
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,6 +19,25 @@ import { Output, concat, interpolate, output } from "../output";
|
||||||
import * as runtime from "../runtime";
|
import * as runtime from "../runtime";
|
||||||
import { asyncTest } from "./util";
|
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", () => {
|
describe("output", () => {
|
||||||
it("propagates true isKnown bit from inner Output", asyncTest(async () => {
|
it("propagates true isKnown bit from inner Output", asyncTest(async () => {
|
||||||
runtime.setIsDryRun(true);
|
runtime.setIsDryRun(true);
|
||||||
|
@ -128,4 +147,45 @@ describe("output", () => {
|
||||||
assert.equal(await result.promise(), "http://a:80/");
|
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);
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// Test the ability to invoke provider functions via RPC.
|
|
||||||
|
|
||||||
let assert = require("assert");
|
let assert = require("assert");
|
||||||
let pulumi = require("../../../../../");
|
let pulumi = require("../../../../../");
|
||||||
|
|
||||||
|
@ -9,5 +7,9 @@ class MyResource extends pulumi.CustomResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let resA = new MyResource("resA", {});
|
// cust1
|
||||||
let resB = new MyResource("resB", { parentId: resA.id }, { parent: resA });
|
// \
|
||||||
|
// cust2
|
||||||
|
|
||||||
|
let cust1 = new MyResource("cust1", {});
|
||||||
|
let cust2 = new MyResource("cust2", { parentId: cust1.id }, { parent: cust1 });
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// Test the ability to invoke provider functions via RPC.
|
|
||||||
|
|
||||||
let assert = require("assert");
|
let assert = require("assert");
|
||||||
let pulumi = require("../../../../../");
|
let pulumi = require("../../../../../");
|
||||||
|
|
||||||
|
@ -9,6 +7,10 @@ class MyResource extends pulumi.CustomResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let resA = new MyResource("resA");
|
// cust1
|
||||||
let resB = new MyResource("resB", { parentId: resA.id }, { parent: resA });
|
// / \
|
||||||
let resC = new MyResource("resC", { parentId: resA.id }, { parent: resA });
|
// 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 });
|
||||||
|
|
|
@ -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 });
|
|
@ -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 });
|
|
@ -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 });
|
|
@ -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 });
|
|
@ -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 });
|
|
@ -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 });
|
|
@ -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 });
|
|
@ -600,7 +600,12 @@ describe("rpc", () => {
|
||||||
pwd: path.join(base, "021.parent_child_dependencies"),
|
pwd: path.join(base, "021.parent_child_dependencies"),
|
||||||
program: "./index.js",
|
program: "./index.js",
|
||||||
expectResourceCount: 2,
|
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 };
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -608,14 +613,124 @@ describe("rpc", () => {
|
||||||
pwd: path.join(base, "022.parent_child_dependencies_2"),
|
pwd: path.join(base, "022.parent_child_dependencies_2"),
|
||||||
program: "./index.js",
|
program: "./index.js",
|
||||||
expectResourceCount: 3,
|
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 };
|
return { urn: makeUrn(t, name), id: undefined, props: undefined };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const casename of Object.keys(cases)) {
|
for (const casename of Object.keys(cases)) {
|
||||||
// if (casename !== "parent_child_dependencies_2") {
|
// if (casename.indexOf("parent_child_dependencies") < 0) {
|
||||||
// continue;
|
// continue;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
|
@ -5358,15 +5358,13 @@ return function () { typescript.parseCommandLine([""]); };
|
||||||
func: function () { console.log(pulumi); },
|
func: function () { console.log(pulumi); },
|
||||||
error: `Error serializing function 'func': tsClosureCases.js(0,0)
|
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
|
module './bin/index.js' which indirectly referenced
|
||||||
function 'debug':(...)
|
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 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.`
|
Module './bin/index.js' is a 'deployment only' module. In general these cannot be captured inside a 'run time' function.`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,9 +134,9 @@ describe("unwrap", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function createOutput<T>(cv: T, ...resources: TestResource[]): Output<T> {
|
function createOutput<T>(cv: T, ...resources: TestResource[]): Output<T> {
|
||||||
return Output.isInstance(cv)
|
return Output.isInstance<T>(cv)
|
||||||
? 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", () => {
|
describe("preserves resources", () => {
|
||||||
|
|
Loading…
Reference in a new issue