2018-05-22 21:43:36 +02:00
|
|
|
// Copyright 2016-2018, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
Implement initial Lumi-as-a-library
This is the initial step towards redefining Lumi as a library that runs
atop vanilla Node.js/V8, rather than as its own runtime.
This change is woefully incomplete but this includes some of the more
stable pieces of my current work-in-progress.
The new structure is that within the sdk/ directory we will have a client
library per language. This client library contains the object model for
Lumi (resources, properties, assets, config, etc), in addition to the
"language runtime host" components required to interoperate with the
Lumi resource monitor. This resource monitor is effectively what we call
"Lumi" today, in that it's the thing orchestrating plans and deployments.
Inside the sdk/ directory, you will find nodejs/, the Node.js client
library, alongside proto/, the definitions for RPC interop between the
different pieces of the system. This includes existing RPC definitions
for resource providers, etc., in addition to the new ones for hosting
different language runtimes from within Lumi.
These new interfaces are surprisingly simple. There is effectively a
bidirectional RPC channel between the Lumi resource monitor, represented
by the lumirpc.ResourceMonitor interface, and each language runtime,
represented by the lumirpc.LanguageRuntime interface.
The overall orchestration goes as follows:
1) Lumi decides it needs to run a program written in language X, so
it dynamically loads the language runtime plugin for language X.
2) Lumi passes that runtime a loopback address to its ResourceMonitor
service, while language X will publish a connection back to its
LanguageRuntime service, which Lumi will talk to.
3) Lumi then invokes LanguageRuntime.Run, passing information like
the desired working directory, program name, arguments, and optional
configuration variables to make available to the program.
4) The language X runtime receives this, unpacks it and sets up the
necessary context, and then invokes the program. The program then
calls into Lumi object model abstractions that internally communicate
back to Lumi using the ResourceMonitor interface.
5) The key here is ResourceMonitor.NewResource, which Lumi uses to
serialize state about newly allocated resources. Lumi receives these
and registers them as part of the plan, doing the usual diffing, etc.,
to decide how to proceed. This interface is perhaps one of the
most subtle parts of the new design, as it necessitates the use of
promises internally to allow parallel evaluation of the resource plan,
letting dataflow determine the available concurrency.
6) The program exits, and Lumi continues on its merry way. If the program
fails, the RunResponse will include information about the failure.
Due to (5), all properties on resources are now instances of a new
Property<T> type. A Property<T> is just a thin wrapper over a T, but it
encodes the special properties of Lumi resource properties. Namely, it
is possible to create one out of a T, other Property<T>, Promise<T>, or
to freshly allocate one. In all cases, the Property<T> does not "settle"
until its final state is known. This cannot occur before the deployment
actually completes, and so in general it's not safe to depend on concrete
resolutions of values (unlike ordinary Promise<T>s which are usually
expected to resolve). As a result, all derived computations are meant to
use the `then` function (as in `someValue.then(v => v+x)`).
Although this change includes tests that may be run in isolation to test
the various RPC interactions, we are nowhere near finished. The remaining
work primarily boils down to three things:
1) Wiring all of this up to the Lumi code.
2) Fixing the handful of known loose ends required to make this work,
primarily around the serialization of properties (waiting on
unresolved ones, serializing assets properly, etc).
3) Implementing lambda closure serialization as a native extension.
This ongoing work is part of pulumi/pulumi-fabric#311.
2017-08-26 21:07:54 +02:00
|
|
|
|
2018-09-25 01:57:20 +02:00
|
|
|
import { ResourceError } from "./errors";
|
2018-02-05 23:44:23 +01:00
|
|
|
import * as runtime from "./runtime";
|
2018-09-12 04:38:45 +02:00
|
|
|
import { readResource, registerResource, registerResourceOutputs } from "./runtime/resource";
|
Implement initial Lumi-as-a-library
This is the initial step towards redefining Lumi as a library that runs
atop vanilla Node.js/V8, rather than as its own runtime.
This change is woefully incomplete but this includes some of the more
stable pieces of my current work-in-progress.
The new structure is that within the sdk/ directory we will have a client
library per language. This client library contains the object model for
Lumi (resources, properties, assets, config, etc), in addition to the
"language runtime host" components required to interoperate with the
Lumi resource monitor. This resource monitor is effectively what we call
"Lumi" today, in that it's the thing orchestrating plans and deployments.
Inside the sdk/ directory, you will find nodejs/, the Node.js client
library, alongside proto/, the definitions for RPC interop between the
different pieces of the system. This includes existing RPC definitions
for resource providers, etc., in addition to the new ones for hosting
different language runtimes from within Lumi.
These new interfaces are surprisingly simple. There is effectively a
bidirectional RPC channel between the Lumi resource monitor, represented
by the lumirpc.ResourceMonitor interface, and each language runtime,
represented by the lumirpc.LanguageRuntime interface.
The overall orchestration goes as follows:
1) Lumi decides it needs to run a program written in language X, so
it dynamically loads the language runtime plugin for language X.
2) Lumi passes that runtime a loopback address to its ResourceMonitor
service, while language X will publish a connection back to its
LanguageRuntime service, which Lumi will talk to.
3) Lumi then invokes LanguageRuntime.Run, passing information like
the desired working directory, program name, arguments, and optional
configuration variables to make available to the program.
4) The language X runtime receives this, unpacks it and sets up the
necessary context, and then invokes the program. The program then
calls into Lumi object model abstractions that internally communicate
back to Lumi using the ResourceMonitor interface.
5) The key here is ResourceMonitor.NewResource, which Lumi uses to
serialize state about newly allocated resources. Lumi receives these
and registers them as part of the plan, doing the usual diffing, etc.,
to decide how to proceed. This interface is perhaps one of the
most subtle parts of the new design, as it necessitates the use of
promises internally to allow parallel evaluation of the resource plan,
letting dataflow determine the available concurrency.
6) The program exits, and Lumi continues on its merry way. If the program
fails, the RunResponse will include information about the failure.
Due to (5), all properties on resources are now instances of a new
Property<T> type. A Property<T> is just a thin wrapper over a T, but it
encodes the special properties of Lumi resource properties. Namely, it
is possible to create one out of a T, other Property<T>, Promise<T>, or
to freshly allocate one. In all cases, the Property<T> does not "settle"
until its final state is known. This cannot occur before the deployment
actually completes, and so in general it's not safe to depend on concrete
resolutions of values (unlike ordinary Promise<T>s which are usually
expected to resolve). As a result, all derived computations are meant to
use the `then` function (as in `someValue.then(v => v+x)`).
Although this change includes tests that may be run in isolation to test
the various RPC interactions, we are nowhere near finished. The remaining
work primarily boils down to three things:
1) Wiring all of this up to the Lumi code.
2) Fixing the handful of known loose ends required to make this work,
primarily around the serialization of properties (waiting on
unresolved ones, serializing assets properly, etc).
3) Implementing lambda closure serialization as a native extension.
This ongoing work is part of pulumi/pulumi-fabric#311.
2017-08-26 21:07:54 +02:00
|
|
|
|
|
|
|
export type ID = string; // a provider-assigned ID.
|
|
|
|
export type URN = string; // an automatically generated logical URN, used to stably identify resources.
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
|
|
|
* Resource represents a class whose CRUD operations are implemented by a provider plugin.
|
|
|
|
*/
|
Implement initial Lumi-as-a-library
This is the initial step towards redefining Lumi as a library that runs
atop vanilla Node.js/V8, rather than as its own runtime.
This change is woefully incomplete but this includes some of the more
stable pieces of my current work-in-progress.
The new structure is that within the sdk/ directory we will have a client
library per language. This client library contains the object model for
Lumi (resources, properties, assets, config, etc), in addition to the
"language runtime host" components required to interoperate with the
Lumi resource monitor. This resource monitor is effectively what we call
"Lumi" today, in that it's the thing orchestrating plans and deployments.
Inside the sdk/ directory, you will find nodejs/, the Node.js client
library, alongside proto/, the definitions for RPC interop between the
different pieces of the system. This includes existing RPC definitions
for resource providers, etc., in addition to the new ones for hosting
different language runtimes from within Lumi.
These new interfaces are surprisingly simple. There is effectively a
bidirectional RPC channel between the Lumi resource monitor, represented
by the lumirpc.ResourceMonitor interface, and each language runtime,
represented by the lumirpc.LanguageRuntime interface.
The overall orchestration goes as follows:
1) Lumi decides it needs to run a program written in language X, so
it dynamically loads the language runtime plugin for language X.
2) Lumi passes that runtime a loopback address to its ResourceMonitor
service, while language X will publish a connection back to its
LanguageRuntime service, which Lumi will talk to.
3) Lumi then invokes LanguageRuntime.Run, passing information like
the desired working directory, program name, arguments, and optional
configuration variables to make available to the program.
4) The language X runtime receives this, unpacks it and sets up the
necessary context, and then invokes the program. The program then
calls into Lumi object model abstractions that internally communicate
back to Lumi using the ResourceMonitor interface.
5) The key here is ResourceMonitor.NewResource, which Lumi uses to
serialize state about newly allocated resources. Lumi receives these
and registers them as part of the plan, doing the usual diffing, etc.,
to decide how to proceed. This interface is perhaps one of the
most subtle parts of the new design, as it necessitates the use of
promises internally to allow parallel evaluation of the resource plan,
letting dataflow determine the available concurrency.
6) The program exits, and Lumi continues on its merry way. If the program
fails, the RunResponse will include information about the failure.
Due to (5), all properties on resources are now instances of a new
Property<T> type. A Property<T> is just a thin wrapper over a T, but it
encodes the special properties of Lumi resource properties. Namely, it
is possible to create one out of a T, other Property<T>, Promise<T>, or
to freshly allocate one. In all cases, the Property<T> does not "settle"
until its final state is known. This cannot occur before the deployment
actually completes, and so in general it's not safe to depend on concrete
resolutions of values (unlike ordinary Promise<T>s which are usually
expected to resolve). As a result, all derived computations are meant to
use the `then` function (as in `someValue.then(v => v+x)`).
Although this change includes tests that may be run in isolation to test
the various RPC interactions, we are nowhere near finished. The remaining
work primarily boils down to three things:
1) Wiring all of this up to the Lumi code.
2) Fixing the handful of known loose ends required to make this work,
primarily around the serialization of properties (waiting on
unresolved ones, serializing assets properly, etc).
3) Implementing lambda closure serialization as a native extension.
This ongoing work is part of pulumi/pulumi-fabric#311.
2017-08-26 21:07:54 +02:00
|
|
|
export abstract class Resource {
|
2018-04-25 02:23:18 +02:00
|
|
|
/**
|
|
|
|
* A private field to help with RTTI that works in SxS scenarios.
|
|
|
|
*/
|
|
|
|
// tslint:disable-next-line:variable-name
|
2018-06-08 06:34:06 +02:00
|
|
|
/* @internal */ private readonly __pulumiResource: boolean = true;
|
2018-04-25 02:23:18 +02:00
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
2018-01-26 03:50:58 +01:00
|
|
|
* urn is the stable logical URN used to distinctly address a resource, both before and after
|
|
|
|
* deployments.
|
2017-09-22 03:15:29 +02:00
|
|
|
*/
|
2018-02-06 03:37:10 +01:00
|
|
|
public readonly urn: Output<URN>;
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
|
2018-08-11 01:18:21 +02:00
|
|
|
/**
|
|
|
|
* When set to true, protect ensures this resource cannot be deleted.
|
|
|
|
*/
|
|
|
|
// tslint:disable-next-line:variable-name
|
|
|
|
/* @internal */ private readonly __protect: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The set of providers to use for child resources. Keyed by package name (e.g. "aws").
|
|
|
|
*/
|
|
|
|
// tslint:disable-next-line:variable-name
|
|
|
|
/* @internal */ private readonly __providers: Record<string, ProviderResource>;
|
|
|
|
|
2018-04-25 02:23:18 +02:00
|
|
|
public static isInstance(obj: any): obj is Resource {
|
|
|
|
return obj && obj.__pulumiResource;
|
|
|
|
}
|
|
|
|
|
2018-08-11 01:18:21 +02:00
|
|
|
// getProvider fetches the provider for the given module member, if any.
|
|
|
|
public getProvider(moduleMember: string): ProviderResource | undefined {
|
|
|
|
const memComponents = moduleMember.split(":");
|
|
|
|
if (memComponents.length !== 3) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const pkg = memComponents[0];
|
|
|
|
return this.__providers[pkg];
|
|
|
|
}
|
|
|
|
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
/**
|
2018-01-26 03:50:58 +01:00
|
|
|
* Creates and registers a new resource object. t 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.
|
|
|
|
* dependsOn is an optional list of other resources that this resource depends on, controlling
|
|
|
|
* the order in which we perform resource operations.
|
2017-09-22 03:15:29 +02:00
|
|
|
*
|
|
|
|
* @param t The type of the resource.
|
2018-02-08 00:01:55 +01:00
|
|
|
* @param name The _unique_ name of the resource.
|
2017-10-15 12:52:04 +02:00
|
|
|
* @param custom True to indicate that this is a custom resource, managed by a plugin.
|
2017-09-22 03:15:29 +02:00
|
|
|
* @param props The arguments to use to populate the new resource.
|
Implement resource protection (#751)
This change implements resource protection, as per pulumi/pulumi#689.
The overall idea is that a resource can be marked as "protect: true",
which will prevent deletion of that resource for any reason whatsoever
(straight deletion, replacement, etc). This is expressed in the
program. To "unprotect" a resource, one must perform an update setting
"protect: false", and then afterwards, they can delete the resource.
For example:
let res = new MyResource("precious", { .. }, { protect: true });
Afterwards, the resource will display in the CLI with a lock icon, and
any attempts to remove it will fail in the usual ways (in planning or,
worst case, during an actual update).
This was done by adding a new ResourceOptions bag parameter to the
base Resource types. This is unfortunately a breaking change, but now
is the right time to take this one. We had been adding new settings
one by one -- like parent and dependsOn -- and this new approach will
set us up to add any number of additional settings down the road,
without needing to worry about breaking anything ever again.
This is related to protected stacks, as described in
pulumi/pulumi-service#399. Most likely this will serve as a foundational
building block that enables the coarser grained policy management.
2017-12-20 23:31:07 +01:00
|
|
|
* @param opts A bag of options that control this resource's behavior.
|
2017-09-22 03:15:29 +02:00
|
|
|
*/
|
2018-04-07 19:15:58 +02:00
|
|
|
constructor(t: string, name: string, custom: boolean, props: Inputs = {}, opts: ResourceOptions = {}) {
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
if (!t) {
|
2018-09-25 01:57:20 +02:00
|
|
|
throw new ResourceError("Missing resource type argument", opts.parent);
|
Implement initial Lumi-as-a-library
This is the initial step towards redefining Lumi as a library that runs
atop vanilla Node.js/V8, rather than as its own runtime.
This change is woefully incomplete but this includes some of the more
stable pieces of my current work-in-progress.
The new structure is that within the sdk/ directory we will have a client
library per language. This client library contains the object model for
Lumi (resources, properties, assets, config, etc), in addition to the
"language runtime host" components required to interoperate with the
Lumi resource monitor. This resource monitor is effectively what we call
"Lumi" today, in that it's the thing orchestrating plans and deployments.
Inside the sdk/ directory, you will find nodejs/, the Node.js client
library, alongside proto/, the definitions for RPC interop between the
different pieces of the system. This includes existing RPC definitions
for resource providers, etc., in addition to the new ones for hosting
different language runtimes from within Lumi.
These new interfaces are surprisingly simple. There is effectively a
bidirectional RPC channel between the Lumi resource monitor, represented
by the lumirpc.ResourceMonitor interface, and each language runtime,
represented by the lumirpc.LanguageRuntime interface.
The overall orchestration goes as follows:
1) Lumi decides it needs to run a program written in language X, so
it dynamically loads the language runtime plugin for language X.
2) Lumi passes that runtime a loopback address to its ResourceMonitor
service, while language X will publish a connection back to its
LanguageRuntime service, which Lumi will talk to.
3) Lumi then invokes LanguageRuntime.Run, passing information like
the desired working directory, program name, arguments, and optional
configuration variables to make available to the program.
4) The language X runtime receives this, unpacks it and sets up the
necessary context, and then invokes the program. The program then
calls into Lumi object model abstractions that internally communicate
back to Lumi using the ResourceMonitor interface.
5) The key here is ResourceMonitor.NewResource, which Lumi uses to
serialize state about newly allocated resources. Lumi receives these
and registers them as part of the plan, doing the usual diffing, etc.,
to decide how to proceed. This interface is perhaps one of the
most subtle parts of the new design, as it necessitates the use of
promises internally to allow parallel evaluation of the resource plan,
letting dataflow determine the available concurrency.
6) The program exits, and Lumi continues on its merry way. If the program
fails, the RunResponse will include information about the failure.
Due to (5), all properties on resources are now instances of a new
Property<T> type. A Property<T> is just a thin wrapper over a T, but it
encodes the special properties of Lumi resource properties. Namely, it
is possible to create one out of a T, other Property<T>, Promise<T>, or
to freshly allocate one. In all cases, the Property<T> does not "settle"
until its final state is known. This cannot occur before the deployment
actually completes, and so in general it's not safe to depend on concrete
resolutions of values (unlike ordinary Promise<T>s which are usually
expected to resolve). As a result, all derived computations are meant to
use the `then` function (as in `someValue.then(v => v+x)`).
Although this change includes tests that may be run in isolation to test
the various RPC interactions, we are nowhere near finished. The remaining
work primarily boils down to three things:
1) Wiring all of this up to the Lumi code.
2) Fixing the handful of known loose ends required to make this work,
primarily around the serialization of properties (waiting on
unresolved ones, serializing assets properly, etc).
3) Implementing lambda closure serialization as a native extension.
This ongoing work is part of pulumi/pulumi-fabric#311.
2017-08-26 21:07:54 +02:00
|
|
|
}
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
if (!name) {
|
2018-09-25 01:57:20 +02:00
|
|
|
throw new ResourceError("Missing resource name argument (for URN creation)", opts.parent);
|
Implement initial Lumi-as-a-library
This is the initial step towards redefining Lumi as a library that runs
atop vanilla Node.js/V8, rather than as its own runtime.
This change is woefully incomplete but this includes some of the more
stable pieces of my current work-in-progress.
The new structure is that within the sdk/ directory we will have a client
library per language. This client library contains the object model for
Lumi (resources, properties, assets, config, etc), in addition to the
"language runtime host" components required to interoperate with the
Lumi resource monitor. This resource monitor is effectively what we call
"Lumi" today, in that it's the thing orchestrating plans and deployments.
Inside the sdk/ directory, you will find nodejs/, the Node.js client
library, alongside proto/, the definitions for RPC interop between the
different pieces of the system. This includes existing RPC definitions
for resource providers, etc., in addition to the new ones for hosting
different language runtimes from within Lumi.
These new interfaces are surprisingly simple. There is effectively a
bidirectional RPC channel between the Lumi resource monitor, represented
by the lumirpc.ResourceMonitor interface, and each language runtime,
represented by the lumirpc.LanguageRuntime interface.
The overall orchestration goes as follows:
1) Lumi decides it needs to run a program written in language X, so
it dynamically loads the language runtime plugin for language X.
2) Lumi passes that runtime a loopback address to its ResourceMonitor
service, while language X will publish a connection back to its
LanguageRuntime service, which Lumi will talk to.
3) Lumi then invokes LanguageRuntime.Run, passing information like
the desired working directory, program name, arguments, and optional
configuration variables to make available to the program.
4) The language X runtime receives this, unpacks it and sets up the
necessary context, and then invokes the program. The program then
calls into Lumi object model abstractions that internally communicate
back to Lumi using the ResourceMonitor interface.
5) The key here is ResourceMonitor.NewResource, which Lumi uses to
serialize state about newly allocated resources. Lumi receives these
and registers them as part of the plan, doing the usual diffing, etc.,
to decide how to proceed. This interface is perhaps one of the
most subtle parts of the new design, as it necessitates the use of
promises internally to allow parallel evaluation of the resource plan,
letting dataflow determine the available concurrency.
6) The program exits, and Lumi continues on its merry way. If the program
fails, the RunResponse will include information about the failure.
Due to (5), all properties on resources are now instances of a new
Property<T> type. A Property<T> is just a thin wrapper over a T, but it
encodes the special properties of Lumi resource properties. Namely, it
is possible to create one out of a T, other Property<T>, Promise<T>, or
to freshly allocate one. In all cases, the Property<T> does not "settle"
until its final state is known. This cannot occur before the deployment
actually completes, and so in general it's not safe to depend on concrete
resolutions of values (unlike ordinary Promise<T>s which are usually
expected to resolve). As a result, all derived computations are meant to
use the `then` function (as in `someValue.then(v => v+x)`).
Although this change includes tests that may be run in isolation to test
the various RPC interactions, we are nowhere near finished. The remaining
work primarily boils down to three things:
1) Wiring all of this up to the Lumi code.
2) Fixing the handful of known loose ends required to make this work,
primarily around the serialization of properties (waiting on
unresolved ones, serializing assets properly, etc).
3) Implementing lambda closure serialization as a native extension.
This ongoing work is part of pulumi/pulumi-fabric#311.
2017-08-26 21:07:54 +02:00
|
|
|
}
|
|
|
|
|
2018-09-18 20:47:34 +02:00
|
|
|
// Check the parent type if one exists and fill in any default options.
|
2018-08-11 01:18:21 +02:00
|
|
|
this.__providers = {};
|
|
|
|
if (opts.parent) {
|
|
|
|
if (!Resource.isInstance(opts.parent)) {
|
2018-09-25 01:57:20 +02:00
|
|
|
throw new ResourceError(`Resource parent is not a valid Resource: ${opts.parent}`, opts.parent);
|
2018-08-11 01:18:21 +02:00
|
|
|
}
|
2017-11-20 19:08:59 +01:00
|
|
|
|
2018-08-11 01:18:21 +02:00
|
|
|
if (opts.protect === undefined) {
|
|
|
|
opts.protect = opts.parent.__protect;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.__providers = opts.parent.__providers;
|
2018-09-07 18:42:19 +02:00
|
|
|
|
2018-08-11 01:18:21 +02:00
|
|
|
if (custom) {
|
|
|
|
const provider = (<CustomResourceOptions>opts).provider;
|
|
|
|
if (provider === undefined) {
|
|
|
|
(<CustomResourceOptions>opts).provider = opts.parent.getProvider(t);
|
2018-12-05 21:35:19 +01:00
|
|
|
} else {
|
|
|
|
// If a provider was specified, add it to the providers map under this type's package so that
|
|
|
|
// any children of this resource inherit its provider.
|
|
|
|
const typeComponents = t.split(":");
|
|
|
|
if (typeComponents.length === 3) {
|
|
|
|
const pkg = typeComponents[0];
|
|
|
|
this.__providers = { ...this.__providers, [pkg]: provider };
|
|
|
|
}
|
2018-08-11 01:18:21 +02:00
|
|
|
}
|
2018-09-07 18:42:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!custom) {
|
|
|
|
const providers = (<ComponentResourceOptions>opts).providers;
|
|
|
|
if (providers) {
|
|
|
|
this.__providers = { ...this.__providers, ...providers };
|
2018-08-11 01:18:21 +02:00
|
|
|
}
|
2018-04-25 02:23:18 +02:00
|
|
|
}
|
2018-08-11 01:18:21 +02:00
|
|
|
this.__protect = !!opts.protect;
|
2018-04-25 02:23:18 +02:00
|
|
|
|
2018-04-07 19:15:58 +02:00
|
|
|
if (opts.id) {
|
2018-04-05 18:48:09 +02:00
|
|
|
// If this resource already exists, read its state rather than registering it anew.
|
|
|
|
if (!custom) {
|
2018-09-25 01:57:20 +02:00
|
|
|
throw new ResourceError(
|
|
|
|
"Cannot read an existing resource unless it has a custom provider", opts.parent);
|
2018-04-05 18:48:09 +02:00
|
|
|
}
|
2018-04-07 19:15:58 +02:00
|
|
|
readResource(this, t, name, props, opts);
|
2018-04-05 18:48:09 +02:00
|
|
|
} else {
|
|
|
|
// Kick off the resource registration. If we are actually performing a deployment, this
|
|
|
|
// resource's properties will be resolved asynchronously after the operation completes, so
|
|
|
|
// that dependent computations resolve normally. If we are just planning, on the other
|
|
|
|
// hand, values will never resolve.
|
|
|
|
registerResource(this, t, name, custom, props, opts);
|
|
|
|
}
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-11 09:11:53 +01:00
|
|
|
(<any>Resource).doNotCapture = true;
|
|
|
|
|
Implement resource protection (#751)
This change implements resource protection, as per pulumi/pulumi#689.
The overall idea is that a resource can be marked as "protect: true",
which will prevent deletion of that resource for any reason whatsoever
(straight deletion, replacement, etc). This is expressed in the
program. To "unprotect" a resource, one must perform an update setting
"protect: false", and then afterwards, they can delete the resource.
For example:
let res = new MyResource("precious", { .. }, { protect: true });
Afterwards, the resource will display in the CLI with a lock icon, and
any attempts to remove it will fail in the usual ways (in planning or,
worst case, during an actual update).
This was done by adding a new ResourceOptions bag parameter to the
base Resource types. This is unfortunately a breaking change, but now
is the right time to take this one. We had been adding new settings
one by one -- like parent and dependsOn -- and this new approach will
set us up to add any number of additional settings down the road,
without needing to worry about breaking anything ever again.
This is related to protected stacks, as described in
pulumi/pulumi-service#399. Most likely this will serve as a foundational
building block that enables the coarser grained policy management.
2017-12-20 23:31:07 +01:00
|
|
|
/**
|
|
|
|
* ResourceOptions is a bag of optional settings that control a resource's behavior.
|
|
|
|
*/
|
|
|
|
export interface ResourceOptions {
|
2018-04-07 19:15:58 +02:00
|
|
|
/**
|
|
|
|
* An optional existing ID to load, rather than create.
|
|
|
|
*/
|
|
|
|
id?: Input<ID>;
|
Implement resource protection (#751)
This change implements resource protection, as per pulumi/pulumi#689.
The overall idea is that a resource can be marked as "protect: true",
which will prevent deletion of that resource for any reason whatsoever
(straight deletion, replacement, etc). This is expressed in the
program. To "unprotect" a resource, one must perform an update setting
"protect: false", and then afterwards, they can delete the resource.
For example:
let res = new MyResource("precious", { .. }, { protect: true });
Afterwards, the resource will display in the CLI with a lock icon, and
any attempts to remove it will fail in the usual ways (in planning or,
worst case, during an actual update).
This was done by adding a new ResourceOptions bag parameter to the
base Resource types. This is unfortunately a breaking change, but now
is the right time to take this one. We had been adding new settings
one by one -- like parent and dependsOn -- and this new approach will
set us up to add any number of additional settings down the road,
without needing to worry about breaking anything ever again.
This is related to protected stacks, as described in
pulumi/pulumi-service#399. Most likely this will serve as a foundational
building block that enables the coarser grained policy management.
2017-12-20 23:31:07 +01:00
|
|
|
/**
|
|
|
|
* An optional parent resource to which this resource belongs.
|
|
|
|
*/
|
|
|
|
parent?: Resource;
|
|
|
|
/**
|
|
|
|
* An optional additional explicit dependencies on other resources.
|
|
|
|
*/
|
2018-11-19 17:22:55 +01:00
|
|
|
dependsOn?: Input<Input<Resource>[]> | Input<Resource>;
|
Implement resource protection (#751)
This change implements resource protection, as per pulumi/pulumi#689.
The overall idea is that a resource can be marked as "protect: true",
which will prevent deletion of that resource for any reason whatsoever
(straight deletion, replacement, etc). This is expressed in the
program. To "unprotect" a resource, one must perform an update setting
"protect: false", and then afterwards, they can delete the resource.
For example:
let res = new MyResource("precious", { .. }, { protect: true });
Afterwards, the resource will display in the CLI with a lock icon, and
any attempts to remove it will fail in the usual ways (in planning or,
worst case, during an actual update).
This was done by adding a new ResourceOptions bag parameter to the
base Resource types. This is unfortunately a breaking change, but now
is the right time to take this one. We had been adding new settings
one by one -- like parent and dependsOn -- and this new approach will
set us up to add any number of additional settings down the road,
without needing to worry about breaking anything ever again.
This is related to protected stacks, as described in
pulumi/pulumi-service#399. Most likely this will serve as a foundational
building block that enables the coarser grained policy management.
2017-12-20 23:31:07 +01:00
|
|
|
/**
|
|
|
|
* When set to true, protect ensures this resource cannot be deleted.
|
|
|
|
*/
|
|
|
|
protect?: boolean;
|
2018-08-11 01:18:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* CustomResourceOptions is a bag of optional settings that control a custom resource's behavior.
|
|
|
|
*/
|
|
|
|
export interface CustomResourceOptions extends ResourceOptions {
|
Implement first-class providers. (#1695)
### First-Class Providers
These changes implement support for first-class providers. First-class
providers are provider plugins that are exposed as resources via the
Pulumi programming model so that they may be explicitly and multiply
instantiated. Each instance of a provider resource may be configured
differently, and configuration parameters may be source from the
outputs of other resources.
### Provider Plugin Changes
In order to accommodate the need to verify and diff provider
configuration and configure providers without complete configuration
information, these changes adjust the high-level provider plugin
interface. Two new methods for validating a provider's configuration
and diffing changes to the same have been added (`CheckConfig` and
`DiffConfig`, respectively), and the type of the configuration bag
accepted by `Configure` has been changed to a `PropertyMap`.
These changes have not yet been reflected in the provider plugin gRPC
interface. We will do this in a set of follow-up changes. Until then,
these methods are implemented by adapters:
- `CheckConfig` validates that all configuration parameters are string
or unknown properties. This is necessary because existing plugins
only accept string-typed configuration values.
- `DiffConfig` either returns "never replace" if all configuration
values are known or "must replace" if any configuration value is
unknown. The justification for this behavior is given
[here](https://github.com/pulumi/pulumi/pull/1695/files#diff-a6cd5c7f337665f5bb22e92ca5f07537R106)
- `Configure` converts the config bag to a legacy config map and
configures the provider plugin if all config values are known. If any
config value is unknown, the underlying plugin is not configured and
the provider may only perform `Check`, `Read`, and `Invoke`, all of
which return empty results. We justify this behavior becuase it is
only possible during a preview and provides the best experience we
can manage with the existing gRPC interface.
### Resource Model Changes
Providers are now exposed as resources that participate in a stack's
dependency graph. Like other resources, they are explicitly created,
may have multiple instances, and may have dependencies on other
resources. Providers are referred to using provider references, which
are a combination of the provider's URN and its ID. This design
addresses the need during a preview to refer to providers that have not
yet been physically created and therefore have no ID.
All custom resources that are not themselves providers must specify a
single provider via a provider reference. The named provider will be
used to manage that resource's CRUD operations. If a resource's
provider reference changes, the resource must be replaced. Though its
URN is not present in the resource's dependency list, the provider
should be treated as a dependency of the resource when topologically
sorting the dependency graph.
Finally, `Invoke` operations must now specify a provider to use for the
invocation via a provider reference.
### Engine Changes
First-class providers support requires a few changes to the engine:
- The engine must have some way to map from provider references to
provider plugins. It must be possible to add providers from a stack's
checkpoint to this map and to register new/updated providers during
the execution of a plan in response to CRUD operations on provider
resources.
- In order to support updating existing stacks using existing Pulumi
programs that may not explicitly instantiate providers, the engine
must be able to manage the "default" providers for each package
referenced by a checkpoint or Pulumi program. The configuration for
a "default" provider is taken from the stack's configuration data.
The former need is addressed by adding a provider registry type that is
responsible for managing all of the plugins required by a plan. In
addition to loading plugins froma checkpoint and providing the ability
to map from a provider reference to a provider plugin, this type serves
as the provider plugin for providers themselves (i.e. it is the
"provider provider").
The latter need is solved via two relatively self-contained changes to
plan setup and the eval source.
During plan setup, the old checkpoint is scanned for custom resources
that do not have a provider reference in order to compute the set of
packages that require a default provider. Once this set has been
computed, the required default provider definitions are conjured and
prepended to the checkpoint's resource list. Each resource that
requires a default provider is then updated to refer to the default
provider for its package.
While an eval source is running, each custom resource registration,
resource read, and invoke that does not name a provider is trapped
before being returned by the source iterator. If no default provider
for the appropriate package has been registered, the eval source
synthesizes an appropriate registration, waits for it to complete, and
records the registered provider's reference. This reference is injected
into the original request, which is then processed as usual. If a
default provider was already registered, the recorded reference is
used and no new registration occurs.
### SDK Changes
These changes only expose first-class providers from the Node.JS SDK.
- A new abstract class, `ProviderResource`, can be subclassed and used
to instantiate first-class providers.
- A new field in `ResourceOptions`, `provider`, can be used to supply
a particular provider instance to manage a `CustomResource`'s CRUD
operations.
- A new type, `InvokeOptions`, can be used to specify options that
control the behavior of a call to `pulumi.runtime.invoke`. This type
includes a `provider` field that is analogous to
`ResourceOptions.provider`.
2018-08-07 02:50:29 +02:00
|
|
|
/**
|
|
|
|
* An optional provider to use for this resource's CRUD operations. If no provider is supplied, the default
|
2018-08-11 01:18:21 +02:00
|
|
|
* provider for the resource's package will be used. The default provider is pulled from the parent's
|
|
|
|
* provider bag (see also ComponentResourceOptions.providers).
|
Implement first-class providers. (#1695)
### First-Class Providers
These changes implement support for first-class providers. First-class
providers are provider plugins that are exposed as resources via the
Pulumi programming model so that they may be explicitly and multiply
instantiated. Each instance of a provider resource may be configured
differently, and configuration parameters may be source from the
outputs of other resources.
### Provider Plugin Changes
In order to accommodate the need to verify and diff provider
configuration and configure providers without complete configuration
information, these changes adjust the high-level provider plugin
interface. Two new methods for validating a provider's configuration
and diffing changes to the same have been added (`CheckConfig` and
`DiffConfig`, respectively), and the type of the configuration bag
accepted by `Configure` has been changed to a `PropertyMap`.
These changes have not yet been reflected in the provider plugin gRPC
interface. We will do this in a set of follow-up changes. Until then,
these methods are implemented by adapters:
- `CheckConfig` validates that all configuration parameters are string
or unknown properties. This is necessary because existing plugins
only accept string-typed configuration values.
- `DiffConfig` either returns "never replace" if all configuration
values are known or "must replace" if any configuration value is
unknown. The justification for this behavior is given
[here](https://github.com/pulumi/pulumi/pull/1695/files#diff-a6cd5c7f337665f5bb22e92ca5f07537R106)
- `Configure` converts the config bag to a legacy config map and
configures the provider plugin if all config values are known. If any
config value is unknown, the underlying plugin is not configured and
the provider may only perform `Check`, `Read`, and `Invoke`, all of
which return empty results. We justify this behavior becuase it is
only possible during a preview and provides the best experience we
can manage with the existing gRPC interface.
### Resource Model Changes
Providers are now exposed as resources that participate in a stack's
dependency graph. Like other resources, they are explicitly created,
may have multiple instances, and may have dependencies on other
resources. Providers are referred to using provider references, which
are a combination of the provider's URN and its ID. This design
addresses the need during a preview to refer to providers that have not
yet been physically created and therefore have no ID.
All custom resources that are not themselves providers must specify a
single provider via a provider reference. The named provider will be
used to manage that resource's CRUD operations. If a resource's
provider reference changes, the resource must be replaced. Though its
URN is not present in the resource's dependency list, the provider
should be treated as a dependency of the resource when topologically
sorting the dependency graph.
Finally, `Invoke` operations must now specify a provider to use for the
invocation via a provider reference.
### Engine Changes
First-class providers support requires a few changes to the engine:
- The engine must have some way to map from provider references to
provider plugins. It must be possible to add providers from a stack's
checkpoint to this map and to register new/updated providers during
the execution of a plan in response to CRUD operations on provider
resources.
- In order to support updating existing stacks using existing Pulumi
programs that may not explicitly instantiate providers, the engine
must be able to manage the "default" providers for each package
referenced by a checkpoint or Pulumi program. The configuration for
a "default" provider is taken from the stack's configuration data.
The former need is addressed by adding a provider registry type that is
responsible for managing all of the plugins required by a plan. In
addition to loading plugins froma checkpoint and providing the ability
to map from a provider reference to a provider plugin, this type serves
as the provider plugin for providers themselves (i.e. it is the
"provider provider").
The latter need is solved via two relatively self-contained changes to
plan setup and the eval source.
During plan setup, the old checkpoint is scanned for custom resources
that do not have a provider reference in order to compute the set of
packages that require a default provider. Once this set has been
computed, the required default provider definitions are conjured and
prepended to the checkpoint's resource list. Each resource that
requires a default provider is then updated to refer to the default
provider for its package.
While an eval source is running, each custom resource registration,
resource read, and invoke that does not name a provider is trapped
before being returned by the source iterator. If no default provider
for the appropriate package has been registered, the eval source
synthesizes an appropriate registration, waits for it to complete, and
records the registered provider's reference. This reference is injected
into the original request, which is then processed as usual. If a
default provider was already registered, the recorded reference is
used and no new registration occurs.
### SDK Changes
These changes only expose first-class providers from the Node.JS SDK.
- A new abstract class, `ProviderResource`, can be subclassed and used
to instantiate first-class providers.
- A new field in `ResourceOptions`, `provider`, can be used to supply
a particular provider instance to manage a `CustomResource`'s CRUD
operations.
- A new type, `InvokeOptions`, can be used to specify options that
control the behavior of a call to `pulumi.runtime.invoke`. This type
includes a `provider` field that is analogous to
`ResourceOptions.provider`.
2018-08-07 02:50:29 +02:00
|
|
|
*/
|
|
|
|
provider?: ProviderResource;
|
Implement resource protection (#751)
This change implements resource protection, as per pulumi/pulumi#689.
The overall idea is that a resource can be marked as "protect: true",
which will prevent deletion of that resource for any reason whatsoever
(straight deletion, replacement, etc). This is expressed in the
program. To "unprotect" a resource, one must perform an update setting
"protect: false", and then afterwards, they can delete the resource.
For example:
let res = new MyResource("precious", { .. }, { protect: true });
Afterwards, the resource will display in the CLI with a lock icon, and
any attempts to remove it will fail in the usual ways (in planning or,
worst case, during an actual update).
This was done by adding a new ResourceOptions bag parameter to the
base Resource types. This is unfortunately a breaking change, but now
is the right time to take this one. We had been adding new settings
one by one -- like parent and dependsOn -- and this new approach will
set us up to add any number of additional settings down the road,
without needing to worry about breaking anything ever again.
This is related to protected stacks, as described in
pulumi/pulumi-service#399. Most likely this will serve as a foundational
building block that enables the coarser grained policy management.
2017-12-20 23:31:07 +01:00
|
|
|
}
|
|
|
|
|
2018-08-11 01:18:21 +02:00
|
|
|
/**
|
|
|
|
* ComponentResourceOptions is a bag of optional settings that control a component resource's behavior.
|
|
|
|
*/
|
|
|
|
export interface ComponentResourceOptions extends ResourceOptions {
|
|
|
|
/**
|
|
|
|
* An optional set of providers to use for child resources. Keyed by package name (e.g. "aws")
|
|
|
|
*/
|
|
|
|
providers?: Record<string, ProviderResource>;
|
|
|
|
}
|
|
|
|
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
/**
|
2018-01-26 03:50:58 +01:00
|
|
|
* CustomResource is a resource whose create, read, update, and delete (CRUD) operations are managed
|
|
|
|
* by performing external operations on some physical entity. The engine understands how to diff
|
|
|
|
* and perform partial updates of them, and these CRUD operations are implemented in a dynamically
|
|
|
|
* loaded plugin for the defining package.
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
*/
|
2017-10-15 12:52:04 +02:00
|
|
|
export abstract class CustomResource extends Resource {
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
/**
|
|
|
|
* A private field to help with RTTI that works in SxS scenarios.
|
|
|
|
*/
|
|
|
|
// tslint:disable-next-line:variable-name
|
2018-06-08 06:34:06 +02:00
|
|
|
/* @internal */ private readonly __pulumiCustomResource: boolean = true;
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
/**
|
2018-01-26 03:50:58 +01:00
|
|
|
* id is the provider-assigned unique ID for this managed resource. It is set during
|
|
|
|
* deployments and may be missing (undefined) during planning phases.
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
*/
|
2018-02-06 03:37:10 +01:00
|
|
|
public readonly id: Output<ID>;
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-04-17 00:03:23 +02:00
|
|
|
public static isInstance(obj: any): obj is CustomResource {
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
return obj && obj.__pulumiCustomResource;
|
|
|
|
}
|
|
|
|
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
/**
|
2018-01-26 03:50:58 +01:00
|
|
|
* Creates and registers a new managed resource. t 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.
|
|
|
|
* dependsOn is an optional list of other resources that this resource depends on, controlling
|
|
|
|
* the order in which we perform resource operations. Creating an instance does not necessarily
|
|
|
|
* perform a create on the physical entity which it represents, and instead, this is dependent
|
|
|
|
* upon the diffing of the new goal state compared to the current known resource state.
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
*
|
|
|
|
* @param t The type of the resource.
|
2018-02-08 00:01:55 +01:00
|
|
|
* @param name The _unique_ name of the resource.
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
* @param props The arguments to use to populate the new resource.
|
Implement resource protection (#751)
This change implements resource protection, as per pulumi/pulumi#689.
The overall idea is that a resource can be marked as "protect: true",
which will prevent deletion of that resource for any reason whatsoever
(straight deletion, replacement, etc). This is expressed in the
program. To "unprotect" a resource, one must perform an update setting
"protect: false", and then afterwards, they can delete the resource.
For example:
let res = new MyResource("precious", { .. }, { protect: true });
Afterwards, the resource will display in the CLI with a lock icon, and
any attempts to remove it will fail in the usual ways (in planning or,
worst case, during an actual update).
This was done by adding a new ResourceOptions bag parameter to the
base Resource types. This is unfortunately a breaking change, but now
is the right time to take this one. We had been adding new settings
one by one -- like parent and dependsOn -- and this new approach will
set us up to add any number of additional settings down the road,
without needing to worry about breaking anything ever again.
This is related to protected stacks, as described in
pulumi/pulumi-service#399. Most likely this will serve as a foundational
building block that enables the coarser grained policy management.
2017-12-20 23:31:07 +01:00
|
|
|
* @param opts A bag of options that control this resource's behavior.
|
Implement components
This change implements core support for "components" in the Pulumi
Fabric. This work is described further in pulumi/pulumi#340, where
we are still discussing some of the finer points.
In a nutshell, resources no longer imply external providers. It's
entirely possible to have a resource that logically represents
something but without having a physical manifestation that needs to
be tracked and managed by our typical CRUD operations.
For example, the aws/serverless/Function helper is one such type.
It aggregates Lambda-related resources and exposes a nice interface.
All of the Pulumi Cloud Framework resources are also examples.
To indicate that a resource does participate in the usual CRUD resource
provider, it simply derives from ExternalResource instead of Resource.
All resources now have the ability to adopt children. This is purely
a metadata/tagging thing, and will help us roll up displays, provide
attribution to the developer, and even hide aspects of the resource
graph as appropriate (e.g., when they are implementation details).
Our use of this capability is ultra limited right now; in fact, the
only place we display children is in the CLI output. For instance:
+ aws:serverless:Function: (create)
[urn=urn:pulumi:demo::serverless::aws:serverless:Function::mylambda]
=> urn:pulumi:demo::serverless::aws:iam/role:Role::mylambda-iamrole
=> urn:pulumi:demo::serverless::aws:iam/rolePolicyAttachment:RolePolicyAttachment::mylambda-iampolicy-0
=> urn:pulumi:demo::serverless::aws:lambda/function:Function::mylambda
The bit indicating whether a resource is external or not is tracked
in the resulting checkpoint file, along with any of its children.
2017-10-14 23:18:43 +02:00
|
|
|
*/
|
2018-08-11 01:18:21 +02:00
|
|
|
constructor(t: string, name: string, props?: Inputs, opts?: CustomResourceOptions) {
|
2018-04-07 19:15:58 +02:00
|
|
|
super(t, name, true, props, opts);
|
Implement initial Lumi-as-a-library
This is the initial step towards redefining Lumi as a library that runs
atop vanilla Node.js/V8, rather than as its own runtime.
This change is woefully incomplete but this includes some of the more
stable pieces of my current work-in-progress.
The new structure is that within the sdk/ directory we will have a client
library per language. This client library contains the object model for
Lumi (resources, properties, assets, config, etc), in addition to the
"language runtime host" components required to interoperate with the
Lumi resource monitor. This resource monitor is effectively what we call
"Lumi" today, in that it's the thing orchestrating plans and deployments.
Inside the sdk/ directory, you will find nodejs/, the Node.js client
library, alongside proto/, the definitions for RPC interop between the
different pieces of the system. This includes existing RPC definitions
for resource providers, etc., in addition to the new ones for hosting
different language runtimes from within Lumi.
These new interfaces are surprisingly simple. There is effectively a
bidirectional RPC channel between the Lumi resource monitor, represented
by the lumirpc.ResourceMonitor interface, and each language runtime,
represented by the lumirpc.LanguageRuntime interface.
The overall orchestration goes as follows:
1) Lumi decides it needs to run a program written in language X, so
it dynamically loads the language runtime plugin for language X.
2) Lumi passes that runtime a loopback address to its ResourceMonitor
service, while language X will publish a connection back to its
LanguageRuntime service, which Lumi will talk to.
3) Lumi then invokes LanguageRuntime.Run, passing information like
the desired working directory, program name, arguments, and optional
configuration variables to make available to the program.
4) The language X runtime receives this, unpacks it and sets up the
necessary context, and then invokes the program. The program then
calls into Lumi object model abstractions that internally communicate
back to Lumi using the ResourceMonitor interface.
5) The key here is ResourceMonitor.NewResource, which Lumi uses to
serialize state about newly allocated resources. Lumi receives these
and registers them as part of the plan, doing the usual diffing, etc.,
to decide how to proceed. This interface is perhaps one of the
most subtle parts of the new design, as it necessitates the use of
promises internally to allow parallel evaluation of the resource plan,
letting dataflow determine the available concurrency.
6) The program exits, and Lumi continues on its merry way. If the program
fails, the RunResponse will include information about the failure.
Due to (5), all properties on resources are now instances of a new
Property<T> type. A Property<T> is just a thin wrapper over a T, but it
encodes the special properties of Lumi resource properties. Namely, it
is possible to create one out of a T, other Property<T>, Promise<T>, or
to freshly allocate one. In all cases, the Property<T> does not "settle"
until its final state is known. This cannot occur before the deployment
actually completes, and so in general it's not safe to depend on concrete
resolutions of values (unlike ordinary Promise<T>s which are usually
expected to resolve). As a result, all derived computations are meant to
use the `then` function (as in `someValue.then(v => v+x)`).
Although this change includes tests that may be run in isolation to test
the various RPC interactions, we are nowhere near finished. The remaining
work primarily boils down to three things:
1) Wiring all of this up to the Lumi code.
2) Fixing the handful of known loose ends required to make this work,
primarily around the serialization of properties (waiting on
unresolved ones, serializing assets properly, etc).
3) Implementing lambda closure serialization as a native extension.
This ongoing work is part of pulumi/pulumi-fabric#311.
2017-08-26 21:07:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-11 09:11:53 +01:00
|
|
|
(<any>CustomResource).doNotCapture = true;
|
|
|
|
|
Implement first-class providers. (#1695)
### First-Class Providers
These changes implement support for first-class providers. First-class
providers are provider plugins that are exposed as resources via the
Pulumi programming model so that they may be explicitly and multiply
instantiated. Each instance of a provider resource may be configured
differently, and configuration parameters may be source from the
outputs of other resources.
### Provider Plugin Changes
In order to accommodate the need to verify and diff provider
configuration and configure providers without complete configuration
information, these changes adjust the high-level provider plugin
interface. Two new methods for validating a provider's configuration
and diffing changes to the same have been added (`CheckConfig` and
`DiffConfig`, respectively), and the type of the configuration bag
accepted by `Configure` has been changed to a `PropertyMap`.
These changes have not yet been reflected in the provider plugin gRPC
interface. We will do this in a set of follow-up changes. Until then,
these methods are implemented by adapters:
- `CheckConfig` validates that all configuration parameters are string
or unknown properties. This is necessary because existing plugins
only accept string-typed configuration values.
- `DiffConfig` either returns "never replace" if all configuration
values are known or "must replace" if any configuration value is
unknown. The justification for this behavior is given
[here](https://github.com/pulumi/pulumi/pull/1695/files#diff-a6cd5c7f337665f5bb22e92ca5f07537R106)
- `Configure` converts the config bag to a legacy config map and
configures the provider plugin if all config values are known. If any
config value is unknown, the underlying plugin is not configured and
the provider may only perform `Check`, `Read`, and `Invoke`, all of
which return empty results. We justify this behavior becuase it is
only possible during a preview and provides the best experience we
can manage with the existing gRPC interface.
### Resource Model Changes
Providers are now exposed as resources that participate in a stack's
dependency graph. Like other resources, they are explicitly created,
may have multiple instances, and may have dependencies on other
resources. Providers are referred to using provider references, which
are a combination of the provider's URN and its ID. This design
addresses the need during a preview to refer to providers that have not
yet been physically created and therefore have no ID.
All custom resources that are not themselves providers must specify a
single provider via a provider reference. The named provider will be
used to manage that resource's CRUD operations. If a resource's
provider reference changes, the resource must be replaced. Though its
URN is not present in the resource's dependency list, the provider
should be treated as a dependency of the resource when topologically
sorting the dependency graph.
Finally, `Invoke` operations must now specify a provider to use for the
invocation via a provider reference.
### Engine Changes
First-class providers support requires a few changes to the engine:
- The engine must have some way to map from provider references to
provider plugins. It must be possible to add providers from a stack's
checkpoint to this map and to register new/updated providers during
the execution of a plan in response to CRUD operations on provider
resources.
- In order to support updating existing stacks using existing Pulumi
programs that may not explicitly instantiate providers, the engine
must be able to manage the "default" providers for each package
referenced by a checkpoint or Pulumi program. The configuration for
a "default" provider is taken from the stack's configuration data.
The former need is addressed by adding a provider registry type that is
responsible for managing all of the plugins required by a plan. In
addition to loading plugins froma checkpoint and providing the ability
to map from a provider reference to a provider plugin, this type serves
as the provider plugin for providers themselves (i.e. it is the
"provider provider").
The latter need is solved via two relatively self-contained changes to
plan setup and the eval source.
During plan setup, the old checkpoint is scanned for custom resources
that do not have a provider reference in order to compute the set of
packages that require a default provider. Once this set has been
computed, the required default provider definitions are conjured and
prepended to the checkpoint's resource list. Each resource that
requires a default provider is then updated to refer to the default
provider for its package.
While an eval source is running, each custom resource registration,
resource read, and invoke that does not name a provider is trapped
before being returned by the source iterator. If no default provider
for the appropriate package has been registered, the eval source
synthesizes an appropriate registration, waits for it to complete, and
records the registered provider's reference. This reference is injected
into the original request, which is then processed as usual. If a
default provider was already registered, the recorded reference is
used and no new registration occurs.
### SDK Changes
These changes only expose first-class providers from the Node.JS SDK.
- A new abstract class, `ProviderResource`, can be subclassed and used
to instantiate first-class providers.
- A new field in `ResourceOptions`, `provider`, can be used to supply
a particular provider instance to manage a `CustomResource`'s CRUD
operations.
- A new type, `InvokeOptions`, can be used to specify options that
control the behavior of a call to `pulumi.runtime.invoke`. This type
includes a `provider` field that is analogous to
`ResourceOptions.provider`.
2018-08-07 02:50:29 +02:00
|
|
|
/**
|
|
|
|
* ProviderResource is a resource that implements CRUD operations for other custom resources. These resources are
|
|
|
|
* managed similarly to other resources, including the usual diffing and update semantics.
|
|
|
|
*/
|
|
|
|
export abstract class ProviderResource extends CustomResource {
|
|
|
|
/**
|
|
|
|
* Creates and registers a new provider resource for a particular package.
|
|
|
|
*
|
|
|
|
* @param pkg The package associated with this provider.
|
|
|
|
* @param name The _unique_ name of the provider.
|
|
|
|
* @param props The configuration to use for this provider.
|
|
|
|
* @param opts A bag of options that control this provider's behavior.
|
|
|
|
*/
|
2018-09-25 01:57:20 +02:00
|
|
|
constructor(pkg: string, name: string, props?: Inputs, opts: ResourceOptions = {}) {
|
|
|
|
if ((<any>opts).provider !== undefined) {
|
|
|
|
throw new ResourceError("Explicit providers may not be used with provider resources", opts.parent);
|
Implement first-class providers. (#1695)
### First-Class Providers
These changes implement support for first-class providers. First-class
providers are provider plugins that are exposed as resources via the
Pulumi programming model so that they may be explicitly and multiply
instantiated. Each instance of a provider resource may be configured
differently, and configuration parameters may be source from the
outputs of other resources.
### Provider Plugin Changes
In order to accommodate the need to verify and diff provider
configuration and configure providers without complete configuration
information, these changes adjust the high-level provider plugin
interface. Two new methods for validating a provider's configuration
and diffing changes to the same have been added (`CheckConfig` and
`DiffConfig`, respectively), and the type of the configuration bag
accepted by `Configure` has been changed to a `PropertyMap`.
These changes have not yet been reflected in the provider plugin gRPC
interface. We will do this in a set of follow-up changes. Until then,
these methods are implemented by adapters:
- `CheckConfig` validates that all configuration parameters are string
or unknown properties. This is necessary because existing plugins
only accept string-typed configuration values.
- `DiffConfig` either returns "never replace" if all configuration
values are known or "must replace" if any configuration value is
unknown. The justification for this behavior is given
[here](https://github.com/pulumi/pulumi/pull/1695/files#diff-a6cd5c7f337665f5bb22e92ca5f07537R106)
- `Configure` converts the config bag to a legacy config map and
configures the provider plugin if all config values are known. If any
config value is unknown, the underlying plugin is not configured and
the provider may only perform `Check`, `Read`, and `Invoke`, all of
which return empty results. We justify this behavior becuase it is
only possible during a preview and provides the best experience we
can manage with the existing gRPC interface.
### Resource Model Changes
Providers are now exposed as resources that participate in a stack's
dependency graph. Like other resources, they are explicitly created,
may have multiple instances, and may have dependencies on other
resources. Providers are referred to using provider references, which
are a combination of the provider's URN and its ID. This design
addresses the need during a preview to refer to providers that have not
yet been physically created and therefore have no ID.
All custom resources that are not themselves providers must specify a
single provider via a provider reference. The named provider will be
used to manage that resource's CRUD operations. If a resource's
provider reference changes, the resource must be replaced. Though its
URN is not present in the resource's dependency list, the provider
should be treated as a dependency of the resource when topologically
sorting the dependency graph.
Finally, `Invoke` operations must now specify a provider to use for the
invocation via a provider reference.
### Engine Changes
First-class providers support requires a few changes to the engine:
- The engine must have some way to map from provider references to
provider plugins. It must be possible to add providers from a stack's
checkpoint to this map and to register new/updated providers during
the execution of a plan in response to CRUD operations on provider
resources.
- In order to support updating existing stacks using existing Pulumi
programs that may not explicitly instantiate providers, the engine
must be able to manage the "default" providers for each package
referenced by a checkpoint or Pulumi program. The configuration for
a "default" provider is taken from the stack's configuration data.
The former need is addressed by adding a provider registry type that is
responsible for managing all of the plugins required by a plan. In
addition to loading plugins froma checkpoint and providing the ability
to map from a provider reference to a provider plugin, this type serves
as the provider plugin for providers themselves (i.e. it is the
"provider provider").
The latter need is solved via two relatively self-contained changes to
plan setup and the eval source.
During plan setup, the old checkpoint is scanned for custom resources
that do not have a provider reference in order to compute the set of
packages that require a default provider. Once this set has been
computed, the required default provider definitions are conjured and
prepended to the checkpoint's resource list. Each resource that
requires a default provider is then updated to refer to the default
provider for its package.
While an eval source is running, each custom resource registration,
resource read, and invoke that does not name a provider is trapped
before being returned by the source iterator. If no default provider
for the appropriate package has been registered, the eval source
synthesizes an appropriate registration, waits for it to complete, and
records the registered provider's reference. This reference is injected
into the original request, which is then processed as usual. If a
default provider was already registered, the recorded reference is
used and no new registration occurs.
### SDK Changes
These changes only expose first-class providers from the Node.JS SDK.
- A new abstract class, `ProviderResource`, can be subclassed and used
to instantiate first-class providers.
- A new field in `ResourceOptions`, `provider`, can be used to supply
a particular provider instance to manage a `CustomResource`'s CRUD
operations.
- A new type, `InvokeOptions`, can be used to specify options that
control the behavior of a call to `pulumi.runtime.invoke`. This type
includes a `provider` field that is analogous to
`ResourceOptions.provider`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
super(`pulumi:providers:${pkg}`, name, props, opts);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-15 12:52:04 +02:00
|
|
|
/**
|
2018-01-26 03:50:58 +01:00
|
|
|
* ComponentResource is a resource that aggregates one or more other child resources into a higher
|
|
|
|
* level abstraction. The component resource itself is a resource, but does not require custom CRUD
|
|
|
|
* operations for provisioning.
|
2017-10-15 12:52:04 +02:00
|
|
|
*/
|
2017-11-18 00:22:41 +01:00
|
|
|
export class ComponentResource extends Resource {
|
2017-10-15 12:52:04 +02:00
|
|
|
/**
|
2018-12-16 00:32:19 +01:00
|
|
|
* 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.
|
|
|
|
* [opts.parent] is the optional parent for this component, and [opts.dependsOn] is an optional
|
|
|
|
* list of other resources that this resource depends on, controlling the order in which we
|
|
|
|
* perform resource operations.
|
2017-10-15 12:52:04 +02:00
|
|
|
*
|
|
|
|
* @param t The type of the resource.
|
2018-02-08 00:01:55 +01:00
|
|
|
* @param name The _unique_ name of the resource.
|
2018-12-16 00:32:19 +01:00
|
|
|
* @param unused [Deprecated]. Component resources do not communicate or store their properties
|
|
|
|
* with the Pulumi engine.
|
Implement resource protection (#751)
This change implements resource protection, as per pulumi/pulumi#689.
The overall idea is that a resource can be marked as "protect: true",
which will prevent deletion of that resource for any reason whatsoever
(straight deletion, replacement, etc). This is expressed in the
program. To "unprotect" a resource, one must perform an update setting
"protect: false", and then afterwards, they can delete the resource.
For example:
let res = new MyResource("precious", { .. }, { protect: true });
Afterwards, the resource will display in the CLI with a lock icon, and
any attempts to remove it will fail in the usual ways (in planning or,
worst case, during an actual update).
This was done by adding a new ResourceOptions bag parameter to the
base Resource types. This is unfortunately a breaking change, but now
is the right time to take this one. We had been adding new settings
one by one -- like parent and dependsOn -- and this new approach will
set us up to add any number of additional settings down the road,
without needing to worry about breaking anything ever again.
This is related to protected stacks, as described in
pulumi/pulumi-service#399. Most likely this will serve as a foundational
building block that enables the coarser grained policy management.
2017-12-20 23:31:07 +01:00
|
|
|
* @param opts A bag of options that control this resource's behavior.
|
2017-10-15 12:52:04 +02:00
|
|
|
*/
|
2018-12-16 00:32:19 +01:00
|
|
|
constructor(type: string, name: string, unused?: Inputs, opts: ComponentResourceOptions = {}) {
|
2018-09-25 01:57:20 +02:00
|
|
|
if ((<any>opts).provider !== undefined) {
|
|
|
|
throw new ResourceError("Explicit providers may not be used with component resources", opts.parent);
|
Implement first-class providers. (#1695)
### First-Class Providers
These changes implement support for first-class providers. First-class
providers are provider plugins that are exposed as resources via the
Pulumi programming model so that they may be explicitly and multiply
instantiated. Each instance of a provider resource may be configured
differently, and configuration parameters may be source from the
outputs of other resources.
### Provider Plugin Changes
In order to accommodate the need to verify and diff provider
configuration and configure providers without complete configuration
information, these changes adjust the high-level provider plugin
interface. Two new methods for validating a provider's configuration
and diffing changes to the same have been added (`CheckConfig` and
`DiffConfig`, respectively), and the type of the configuration bag
accepted by `Configure` has been changed to a `PropertyMap`.
These changes have not yet been reflected in the provider plugin gRPC
interface. We will do this in a set of follow-up changes. Until then,
these methods are implemented by adapters:
- `CheckConfig` validates that all configuration parameters are string
or unknown properties. This is necessary because existing plugins
only accept string-typed configuration values.
- `DiffConfig` either returns "never replace" if all configuration
values are known or "must replace" if any configuration value is
unknown. The justification for this behavior is given
[here](https://github.com/pulumi/pulumi/pull/1695/files#diff-a6cd5c7f337665f5bb22e92ca5f07537R106)
- `Configure` converts the config bag to a legacy config map and
configures the provider plugin if all config values are known. If any
config value is unknown, the underlying plugin is not configured and
the provider may only perform `Check`, `Read`, and `Invoke`, all of
which return empty results. We justify this behavior becuase it is
only possible during a preview and provides the best experience we
can manage with the existing gRPC interface.
### Resource Model Changes
Providers are now exposed as resources that participate in a stack's
dependency graph. Like other resources, they are explicitly created,
may have multiple instances, and may have dependencies on other
resources. Providers are referred to using provider references, which
are a combination of the provider's URN and its ID. This design
addresses the need during a preview to refer to providers that have not
yet been physically created and therefore have no ID.
All custom resources that are not themselves providers must specify a
single provider via a provider reference. The named provider will be
used to manage that resource's CRUD operations. If a resource's
provider reference changes, the resource must be replaced. Though its
URN is not present in the resource's dependency list, the provider
should be treated as a dependency of the resource when topologically
sorting the dependency graph.
Finally, `Invoke` operations must now specify a provider to use for the
invocation via a provider reference.
### Engine Changes
First-class providers support requires a few changes to the engine:
- The engine must have some way to map from provider references to
provider plugins. It must be possible to add providers from a stack's
checkpoint to this map and to register new/updated providers during
the execution of a plan in response to CRUD operations on provider
resources.
- In order to support updating existing stacks using existing Pulumi
programs that may not explicitly instantiate providers, the engine
must be able to manage the "default" providers for each package
referenced by a checkpoint or Pulumi program. The configuration for
a "default" provider is taken from the stack's configuration data.
The former need is addressed by adding a provider registry type that is
responsible for managing all of the plugins required by a plan. In
addition to loading plugins froma checkpoint and providing the ability
to map from a provider reference to a provider plugin, this type serves
as the provider plugin for providers themselves (i.e. it is the
"provider provider").
The latter need is solved via two relatively self-contained changes to
plan setup and the eval source.
During plan setup, the old checkpoint is scanned for custom resources
that do not have a provider reference in order to compute the set of
packages that require a default provider. Once this set has been
computed, the required default provider definitions are conjured and
prepended to the checkpoint's resource list. Each resource that
requires a default provider is then updated to refer to the default
provider for its package.
While an eval source is running, each custom resource registration,
resource read, and invoke that does not name a provider is trapped
before being returned by the source iterator. If no default provider
for the appropriate package has been registered, the eval source
synthesizes an appropriate registration, waits for it to complete, and
records the registered provider's reference. This reference is injected
into the original request, which is then processed as usual. If a
default provider was already registered, the recorded reference is
used and no new registration occurs.
### SDK Changes
These changes only expose first-class providers from the Node.JS SDK.
- A new abstract class, `ProviderResource`, can be subclassed and used
to instantiate first-class providers.
- A new field in `ResourceOptions`, `provider`, can be used to supply
a particular provider instance to manage a `CustomResource`'s CRUD
operations.
- A new type, `InvokeOptions`, can be used to specify options that
control the behavior of a call to `pulumi.runtime.invoke`. This type
includes a `provider` field that is analogous to
`ResourceOptions.provider`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
2018-12-16 00:32:19 +01:00
|
|
|
// Explicitly ignore the props passed in. We allow them for back compat reasons. However,
|
|
|
|
// we explicitly do not want to pass them along to the engine. The ComponentResource acts
|
|
|
|
// only as a container for other resources. Another way to think about this is that a normal
|
|
|
|
// 'custom resource' corresponds to real piece of cloud infrastructure. So, when it changes
|
|
|
|
// in some way, the cloud resource needs to be updated (and vice versa). That is not true
|
|
|
|
// for a component resource. The component is just used for organizational purposes and does
|
|
|
|
// 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);
|
2017-11-20 19:08:59 +01:00
|
|
|
}
|
|
|
|
|
2018-12-16 00:32:19 +01:00
|
|
|
// registerOutputs registers synthetic outputs that a component has initialized, usually by
|
|
|
|
// allocating other child sub-resources and propagating their resulting property values.
|
|
|
|
// ComponentResources should always call this at the end of their constructor to indicate that
|
|
|
|
// they are done creating child resources. While not strictly necessary, this helps the
|
|
|
|
// experience by ensuring the UI transitions the ComponentResource to the 'complete' state as
|
|
|
|
// quickly as possible (instead of waiting until the entire application completes).
|
|
|
|
protected registerOutputs(outputs?: Inputs | Promise<Inputs> | Output<Inputs>): void {
|
2017-11-29 20:27:32 +01:00
|
|
|
if (outputs) {
|
|
|
|
registerResourceOutputs(this, outputs);
|
|
|
|
}
|
2017-10-15 12:52:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-11 09:11:53 +01:00
|
|
|
(<any>ComponentResource).doNotCapture = true;
|
|
|
|
(<any>ComponentResource.prototype).registerOutputs.doNotCapture = true;
|
|
|
|
|
2018-09-26 06:29:27 +02:00
|
|
|
/* @internal */
|
|
|
|
export const testingOptions = {
|
|
|
|
isDryRun: false,
|
|
|
|
};
|
|
|
|
|
2018-02-05 23:44:23 +01:00
|
|
|
/**
|
2018-03-18 08:15:22 +01:00
|
|
|
* 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
|
2018-02-08 00:01:55 +01:00
|
|
|
* dependency graph' to be created, which properly tracks the relationship between resources.
|
2018-02-05 23:44:23 +01:00
|
|
|
*/
|
|
|
|
export class Output<T> {
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
/**
|
|
|
|
* A private field to help with RTTI that works in SxS scenarios.
|
|
|
|
*
|
|
|
|
* This is internal instead of being truly private, to support mixins and our serialization model.
|
|
|
|
*/
|
|
|
|
// tslint:disable-next-line:variable-name
|
|
|
|
/* @internal */ public readonly __pulumiOutput?: boolean = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not this 'Output' should actually perform .apply calls. During a preview,
|
|
|
|
* an Output value may not be known (because it would have to actually be computed by doing an
|
|
|
|
* 'update'). In that case, we don't want to perform any .apply calls as the callbacks
|
|
|
|
* may not expect an undefined value. So, instead, we just transition to another Output
|
|
|
|
* value that itself knows it should not perform .apply calls.
|
|
|
|
*/
|
2018-05-23 23:47:40 +02:00
|
|
|
/* @internal */ public isKnown: Promise<boolean>;
|
2018-03-18 08:15:22 +01:00
|
|
|
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
/**
|
|
|
|
* Method that actually produces the concrete value of this output, as well as the total
|
|
|
|
* deployment-time set of resources this output depends on.
|
|
|
|
*
|
|
|
|
* Only callable on the outside.
|
|
|
|
*/
|
2018-02-05 23:44:23 +01:00
|
|
|
/* @internal */ public readonly promise: () => Promise<T>;
|
|
|
|
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
/**
|
|
|
|
* The list of resource that this output value depends on.
|
|
|
|
*
|
|
|
|
* Only callable on the outside.
|
|
|
|
*/
|
2018-02-05 23:44:23 +01:00
|
|
|
/* @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
|
2018-02-08 00:01:55 +01:00
|
|
|
* of the Output during cloud runtime execution, use `get()`.
|
2018-02-05 23:44:23 +01:00
|
|
|
*/
|
|
|
|
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.
|
|
|
|
*/
|
2018-10-27 05:49:16 +02:00
|
|
|
public readonly get: () => T;
|
2018-02-05 23:44:23 +01:00
|
|
|
|
|
|
|
// Statics
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
|
2018-10-27 22:27:04 +02:00
|
|
|
/**
|
|
|
|
* create takes any Input value and converts it into an Output, deeply unwrapping nested Input
|
|
|
|
* values as necessary.
|
|
|
|
*/
|
|
|
|
public static create<T>(val: Input<T>): Output<Unwrap<T>>;
|
|
|
|
public static create<T>(val: Input<T> | undefined): Output<Unwrap<T | undefined>>;
|
|
|
|
public static create<T>(val: Input<T | undefined>): Output<Unwrap<T | undefined>> {
|
|
|
|
return output<T>(<any>val);
|
|
|
|
}
|
|
|
|
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
/**
|
|
|
|
* Returns true if the given object is an instance of Output<T>. This is designed to work even when
|
|
|
|
* multiple copies of the Pulumi SDK have been loaded into the same process.
|
|
|
|
*/
|
2018-04-17 00:03:23 +02:00
|
|
|
public static isInstance<T>(obj: any): obj is Output<T> {
|
2018-10-28 21:02:37 +01:00
|
|
|
return obj && obj.__pulumiOutput ? true : false;
|
Don't use `instanceof` for RTTI
This change moves us away from using JavaScript RTTI, by way of
`instanceof`, for built-in Pulumi types. If we use `instanceof`,
then the same logical type loaded from separate copies of the
SDK package -- as will happen in SxS scenarios -- are considered
different. This isn't actually what we want. The solution is
simple: implement our own quasi-RTTI solution, using __pulumi*
properties and manual as* and is* functions. Note that we could
have skipped the as* and is* functions, but I found that they led
to slightly easier to read code.
There is one strange thing in here, which I spoke to
@CyrusNajmabadi about: SerializedOutput<T>, because it implements
Output<T> as an _interface_, did not previously masquerade as an
actual Output<T>. In other words, `instanceof` would have returned
false, and indeed a few important properties (like promise) are
missing. This change preserves that behavior, although I'll admit
that this is slightly odd. I suspect we'll want to revisit this as
part of https://github.com/pulumi/pulumi/issues/1074.
Fixes https://github.com/pulumi/pulumi/issues/1203.
2018-04-16 23:03:37 +02:00
|
|
|
}
|
|
|
|
|
2018-03-18 08:15:22 +01:00
|
|
|
/* @internal */ public constructor(
|
2018-10-27 05:49:16 +02:00
|
|
|
resources: Set<Resource> | Resource[] | Resource, promise: Promise<T>, isKnown: Promise<boolean>) {
|
2018-05-23 23:47:40 +02:00
|
|
|
this.isKnown = isKnown;
|
2018-03-18 08:15:22 +01:00
|
|
|
|
2018-02-05 23:44:23 +01:00
|
|
|
// Always create a copy so that no one accidentally modifies our Resource list.
|
2018-10-27 05:49:16 +02:00
|
|
|
if (Array.isArray(resources)) {
|
|
|
|
this.resources = () => new Set<Resource>(resources);
|
|
|
|
} else if (resources instanceof Set) {
|
|
|
|
this.resources = () => new Set<Resource>(resources);
|
|
|
|
} else {
|
|
|
|
this.resources = () => new Set<Resource>([resources]);
|
|
|
|
}
|
2018-02-05 23:44:23 +01:00
|
|
|
|
|
|
|
this.promise = () => promise;
|
|
|
|
|
|
|
|
this.apply = <U>(func: (t: T) => Input<U>) => {
|
2018-09-26 06:29:27 +02:00
|
|
|
let innerIsKnownResolve: (val: boolean) => void;
|
|
|
|
const innerIsKnown = new Promise<boolean>(resolve => {
|
|
|
|
innerIsKnownResolve = resolve;
|
|
|
|
});
|
|
|
|
|
|
|
|
// The known state of the output we're returning depends on if we're known as well, and
|
|
|
|
// if a potential lifted inner Output is known. If we get an inner Output, and it is
|
|
|
|
// not known itself, then the result we return should not be known.
|
|
|
|
const resultIsKnown = Promise.all([isKnown, innerIsKnown]).then(([k1, k2]) => k1 && k2);
|
|
|
|
|
2018-02-05 23:44:23 +01:00
|
|
|
return new Output<U>(resources, promise.then(async v => {
|
2018-09-26 06:29:27 +02:00
|
|
|
try {
|
|
|
|
if (runtime.isDryRun()) {
|
|
|
|
// During previews only perform the apply if the engine was able to
|
|
|
|
// give us an actual value for this Output.
|
|
|
|
const applyDuringPreview = await isKnown;
|
|
|
|
|
|
|
|
if (!applyDuringPreview) {
|
|
|
|
// We didn't actually run the function, our new Output is definitely
|
|
|
|
// **not** known.
|
|
|
|
innerIsKnownResolve(false);
|
|
|
|
return <U><any>undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const transformed = await func(v);
|
|
|
|
if (Output.isInstance(transformed)) {
|
|
|
|
// Note: if the func returned a Output, we unwrap that to get the inner value
|
|
|
|
// returned by that Output. Note that we are *not* capturing the Resources of
|
|
|
|
// this inner Output. That's intentional. As the Output returned is only
|
|
|
|
// supposed to be related this *this* Output object, those resources should
|
|
|
|
// already be in our transitively reachable resource graph.
|
|
|
|
|
|
|
|
// The callback func has produced an inner Output that may be 'known' or 'unknown'.
|
|
|
|
// We have to properly forward that along to our outer output. That way the Outer
|
|
|
|
// output doesn't consider itself 'known' then the inner Output did not.
|
|
|
|
innerIsKnownResolve(await transformed.isKnown);
|
|
|
|
return await transformed.promise();
|
|
|
|
} else {
|
|
|
|
// We successfully ran the inner function. Our new Output should be considered known.
|
|
|
|
innerIsKnownResolve(true);
|
|
|
|
return transformed;
|
|
|
|
}
|
2018-03-18 08:15:22 +01:00
|
|
|
}
|
2018-09-26 06:29:27 +02:00
|
|
|
finally {
|
|
|
|
// Ensure we always resolve the inner isKnown value no matter what happens
|
|
|
|
// above. If anything failed along the way, consider this output to be
|
|
|
|
// not-known. Awaiting this Output's promise() will still throw, but await'ing
|
|
|
|
// the isKnown bit will just return 'false'.
|
|
|
|
innerIsKnownResolve(false);
|
2018-02-05 23:44:23 +01:00
|
|
|
}
|
2018-09-26 06:29:27 +02:00
|
|
|
}), resultIsKnown);
|
2018-02-05 23:44:23 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
this.get = () => {
|
2018-09-25 01:57:20 +02:00
|
|
|
throw new Error(`Cannot call '.get' during update or preview.
|
2018-08-01 06:09:47 +02:00
|
|
|
To manipulate the value of this Output, use '.apply' instead.`);
|
2018-02-05 23:44:23 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-12 04:38:45 +02:00
|
|
|
/**
|
2018-09-12 06:17:52 +02:00
|
|
|
* [output] takes any Input value and converts it into an Output, deeply unwrapping nested Input
|
2018-10-27 05:49:16 +02:00
|
|
|
* values as necessary.
|
2018-09-12 04:38:45 +02:00
|
|
|
*
|
2018-09-12 06:17:52 +02:00
|
|
|
* The expected way to use this function is like so:
|
2018-09-12 04:38:45 +02:00
|
|
|
*
|
|
|
|
* ```ts
|
2018-09-12 06:17:52 +02:00
|
|
|
* var transformed = pulumi.output(someVal).apply(unwrapped => {
|
2018-09-12 04:38:45 +02:00
|
|
|
* // Do whatever you want now. 'unwrapped' will contain no outputs/promises inside
|
|
|
|
* // here, so you can easily do whatever sort of transformation is most convenient.
|
|
|
|
* });
|
|
|
|
*
|
2018-09-12 06:17:52 +02:00
|
|
|
* // the result can be passed to another Resource. The dependency information will be
|
2018-09-12 04:38:45 +02:00
|
|
|
* // properly maintained.
|
|
|
|
* var someResource = new SomeResource(name, { data: transformed ... });
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
export function output<T>(val: Input<T>): Output<Unwrap<T>>;
|
|
|
|
export function output<T>(val: Input<T> | undefined): Output<Unwrap<T | undefined>>;
|
|
|
|
export function output<T>(val: Input<T | undefined>): Output<Unwrap<T | undefined>> {
|
|
|
|
if (val === null || typeof val !== "object") {
|
2018-09-12 06:49:36 +02:00
|
|
|
// strings, numbers, booleans, functions, symbols, undefineds, nulls are all returned as
|
2018-09-12 04:38:45 +02:00
|
|
|
// themselves. They are always 'known' (i.e. we can safely 'apply' off of them even during
|
|
|
|
// preview).
|
2018-09-12 06:49:36 +02:00
|
|
|
return createSimpleOutput(val);
|
|
|
|
}
|
|
|
|
else if (Resource.isInstance(val)) {
|
|
|
|
// Don't unwrap Resources, there are existing codepaths that return Resources through
|
|
|
|
// Outputs and we want to preserve them as is when flattening.
|
|
|
|
return createSimpleOutput(val);
|
2018-09-12 04:38:45 +02:00
|
|
|
}
|
|
|
|
else if (val instanceof Promise) {
|
|
|
|
// For a promise, we can just treat the same as an output that points to that resource. So
|
|
|
|
// we just create an Output around the Promise, and immediately apply the unwrap function on
|
|
|
|
// it to transform the value it points at.
|
|
|
|
return <any>new Output(new Set(), val, /*isKnown*/ Promise.resolve(true)).apply(output);
|
|
|
|
}
|
|
|
|
else if (Output.isInstance(val)) {
|
|
|
|
return <any>val.apply(output);
|
|
|
|
}
|
|
|
|
else if (val instanceof Array) {
|
|
|
|
return <any>all(val.map(output));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const unwrappedObject: any = {};
|
|
|
|
Object.keys(val).forEach(k => {
|
|
|
|
unwrappedObject[k] = output((<any>val)[k]);
|
|
|
|
});
|
|
|
|
|
|
|
|
return <any>all(unwrappedObject);
|
|
|
|
}
|
2018-02-05 23:44:23 +01:00
|
|
|
}
|
|
|
|
|
2018-09-12 06:49:36 +02:00
|
|
|
function createSimpleOutput(val: any) {
|
|
|
|
return new Output(new Set(), Promise.resolve(val), /*isKnown*/ Promise.resolve(true));
|
|
|
|
}
|
|
|
|
|
2018-02-05 23:44:23 +01:00
|
|
|
/**
|
|
|
|
* Allows for multiple Output objects to be combined into a single Output object. The single Output
|
|
|
|
* will depend on the union of Resources that the individual dependencies depend on.
|
|
|
|
*
|
|
|
|
* This can be used in the following manner:
|
|
|
|
*
|
|
|
|
* ```ts
|
|
|
|
* var d1: Output<string>;
|
|
|
|
* var d2: Output<number>;
|
|
|
|
*
|
|
|
|
* var d3: Output<ResultType> = Output.all([d1, d2]).apply(([s, n]) => ...);
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* In this example, taking a dependency on d3 means a resource will depend on all the resources of
|
|
|
|
* d1 and d2.
|
|
|
|
*/
|
2018-10-27 22:55:54 +02:00
|
|
|
// tslint:disable:max-line-length
|
2018-09-12 04:38:45 +02:00
|
|
|
export function all<T>(val: Record<string, Input<T>>): Output<Record<string, Unwrap<T>>>;
|
2018-10-27 22:55:54 +02:00
|
|
|
export function all<T1, T2, T3, T4, T5, T6, T7, T8>(values: [Input<T1> | undefined, Input<T2> | undefined, Input<T3> | undefined, Input<T4> | undefined, Input<T5> | undefined, Input<T6> | undefined, Input<T7> | undefined, Input<T8> | undefined]): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>, Unwrap<T5>, Unwrap<T6>, Unwrap<T7>, Unwrap<T8>]>;
|
|
|
|
export function all<T1, T2, T3, T4, T5, T6, T7>(values: [Input<T1> | undefined, Input<T2> | undefined, Input<T3> | undefined, Input<T4> | undefined, Input<T5> | undefined, Input<T6> | undefined, Input<T7> | undefined]): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>, Unwrap<T5>, Unwrap<T6>, Unwrap<T7>]>;
|
|
|
|
export function all<T1, T2, T3, T4, T5, T6>(values: [Input<T1> | undefined, Input<T2> | undefined, Input<T3> | undefined, Input<T4> | undefined, Input<T5> | undefined, Input<T6> | undefined]): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>, Unwrap<T5>, Unwrap<T6>]>;
|
|
|
|
export function all<T1, T2, T3, T4, T5>(values: [Input<T1> | undefined, Input<T2> | undefined, Input<T3> | undefined, Input<T4> | undefined, Input<T5> | undefined]): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>, Unwrap<T5>]>;
|
|
|
|
export function all<T1, T2, T3, T4>(values: [Input<T1> | undefined, Input<T2> | undefined, Input<T3> | undefined, Input<T4> | undefined]): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>, Unwrap<T4>]>;
|
|
|
|
export function all<T1, T2, T3>(values: [Input<T1> | undefined, Input<T2> | undefined, Input<T3> | undefined]): Output<[Unwrap<T1>, Unwrap<T2>, Unwrap<T3>]>;
|
2018-09-12 04:38:45 +02:00
|
|
|
export function all<T1, T2>(values: [Input<T1> | undefined, Input<T2> | undefined]): Output<[Unwrap<T1>, Unwrap<T2>]>;
|
|
|
|
export function all<T>(ds: (Input<T> | undefined)[]): Output<Unwrap<T>[]>;
|
|
|
|
export function all<T>(val: Input<T>[] | Record<string, Input<T>>): Output<any> {
|
2018-02-05 23:44:23 +01:00
|
|
|
if (val instanceof Array) {
|
2018-03-18 08:15:22 +01:00
|
|
|
const allOutputs = val.map(v => output(v));
|
|
|
|
|
2018-09-12 04:38:45 +02:00
|
|
|
const [resources, isKnown] = getResourcesAndIsKnown(allOutputs);
|
|
|
|
const promisedArray = Promise.all(allOutputs.map(o => o.promise()));
|
2018-02-05 23:44:23 +01:00
|
|
|
|
2018-09-12 04:38:45 +02:00
|
|
|
return new Output<Unwrap<T>[]>(new Set<Resource>(resources), promisedArray, isKnown);
|
2018-02-05 23:44:23 +01:00
|
|
|
} else {
|
2018-09-12 04:38:45 +02:00
|
|
|
const keysAndOutputs = Object.keys(val).map(key => ({ key, value: output(val[key]) }));
|
|
|
|
const allOutputs = keysAndOutputs.map(kvp => kvp.value);
|
2018-02-05 23:44:23 +01:00
|
|
|
|
2018-09-12 04:38:45 +02:00
|
|
|
const [resources, isKnown] = getResourcesAndIsKnown(allOutputs);
|
|
|
|
const promisedObject = getPromisedObject(keysAndOutputs);
|
2018-02-05 23:44:23 +01:00
|
|
|
|
2018-09-12 04:38:45 +02:00
|
|
|
return new Output<Record<string, Unwrap<T>>>(new Set<Resource>(resources), promisedObject, isKnown);
|
2018-02-05 23:44:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-27 22:27:04 +02:00
|
|
|
async function getPromisedObject<T>(
|
|
|
|
keysAndOutputs: { key: string, value: Output<Unwrap<T>> }[]): Promise<Record<string, Unwrap<T>>> {
|
2018-09-12 04:38:45 +02:00
|
|
|
const result: Record<string, Unwrap<T>> = {};
|
|
|
|
for (const kvp of keysAndOutputs) {
|
|
|
|
result[kvp.key] = await kvp.value.promise();
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getResourcesAndIsKnown<T>(allOutputs: Output<Unwrap<T>>[]): [Resource[], Promise<boolean>] {
|
|
|
|
const allResources = allOutputs.reduce<Resource[]>((arr, o) => (arr.push(...o.resources()), arr), []);
|
|
|
|
|
|
|
|
// A merged output is known if all of its inputs are known.
|
|
|
|
const isKnown = Promise.all(allOutputs.map(o => o.isKnown)).then(ps => ps.every(b => b));
|
|
|
|
|
|
|
|
return [allResources, isKnown];
|
|
|
|
}
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
2018-02-06 03:37:10 +01:00
|
|
|
* 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.
|
2017-09-22 03:15:29 +02:00
|
|
|
*/
|
2018-02-05 23:44:23 +01:00
|
|
|
export type Input<T> = T | Promise<T> | Output<T>;
|
Eliminate Computed/Property in favor of Promises
As part of pulumi/pulumi-fabric#331, we've been exploring just using
undefined to indicate that a property value is absent during planning.
We also considered blocking the message loop to simplify the overall
programming model, so that all asynchrony is hidden.
It turns out ThereBeDragons :dragon_face: anytime you try to block the
message loop. So, we aren't quite sure about that bit.
But the part we are convicted about is that this Computed/Property
model is far too complex. Furthermore, it's very close to promises, and
yet frustratingly so far away. Indeed, the original thinking in
pulumi/pulumi-fabric#271 was simply to use promises, but we wanted to
encourage dataflow styles, rather than control flow. But we muddied up
our thinking by worrying about awaiting a promise that would never resolve.
It turns out we can achieve a middle ground: resolve planning promises to
undefined, so that they don't lead to hangs, but still use promises so
that asynchrony is explicit in the system. This also avoids blocking the
message loop. Who knows, this may actually be a fine final destination.
2017-09-20 16:40:09 +02:00
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
2018-02-06 03:37:10 +01:00
|
|
|
* Inputs is a map of property name to property input, one for each resource
|
2018-01-26 03:50:58 +01:00
|
|
|
* property value.
|
2017-09-22 03:15:29 +02:00
|
|
|
*/
|
2018-02-06 03:37:10 +01:00
|
|
|
export type Inputs = Record<string, Input<any>>;
|
2018-09-12 04:38:45 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The 'Unwrap' type allows us to express the operation of taking a type, with potentially deeply
|
|
|
|
* nested Promises and Outputs and to then get that same type with all the Promises and Outputs
|
|
|
|
* replaced with their wrapped type. Note that this Unwrapping is 'deep'. So if you had:
|
|
|
|
*
|
|
|
|
* `type X = { A: Promise<{ B: Output<{ c: Input<boolean> }> }> }`
|
|
|
|
*
|
|
|
|
* Then `Unwrap<X>` would be equivalent to:
|
|
|
|
*
|
|
|
|
* `... = { A: { B: { C: boolean } } }`
|
|
|
|
*
|
|
|
|
* Unwrapping sees through Promises, Outputs, Arrays and Objects.
|
|
|
|
*
|
|
|
|
* Note: due to TypeScript limitations there are some things that cannot be expressed. Specifically,
|
|
|
|
* if you had a `Promise<Output<T>>` then the Unwrap type would not be able to undo both of those
|
|
|
|
* wraps. In practice that should be ok. Values in an object graph should not wrap Outputs in
|
|
|
|
* Promises. Instead, any code that needs to work Outputs and also be async should either create
|
|
|
|
* the Output with the Promise (which will collapse into just an Output). Or, it should start with
|
|
|
|
* an Output and call [apply] on it, passing in an async function. This will also collapse and just
|
|
|
|
* produce an Output.
|
|
|
|
*
|
|
|
|
* In other words, this should not be used as the shape of an object: `{ a: Promise<Output<...>> }`.
|
|
|
|
* It should always either be `{ a: Promise<NonOutput> }` or just `{ a: Output<...> }`.
|
|
|
|
*/
|
2018-11-28 22:33:33 +01:00
|
|
|
export type Unwrap<T> =
|
2018-09-12 04:38:45 +02:00
|
|
|
// 1. If we have a promise, just get the type it itself is wrapping and recursively unwrap that.
|
|
|
|
// 2. Otherwise, if we have an output, do the same as a promise and just unwrap the inner type.
|
|
|
|
// 3. Otherwise, we have a basic type. Just unwrap that.
|
2018-09-15 11:59:01 +02:00
|
|
|
T extends Promise<infer U1> ? UnwrapSimple<U1> :
|
|
|
|
T extends Output<infer U2> ? UnwrapSimple<U2> :
|
2018-09-12 04:38:45 +02:00
|
|
|
UnwrapSimple<T>;
|
|
|
|
|
2018-10-15 21:49:44 +02:00
|
|
|
type primitive = Function | string | number | boolean | undefined | null;
|
2018-09-12 04:38:45 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles encountering basic types when unwrapping.
|
|
|
|
*/
|
2018-11-28 22:33:33 +01:00
|
|
|
export type UnwrapSimple<T> =
|
2018-09-12 04:38:45 +02:00
|
|
|
// 1. Any of the primitive types just unwrap to themselves.
|
|
|
|
// 2. An array of some types unwraps to an array of that type itself unwrapped. Note, due to a
|
|
|
|
// TS limitation we cannot express that as Array<Unwrap<U>> due to how it handles recursive
|
|
|
|
// types. We work around that by introducing an structurally equivalent interface that then
|
|
|
|
// helps make typescript defer type-evaluation instead of doing it eagerly.
|
|
|
|
// 3. An object unwraps to an object with properties of the same name, but where the property
|
|
|
|
// types have been unwrapped.
|
|
|
|
// 4. return 'never' at the end so that if we've missed something we'll discover it.
|
|
|
|
T extends primitive ? T :
|
2018-09-12 06:49:36 +02:00
|
|
|
T extends Resource ? T :
|
2018-09-12 04:38:45 +02:00
|
|
|
T extends Array<infer U> ? UnwrappedArray<U> :
|
|
|
|
T extends object ? UnwrappedObject<T> :
|
|
|
|
never;
|
|
|
|
|
2018-11-28 22:33:33 +01:00
|
|
|
export interface UnwrappedArray<T> extends Array<Unwrap<T>> {}
|
2018-09-12 04:38:45 +02:00
|
|
|
|
2018-11-28 22:33:33 +01:00
|
|
|
export type UnwrappedObject<T> = {
|
2018-09-12 04:38:45 +02:00
|
|
|
[P in keyof T]: Unwrap<T[P]>;
|
|
|
|
};
|