detect nodejs side by side pulumi for inline automation programs and fail fast (#7349)
This commit is contained in:
parent
414367963f
commit
a850648504
|
@ -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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue