pulumi/sdk/nodejs/dynamic/index.ts

266 lines
8.1 KiB
TypeScript
Raw Permalink Normal View History

// Copyright 2016-2022, Pulumi Corporation.
2018-05-22 19:43:36 +00:00
//
// 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 { Inputs, Input, Output } from "../output";
Switch to parent pointers; display components nicely This change switches from child lists to parent pointers, in the way resource ancestries are represented. This cleans up a fair bit of the old parenting logic, including all notion of ambient parent scopes (and will notably address pulumi/pulumi#435). This lets us show a more parent/child display in the output when doing planning and updating. For instance, here is an update of a lambda's text, which is logically part of a cloud timer: * cloud:timer:Timer: (same) [urn=urn:pulumi:malta::lm-cloud::cloud:timer:Timer::lm-cts-malta-job-CleanSnapshots] * cloud:function:Function: (same) [urn=urn:pulumi:malta::lm-cloud::cloud:function:Function::lm-cts-malta-job-CleanSnapshots] * aws:serverless:Function: (same) [urn=urn:pulumi:malta::lm-cloud::aws:serverless:Function::lm-cts-malta-job-CleanSnapshots] ~ aws:lambda/function:Function: (modify) [id=lm-cts-malta-job-CleanSnapshots-fee4f3bf41280741] [urn=urn:pulumi:malta::lm-cloud::aws:lambda/function:Function::lm-cts-malta-job-CleanSnapshots] - code : archive(assets:2092f44) { // etc etc etc Note that we still get walls of text, but this will be actually quite nice when combined with pulumi/pulumi#454. I've also suppressed printing properties that didn't change during updates when --detailed was not passed, and also suppressed empty strings and zero-length arrays (since TF uses these as defaults in many places and it just makes creation and deletion quite verbose). Note that this is a far cry from everything we can possibly do here as part of pulumi/pulumi#340 (and even pulumi/pulumi#417). But it's a good start towards taming some of our output spew.
2017-11-17 02:21:41 +00:00
import * as resource from "../resource";
import * as settings from "../runtime/settings";
import * as serializeClosure from "../runtime/closure/serializeClosure";
/**
* {@link CheckResult} represents the results of a call to {@link ResourceProvider.check}.
*/
export interface CheckResult<Inputs = any> {
/**
* The inputs to use, if any.
*/
readonly inputs?: Inputs;
/**
* Any validation failures that occurred.
*/
2017-12-06 03:14:28 +00:00
readonly failures?: CheckFailure[];
}
/**
* {@link CheckFailure} represents a single failure in the results of a call to
* {@link ResourceProvider.check}.
*/
2017-12-06 03:14:28 +00:00
export interface CheckFailure {
/**
* The property that failed validation.
*/
2017-12-06 03:14:28 +00:00
readonly property: string;
/**
* The reason that the property failed validation.
*/
2017-12-06 03:14:28 +00:00
readonly reason: string;
}
/**
* {@link DiffResult} represents the results of a call to
* {@link ResourceProvider.diff}.
*/
2017-12-06 03:14:28 +00:00
export interface DiffResult {
/**
* If true, this diff detected changes and suggests an update.
*/
readonly changes?: boolean;
/**
* If this update requires a replacement, the set of properties triggering it.
*/
2017-12-06 03:14:28 +00:00
readonly replaces?: string[];
/**
* An optional list of properties that will not ever change.
*/
2017-12-06 03:14:28 +00:00
readonly stables?: string[];
/**
* If true, and a replacement occurs, the resource will first be deleted
* before being recreated. This is to avoid potential side-by-side issues
* with the default create before delete behavior.
*/
readonly deleteBeforeReplace?: boolean;
}
/**
* {@link CreateResult} represents the results of a call to
* {@link ResourceProvider.create}.
*/
export interface CreateResult<Outputs = any> {
/**
* The ID of the created resource.
*/
2017-12-06 03:14:28 +00:00
readonly id: resource.ID;
/**
* Any properties that were computed during creation.
*/
readonly outs?: Outputs;
}
/**
* {@link ReadResult} represents the results of a call to
* {@link ResourceProvider.read}.
*/
export interface ReadResult<Outputs = any> {
/**
* The ID of the resource ready back (or blank if missing).
*/
readonly id?: resource.ID;
/**
* The current property state read from the live environment.
*/
readonly props?: Outputs;
}
/**
* {@link UpdateResult} represents the results of a call to
* {@link ResourceProvider.update}.
*/
export interface UpdateResult<Outputs = any> {
/**
* Any properties that were computed during updating.
*/
readonly outs?: Outputs;
}
/**
* {@link ResourceProvider} represents an object that provides CRUD operations
* for a particular type of resource.
*/
export interface ResourceProvider<Inputs = any, Outputs = any> {
/**
* Validates that the given property bag is valid for a resource of the given type.
*
* @param olds
* The old input properties to use for validation.
* @param news
* The new input properties to use for validation.
*/
check?: (olds: Inputs, news: Inputs) => Promise<CheckResult<Inputs>>;
/**
* Checks what impacts a hypothetical update will have on the resource's
* properties.
*
* @param id
* The ID of the resource to diff.
* @param olds
* The old values of properties to diff.
* @param news
* The new values of properties to diff.
*/
diff?: (id: resource.ID, olds: Outputs, news: Inputs) => Promise<DiffResult>;
/**
* Allocates a new instance of the provided resource and returns its unique
* ID afterwards. If this call fails, the resource must not have been
* created (i.e., it is "transactional").
*
* @param inputs
* The properties to set during creation.
*/
create: (inputs: Inputs) => Promise<CreateResult<Outputs>>;
/**
* Reads the current live state associated with a resource. Enough state
* must be included in the inputs to uniquely identify the resource; this is
* typically just the resource ID, but it may also include some properties.
*/
read?: (id: resource.ID, props?: Outputs) => Promise<ReadResult<Outputs>>;
/**
* Updates an existing resource with new values.
*
* @param id
* The ID of the resource to update.
* @param olds
* The old values of properties to update.
* @param news
* The new values of properties to update.
*/
update?: (id: resource.ID, olds: Outputs, news: Inputs) => Promise<UpdateResult<Outputs>>;
/**
* Tears down an existing resource with the given ID. If it fails,
* the resource is assumed to still exist.
*
* @param id
* The ID of the resource to delete.
* @param props
* The current properties on the resource.
*/
delete?: (id: resource.ID, props: Outputs) => Promise<void>;
}
2017-10-16 20:01:13 +00:00
const providerCache = new WeakMap<ResourceProvider, Input<string>>();
function serializeProvider(provider: ResourceProvider): Input<string> {
let result: Input<string>;
// caching is enabled by default as of 3.0
if (settings.cacheDynamicProviders()) {
const cachedProvider = providerCache.get(provider);
if (cachedProvider) {
result = cachedProvider;
} else {
result = serializeFunctionMaybeSecret(provider);
providerCache.set(provider, result);
}
} else {
result = serializeFunctionMaybeSecret(provider);
}
return result;
}
function serializeFunctionMaybeSecret(provider: ResourceProvider): Output<string> {
// Load runtime/closure on demand, as its dependencies are slow to load.
//
// See https://www.typescriptlang.org/docs/handbook/modules.html#optional-module-loading-and-other-advanced-loading-scenarios
const sc: typeof serializeClosure = require("../runtime/closure/serializeClosure");
const sfPromise = sc.serializeFunction(() => provider, { allowSecrets: true });
// Create an Output from the promise's text and containsSecrets properties. Uses the internal API since we don't provide a public interface for this.
return new Output(
[],
sfPromise.then((sf) => sf.text),
new Promise((resolve) => resolve(true)),
sfPromise.then((sf) => sf.containsSecrets),
new Promise((resolve) => resolve([])),
);
}
2017-10-14 21:29:49 +00:00
/**
* {@link Resource} represents a Pulumi resource that incorporates an inline
* implementation of the Resource's CRUD operations.
2017-10-14 21:29:49 +00:00
*/
export abstract class Resource extends resource.CustomResource {
2017-10-14 21:29:49 +00:00
/**
* Creates a new dynamic resource.
*
* @param provider
* The implementation of the resource's CRUD operations.
* @param name
* The name of the resource.
* @param props
* The arguments to use to populate the new resource. Must not define the
* reserved property "__provider".
* @param opts
* A bag of options that control this resource's behavior.
* @param module
* The module of the resource.
* @param type
* The type of the resource.
2017-10-14 21:29:49 +00:00
*/
constructor(
provider: ResourceProvider,
name: string,
props: Inputs,
opts?: resource.CustomResourceOptions,
module?: string,
type: string = "Resource",
) {
2017-10-14 21:29:49 +00:00
const providerKey: string = "__provider";
if (props[providerKey]) {
throw new Error("A dynamic resource must not define the __provider key");
}
props[providerKey] = serializeProvider(provider);
2017-10-14 21:29:49 +00:00
super(`pulumi-nodejs:dynamic${module ? `/${module}` : ""}:${type}`, name, props, opts);
}
}