pulumi/sdk/nodejs/runtime/settings.ts
Pat Gavlin 58a75cbbf4
Pull default options from a resource's parent. (#1748)
If a resource's options bag does not specify `protect` or `provider`,
pull a default value from the resource's parent.

In order to allow a parent resource to specify providers for multiple
resource types, component resources now accept an optional map from
package name to provider instance. When a custom resource needs a
default provider from its parent, it checks its parent provider bag for
an entry under its package. If a component resource does not have a
provider bag, its pulls a default from its parent.

These changes also add a `parent` field to `InvokeOptions` s.t. calls to
invoke can use the same behavior as resource creation w.r.t. providers.

Fixes #1735, #1736.
2018-08-10 16:18:21 -07:00

231 lines
7.6 KiB
TypeScript

// 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 { RunError } from "../errors";
import { ComponentResource } from "../resource";
import { debuggablePromise } from "./debuggable";
const grpc = require("grpc");
const engrpc = require("../proto/engine_grpc_pb.js");
const resrpc = require("../proto/resource_grpc_pb.js");
/**
* excessiveDebugOutput enables, well, pretty excessive debug output pertaining to resources and properties.
*/
export let excessiveDebugOutput: boolean = false;
/**
* Options is a bag of settings that controls the behavior of previews and deployments
*/
export interface Options {
readonly project?: string; // the name of the current project.
readonly stack?: string; // the name of the current stack being deployed into.
readonly dryRun?: boolean; // whether we are performing a preview (true) or a real deployment (false).
readonly parallel?: number; // the degree of parallelism for resource operations (default is serial).
readonly engineAddr?: string; // a connection string to the engine's RPC, in case we need to reestablish.
readonly monitorAddr?: string; // a connection string to the monitor's RPC, in case we need to reestablish.
}
/**
* _options are the current deployment options being used for this entire session.
*/
const options = loadOptions();
/**
* Returns true if we're currently performing a dry-run, or false if this is a true update.
*/
export function isDryRun(): boolean {
return options.dryRun === true;
}
/**
* Get the project being run by the current update.
*/
export function getProject(): string | undefined {
return options.project;
}
/**
* Get the stack being targeted by the current update.
*/
export function getStack(): string | undefined {
return options.stack;
}
/**
* monitor is a live connection to the resource monitor that tracks deployments (lazily initialized).
*/
let monitor: any | undefined;
/**
* hasMonitor returns true if we are currently connected to a resource monitoring service.
*/
export function hasMonitor(): boolean {
return !!monitor && !!options.monitorAddr;
}
/**
* getMonitor returns the current resource monitoring service client for RPC communications.
*/
export function getMonitor(): Object {
if (!monitor) {
const addr = options.monitorAddr;
if (addr) {
// Lazily initialize the RPC connection to the monitor.
monitor = new resrpc.ResourceMonitorClient(addr, grpc.credentials.createInsecure());
} else {
// Otherwise, this is an error.
throw new RunError(
"Pulumi program not connected to the engine -- are you running with the `pulumi` CLI?");
}
}
return monitor!;
}
/**
* engine is a live connection to the engine, used for logging, etc. (lazily initialized).
*/
let engine: any | undefined;
/**
* getEngine returns the current engine, if any, for RPC communications back to the resource engine.
*/
export function getEngine(): Object | undefined {
if (!engine) {
const addr = options.engineAddr;
if (addr) {
// Lazily initialize the RPC connection to the engine.
engine = new engrpc.EngineClient(addr, grpc.credentials.createInsecure());
}
}
return engine;
}
/**
* serialize returns true if resource operations should be serialized.
*/
export function serialize(): boolean {
const p = options.parallel;
return !p || p <= 1;
}
/**
* loadOptions recovers the options from the environment, which is set before we begin executing. This ensures
* that even when multiple copies of this module are loaded, they all get the same values.
*/
function loadOptions(): Options {
// The only option that needs parsing is the parallelism flag. Ignore any failures.
let parallel: number | undefined;
const parallelOpt = process.env["PULUMI_NODEJS_PARALLEL"];
if (parallelOpt) {
try {
parallel = parseInt(parallelOpt, 10);
}
catch (err) {
// ignore.
}
}
// Now just hydrate the rest from environment variables. These might be missing, in which case
// we will fail later on when we actually need to create an RPC connection back to the engine.
return {
project: process.env["PULUMI_NODEJS_PROJECT"],
stack: process.env["PULUMI_NODEJS_STACK"],
dryRun: (process.env["PULUMI_NODEJS_DRY_RUN"] === "true"),
parallel: parallel,
monitorAddr: process.env["PULUMI_NODEJS_MONITOR"],
engineAddr: process.env["PULUMI_NODEJS_ENGINE"],
};
}
/**
* disconnect permanently disconnects from the server, closing the connections. It waits for the existing RPC
* queue to drain. If any RPCs come in afterwards, however, they will crash the process.
*/
export function disconnect(): void {
let done: Promise<any> | undefined;
const closeCallback: () => Promise<void> = () => {
if (done !== rpcDone) {
// If the done promise has changed, some activity occurred in between callbacks. Wait again.
done = rpcDone;
return debuggablePromise(done.then(closeCallback));
}
disconnectSync();
return Promise.resolve();
};
closeCallback();
}
/**
* disconnectSync permanently disconnects from the server, closing the connections. Unlike `disconnect`. it does not
* wait for the existing RPC queue to drain. Any RPCs that come in after this call will crash the process.
*/
export function disconnectSync(): void {
// Otherwise, actually perform the close activities (ignoring errors and crashes).
if (monitor) {
try {
monitor.close();
}
catch (err) {
// ignore.
}
monitor = null;
}
if (engine) {
try {
engine.close();
}
catch (err) {
// ignore.
}
engine = null;
}
}
/**
* rpcDone resolves when the last known client-side RPC call finishes.
*/
let rpcDone: Promise<any> = Promise.resolve();
/**
* rpcKeepAlive registers a pending call to ensure that we don't prematurely disconnect from the server. It returns
* a function that, when invoked, signals that the RPC has completed.
*/
export function rpcKeepAlive(): () => void {
let done: (() => void) | undefined = undefined;
const donePromise = debuggablePromise(new Promise<void>((resolve) => { done = resolve; }));
rpcDone = rpcDone.then(() => donePromise);
return done!;
}
let rootResource: ComponentResource | undefined;
/**
* getRootResource returns a root resource that will automatically become the default parent of all resources. This
* can be used to ensure that all resources without explicit parents are parented to a common parent resource.
*/
export function getRootResource(): ComponentResource | undefined {
return rootResource;
}
/**
* setRootResource registers a resource that will become the default parent for all resources without explicit parents.
*/
export function setRootResource(res: ComponentResource | undefined): void {
if (rootResource && res) {
throw new Error("Cannot set multiple root resources");
}
rootResource = res;
}