mirror of https://github.com/pulumi/pulumi.git
180 lines
6.9 KiB
TypeScript
180 lines
6.9 KiB
TypeScript
// Copyright 2016-2022, 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 grpc from "@grpc/grpc-js";
|
|
import { isGrpcError, ResourceError, RunError } from "../errors";
|
|
import * as log from "../log";
|
|
import * as runtimeConfig from "../runtime/config";
|
|
import * as debuggable from "../runtime/debuggable";
|
|
import * as settings from "../runtime/settings";
|
|
import * as stack from "../runtime/stack";
|
|
import * as localState from "../runtime/state";
|
|
|
|
import * as langproto from "../proto/language_pb";
|
|
import * as plugproto from "../proto/plugin_pb";
|
|
|
|
/**
|
|
* Raises the gRPC Max Message size from `4194304` (4mb) to `419430400` (400mb).
|
|
*
|
|
* @internal
|
|
*/
|
|
export const maxRPCMessageSize: number = 1024 * 1024 * 400;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
export class LanguageServer<T> implements grpc.UntypedServiceImplementation {
|
|
readonly program: () => Promise<T>;
|
|
|
|
// Satisfy the grpc.UntypedServiceImplementation interface.
|
|
[name: string]: any;
|
|
|
|
constructor(program: () => Promise<T>) {
|
|
this.program = program;
|
|
}
|
|
|
|
onPulumiExit(hasError: boolean) {
|
|
// Check for leaks once the CLI exits but skip if the program otherwise
|
|
// errored to keep error output clean
|
|
if (!hasError) {
|
|
const [leaks, leakMessage] = debuggable.leakedPromises();
|
|
if (leaks.size !== 0) {
|
|
throw new Error(leakMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
getRequiredPlugins(call: any, callback: any): void {
|
|
const resp: any = new langproto.GetRequiredPluginsResponse();
|
|
resp.setPluginsList([]);
|
|
callback(undefined, resp);
|
|
}
|
|
|
|
run(call: any, callback: any): Promise<void> {
|
|
const req: any = call.request;
|
|
const resp: any = new langproto.RunResponse();
|
|
|
|
// Setup a new async state store for this run
|
|
const store = new localState.LocalStore();
|
|
return localState.asyncLocalStorage.run(store, async () => {
|
|
const errorSet = new Set<Error>();
|
|
const uncaughtHandler = newUncaughtHandler(errorSet);
|
|
try {
|
|
const args = req.getArgsList();
|
|
const engineAddr = args && args.length > 0 ? args[0] : "";
|
|
|
|
settings.resetOptions(
|
|
req.getProject(),
|
|
req.getStack(),
|
|
req.getParallel(),
|
|
engineAddr,
|
|
req.getMonitorAddress(),
|
|
req.getDryrun(),
|
|
req.getOrganization(),
|
|
);
|
|
|
|
const config: { [key: string]: string } = {};
|
|
for (const [k, v] of req.getConfigMap()?.entries() || []) {
|
|
config[<string>k] = <string>v;
|
|
}
|
|
runtimeConfig.setAllConfig(config, req.getConfigsecretkeysList() || []);
|
|
|
|
process.setMaxListeners(settings.getMaximumListeners());
|
|
|
|
process.on("uncaughtException", uncaughtHandler);
|
|
// @ts-ignore 'unhandledRejection' will almost always invoke uncaughtHandler with an Error. so
|
|
// just suppress the TS strictness here.
|
|
process.on("unhandledRejection", uncaughtHandler);
|
|
|
|
try {
|
|
await stack.runInPulumiStack(this.program);
|
|
await settings.disconnect();
|
|
process.off("uncaughtException", uncaughtHandler);
|
|
process.off("unhandledRejection", uncaughtHandler);
|
|
} catch (e) {
|
|
await settings.disconnect();
|
|
process.off("uncaughtException", uncaughtHandler);
|
|
process.off("unhandledRejection", uncaughtHandler);
|
|
|
|
if (!isGrpcError(e)) {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
if (errorSet.size !== 0 || log.hasErrors()) {
|
|
let errorMessage: string = "";
|
|
if (errorSet.size !== 0) {
|
|
errorMessage = ": ";
|
|
errorSet.forEach((error) => {
|
|
errorMessage += `${error.message}, `;
|
|
});
|
|
errorMessage = errorMessage.slice(0, -2);
|
|
} else {
|
|
errorMessage = ". Check logs for more details";
|
|
}
|
|
throw new Error(`One or more errors occurred${errorMessage}`);
|
|
}
|
|
} catch (e) {
|
|
const err = e instanceof Error ? e : new Error(`unknown error ${e}`);
|
|
resp.setError(err.message);
|
|
callback(err, undefined);
|
|
}
|
|
|
|
callback(undefined, resp);
|
|
});
|
|
}
|
|
|
|
getPluginInfo(call: any, callback: any): void {
|
|
const resp: any = new plugproto.PluginInfo();
|
|
resp.setVersion("1.0.0");
|
|
callback(undefined, resp);
|
|
}
|
|
}
|
|
|
|
function newUncaughtHandler(errorSet: Set<Error>): (err: Error) => void {
|
|
return (err: Error) => {
|
|
// In node, if you throw an error in a chained promise, but the exception is not finally
|
|
// handled, then you can end up getting an unhandledRejection for each exception/promise
|
|
// pair. Because the exception is the same through all of these, we keep track of it and
|
|
// only report it once so the user doesn't get N messages for the same thing.
|
|
if (errorSet.has(err)) {
|
|
return;
|
|
}
|
|
|
|
errorSet.add(err);
|
|
|
|
// Default message should be to include the full stack (which includes the message), or
|
|
// fallback to just the message if we can't get the stack.
|
|
//
|
|
// If both the stack and message are empty, then just stringify the err object itself. This
|
|
// is also necessary as users can throw arbitrary things in JS (including non-Errors).
|
|
let defaultMessage = "";
|
|
if (err) {
|
|
defaultMessage = err.stack || err.message || "" + err;
|
|
}
|
|
|
|
// First, log the error.
|
|
if (RunError.isInstance(err)) {
|
|
// Always hide the stack for RunErrors.
|
|
log.error(err.message);
|
|
} else if (ResourceError.isInstance(err)) {
|
|
// Hide the stack if requested to by the ResourceError creator.
|
|
const message = err.hideStack ? err.message : defaultMessage;
|
|
log.error(message, err.resource);
|
|
} else if (!isGrpcError(err)) {
|
|
log.error(`Unhandled exception: ${defaultMessage}`);
|
|
}
|
|
};
|
|
}
|