67e5750742
There's a fair bit of clean up in here, but the meat is: * Allocate the language runtime gRPC client connection on the goroutine that will use it; this eliminates race conditions. * The biggie: there *appears* to be a bug in gRPC's implementation on Linux, where it doesn't implement WaitForReady properly. The behavior I'm observing is that RPC calls will not retry as they are supposed to, but will instead spuriously fail during the RPC startup. To work around this, I've added manual retry logic in the shared plugin creation function so that we won't even try to use the client connection until it is in a well-known state. pulumi/pulumi-fabric#337 tracks getting to the bottom of this and, ideally, removing the work around. The other minor things are: * Separate run.js into its own module, so it doesn't include index.js and do a bunch of random stuff it shouldn't be doing. * Allow run.js to be invoked without a --monitor. This makes testing just the run part of invocation easier (including config, which turned out to be super useful as I was debugging). * Tidy up some messages.
131 lines
4.9 KiB
TypeScript
131 lines
4.9 KiB
TypeScript
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
// This is the entrypoint for running a Node.js program with minimal scaffolding.
|
|
|
|
import * as minimist from "minimist";
|
|
import * as path from "path";
|
|
import * as runtime from "../../runtime";
|
|
|
|
let grpc = require("grpc");
|
|
let engrpc = require("../../proto/engine_grpc_pb.js");
|
|
let langproto = require("../../proto/languages_pb.js");
|
|
let langrpc = require("../../proto/languages_grpc_pb.js");
|
|
|
|
function usage(): void {
|
|
console.error(`usage: RUN <flags> [program] <[arg]...>`);
|
|
console.error(``);
|
|
console.error(` where [flags] may include`);
|
|
console.error(` --config.k=v... set runtime config key k to value v`);
|
|
console.error(` --dry-run true to simulate resource changes, but without making them`);
|
|
console.error(` --pwd=pwd change the working directory before running the program`);
|
|
console.error(` --monitor=addr the RPC address for a resource monitor to connect to`);
|
|
console.error(` --engine=addr the RPC address for a resource engine to connect to`);
|
|
console.error(``);
|
|
console.error(` and [program] is a JavaScript program to run in Node.js, and [arg]... optional args to it.`);
|
|
}
|
|
|
|
export function main(args: string[]): void {
|
|
// See usage above for the intended usage of this program, including flags and required args.
|
|
let config: {[key: string]: string} = {};
|
|
let argv: minimist.ParsedArgs = minimist(args, {
|
|
boolean: [ "dry-run" ],
|
|
string: [ "pwd", "monitor", "engine" ],
|
|
unknown: (arg: string) => {
|
|
// If unknown, first see if it's a --config.k=v flag.
|
|
let cix = arg.indexOf("-config");
|
|
if (cix === 0 || cix === 1) {
|
|
let kix = arg.indexOf(".");
|
|
let vix = arg.indexOf("=");
|
|
if (kix === -1 || vix === -1) {
|
|
console.error(`fatal: --config flag malformed (expected '--config.key=val')`);
|
|
usage();
|
|
process.exit(-1);
|
|
}
|
|
config[arg.substring(kix+1, vix)] = arg.substring(vix+1);
|
|
return false;
|
|
} else if (arg.indexOf("-") === 0) {
|
|
console.error(`fatal: Unrecognized flag ${arg}`);
|
|
usage();
|
|
process.exit(-1);
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
stopEarly: true,
|
|
});
|
|
|
|
// Set any configuration keys/values that were found.
|
|
for (let key of Object.keys(config)) {
|
|
runtime.setConfig(key, config[key]);
|
|
}
|
|
|
|
// If there is a --pwd directive, switch directories.
|
|
let pwd: string | undefined = argv["pwd"];
|
|
if (pwd) {
|
|
process.chdir(pwd);
|
|
}
|
|
|
|
// If ther is a --dry-run directive, flip the switch. This controls whether we are planning vs. really doing it.
|
|
let dryrun = false;
|
|
if (argv["dry-run"]) {
|
|
dryrun = true;
|
|
}
|
|
|
|
// If there is a monitor argument, connect to it.
|
|
let monitor: any | undefined;
|
|
let monitorAddr: string | undefined = argv["monitor"];
|
|
if (monitorAddr) {
|
|
monitor = new langrpc.ResourceMonitorClient(monitorAddr, grpc.credentials.createInsecure());
|
|
}
|
|
|
|
// If there is an engine argument, connect to it too.
|
|
let engine: any | undefined;
|
|
let engineAddr: string | undefined = argv["engine"];
|
|
if (engineAddr) {
|
|
engine = new engrpc.EngineClient(engineAddr, grpc.credentials.createInsecure());
|
|
}
|
|
|
|
// Now configure the runtime and get it ready to run the program.
|
|
runtime.configure(monitor, engine, dryrun);
|
|
|
|
// Pluck out the program and arguments.
|
|
if (argv._.length === 0) {
|
|
console.error("fatal: Missing program to execute");
|
|
usage();
|
|
process.exit(-1);
|
|
}
|
|
let program: string = argv._[0];
|
|
if (program.indexOf(".") === 0) {
|
|
// If there was a pwd change, make this relative to it.
|
|
if (pwd) {
|
|
program = path.join(pwd, program);
|
|
}
|
|
} else if (program.indexOf("/") !== 0) {
|
|
// Neither absolute nor relative module, we refuse to execute it.
|
|
console.error(`fatal: Program path '${program}' must be an absolute or relative path to the program`);
|
|
usage();
|
|
process.exit(-1);
|
|
}
|
|
|
|
// Now fake out the process-wide argv, to make the program think it was run normally.
|
|
let programArgs: string[] = argv._.slice(1);
|
|
process.argv = [ process.argv[0], process.argv[1], ...programArgs ];
|
|
|
|
// Now go ahead and execute the code. This keeps the process alive until the message loop exits.
|
|
runtime.Log.debug(`Running program '${program}' in pwd '${process.cwd()}' w/ args: ${programArgs}`);
|
|
try {
|
|
require(program);
|
|
}
|
|
catch (err) {
|
|
runtime.Log.debug(`Running program '${program}' failed with an unhandled exception:`);
|
|
runtime.Log.debug(err);
|
|
throw err;
|
|
}
|
|
finally {
|
|
runtime.disconnect();
|
|
}
|
|
}
|
|
|
|
main(process.argv.slice(2));
|
|
|