2021-09-16 01:25:26 +00:00
|
|
|
// Copyright 2016-2021, 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.
|
2017-09-04 15:30:39 +00:00
|
|
|
|
2020-04-14 08:30:25 +00:00
|
|
|
import * as grpc from "@grpc/grpc-js";
|
2019-10-15 05:08:06 +00:00
|
|
|
import * as fs from "fs";
|
|
|
|
import * as path from "path";
|
2022-06-30 10:04:49 +00:00
|
|
|
import { ComponentResource } from "../resource";
|
2024-03-07 08:52:34 +00:00
|
|
|
import { CallbackServer, ICallbackServer } from "./callbacks";
|
2017-09-07 21:50:17 +00:00
|
|
|
import { debuggablePromise } from "./debuggable";
|
2022-09-01 18:39:09 +00:00
|
|
|
import { getLocalStore, getStore } from "./state";
|
2017-09-07 21:50:17 +00:00
|
|
|
|
2023-12-04 15:22:44 +00:00
|
|
|
import * as engrpc from "../proto/engine_grpc_pb";
|
|
|
|
import * as engproto from "../proto/engine_pb";
|
|
|
|
import * as resrpc from "../proto/resource_grpc_pb";
|
|
|
|
import * as resproto from "../proto/resource_pb";
|
2018-04-07 15:02:59 +00:00
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* Raises the gRPC Max Message size from `4194304` (4mb) to `419430400` (400mb).
|
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
*/
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
export const maxRPCMessageSize: number = 1024 * 1024 * 400;
|
2020-04-24 00:30:30 +00:00
|
|
|
const grpcChannelOptions = { "grpc.max_receive_message_length": maxRPCMessageSize };
|
2020-04-20 22:02:09 +00:00
|
|
|
|
2017-09-27 19:34:44 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* excessiveDebugOutput enables, well, pretty excessive debug output pertaining
|
|
|
|
* to resources and properties.
|
2017-09-27 19:34:44 +00:00
|
|
|
*/
|
2021-08-10 18:31:59 +00:00
|
|
|
export const excessiveDebugOutput: boolean = false;
|
2017-09-27 19:34:44 +00:00
|
|
|
|
2017-09-22 01:15:29 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* {@link Options} is a bag of settings that controls the behavior of previews
|
|
|
|
* and deployments.
|
2017-09-22 01:15:29 +00:00
|
|
|
*/
|
2017-09-15 23:38:52 +00:00
|
|
|
export interface Options {
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* The name of the current project.
|
|
|
|
*/
|
|
|
|
readonly project?: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The name of the current stack being deployed into.
|
|
|
|
*/
|
|
|
|
readonly stack?: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The degree of parallelism for resource operations (default is serial).
|
|
|
|
*/
|
|
|
|
readonly parallel?: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A connection string to the engine's RPC, in case we need to reestablish.
|
|
|
|
*/
|
|
|
|
readonly engineAddr?: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A connection string to the monitor's RPC, in case we need to reestablish.
|
|
|
|
*/
|
|
|
|
readonly monitorAddr?: string;
|
2019-10-15 05:08:06 +00:00
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Whether we are performing a preview (true) or a real deployment (false).
|
|
|
|
*/
|
|
|
|
readonly dryRun?: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* True if we're in testing mode (allows execution without the CLI).
|
|
|
|
*/
|
|
|
|
readonly testModeEnabled?: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* True if we're in query mode (does not allow resource registration).
|
|
|
|
*/
|
|
|
|
readonly queryMode?: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* True if we will resolve missing outputs to inputs during preview.
|
|
|
|
*/
|
|
|
|
readonly legacyApply?: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* True if we will cache serialized dynamic providers on the program side.
|
|
|
|
*/
|
|
|
|
readonly cacheDynamicProviders?: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The name of the current organization.
|
|
|
|
*/
|
|
|
|
readonly organization?: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A directory containing the send/receive files for making synchronous
|
|
|
|
* invokes to the engine.
|
2019-10-15 05:08:06 +00:00
|
|
|
*/
|
|
|
|
readonly syncDir?: string;
|
2018-04-07 15:02:59 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 15:22:44 +00:00
|
|
|
let monitor: resrpc.ResourceMonitorClient | undefined;
|
|
|
|
let engine: engrpc.EngineClient | undefined;
|
2021-01-26 22:59:32 +00:00
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* Resets NodeJS runtime global state (such as RPC clients), and sets NodeJS
|
|
|
|
* runtime option environment variables to the specified values.
|
|
|
|
*/
|
2020-09-24 02:06:26 +00:00
|
|
|
export function resetOptions(
|
2023-04-28 22:27:10 +00:00
|
|
|
project: string,
|
|
|
|
stack: string,
|
|
|
|
parallel: number,
|
|
|
|
engineAddr: string,
|
|
|
|
monitorAddr: string,
|
|
|
|
preview: boolean,
|
|
|
|
organization: string,
|
|
|
|
) {
|
2023-12-19 14:35:23 +00:00
|
|
|
const store = getStore();
|
2022-09-01 18:39:09 +00:00
|
|
|
|
2020-09-24 02:06:26 +00:00
|
|
|
monitor = undefined;
|
|
|
|
engine = undefined;
|
2023-12-19 14:35:23 +00:00
|
|
|
|
|
|
|
store.settings.monitor = undefined;
|
|
|
|
store.settings.engine = undefined;
|
|
|
|
store.settings.rpcDone = Promise.resolve();
|
|
|
|
store.settings.featureSupport = {};
|
2022-09-01 18:39:09 +00:00
|
|
|
|
2021-01-26 22:59:32 +00:00
|
|
|
// reset node specific environment variables in the process
|
2023-12-19 14:35:23 +00:00
|
|
|
store.settings.options.project = project;
|
|
|
|
store.settings.options.stack = stack;
|
|
|
|
store.settings.options.dryRun = preview;
|
|
|
|
store.settings.options.queryMode = isQueryMode();
|
|
|
|
store.settings.options.parallel = parallel;
|
|
|
|
store.settings.options.monitorAddr = monitorAddr;
|
|
|
|
store.settings.options.engineAddr = engineAddr;
|
|
|
|
store.settings.options.organization = organization;
|
|
|
|
|
|
|
|
store.leakCandidates = new Set<Promise<any>>();
|
|
|
|
store.logErrorCount = 0;
|
|
|
|
store.stackResource = undefined;
|
|
|
|
store.supportsSecrets = false;
|
|
|
|
store.supportsResourceReferences = false;
|
|
|
|
store.supportsOutputValues = false;
|
|
|
|
store.supportsDeletedWith = false;
|
|
|
|
store.supportsAliasSpecs = false;
|
2024-03-07 08:52:34 +00:00
|
|
|
store.supportsTransforms = false;
|
2024-07-18 08:41:39 +00:00
|
|
|
store.supportsInvokeTransforms = false;
|
2024-03-07 08:52:34 +00:00
|
|
|
store.callbacks = undefined;
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
}
|
2020-02-29 01:22:50 +00:00
|
|
|
|
2023-04-28 22:27:10 +00:00
|
|
|
export function setMockOptions(
|
|
|
|
mockMonitor: any,
|
|
|
|
project?: string,
|
|
|
|
stack?: string,
|
|
|
|
preview?: boolean,
|
|
|
|
organization?: string,
|
|
|
|
) {
|
2021-01-26 22:59:32 +00:00
|
|
|
const opts = options();
|
|
|
|
resetOptions(
|
|
|
|
project || opts.project || "project",
|
|
|
|
stack || opts.stack || "stack",
|
|
|
|
opts.parallel || -1,
|
|
|
|
opts.engineAddr || "",
|
|
|
|
opts.monitorAddr || "",
|
|
|
|
preview || false,
|
2022-08-31 09:33:29 +00:00
|
|
|
organization || "",
|
2021-01-26 22:59:32 +00:00
|
|
|
);
|
2020-02-29 01:22:50 +00:00
|
|
|
|
|
|
|
monitor = mockMonitor;
|
|
|
|
}
|
2018-04-07 15:02:59 +00:00
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* Used only for testing purposes.
|
|
|
|
*/
|
2019-04-17 05:20:01 +00:00
|
|
|
export function _setIsDryRun(val: boolean) {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { settings } = getStore();
|
|
|
|
settings.options.dryRun = val;
|
2018-09-26 04:29:27 +00:00
|
|
|
}
|
|
|
|
|
2018-04-07 15:02:59 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns true if we are currently doing a preview.
|
2022-06-24 17:11:26 +00:00
|
|
|
*
|
2024-07-15 11:27:47 +00:00
|
|
|
* When writing unit tests, you can set this flag via either `setMocks` or
|
|
|
|
* `_setIsDryRun`.
|
2018-04-07 15:02:59 +00:00
|
|
|
*/
|
|
|
|
export function isDryRun(): boolean {
|
2021-01-26 22:59:32 +00:00
|
|
|
return options().dryRun === true;
|
2019-04-17 05:20:01 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 14:35:23 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns a promise that when resolved tells you if the resource monitor we are
|
|
|
|
* connected to is able to support a particular feature.
|
2023-12-19 14:35:23 +00:00
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
async function monitorSupportsFeature(monitorClient: resrpc.IResourceMonitorClient, feature: string): Promise<boolean> {
|
|
|
|
const req = new resproto.SupportsFeatureRequest();
|
|
|
|
req.setId(feature);
|
|
|
|
|
|
|
|
const result = await new Promise<boolean>((resolve, reject) => {
|
|
|
|
monitorClient.supportsFeature(
|
|
|
|
req,
|
|
|
|
(err: grpc.ServiceError | null, resp: resproto.SupportsFeatureResponse | undefined) => {
|
|
|
|
// Back-compat case - if the monitor doesn't let us ask if it supports a feature, it doesn't support
|
|
|
|
// any features.
|
|
|
|
if (err && err.code === grpc.status.UNIMPLEMENTED) {
|
|
|
|
return resolve(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resp === undefined) {
|
|
|
|
return reject(new Error("No response from resource monitor"));
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve(resp.getHassupport());
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Queries the resource monitor for its capabilities and sets the appropriate
|
|
|
|
* flags in the store.
|
2023-12-19 14:35:23 +00:00
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
**/
|
|
|
|
export async function awaitFeatureSupport(): Promise<void> {
|
|
|
|
const monitorRef = getMonitor();
|
|
|
|
if (monitorRef !== undefined) {
|
|
|
|
const store = getStore();
|
|
|
|
store.supportsSecrets = await monitorSupportsFeature(monitorRef, "secrets");
|
|
|
|
store.supportsResourceReferences = await monitorSupportsFeature(monitorRef, "resourceReferences");
|
|
|
|
store.supportsOutputValues = await monitorSupportsFeature(monitorRef, "outputValues");
|
|
|
|
store.supportsDeletedWith = await monitorSupportsFeature(monitorRef, "deletedWith");
|
|
|
|
store.supportsAliasSpecs = await monitorSupportsFeature(monitorRef, "aliasSpecs");
|
2024-03-07 08:52:34 +00:00
|
|
|
store.supportsTransforms = await monitorSupportsFeature(monitorRef, "transforms");
|
2024-07-18 08:41:39 +00:00
|
|
|
store.supportsInvokeTransforms = await monitorSupportsFeature(monitorRef, "invokeTransforms");
|
2023-12-19 14:35:23 +00:00
|
|
|
}
|
2020-12-17 18:49:22 +00:00
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* Used only for testing purposes.
|
|
|
|
*/
|
2019-05-01 01:09:44 +00:00
|
|
|
export function _setQueryMode(val: boolean) {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { settings } = getStore();
|
|
|
|
settings.options.queryMode = val;
|
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* Used only for testing purposes.
|
|
|
|
*/
|
2022-09-01 18:39:09 +00:00
|
|
|
export function _reset(): void {
|
|
|
|
resetOptions("", "", -1, "", "", false, "");
|
2019-05-01 01:09:44 +00:00
|
|
|
}
|
|
|
|
|
2020-01-27 18:22:32 +00:00
|
|
|
/**
|
2019-05-01 01:09:44 +00:00
|
|
|
* Returns true if query mode is enabled.
|
|
|
|
*/
|
|
|
|
export function isQueryMode(): boolean {
|
2021-01-26 22:59:32 +00:00
|
|
|
return options().queryMode === true;
|
2019-05-01 01:09:44 +00:00
|
|
|
}
|
|
|
|
|
2019-08-05 19:44:04 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns true if we will resolve missing outputs to inputs during preview
|
|
|
|
* (`PULUMI_ENABLE_LEGACY_APPLY`).
|
2019-08-05 19:44:04 +00:00
|
|
|
*/
|
|
|
|
export function isLegacyApplyEnabled(): boolean {
|
2021-01-26 22:59:32 +00:00
|
|
|
return options().legacyApply === true;
|
2019-08-05 19:44:04 +00:00
|
|
|
}
|
|
|
|
|
2021-04-05 21:37:45 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns true if we will cache serialized dynamic providers on the program
|
|
|
|
* side (the default is true).
|
2021-04-05 21:37:45 +00:00
|
|
|
*/
|
|
|
|
export function cacheDynamicProviders(): boolean {
|
|
|
|
return options().cacheDynamicProviders === true;
|
|
|
|
}
|
|
|
|
|
2022-08-31 09:33:29 +00:00
|
|
|
/**
|
|
|
|
* Get the organization being run by the current update.
|
|
|
|
*/
|
2022-09-21 20:51:15 +00:00
|
|
|
export function getOrganization(): string {
|
2022-08-31 09:33:29 +00:00
|
|
|
const organization = options().organization;
|
|
|
|
if (organization) {
|
|
|
|
return organization;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the organization is missing, specialize the error.
|
2022-09-19 09:28:04 +00:00
|
|
|
// Throw an error if test mode is enabled, instructing how to manually configure the organization:
|
2022-08-31 09:33:29 +00:00
|
|
|
throw new Error("Missing organization name; for test mode, please call `pulumi.runtime.setMocks`");
|
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* Used only for testing purposes.
|
|
|
|
*/
|
2022-08-31 09:33:29 +00:00
|
|
|
export function _setOrganization(val: string | undefined) {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { settings } = getStore();
|
|
|
|
settings.options.organization = val;
|
|
|
|
return settings.options.organization;
|
2022-08-31 09:33:29 +00:00
|
|
|
}
|
|
|
|
|
2018-04-07 15:02:59 +00:00
|
|
|
/**
|
|
|
|
* Get the project being run by the current update.
|
|
|
|
*/
|
2019-04-17 05:20:01 +00:00
|
|
|
export function getProject(): string {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { project } = options();
|
|
|
|
return project || "";
|
2019-04-17 05:20:01 +00:00
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* Used only for testing purposes.
|
|
|
|
*/
|
2019-06-01 06:01:01 +00:00
|
|
|
export function _setProject(val: string | undefined) {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { settings } = getStore();
|
|
|
|
settings.options.project = val;
|
|
|
|
return settings.options.project;
|
2018-04-07 15:02:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the stack being targeted by the current update.
|
|
|
|
*/
|
2019-04-17 05:20:01 +00:00
|
|
|
export function getStack(): string {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { stack } = options();
|
|
|
|
return stack || "";
|
2019-04-17 05:20:01 +00:00
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
* Used only for testing purposes.
|
|
|
|
*/
|
2019-06-01 06:01:01 +00:00
|
|
|
export function _setStack(val: string | undefined) {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { settings } = getStore();
|
|
|
|
settings.options.stack = val;
|
|
|
|
return settings.options.stack;
|
2017-09-09 20:43:51 +00:00
|
|
|
}
|
|
|
|
|
2017-09-22 01:15:29 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns true if we are currently connected to a resource monitoring service.
|
2017-09-22 01:15:29 +00:00
|
|
|
*/
|
2017-09-04 15:30:39 +00:00
|
|
|
export function hasMonitor(): boolean {
|
2021-01-26 22:59:32 +00:00
|
|
|
return !!monitor && !!options().monitorAddr;
|
2017-09-04 15:30:39 +00:00
|
|
|
}
|
|
|
|
|
2017-09-22 01:15:29 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns the current resource monitoring service client for RPC
|
|
|
|
* communications.
|
2017-09-22 01:15:29 +00:00
|
|
|
*/
|
2023-12-19 14:35:23 +00:00
|
|
|
export function getMonitor(): resrpc.IResourceMonitorClient | undefined {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { settings } = getStore();
|
|
|
|
const addr = options().monitorAddr;
|
|
|
|
if (getLocalStore() === undefined) {
|
|
|
|
if (monitor === undefined) {
|
|
|
|
if (addr) {
|
|
|
|
// Lazily initialize the RPC connection to the monitor.
|
2023-04-28 22:27:10 +00:00
|
|
|
monitor = new resrpc.ResourceMonitorClient(addr, grpc.credentials.createInsecure(), grpcChannelOptions);
|
2022-09-01 18:39:09 +00:00
|
|
|
settings.options.monitorAddr = addr;
|
|
|
|
}
|
2018-04-07 15:02:59 +00:00
|
|
|
}
|
2022-09-01 18:39:09 +00:00
|
|
|
return monitor;
|
|
|
|
} else {
|
|
|
|
if (settings.monitor === undefined) {
|
|
|
|
if (addr) {
|
|
|
|
// Lazily initialize the RPC connection to the monitor.
|
|
|
|
settings.monitor = new resrpc.ResourceMonitorClient(
|
|
|
|
addr,
|
|
|
|
grpc.credentials.createInsecure(),
|
|
|
|
grpcChannelOptions,
|
|
|
|
);
|
|
|
|
settings.options.monitorAddr = addr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return settings.monitor;
|
2017-09-04 15:30:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-07 08:52:34 +00:00
|
|
|
/**
|
|
|
|
* Waits for any pending stack transforms to register.
|
|
|
|
*/
|
|
|
|
export async function awaitStackRegistrations(): Promise<void> {
|
|
|
|
const store = getStore();
|
|
|
|
const callbacks = store.callbacks;
|
|
|
|
if (callbacks === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return await callbacks.awaitStackRegistrations();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns the current callbacks for RPC communications.
|
2024-03-07 08:52:34 +00:00
|
|
|
*/
|
|
|
|
export function getCallbacks(): ICallbackServer | undefined {
|
|
|
|
const store = getStore();
|
|
|
|
const callbacks = store.callbacks;
|
|
|
|
if (callbacks !== undefined) {
|
|
|
|
return callbacks;
|
|
|
|
}
|
|
|
|
|
|
|
|
const monitorRef = getMonitor();
|
|
|
|
if (monitorRef === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const callbackServer = new CallbackServer(monitorRef);
|
|
|
|
store.callbacks = callbackServer;
|
|
|
|
return callbackServer;
|
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2019-10-15 05:08:06 +00:00
|
|
|
export interface SyncInvokes {
|
|
|
|
requests: number;
|
|
|
|
responses: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
let syncInvokes: SyncInvokes | undefined;
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2019-10-15 05:08:06 +00:00
|
|
|
export function tryGetSyncInvokes(): SyncInvokes | undefined {
|
2021-01-26 22:59:32 +00:00
|
|
|
const syncDir = options().syncDir;
|
|
|
|
if (syncInvokes === undefined && syncDir) {
|
|
|
|
const requests = fs.openSync(path.join(syncDir, "invoke_req"), fs.constants.O_WRONLY | fs.constants.O_SYNC);
|
|
|
|
const responses = fs.openSync(path.join(syncDir, "invoke_res"), fs.constants.O_RDONLY | fs.constants.O_SYNC);
|
2019-10-15 05:08:06 +00:00
|
|
|
syncInvokes = { requests, responses };
|
|
|
|
}
|
|
|
|
|
|
|
|
return syncInvokes;
|
|
|
|
}
|
|
|
|
|
2020-09-24 02:06:26 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns true if we are currently connected to an engine.
|
2020-09-24 02:06:26 +00:00
|
|
|
*/
|
|
|
|
export function hasEngine(): boolean {
|
2021-01-26 22:59:32 +00:00
|
|
|
return !!engine && !!options().engineAddr;
|
2020-09-24 02:06:26 +00:00
|
|
|
}
|
|
|
|
|
2017-09-22 01:15:29 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns the current engine, if any, for RPC communications back to the
|
|
|
|
* resource engine.
|
2017-09-22 01:15:29 +00:00
|
|
|
*/
|
2023-12-19 14:35:23 +00:00
|
|
|
export function getEngine(): engrpc.IEngineClient | undefined {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { settings } = getStore();
|
|
|
|
if (getLocalStore() === undefined) {
|
|
|
|
if (engine === undefined) {
|
|
|
|
const addr = options().engineAddr;
|
|
|
|
if (addr) {
|
|
|
|
// Lazily initialize the RPC connection to the engine.
|
2023-04-28 22:27:10 +00:00
|
|
|
engine = new engrpc.EngineClient(addr, grpc.credentials.createInsecure(), grpcChannelOptions);
|
2022-09-01 18:39:09 +00:00
|
|
|
}
|
2018-04-07 15:02:59 +00:00
|
|
|
}
|
2022-09-01 18:39:09 +00:00
|
|
|
return engine;
|
|
|
|
} else {
|
|
|
|
if (settings.engine === undefined) {
|
|
|
|
const addr = options().engineAddr;
|
|
|
|
if (addr) {
|
|
|
|
// Lazily initialize the RPC connection to the engine.
|
2023-04-28 22:27:10 +00:00
|
|
|
settings.engine = new engrpc.EngineClient(addr, grpc.credentials.createInsecure(), grpcChannelOptions);
|
2022-09-01 18:39:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return settings.engine;
|
2018-04-07 15:02:59 +00:00
|
|
|
}
|
2017-09-04 15:30:39 +00:00
|
|
|
}
|
|
|
|
|
2020-09-24 02:06:26 +00:00
|
|
|
export function terminateRpcs() {
|
|
|
|
disconnectSync();
|
|
|
|
}
|
|
|
|
|
2017-09-22 01:15:29 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns true if resource operations should be serialized.
|
2017-09-22 01:15:29 +00:00
|
|
|
*/
|
2017-09-17 15:10:46 +00:00
|
|
|
export function serialize(): boolean {
|
2021-01-26 22:59:32 +00:00
|
|
|
return options().parallel === 1;
|
2017-09-17 15:10:46 +00:00
|
|
|
}
|
|
|
|
|
2018-02-14 17:55:02 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns the options from the environment, which is the source of truth.
|
|
|
|
* Options are global per process.
|
|
|
|
*
|
|
|
|
* For CLI driven programs, `pulumi-language-nodejs` sets environment variables
|
|
|
|
* prior to the user program loading, meaning that options could be loaded up
|
|
|
|
* front and cached. Automation API and multi-language components introduced
|
|
|
|
* more complex lifecycles for runtime `options()`. These language hosts manage
|
|
|
|
* the lifecycle of options manually throughout the lifetime of the NodeJS
|
|
|
|
* process. In addition, NodeJS module resolution can lead to duplicate copies
|
|
|
|
* of `@pulumi/pulumi` and thus duplicate options objects that may not be synced
|
|
|
|
* if options are cached upfront. Mutating options must write to the environment
|
2021-01-26 22:59:32 +00:00
|
|
|
* and reading options must always read directly from the environment.
|
2017-09-22 01:15:29 +00:00
|
|
|
*/
|
2021-01-26 22:59:32 +00:00
|
|
|
function options(): Options {
|
2022-09-01 18:39:09 +00:00
|
|
|
const { settings } = getStore();
|
2018-04-07 15:02:59 +00:00
|
|
|
|
2022-09-01 18:39:09 +00:00
|
|
|
return settings.options;
|
2017-09-04 15:30:39 +00:00
|
|
|
}
|
|
|
|
|
2017-09-22 01:15:29 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* 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-22 01:15:29 +00:00
|
|
|
*/
|
2020-09-24 02:06:26 +00:00
|
|
|
export function disconnect(): Promise<void> {
|
2021-06-30 14:48:56 +00:00
|
|
|
return waitForRPCs(/*disconnectFromServers*/ true);
|
|
|
|
}
|
|
|
|
|
2024-07-15 11:27:47 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2021-06-30 14:48:56 +00:00
|
|
|
export function waitForRPCs(disconnectFromServers = false): Promise<void> {
|
2022-09-01 18:39:09 +00:00
|
|
|
const localStore = getStore();
|
2017-09-09 21:09:21 +00:00
|
|
|
let done: Promise<any> | undefined;
|
2017-10-18 22:03:56 +00:00
|
|
|
const closeCallback: () => Promise<void> = () => {
|
2022-09-01 18:39:09 +00:00
|
|
|
if (done !== localStore.settings.rpcDone) {
|
2017-09-09 21:09:21 +00:00
|
|
|
// If the done promise has changed, some activity occurred in between callbacks. Wait again.
|
2022-09-01 18:39:09 +00:00
|
|
|
done = localStore.settings.rpcDone;
|
2018-11-25 02:57:17 +00:00
|
|
|
return debuggablePromise(done.then(closeCallback), "disconnect");
|
2017-09-09 21:09:21 +00:00
|
|
|
}
|
2021-06-30 14:48:56 +00:00
|
|
|
if (disconnectFromServers) {
|
|
|
|
disconnectSync();
|
|
|
|
}
|
2017-09-09 21:09:21 +00:00
|
|
|
return Promise.resolve();
|
|
|
|
};
|
2020-09-24 02:06:26 +00:00
|
|
|
return closeCallback();
|
2017-09-07 21:50:17 +00:00
|
|
|
}
|
|
|
|
|
2022-09-01 18:39:09 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Returns the configured number of process listeners available.
|
2022-09-01 18:39:09 +00:00
|
|
|
*/
|
|
|
|
export function getMaximumListeners(): number {
|
|
|
|
const { settings } = getStore();
|
|
|
|
return settings.options.maximumProcessListeners;
|
|
|
|
}
|
|
|
|
|
2017-10-26 18:30:25 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* 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.
|
2017-10-26 18:30:25 +00:00
|
|
|
*/
|
|
|
|
export function disconnectSync(): void {
|
2018-04-07 15:02:59 +00:00
|
|
|
// Otherwise, actually perform the close activities (ignoring errors and crashes).
|
2024-03-07 08:52:34 +00:00
|
|
|
const store = getStore();
|
|
|
|
if (store.callbacks) {
|
|
|
|
store.callbacks.shutdown();
|
|
|
|
store.callbacks = undefined;
|
|
|
|
}
|
|
|
|
|
2018-04-07 15:02:59 +00:00
|
|
|
if (monitor) {
|
|
|
|
try {
|
|
|
|
monitor.close();
|
2023-04-28 22:27:10 +00:00
|
|
|
} catch (err) {
|
2018-04-07 15:02:59 +00:00
|
|
|
// ignore.
|
2017-10-30 18:48:54 +00:00
|
|
|
}
|
2023-12-04 15:22:44 +00:00
|
|
|
monitor = undefined;
|
2017-10-26 18:30:25 +00:00
|
|
|
}
|
2018-04-07 15:02:59 +00:00
|
|
|
if (engine) {
|
|
|
|
try {
|
|
|
|
engine.close();
|
2023-04-28 22:27:10 +00:00
|
|
|
} catch (err) {
|
2018-04-07 15:02:59 +00:00
|
|
|
// ignore.
|
|
|
|
}
|
2023-12-04 15:22:44 +00:00
|
|
|
engine = undefined;
|
2017-10-26 18:30:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-22 01:15:29 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* 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-22 01:15:29 +00:00
|
|
|
*/
|
2017-09-07 21:50:17 +00:00
|
|
|
export function rpcKeepAlive(): () => void {
|
2022-09-01 18:39:09 +00:00
|
|
|
const localStore = getStore();
|
2017-09-07 21:50:17 +00:00
|
|
|
let done: (() => void) | undefined = undefined;
|
2023-05-15 18:49:20 +00:00
|
|
|
const donePromise = debuggablePromise(
|
|
|
|
new Promise<void>((resolve) => {
|
|
|
|
done = resolve;
|
|
|
|
return done;
|
|
|
|
}),
|
|
|
|
"rpcKeepAlive",
|
|
|
|
);
|
2022-09-01 18:39:09 +00:00
|
|
|
localStore.settings.rpcDone = localStore.settings.rpcDone.then(() => donePromise);
|
2017-09-07 21:50:17 +00:00
|
|
|
return done!;
|
2017-09-07 19:33:43 +00:00
|
|
|
}
|
|
|
|
|
2017-11-26 18:04:15 +00:00
|
|
|
/**
|
2024-07-15 11:27:47 +00:00
|
|
|
* Registers a resource that will become the default parent for all resources
|
|
|
|
* without explicit parents.
|
2017-11-26 18:04:15 +00:00
|
|
|
*/
|
2018-09-18 18:47:34 +00:00
|
|
|
export async function setRootResource(res: ComponentResource): Promise<void> {
|
2023-12-19 14:35:23 +00:00
|
|
|
// This is the first async point of program startup where we can query the resource monitor for its capabilities.
|
|
|
|
await awaitFeatureSupport();
|
|
|
|
|
2023-12-04 15:22:44 +00:00
|
|
|
const engineRef = getEngine();
|
2018-09-18 18:47:34 +00:00
|
|
|
if (!engineRef) {
|
|
|
|
return Promise.resolve();
|
2017-11-26 18:04:15 +00:00
|
|
|
}
|
2018-09-18 18:47:34 +00:00
|
|
|
|
2022-06-30 10:04:49 +00:00
|
|
|
// Back-compat case - Try to set the root URN for SxS old SDKs that expect the engine to roundtrip the
|
|
|
|
// stack URN.
|
2018-09-18 18:47:34 +00:00
|
|
|
const req = new engproto.SetRootResourceRequest();
|
|
|
|
const urn = await res.urn.promise();
|
|
|
|
req.setUrn(urn);
|
|
|
|
return new Promise<void>((resolve, reject) => {
|
2023-12-04 15:22:44 +00:00
|
|
|
engineRef.setRootResource(
|
|
|
|
req,
|
|
|
|
(err: grpc.ServiceError | null, resp: engproto.SetRootResourceResponse | undefined) => {
|
|
|
|
// Back-compat case - if the engine we're speaking to isn't aware that it can save and load root
|
|
|
|
// resources, just ignore there's nothing we can do.
|
|
|
|
if (err && err.code === grpc.status.UNIMPLEMENTED) {
|
|
|
|
return resolve();
|
|
|
|
}
|
2018-09-18 18:47:34 +00:00
|
|
|
|
2023-12-04 15:22:44 +00:00
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
2018-09-18 18:47:34 +00:00
|
|
|
|
2023-12-04 15:22:44 +00:00
|
|
|
return resolve();
|
|
|
|
},
|
|
|
|
);
|
2018-09-18 18:47:34 +00:00
|
|
|
});
|
2017-11-26 18:04:15 +00:00
|
|
|
}
|