pulumi/sdk/nodejs/runtime/stack.ts

272 lines
10 KiB
TypeScript
Raw Permalink Normal View History

2018-05-22 19:43:36 +00: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.
import * as asset from "../asset";
import { warn } from "../log";
import { getProject, getStack } from "../metadata";
import { Inputs, Output, output } from "../output";
NodeJS transforms (#15532) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> This adds a new experimental feature to the NodeJS SDK to register remote transform functions. These are currently all prefixed 'X' to show they're experimental. These transform functions will run even for resources created inside MLCs. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-07 08:52:34 +00:00
import { ComponentResource, Resource, ResourceTransform, ResourceTransformation } from "../resource";
import { InvokeTransform } from "../invoke";
NodeJS transforms (#15532) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> This adds a new experimental feature to the NodeJS SDK to register remote transform functions. These are currently all prefixed 'X' to show they're experimental. These transform functions will run even for resources created inside MLCs. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-07 08:52:34 +00:00
import { getCallbacks, isDryRun, isQueryMode, setRootResource } from "./settings";
import { getStore, setStackResource, getStackResource as stateGetStackResource } from "./state";
/**
* The type name that should be used to construct the root component in the tree
* of Pulumi resources allocated by a deployment. This must be kept up to date
* with `github.com/pulumi/pulumi/sdk/v3/go/common/resource/stack.RootStackType`.
*/
export const rootPulumiStackTypeName = "pulumi:pulumi:Stack";
/**
* Creates a new Pulumi stack resource and executes the callback inside of it.
* Any outputs returned by the callback will be stored as output properties on
* this resulting Stack object.
*/
export function runInPulumiStack(init: () => Promise<any>): Promise<Inputs | undefined> {
if (!isQueryMode()) {
const stack = new Stack(init);
return stack.outputs.promise();
} else {
return init();
}
}
/**
* {@link Stack} is the root resource for a Pulumi stack. Before invoking the
* `init` callback, it registers itself as the root resource with the Pulumi
* engine.
*/
export class Stack extends ComponentResource<Inputs> {
/**
* The outputs of this stack, if the `init` callback exited normally.
*/
public readonly outputs: Output<Inputs>;
constructor(init: () => Promise<Inputs>) {
// Clear the stackResource so that when the Resource constructor runs it gives us no parent, we'll
// then set this to ourselves in init before calling the user code that will then create other
// resources.
setStackResource(undefined);
super(rootPulumiStackTypeName, `${getProject()}-${getStack()}`, { init });
const data = this.getData();
this.outputs = output(data);
}
/**
* Invokes the given `init` callback with this resource set as the root
* resource. The return value of init is used as the stack's output
* properties.
*
* @param args.init
* The callback to run in the context of this Pulumi stack
*/
async initialize(args: { init: () => Promise<Inputs> }): Promise<Inputs> {
await setRootResource(this);
Transformations (#3174) Adds the ability to provide `transformations` to modify the properties and resource options that will be used for any child resource of a component or stack. This offers an "escape hatch" to modify the behaviour of a component by peeking behind it's abstraction. For example, it can be used to add a resource option (`additionalSecretOutputs`, `aliases`, `protect`, etc.) to a specific known child of a component, or to modify some input property to a child resource if the component does not (yet) expose the ability to control that input directly. It could also be used for more interesting scenarios - such as: 1. Automatically applying tags to all resources that support them in a stack (or component) 2. Injecting real dependencies between stringly-referenced resources in a Helm Chart 3. Injecting explicit names using a preferred naming convention across all resources in a stack 4. Injecting `import` onto all resources by doing a lookup into a name=>id mapping Because this feature makes it possible to peek behind a component abstraction, it must be used with care in cases where the component is versioned independently of the use of transformations. Also, this can result in "spooky action at a distance", so should be used judiciously. That said - this can be used as an escape hatch to unblock a wide variety of common use cases without waiting on changes to be made in a component implementation. Each transformation is passed the `resource`, `name`, `type`, `props` and `opts` that are passed into the `Resource` constructor for any resource descended from the resource that has the transformation applied. The transformation callback can optionally return alternate versions of the `props` and `opts` to be used in place of the original values provided to the resource constructor. Fixes #2068.
2019-09-29 18:27:37 +00:00
// Set the global reference to the stack resource before invoking this init() function
setStackResource(this);
Transformations (#3174) Adds the ability to provide `transformations` to modify the properties and resource options that will be used for any child resource of a component or stack. This offers an "escape hatch" to modify the behaviour of a component by peeking behind it's abstraction. For example, it can be used to add a resource option (`additionalSecretOutputs`, `aliases`, `protect`, etc.) to a specific known child of a component, or to modify some input property to a child resource if the component does not (yet) expose the ability to control that input directly. It could also be used for more interesting scenarios - such as: 1. Automatically applying tags to all resources that support them in a stack (or component) 2. Injecting real dependencies between stringly-referenced resources in a Helm Chart 3. Injecting explicit names using a preferred naming convention across all resources in a stack 4. Injecting `import` onto all resources by doing a lookup into a name=>id mapping Because this feature makes it possible to peek behind a component abstraction, it must be used with care in cases where the component is versioned independently of the use of transformations. Also, this can result in "spooky action at a distance", so should be used judiciously. That said - this can be used as an escape hatch to unblock a wide variety of common use cases without waiting on changes to be made in a component implementation. Each transformation is passed the `resource`, `name`, `type`, `props` and `opts` that are passed into the `Resource` constructor for any resource descended from the resource that has the transformation applied. The transformation callback can optionally return alternate versions of the `props` and `opts` to be used in place of the original values provided to the resource constructor. Fixes #2068.
2019-09-29 18:27:37 +00:00
let outputs: Inputs | undefined;
try {
const inputs = await args.init();
outputs = await massage(undefined, inputs, []);
} finally {
// We want to expose stack outputs as simple pojo objects (including Resources). This
// helps ensure that outputs can point to resources, and that that is stored and
// presented as something reasonable, and not as just an id/urn in the case of
// Resources.
super.registerOutputs(outputs);
}
return outputs!;
}
}
async function massage(key: string | undefined, prop: any, objectStack: any[]): Promise<any> {
if (prop === undefined && objectStack.length === 1) {
// This is a top level undefined value, it will not show up in stack outputs, warn the user about
// this.
warn(`Undefined value (${key}) will not show as a stack output.`);
return undefined;
}
if (
prop === undefined ||
prop === null ||
typeof prop === "boolean" ||
typeof prop === "number" ||
typeof prop === "string" ||
typeof prop === "bigint"
) {
return prop;
}
if (prop instanceof Promise) {
return await massage(key, await prop, objectStack);
}
if (Output.isInstance(prop)) {
const result = prop.apply((v) => massage(key, v, objectStack));
// explicitly await the underlying promise of the output here. This is necessary to get a
// deterministic walk of the object graph. We need that deterministic walk, otherwise our
// actual cycle detection logic (using 'objectStack') doesn't work. i.e. if we don't do
// this then the main walking logic will be interleaved with the async function this output
// is executing. This interleaving breaks out assumption about pushing/popping values onto
// objectStack'
await result.promise();
return result;
}
// from this point on, we have complex objects. If we see them again, we don't want to emit
// them again fully or else we'd loop infinitely.
if (objectStack.indexOf(prop) >= 0) {
// Note: for Resources we hit again, emit their urn so cycles can be easily understood
// in the pojo objects.
if (Resource.isInstance(prop)) {
return await massage(key, prop.urn, objectStack);
}
return undefined;
}
try {
// push and pop what we see into a stack. That way if we see the same object through
// different paths, we will still print it out. We only skip it if it would truly cause
// recursion.
objectStack.push(prop);
return await massageComplex(prop, objectStack);
} finally {
const popped = objectStack.pop();
if (popped !== prop) {
throw new Error("Invariant broken when processing stack outputs");
}
}
}
async function massageComplex(prop: any, objectStack: any[]): Promise<any> {
if (asset.Asset.isInstance(prop)) {
if ((<asset.FileAsset>prop).path !== undefined) {
return { path: (<asset.FileAsset>prop).path };
} else if ((<asset.RemoteAsset>prop).uri !== undefined) {
return { uri: (<asset.RemoteAsset>prop).uri };
} else if ((<asset.StringAsset>prop).text !== undefined) {
return { text: "..." };
}
return undefined;
}
if (asset.Archive.isInstance(prop)) {
if ((<asset.AssetArchive>prop).assets) {
return { assets: await massage("assets", (<asset.AssetArchive>prop).assets, objectStack) };
} else if ((<asset.FileArchive>prop).path !== undefined) {
return { path: (<asset.FileArchive>prop).path };
} else if ((<asset.RemoteArchive>prop).uri !== undefined) {
return { uri: (<asset.RemoteArchive>prop).uri };
}
return undefined;
}
if (Resource.isInstance(prop)) {
// Emit a resource as a normal pojo. But filter out all our internal properties so that
// they don't clutter the display/checkpoint with values not relevant to the application.
//
// In preview only, we mark the POJO with "@isPulumiResource" to indicate that it is derived
// from a resource. This allows the engine to perform resource-specific filtering of unknowns
// from output diffs during a preview. This filtering is not necessary during an update because
// all property values are known.
const pojo = await serializeAllKeys((n) => !n.startsWith("__"));
return !isDryRun() ? pojo : { ...pojo, "@isPulumiResource": true };
}
if (prop instanceof Array) {
const result = [];
for (let i = 0; i < prop.length; i++) {
result[i] = await massage(undefined, prop[i], objectStack);
}
return result;
}
return await serializeAllKeys((n) => true);
async function serializeAllKeys(include: (name: string) => boolean) {
const obj: Record<string, any> = {};
for (const k of Object.keys(prop)) {
if (include(k)) {
obj[k] = await massage(k, prop[k], objectStack);
}
}
return obj;
}
}
Transformations (#3174) Adds the ability to provide `transformations` to modify the properties and resource options that will be used for any child resource of a component or stack. This offers an "escape hatch" to modify the behaviour of a component by peeking behind it's abstraction. For example, it can be used to add a resource option (`additionalSecretOutputs`, `aliases`, `protect`, etc.) to a specific known child of a component, or to modify some input property to a child resource if the component does not (yet) expose the ability to control that input directly. It could also be used for more interesting scenarios - such as: 1. Automatically applying tags to all resources that support them in a stack (or component) 2. Injecting real dependencies between stringly-referenced resources in a Helm Chart 3. Injecting explicit names using a preferred naming convention across all resources in a stack 4. Injecting `import` onto all resources by doing a lookup into a name=>id mapping Because this feature makes it possible to peek behind a component abstraction, it must be used with care in cases where the component is versioned independently of the use of transformations. Also, this can result in "spooky action at a distance", so should be used judiciously. That said - this can be used as an escape hatch to unblock a wide variety of common use cases without waiting on changes to be made in a component implementation. Each transformation is passed the `resource`, `name`, `type`, `props` and `opts` that are passed into the `Resource` constructor for any resource descended from the resource that has the transformation applied. The transformation callback can optionally return alternate versions of the `props` and `opts` to be used in place of the original values provided to the resource constructor. Fixes #2068.
2019-09-29 18:27:37 +00:00
/**
* Add a transformation to all future resources constructed in this Pulumi
* stack.
Transformations (#3174) Adds the ability to provide `transformations` to modify the properties and resource options that will be used for any child resource of a component or stack. This offers an "escape hatch" to modify the behaviour of a component by peeking behind it's abstraction. For example, it can be used to add a resource option (`additionalSecretOutputs`, `aliases`, `protect`, etc.) to a specific known child of a component, or to modify some input property to a child resource if the component does not (yet) expose the ability to control that input directly. It could also be used for more interesting scenarios - such as: 1. Automatically applying tags to all resources that support them in a stack (or component) 2. Injecting real dependencies between stringly-referenced resources in a Helm Chart 3. Injecting explicit names using a preferred naming convention across all resources in a stack 4. Injecting `import` onto all resources by doing a lookup into a name=>id mapping Because this feature makes it possible to peek behind a component abstraction, it must be used with care in cases where the component is versioned independently of the use of transformations. Also, this can result in "spooky action at a distance", so should be used judiciously. That said - this can be used as an escape hatch to unblock a wide variety of common use cases without waiting on changes to be made in a component implementation. Each transformation is passed the `resource`, `name`, `type`, `props` and `opts` that are passed into the `Resource` constructor for any resource descended from the resource that has the transformation applied. The transformation callback can optionally return alternate versions of the `props` and `opts` to be used in place of the original values provided to the resource constructor. Fixes #2068.
2019-09-29 18:27:37 +00:00
*/
export function registerStackTransformation(t: ResourceTransformation) {
const stackResource = getStackResource();
if (!stackResource) {
Transformations (#3174) Adds the ability to provide `transformations` to modify the properties and resource options that will be used for any child resource of a component or stack. This offers an "escape hatch" to modify the behaviour of a component by peeking behind it's abstraction. For example, it can be used to add a resource option (`additionalSecretOutputs`, `aliases`, `protect`, etc.) to a specific known child of a component, or to modify some input property to a child resource if the component does not (yet) expose the ability to control that input directly. It could also be used for more interesting scenarios - such as: 1. Automatically applying tags to all resources that support them in a stack (or component) 2. Injecting real dependencies between stringly-referenced resources in a Helm Chart 3. Injecting explicit names using a preferred naming convention across all resources in a stack 4. Injecting `import` onto all resources by doing a lookup into a name=>id mapping Because this feature makes it possible to peek behind a component abstraction, it must be used with care in cases where the component is versioned independently of the use of transformations. Also, this can result in "spooky action at a distance", so should be used judiciously. That said - this can be used as an escape hatch to unblock a wide variety of common use cases without waiting on changes to be made in a component implementation. Each transformation is passed the `resource`, `name`, `type`, `props` and `opts` that are passed into the `Resource` constructor for any resource descended from the resource that has the transformation applied. The transformation callback can optionally return alternate versions of the `props` and `opts` to be used in place of the original values provided to the resource constructor. Fixes #2068.
2019-09-29 18:27:37 +00:00
throw new Error("The root stack resource was referenced before it was initialized.");
}
stackResource.__transformations = [...(stackResource.__transformations || []), t];
}
NodeJS transforms (#15532) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> This adds a new experimental feature to the NodeJS SDK to register remote transform functions. These are currently all prefixed 'X' to show they're experimental. These transform functions will run even for resources created inside MLCs. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-07 08:52:34 +00:00
/**
* Add a transformation to all future resources constructed in this Pulumi
* stack.
NodeJS transforms (#15532) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> This adds a new experimental feature to the NodeJS SDK to register remote transform functions. These are currently all prefixed 'X' to show they're experimental. These transform functions will run even for resources created inside MLCs. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-07 08:52:34 +00:00
*/
export function registerResourceTransform(t: ResourceTransform): void {
NodeJS transforms (#15532) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> This adds a new experimental feature to the NodeJS SDK to register remote transform functions. These are currently all prefixed 'X' to show they're experimental. These transform functions will run even for resources created inside MLCs. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-07 08:52:34 +00:00
if (!getStore().supportsTransforms) {
throw new Error("The Pulumi CLI does not support transforms. Please update the Pulumi CLI");
}
const callbacks = getCallbacks();
if (!callbacks) {
throw new Error("No callback server registered.");
}
callbacks.registerStackTransform(t);
}
/**
* Add a transformation to all future resources constructed in this Pulumi
* stack.
*
* @deprecated
* Use `registerResourceTransform` instead.
*/
export function registerStackTransform(t: ResourceTransform) {
registerResourceTransform(t);
}
/**
* Add a transformation to all future invoke calls in this Pulumi stack.
*/
export function registerInvokeTransform(t: InvokeTransform): void {
if (!getStore().supportsInvokeTransforms) {
throw new Error("The Pulumi CLI does not support transforms. Please update the Pulumi CLI");
}
const callbacks = getCallbacks();
if (!callbacks) {
throw new Error("No callback server registered.");
}
callbacks.registerStackInvokeTransform(t);
}
export function getStackResource(): Stack | undefined {
return stateGetStackResource();
Transformations (#3174) Adds the ability to provide `transformations` to modify the properties and resource options that will be used for any child resource of a component or stack. This offers an "escape hatch" to modify the behaviour of a component by peeking behind it's abstraction. For example, it can be used to add a resource option (`additionalSecretOutputs`, `aliases`, `protect`, etc.) to a specific known child of a component, or to modify some input property to a child resource if the component does not (yet) expose the ability to control that input directly. It could also be used for more interesting scenarios - such as: 1. Automatically applying tags to all resources that support them in a stack (or component) 2. Injecting real dependencies between stringly-referenced resources in a Helm Chart 3. Injecting explicit names using a preferred naming convention across all resources in a stack 4. Injecting `import` onto all resources by doing a lookup into a name=>id mapping Because this feature makes it possible to peek behind a component abstraction, it must be used with care in cases where the component is versioned independently of the use of transformations. Also, this can result in "spooky action at a distance", so should be used judiciously. That said - this can be used as an escape hatch to unblock a wide variety of common use cases without waiting on changes to be made in a component implementation. Each transformation is passed the `resource`, `name`, `type`, `props` and `opts` that are passed into the `Resource` constructor for any resource descended from the resource that has the transformation applied. The transformation callback can optionally return alternate versions of the `props` and `opts` to be used in place of the original values provided to the resource constructor. Fixes #2068.
2019-09-29 18:27:37 +00:00
}