detect nodejs side by side pulumi for inline automation programs and fail fast (#7349)

This commit is contained in:
Evan Boyle 2021-06-23 07:57:36 -07:00 committed by GitHub
parent 414367963f
commit a850648504
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 1 deletions

View file

@ -6,6 +6,8 @@
to be watched by the `pulumi watch` command.
[#7115](https://github.com/pulumi/pulumi/pull/7247)
- [auto/nodejs] - Fail early when multiple versions of `@pulumi/pulumi` are detected in nodejs inline programs.'
[#7349](https://github.com/pulumi/pulumi/pull/7349)
### Bug Fixes

View file

@ -37,6 +37,10 @@ export class LanguageServer<T> implements grpc.UntypedServiceImplementation {
this.program = program;
this.running = false;
// set a bit in runtime settings to indicate that we're running in inline mode.
// this allows us to detect and fail fast for side by side pulumi scenarios.
runtime.setInline();
}
onPulumiExit(hasError: boolean) {

View file

@ -239,6 +239,8 @@ export function hasMonitor(): boolean {
* getMonitor returns the current resource monitoring service client for RPC communications.
*/
export function getMonitor(): Object | undefined {
// pre-emptive fail fast check for node inline programs
runSxSCheck();
if (monitor === undefined) {
const addr = options().monitorAddr;
if (addr) {
@ -329,6 +331,8 @@ export function serialize(): boolean {
*/
function options(): Options {
// pre-emptive fail fast check for node inline programs
runSxSCheck();
// The only option that needs parsing is the parallelism flag. Ignore any failures.
let parallel: number | undefined;
const parallelOpt = process.env[nodeEnvKeys.parallel];
@ -545,3 +549,37 @@ export function monitorSupportsSecrets(): Promise<boolean> {
export async function monitorSupportsResourceReferences(): Promise<boolean> {
return monitorSupportsFeature("resourceReferences");
}
// sxsRandomIdentifier is a module level global that is transfered to process.env.
// the goal is to detect side by side (sxs) pulumi/pulumi situations for inline programs
// and fail fast. See https://github.com/pulumi/pulumi/issues/7333 for details.
const sxsRandomIdentifier = Math.random().toString();
// indicates that the current runtime context is via an inline program via automation api.
let isInline = false;
/** @internal only used by the internal inline language host implementation */
export function setInline() {
isInline = true;
}
const pulumiSxSEnv = "PULUMI_NODEJS_SXS_FLAG";
/**
* runSxSCheck checks an identifier stored in the environment to detect multiple versions of pulumi.
* if we're running in inline mode, it will throw an error to fail fast due to global state collisions that can occur.
*/
function runSxSCheck() {
const envSxS = process.env[pulumiSxSEnv];
process.env[pulumiSxSEnv] = sxsRandomIdentifier;
if (!isInline) {
return;
}
// if we see a different identifier, another version of pulumi has been loaded and we should fail.
if (!!envSxS && envSxS !== sxsRandomIdentifier) {
throw new Error("Detected multiple versions of '@pulumi/pulumi' in use in an inline automation api program.\n" +
"Use the yarn 'resolutions' field to pin to a single version: https://github.com/pulumi/pulumi/issues/5449.");
}
}

View file

@ -26,7 +26,7 @@ import {
Stack,
validatePulumiVersion,
} from "../../automation";
import { Config } from "../../index";
import { Config, output } from "../../index";
import { asyncTest } from "../util";
const versionRegex = /(\d+\.)(\d+\.)(\d+)(-.*)?/;
@ -606,6 +606,38 @@ describe("LocalWorkspace", () => {
await stack.workspace.removeStack(stackName);
}));
it(`detects inline programs with side by side pulumi and throws an error`, asyncTest(async () => {
const program = async () => {
// clear pulumi/pulumi from require cache
delete require.cache[require.resolve("../../runtime")];
delete require.cache[require.resolve("../../runtime/config")];
delete require.cache[require.resolve("../../runtime/settings")];
// load up a fresh instance of pulumi
const p1 = require("../../runtime/settings");
// do some work that happens to observe runtime options with the new instance
p1.monitorSupportsSecrets();
return {
// export an output from originally pulumi causing settings to be observed again (boom).
test: output("original_pulumi"),
};
};
const projectName = "inline_node_sxs";
const stackName = fullyQualifiedStackName(getTestOrg(), projectName, `int_test${getTestSuffix()}`);
const stack = await LocalWorkspace.createStack({ stackName, projectName, program });
// pulumi up
await assert.rejects(stack.up(), (err: Error) => {
return err.stack!.indexOf("Detected multiple versions of '@pulumi/pulumi'") >= 0;
});
// pulumi destroy
const destroyRes = await stack.destroy();
assert.strictEqual(destroyRes.summary.kind, "destroy");
assert.strictEqual(destroyRes.summary.result, "succeeded");
await stack.workspace.removeStack(stackName);
}));
it(`sets pulumi version`, asyncTest(async () => {
const ws = await LocalWorkspace.create({});
assert(ws.pulumiVersion);