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.
|
2017-09-04 17:30:39 +02:00
|
|
|
|
2020-04-14 10:30:25 +02:00
|
|
|
import * as grpc from "@grpc/grpc-js";
|
2019-10-15 07:08:06 +02:00
|
|
|
import * as fs from "fs";
|
|
|
|
import * as path from "path";
|
2018-09-18 20:47:34 +02:00
|
|
|
import { ComponentResource, URN } from "../resource";
|
2017-09-07 23:50:17 +02:00
|
|
|
import { debuggablePromise } from "./debuggable";
|
|
|
|
|
2018-04-07 17:02:59 +02:00
|
|
|
const engrpc = require("../proto/engine_grpc_pb.js");
|
2018-09-18 20:47:34 +02:00
|
|
|
const engproto = require("../proto/engine_pb.js");
|
2020-02-29 02:22:50 +01:00
|
|
|
const provproto = require("../proto/provider_pb.js");
|
2018-04-07 17:02:59 +02:00
|
|
|
const resrpc = require("../proto/resource_grpc_pb.js");
|
2019-04-13 03:25:33 +02:00
|
|
|
const resproto = require("../proto/resource_pb.js");
|
2020-02-29 02:22:50 +01:00
|
|
|
const structproto = require("google-protobuf/google/protobuf/struct_pb.js");
|
2018-04-07 17:02:59 +02:00
|
|
|
|
2020-04-21 02:13:19 +02:00
|
|
|
// maxRPCMessageSize raises the gRPC Max Message size from `4194304` (4mb) to `419430400` (400mb)
|
2020-04-21 00:02:09 +02:00
|
|
|
const maxRPCMessageSize: number = 1024 * 1024 * 400;
|
2020-04-24 02:30:30 +02:00
|
|
|
const grpcChannelOptions = { "grpc.max_receive_message_length": maxRPCMessageSize };
|
2020-04-21 00:02:09 +02:00
|
|
|
|
2017-09-27 21:34:44 +02:00
|
|
|
/**
|
|
|
|
* excessiveDebugOutput enables, well, pretty excessive debug output pertaining to resources and properties.
|
|
|
|
*/
|
|
|
|
export let excessiveDebugOutput: boolean = false;
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
2017-09-23 00:15:42 +02:00
|
|
|
* Options is a bag of settings that controls the behavior of previews and deployments
|
2017-09-22 03:15:29 +02:00
|
|
|
*/
|
2017-09-16 01:38:52 +02:00
|
|
|
export interface Options {
|
2017-10-19 17:26:57 +02:00
|
|
|
readonly project?: string; // the name of the current project.
|
|
|
|
readonly stack?: string; // the name of the current stack being deployed into.
|
2018-04-07 17:02:59 +02:00
|
|
|
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.
|
2019-04-17 07:20:01 +02:00
|
|
|
readonly dryRun?: boolean; // whether we are performing a preview (true) or a real deployment (false).
|
|
|
|
readonly testModeEnabled?: boolean; // true if we're in testing mode (allows execution without the CLI).
|
2019-05-01 03:09:44 +02:00
|
|
|
readonly queryMode?: boolean; // true if we're in query mode (does not allow resource registration).
|
2019-08-05 21:44:04 +02:00
|
|
|
readonly legacyApply?: boolean; // true if we will resolve missing outputs to inputs during preview.
|
2019-10-15 07:08:06 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Directory containing the send/receive files for making synchronous invokes to the engine.
|
|
|
|
*/
|
|
|
|
readonly syncDir?: string;
|
2018-04-07 17:02:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-04-17 07:20:01 +02:00
|
|
|
* options are the current deployment options being used for this entire session.
|
2018-04-07 17:02:59 +02:00
|
|
|
*/
|
2020-02-29 02:22:50 +01:00
|
|
|
let options = loadOptions();
|
|
|
|
|
|
|
|
|
|
|
|
export function setMockOptions(mockMonitor: any, project?: string, stack?: string, preview?: boolean) {
|
|
|
|
options = {
|
|
|
|
project: project || options.project || "project",
|
|
|
|
stack: stack || options.stack || "stack",
|
|
|
|
dryRun: preview,
|
|
|
|
queryMode: options.queryMode,
|
|
|
|
parallel: options.parallel,
|
|
|
|
monitorAddr: options.monitorAddr,
|
|
|
|
engineAddr: options.engineAddr,
|
|
|
|
testModeEnabled: true,
|
|
|
|
legacyApply: options.legacyApply,
|
|
|
|
syncDir: options.syncDir,
|
|
|
|
};
|
|
|
|
|
|
|
|
monitor = mockMonitor;
|
|
|
|
}
|
2018-04-07 17:02:59 +02:00
|
|
|
|
2020-01-26 18:06:35 +01:00
|
|
|
/** @internal Used only for testing purposes. */
|
2019-04-17 07:20:01 +02:00
|
|
|
export function _setIsDryRun(val: boolean) {
|
|
|
|
(options as any).dryRun = val;
|
2018-09-26 06:29:27 +02:00
|
|
|
}
|
|
|
|
|
2018-04-07 17:02:59 +02:00
|
|
|
/**
|
2019-04-17 07:20:01 +02:00
|
|
|
* Returns true if we're currently performing a dry-run, or false if this is a true update. Note that we
|
|
|
|
* always consider executions in test mode to be "dry-runs", since we will never actually carry out an update,
|
|
|
|
* and therefore certain output properties will never be resolved.
|
2018-04-07 17:02:59 +02:00
|
|
|
*/
|
|
|
|
export function isDryRun(): boolean {
|
2020-02-29 02:22:50 +01:00
|
|
|
return options.dryRun === true;
|
2019-04-17 07:20:01 +02:00
|
|
|
}
|
|
|
|
|
2019-04-24 04:24:06 +02:00
|
|
|
/** @internal Used only for testing purposes */
|
2019-04-17 07:20:01 +02:00
|
|
|
export function _setTestModeEnabled(val: boolean) {
|
|
|
|
(options as any).testModeEnabled = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if test mode is enabled (PULUMI_TEST_MODE).
|
|
|
|
*/
|
|
|
|
export function isTestModeEnabled(): boolean {
|
|
|
|
return options.testModeEnabled === true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks that test mode is enabled and, if not, throws an error.
|
|
|
|
*/
|
|
|
|
function requireTestModeEnabled(): void {
|
|
|
|
if (!isTestModeEnabled()) {
|
2019-08-27 03:49:13 +02:00
|
|
|
throw new Error("Program run without the Pulumi engine available; re-run using the `pulumi` CLI");
|
2019-04-17 07:20:01 +02:00
|
|
|
}
|
2018-04-07 17:02:59 +02:00
|
|
|
}
|
|
|
|
|
2020-01-27 19:22:32 +01:00
|
|
|
/** @internal Used only for testing purposes. */
|
2019-05-01 03:09:44 +02:00
|
|
|
export function _setQueryMode(val: boolean) {
|
|
|
|
(options as any).queryMode = val;
|
|
|
|
}
|
|
|
|
|
2020-01-27 19:22:32 +01:00
|
|
|
/**
|
2019-05-01 03:09:44 +02:00
|
|
|
* Returns true if query mode is enabled.
|
|
|
|
*/
|
|
|
|
export function isQueryMode(): boolean {
|
|
|
|
return options.queryMode === true;
|
|
|
|
}
|
|
|
|
|
2019-08-05 21:44:04 +02:00
|
|
|
/**
|
|
|
|
* Returns true if we will resolve missing outputs to inputs during preview (PULUMI_ENABLE_LEGACY_APPLY).
|
|
|
|
*/
|
|
|
|
export function isLegacyApplyEnabled(): boolean {
|
|
|
|
return options.legacyApply === true;
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:02:59 +02:00
|
|
|
/**
|
|
|
|
* Get the project being run by the current update.
|
|
|
|
*/
|
2019-04-17 07:20:01 +02:00
|
|
|
export function getProject(): string {
|
|
|
|
if (options.project) {
|
|
|
|
return options.project;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the project is missing, specialize the error. First, if test mode is disabled:
|
|
|
|
requireTestModeEnabled();
|
|
|
|
|
|
|
|
// And now an error if test mode is enabled, instructing how to manually configure the project:
|
2020-03-09 16:23:49 +01:00
|
|
|
throw new Error("Missing project name; for test mode, please call `pulumi.runtime.setMocks`");
|
2019-04-17 07:20:01 +02:00
|
|
|
}
|
|
|
|
|
2019-04-24 04:24:06 +02:00
|
|
|
/** @internal Used only for testing purposes. */
|
2019-06-01 08:01:01 +02:00
|
|
|
export function _setProject(val: string | undefined) {
|
2019-04-17 07:20:01 +02:00
|
|
|
(options as any).project = val;
|
2018-04-07 17:02:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the stack being targeted by the current update.
|
|
|
|
*/
|
2019-04-17 07:20:01 +02:00
|
|
|
export function getStack(): string {
|
|
|
|
if (options.stack) {
|
|
|
|
return options.stack;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the stack is missing, specialize the error. First, if test mode is disabled:
|
|
|
|
requireTestModeEnabled();
|
|
|
|
|
|
|
|
// And now an error if test mode is enabled, instructing how to manually configure the stack:
|
|
|
|
throw new Error("Missing stack name; for test mode, please set PULUMI_NODEJS_STACK");
|
|
|
|
}
|
|
|
|
|
2019-04-24 04:24:06 +02:00
|
|
|
/** @internal Used only for testing purposes. */
|
2019-06-01 08:01:01 +02:00
|
|
|
export function _setStack(val: string | undefined) {
|
2019-04-17 07:20:01 +02:00
|
|
|
(options as any).stack = val;
|
2017-09-09 22:43:51 +02:00
|
|
|
}
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
2018-04-07 17:02:59 +02:00
|
|
|
* monitor is a live connection to the resource monitor that tracks deployments (lazily initialized).
|
2017-09-22 03:15:29 +02:00
|
|
|
*/
|
2018-04-07 17:02:59 +02:00
|
|
|
let monitor: any | undefined;
|
2017-09-16 01:38:52 +02:00
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
|
|
|
* hasMonitor returns true if we are currently connected to a resource monitoring service.
|
|
|
|
*/
|
2017-09-04 17:30:39 +02:00
|
|
|
export function hasMonitor(): boolean {
|
Do not lazy initialize config or settings
The pulumi runtime used to lazily load and parse both config and
settings data set by the language host. The initial reason for this
design was that we wanted the runtime to be usable in a normal node
environment, but we have moved away from supporting that.
In addition, while we claimed we loaded these value "lazily", we
actually forced their loading quite eagerly when we started
up. However, when capturing config (or settings, as we now do), we
would capture all the logic about loading these values from the
environment.
Even worse, in the case where you had two copies of @pulumi/pulumi
loaded, it would be possible to capture a config object which was not
initialized and then at runtime the initialization logic would try to
read PULUMI_CONFIG from the process environment and fail.
So we adopt a new model where configuration and settings are parsed as
we load their containing modules. In addition, to support SxS
scinerios, we continue to use `process.env` as a way to control both
configuration and settings. This means that `run.ts` must now ensure
that these values are present in the environment before either the
config or runtime modules have been loaded.
2018-08-04 00:33:15 +02:00
|
|
|
return !!monitor && !!options.monitorAddr;
|
2017-09-04 17:30:39 +02:00
|
|
|
}
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
|
|
|
* getMonitor returns the current resource monitoring service client for RPC communications.
|
|
|
|
*/
|
2019-04-17 07:20:01 +02:00
|
|
|
export function getMonitor(): Object | undefined {
|
2018-04-07 17:02:59 +02:00
|
|
|
if (!monitor) {
|
Do not lazy initialize config or settings
The pulumi runtime used to lazily load and parse both config and
settings data set by the language host. The initial reason for this
design was that we wanted the runtime to be usable in a normal node
environment, but we have moved away from supporting that.
In addition, while we claimed we loaded these value "lazily", we
actually forced their loading quite eagerly when we started
up. However, when capturing config (or settings, as we now do), we
would capture all the logic about loading these values from the
environment.
Even worse, in the case where you had two copies of @pulumi/pulumi
loaded, it would be possible to capture a config object which was not
initialized and then at runtime the initialization logic would try to
read PULUMI_CONFIG from the process environment and fail.
So we adopt a new model where configuration and settings are parsed as
we load their containing modules. In addition, to support SxS
scinerios, we continue to use `process.env` as a way to control both
configuration and settings. This means that `run.ts` must now ensure
that these values are present in the environment before either the
config or runtime modules have been loaded.
2018-08-04 00:33:15 +02:00
|
|
|
const addr = options.monitorAddr;
|
2018-04-07 17:02:59 +02:00
|
|
|
if (addr) {
|
|
|
|
// Lazily initialize the RPC connection to the monitor.
|
2020-04-21 00:02:09 +02:00
|
|
|
monitor = new resrpc.ResourceMonitorClient(
|
|
|
|
addr,
|
|
|
|
grpc.credentials.createInsecure(),
|
|
|
|
grpcChannelOptions,
|
|
|
|
);
|
2018-04-07 17:02:59 +02:00
|
|
|
} else {
|
2019-04-17 07:20:01 +02:00
|
|
|
// If test mode isn't enabled, we can't run the program without an engine.
|
|
|
|
requireTestModeEnabled();
|
2018-04-07 17:02:59 +02:00
|
|
|
}
|
2017-09-04 17:30:39 +02:00
|
|
|
}
|
2019-04-17 07:20:01 +02:00
|
|
|
return monitor;
|
2017-09-04 17:30:39 +02:00
|
|
|
}
|
|
|
|
|
2019-10-15 07:08:06 +02:00
|
|
|
/** @internal */
|
|
|
|
export interface SyncInvokes {
|
|
|
|
requests: number;
|
|
|
|
responses: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
let syncInvokes: SyncInvokes | undefined;
|
|
|
|
|
|
|
|
/** @internal */
|
|
|
|
export function tryGetSyncInvokes(): SyncInvokes | undefined {
|
|
|
|
if (syncInvokes === undefined && options.syncDir) {
|
|
|
|
const requests = fs.openSync(path.join(options.syncDir, "invoke_req"), fs.constants.O_WRONLY|fs.constants.O_SYNC);
|
|
|
|
const responses = fs.openSync(path.join(options.syncDir, "invoke_res"), fs.constants.O_RDONLY|fs.constants.O_SYNC);
|
|
|
|
syncInvokes = { requests, responses };
|
|
|
|
}
|
|
|
|
|
|
|
|
return syncInvokes;
|
|
|
|
}
|
|
|
|
|
2018-04-07 17:02:59 +02:00
|
|
|
/**
|
|
|
|
* engine is a live connection to the engine, used for logging, etc. (lazily initialized).
|
|
|
|
*/
|
|
|
|
let engine: any | undefined;
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
|
|
|
* getEngine returns the current engine, if any, for RPC communications back to the resource engine.
|
|
|
|
*/
|
2017-09-04 17:30:39 +02:00
|
|
|
export function getEngine(): Object | undefined {
|
2018-04-07 17:02:59 +02:00
|
|
|
if (!engine) {
|
Do not lazy initialize config or settings
The pulumi runtime used to lazily load and parse both config and
settings data set by the language host. The initial reason for this
design was that we wanted the runtime to be usable in a normal node
environment, but we have moved away from supporting that.
In addition, while we claimed we loaded these value "lazily", we
actually forced their loading quite eagerly when we started
up. However, when capturing config (or settings, as we now do), we
would capture all the logic about loading these values from the
environment.
Even worse, in the case where you had two copies of @pulumi/pulumi
loaded, it would be possible to capture a config object which was not
initialized and then at runtime the initialization logic would try to
read PULUMI_CONFIG from the process environment and fail.
So we adopt a new model where configuration and settings are parsed as
we load their containing modules. In addition, to support SxS
scinerios, we continue to use `process.env` as a way to control both
configuration and settings. This means that `run.ts` must now ensure
that these values are present in the environment before either the
config or runtime modules have been loaded.
2018-08-04 00:33:15 +02:00
|
|
|
const addr = options.engineAddr;
|
2018-04-07 17:02:59 +02:00
|
|
|
if (addr) {
|
|
|
|
// Lazily initialize the RPC connection to the engine.
|
2020-04-21 00:02:09 +02:00
|
|
|
engine = new engrpc.EngineClient(
|
|
|
|
addr,
|
|
|
|
grpc.credentials.createInsecure(),
|
|
|
|
grpcChannelOptions,
|
|
|
|
);
|
2018-04-07 17:02:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return engine;
|
2017-09-04 17:30:39 +02:00
|
|
|
}
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
|
|
|
* serialize returns true if resource operations should be serialized.
|
|
|
|
*/
|
2017-09-17 17:10:46 +02:00
|
|
|
export function serialize(): boolean {
|
2018-10-18 00:33:26 +02:00
|
|
|
return options.parallel === 1;
|
2017-09-17 17:10:46 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 18:55:02 +01:00
|
|
|
/**
|
Do not lazy initialize config or settings
The pulumi runtime used to lazily load and parse both config and
settings data set by the language host. The initial reason for this
design was that we wanted the runtime to be usable in a normal node
environment, but we have moved away from supporting that.
In addition, while we claimed we loaded these value "lazily", we
actually forced their loading quite eagerly when we started
up. However, when capturing config (or settings, as we now do), we
would capture all the logic about loading these values from the
environment.
Even worse, in the case where you had two copies of @pulumi/pulumi
loaded, it would be possible to capture a config object which was not
initialized and then at runtime the initialization logic would try to
read PULUMI_CONFIG from the process environment and fail.
So we adopt a new model where configuration and settings are parsed as
we load their containing modules. In addition, to support SxS
scinerios, we continue to use `process.env` as a way to control both
configuration and settings. This means that `run.ts` must now ensure
that these values are present in the environment before either the
config or runtime modules have been loaded.
2018-08-04 00:33:15 +02:00
|
|
|
* 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.
|
2017-09-22 03:15:29 +02:00
|
|
|
*/
|
2018-04-07 17:02:59 +02:00
|
|
|
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.
|
|
|
|
}
|
2017-09-04 17:30:39 +02:00
|
|
|
}
|
2018-04-07 17:02:59 +02:00
|
|
|
|
|
|
|
// 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"),
|
2019-05-01 03:09:44 +02:00
|
|
|
queryMode: (process.env["PULUMI_NODEJS_QUERY_MODE"] === "true"),
|
2018-04-07 17:02:59 +02:00
|
|
|
parallel: parallel,
|
|
|
|
monitorAddr: process.env["PULUMI_NODEJS_MONITOR"],
|
|
|
|
engineAddr: process.env["PULUMI_NODEJS_ENGINE"],
|
2019-04-17 07:20:01 +02:00
|
|
|
testModeEnabled: (process.env["PULUMI_TEST_MODE"] === "true"),
|
2019-08-05 21:44:04 +02:00
|
|
|
legacyApply: (process.env["PULUMI_ENABLE_LEGACY_APPLY"] === "true"),
|
2019-10-15 07:08:06 +02:00
|
|
|
syncDir: process.env["PULUMI_NODEJS_SYNC"],
|
2018-04-07 17:02:59 +02:00
|
|
|
};
|
2017-09-04 17:30:39 +02:00
|
|
|
}
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2017-09-07 21:33:43 +02:00
|
|
|
export function disconnect(): void {
|
2017-09-09 23:09:21 +02:00
|
|
|
let done: Promise<any> | undefined;
|
2017-10-19 00:03:56 +02:00
|
|
|
const closeCallback: () => Promise<void> = () => {
|
2017-09-09 23:09:21 +02:00
|
|
|
if (done !== rpcDone) {
|
|
|
|
// If the done promise has changed, some activity occurred in between callbacks. Wait again.
|
|
|
|
done = rpcDone;
|
2018-11-25 03:57:17 +01:00
|
|
|
return debuggablePromise(done.then(closeCallback), "disconnect");
|
2017-09-09 23:09:21 +02:00
|
|
|
}
|
2017-10-26 20:30:25 +02:00
|
|
|
disconnectSync();
|
2017-09-09 23:09:21 +02:00
|
|
|
return Promise.resolve();
|
|
|
|
};
|
|
|
|
closeCallback();
|
2017-09-07 23:50:17 +02:00
|
|
|
}
|
|
|
|
|
2017-10-26 20:30:25 +02:00
|
|
|
/**
|
|
|
|
* 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 {
|
2018-04-07 17:02:59 +02:00
|
|
|
// Otherwise, actually perform the close activities (ignoring errors and crashes).
|
|
|
|
if (monitor) {
|
|
|
|
try {
|
|
|
|
monitor.close();
|
2017-10-30 19:48:54 +01:00
|
|
|
}
|
2018-04-07 17:02:59 +02:00
|
|
|
catch (err) {
|
|
|
|
// ignore.
|
2017-10-30 19:48:54 +01:00
|
|
|
}
|
2018-04-07 17:02:59 +02:00
|
|
|
monitor = null;
|
2017-10-26 20:30:25 +02:00
|
|
|
}
|
2018-04-07 17:02:59 +02:00
|
|
|
if (engine) {
|
|
|
|
try {
|
|
|
|
engine.close();
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
// ignore.
|
|
|
|
}
|
|
|
|
engine = null;
|
2017-10-26 20:30:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
|
|
|
* rpcDone resolves when the last known client-side RPC call finishes.
|
|
|
|
*/
|
2017-09-07 23:50:17 +02:00
|
|
|
let rpcDone: Promise<any> = Promise.resolve();
|
|
|
|
|
2017-09-22 03:15:29 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2017-09-07 23:50:17 +02:00
|
|
|
export function rpcKeepAlive(): () => void {
|
|
|
|
let done: (() => void) | undefined = undefined;
|
2018-11-25 03:57:17 +01:00
|
|
|
const donePromise = debuggablePromise(new Promise<void>((resolve) => { done = resolve; }), "rpcKeepAlive");
|
2017-09-07 23:50:17 +02:00
|
|
|
rpcDone = rpcDone.then(() => donePromise);
|
|
|
|
return done!;
|
2017-09-07 21:33:43 +02:00
|
|
|
}
|
|
|
|
|
2018-09-18 20:47:34 +02:00
|
|
|
let rootResource: Promise<URN> | undefined;
|
2017-11-26 19:04:15 +01:00
|
|
|
|
|
|
|
/**
|
2018-09-18 20:47:34 +02:00
|
|
|
* getRootResource returns a root resource URN that will automatically become the default parent of all resources. This
|
2017-11-26 19:04:15 +01:00
|
|
|
* can be used to ensure that all resources without explicit parents are parented to a common parent resource.
|
|
|
|
*/
|
2018-09-18 20:47:34 +02:00
|
|
|
export function getRootResource(): Promise<URN | undefined> {
|
|
|
|
const engineRef: any = getEngine();
|
|
|
|
if (!engineRef) {
|
|
|
|
return Promise.resolve(undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
const req = new engproto.GetRootResourceRequest();
|
|
|
|
return new Promise<URN | undefined>((resolve, reject) => {
|
|
|
|
engineRef.getRootResource(req, (err: grpc.ServiceError, resp: any) => {
|
|
|
|
// Back-compat case - if the engine we're speaking to isn't aware that it can save and load root resources,
|
|
|
|
// fall back to the old behavior.
|
|
|
|
if (err && err.code === grpc.status.UNIMPLEMENTED) {
|
|
|
|
if (rootResource) {
|
|
|
|
rootResource.then(resolve);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
const urn = resp.getUrn();
|
|
|
|
if (urn) {
|
|
|
|
return resolve(urn);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve(undefined);
|
|
|
|
});
|
|
|
|
});
|
2017-11-26 19:04:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* setRootResource registers a resource that will become the default parent for all resources without explicit parents.
|
|
|
|
*/
|
2018-09-18 20:47:34 +02:00
|
|
|
export async function setRootResource(res: ComponentResource): Promise<void> {
|
|
|
|
const engineRef: any = getEngine();
|
|
|
|
if (!engineRef) {
|
|
|
|
return Promise.resolve();
|
2017-11-26 19:04:15 +01:00
|
|
|
}
|
2018-09-18 20:47:34 +02:00
|
|
|
|
|
|
|
const req = new engproto.SetRootResourceRequest();
|
|
|
|
const urn = await res.urn.promise();
|
|
|
|
req.setUrn(urn);
|
|
|
|
return new Promise<void>((resolve, reject) => {
|
|
|
|
engineRef.setRootResource(req, (err: grpc.ServiceError, resp: any) => {
|
|
|
|
// Back-compat case - if the engine we're speaking to isn't aware that it can save and load root resources,
|
|
|
|
// fall back to the old behavior.
|
|
|
|
if (err && err.code === grpc.status.UNIMPLEMENTED) {
|
|
|
|
rootResource = res.urn.promise();
|
|
|
|
return resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve();
|
|
|
|
});
|
|
|
|
});
|
2017-11-26 19:04:15 +01:00
|
|
|
}
|
2019-04-13 03:25:33 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* monitorSupportsSecrets returns a promise that when resolved tells you if the resource monitor we are connected
|
|
|
|
* to is able to support secrets across it's RPC interface. When it does, we marshal outputs marked with the secret
|
|
|
|
* bit in a special way.
|
|
|
|
*/
|
|
|
|
export function monitorSupportsSecrets(): Promise<boolean> {
|
|
|
|
const monitorRef: any = getMonitor();
|
|
|
|
if (!monitorRef) {
|
|
|
|
return Promise.resolve(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
const req = new resproto.SupportsFeatureRequest();
|
|
|
|
req.setId("secrets");
|
|
|
|
|
|
|
|
return new Promise<boolean>((resolve, reject) => {
|
|
|
|
monitorRef.supportsFeature(req, (err: grpc.ServiceError, resp: any) => {
|
|
|
|
// Back-compat case - if the monitor doesn't let us ask if it supports a feature, it doesn't support
|
|
|
|
// secrets.
|
|
|
|
if (err && err.code === grpc.status.UNIMPLEMENTED) {
|
|
|
|
return resolve(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve(resp.getHassupport());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|