New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #9065: uncaught error "ENOENT: no such file or directory" #9097
Conversation
PR is now waiting for a maintainer to take action. Note for the maintainer: Commands available:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks reasonable
PR is now waiting for a maintainer to take action. Note for the maintainer: Commands available:
|
@Frassle Do you need something else to merge this PR ? |
No sorry just slipped my attention, I'm on PTO tomorrow but I'll pick this up on Friday 👍 |
I just experienced this bug. Any word on when this will be released? |
For now I'll try and patch the JS file by hand. I could probably figure out how to install a pulumi prerelease if I hadn't started doing this already: /workspace/node_modules/@pulumi/pulumi/automation/stack.js "use strict";
// Copyright 2016-2020, 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.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const os = require("os");
const path = require("path");
const readline = require("readline");
const upath = require("upath");
const grpc = require("@grpc/grpc-js");
const TailFile = require("@logdna/tail-file");
const log = require("../log");
const cmd_1 = require("./cmd");
const errors_1 = require("./errors");
const server_1 = require("./server");
const langrpc = require("../proto/language_grpc_pb.js");
/**
* Stack is an isolated, independently configurable instance of a Pulumi program.
* Stack exposes methods for the full pulumi lifecycle (up/preview/refresh/destroy), as well as managing configuration.
* Multiple Stacks are commonly used to denote different phases of development
* (such as development, staging and production) or feature branches (such as feature-x-dev, jane-feature-x-dev).
*
* @alpha
*/
class Stack {
constructor(name, workspace, mode) {
this.name = name;
this.workspace = workspace;
switch (mode) {
case "create":
this.ready = workspace.createStack(name);
return this;
case "select":
this.ready = workspace.selectStack(name);
return this;
case "createOrSelect":
this.ready = workspace.createStack(name).catch((err) => {
if (err instanceof errors_1.StackAlreadyExistsError) {
return workspace.selectStack(name);
}
throw err;
});
return this;
default:
throw new Error(`unexpected Stack creation mode: ${mode}`);
}
}
/**
* Creates a new stack using the given workspace, and stack name.
* It fails if a stack with that name already exists
*
* @param name The name identifying the Stack.
* @param workspace The Workspace the Stack was created from.
*/
static create(name, workspace) {
return __awaiter(this, void 0, void 0, function* () {
const stack = new Stack(name, workspace, "create");
yield stack.ready;
return stack;
});
}
/**
* Selects stack using the given workspace, and stack name.
* It returns an error if the given Stack does not exist.
*
* @param name The name identifying the Stack.
* @param workspace The Workspace the Stack was created from.
*/
static select(name, workspace) {
return __awaiter(this, void 0, void 0, function* () {
const stack = new Stack(name, workspace, "select");
yield stack.ready;
return stack;
});
}
/**
* Tries to create a new stack using the given workspace and
* stack name if the stack does not already exist,
* or falls back to selecting the existing stack. If the stack does not exist,
* it will be created and selected.
*
* @param name The name identifying the Stack.
* @param workspace The Workspace the Stack was created from.
*/
static createOrSelect(name, workspace) {
return __awaiter(this, void 0, void 0, function* () {
const stack = new Stack(name, workspace, "createOrSelect");
yield stack.ready;
return stack;
});
}
readLines(logPath, callback) {
return __awaiter(this, void 0, void 0, function* () {
const eventLogTail = new TailFile(logPath, { startPos: 0, pollFileIntervalMs: 200 })
.on("tail_error", (err) => {
throw err;
});
yield eventLogTail.start();
const lineSplitter = readline.createInterface({ input: eventLogTail });
lineSplitter.on("line", (line) => {
let event;
try {
event = JSON.parse(line);
callback(event);
}
catch (e) {
log.warn(`Failed to parse engine event
If you're seeing this warning, please comment on https://github.com/pulumi/pulumi/issues/6768 with the event and any
details about your environment.
Event: ${line}\n${e.toString()}`);
}
});
return {
tail: eventLogTail,
rl: lineSplitter,
};
});
}
/**
* Creates or updates the resources in a stack by executing the program in the Workspace.
* https://www.pulumi.com/docs/reference/cli/pulumi_up/
*
* @param opts Options to customize the behavior of the update.
*/
up(opts) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const args = ["up", "--yes", "--skip-preview"];
let kind = execKind.local;
let program = this.workspace.program;
if (opts) {
if (opts.program) {
program = opts.program;
}
if (opts.message) {
args.push("--message", opts.message);
}
if (opts.expectNoChanges) {
args.push("--expect-no-changes");
}
if (opts.diff) {
args.push("--diff");
}
if (opts.replace) {
for (const rURN of opts.replace) {
args.push("--replace", rURN);
}
}
if (opts.target) {
for (const tURN of opts.target) {
args.push("--target", tURN);
}
}
if (opts.targetDependents) {
args.push("--target-dependents");
}
if (opts.parallel) {
args.push("--parallel", opts.parallel.toString());
}
if (opts.userAgent) {
args.push("--exec-agent", opts.userAgent);
}
if (opts.color) {
args.push("--color", opts.color);
}
}
let onExit = (hasError) => { return; };
let didError = false;
if (program) {
kind = execKind.inline;
const server = new grpc.Server({
"grpc.max_receive_message_length": server_1.maxRPCMessageSize,
});
const languageServer = new server_1.LanguageServer(program);
server.addService(langrpc.LanguageRuntimeService, languageServer);
const port = yield new Promise((resolve, reject) => {
server.bindAsync(`0.0.0.0:0`, grpc.ServerCredentials.createInsecure(), (err, p) => {
if (err) {
reject(err);
}
else {
resolve(p);
}
});
});
server.start();
onExit = (hasError) => {
languageServer.onPulumiExit(hasError);
server.forceShutdown();
};
args.push(`--client=127.0.0.1:${port}`);
}
args.push("--exec-kind", kind);
let logPromise;
let logFile;
// Set up event log tailing
if ((_a = opts) === null || _a === void 0 ? void 0 : _a.onEvent) {
const onEvent = opts.onEvent;
logFile = createLogFile("up");
args.push("--event-log", logFile);
logPromise = this.readLines(logFile, (event) => {
onEvent(event);
});
}
const upPromise = this.runPulumiCmd(args, (_b = opts) === null || _b === void 0 ? void 0 : _b.onOutput);
let upResult;
try {
upResult = yield upPromise
}
catch (e) {
didError = true;
throw e;
}
finally {
onExit(didError);
yield cleanUp(logFile, yield logPromise);
}
// TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/6050
const outputs = yield this.outputs();
const summary = yield this.info();
return {
stdout: upResult.stdout,
stderr: upResult.stderr,
summary: summary,
outputs: outputs,
};
});
}
/**
* Performs a dry-run update to a stack, returning pending changes.
* https://www.pulumi.com/docs/reference/cli/pulumi_preview/
*
* @param opts Options to customize the behavior of the preview.
*/
preview(opts) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const args = ["preview"];
let kind = execKind.local;
let program = this.workspace.program;
if (opts) {
if (opts.program) {
program = opts.program;
}
if (opts.message) {
args.push("--message", opts.message);
}
if (opts.expectNoChanges) {
args.push("--expect-no-changes");
}
if (opts.diff) {
args.push("--diff");
}
if (opts.replace) {
for (const rURN of opts.replace) {
args.push("--replace", rURN);
}
}
if (opts.target) {
for (const tURN of opts.target) {
args.push("--target", tURN);
}
}
if (opts.targetDependents) {
args.push("--target-dependents");
}
if (opts.parallel) {
args.push("--parallel", opts.parallel.toString());
}
if (opts.userAgent) {
args.push("--exec-agent", opts.userAgent);
}
if (opts.color) {
args.push("--color", opts.color);
}
}
let onExit = (hasError) => { return; };
let didError = false;
if (program) {
console.log('Running program....')
kind = execKind.inline;
const server = new grpc.Server({
"grpc.max_receive_message_length": server_1.maxRPCMessageSize,
});
const languageServer = new server_1.LanguageServer(program);
server.addService(langrpc.LanguageRuntimeService, languageServer);
const port = yield new Promise((resolve, reject) => {
server.bindAsync(`0.0.0.0:0`, grpc.ServerCredentials.createInsecure(), (err, p) => {
if (err) {
reject(err);
}
else {
resolve(p);
}
});
});
server.start();
onExit = (hasError) => {
languageServer.onPulumiExit(hasError);
server.forceShutdown();
};
args.push(`--client=127.0.0.1:${port}`);
}
args.push("--exec-kind", kind);
// Set up event log tailing
const logFile = createLogFile("preview");
args.push("--event-log", logFile);
let summaryEvent;
const rlPromise = this.readLines(logFile, (event) => {
var _a;
if (event.summaryEvent) {
summaryEvent = event.summaryEvent;
}
if ((_a = opts) === null || _a === void 0 ? void 0 : _a.onEvent) {
const onEvent = opts.onEvent;
onEvent(event);
}
});
const prePromise = this.runPulumiCmd(args, (_a = opts) === null || _a === void 0 ? void 0 : _a.onOutput);
let preResult;
let rlResult;
try {
// TODO: This line in the preview function also probably needs to be fixed:
[preResult, rlResult] = yield prePromise;
}
catch (e) {
didError = true;
throw e;
}
finally {
onExit(didError);
yield cleanUp(logFile, yield rlPromise);
}
if (!summaryEvent) {
log.warn("Failed to parse summary event, but preview succeeded. PreviewResult `changeSummary` will be empty.");
}
return {
stdout: preResult.stdout,
stderr: preResult.stderr,
changeSummary: ((_b = summaryEvent) === null || _b === void 0 ? void 0 : _b.resourceChanges) || {},
};
});
}
/**
* Compares the current stack’s resource state with the state known to exist in the actual
* cloud provider. Any such changes are adopted into the current stack.
*
* @param opts Options to customize the behavior of the refresh.
*/
refresh(opts) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const args = ["refresh", "--yes", "--skip-preview"];
if (opts) {
if (opts.message) {
args.push("--message", opts.message);
}
if (opts.expectNoChanges) {
args.push("--expect-no-changes");
}
if (opts.target) {
for (const tURN of opts.target) {
args.push("--target", tURN);
}
}
if (opts.parallel) {
args.push("--parallel", opts.parallel.toString());
}
if (opts.userAgent) {
args.push("--exec-agent", opts.userAgent);
}
if (opts.color) {
args.push("--color", opts.color);
}
}
let logPromise;
let logFile;
// Set up event log tailing
if ((_a = opts) === null || _a === void 0 ? void 0 : _a.onEvent) {
const onEvent = opts.onEvent;
logFile = createLogFile("refresh");
args.push("--event-log", logFile);
logPromise = this.readLines(logFile, (event) => {
onEvent(event);
});
}
const kind = this.workspace.program ? execKind.inline : execKind.local;
args.push("--exec-kind", kind);
const refPromise = this.runPulumiCmd(args, (_b = opts) === null || _b === void 0 ? void 0 : _b.onOutput);
const [refResult, logResult] = yield Promise.all([refPromise, logPromise]);
yield cleanUp(logFile, logResult);
const summary = yield this.info();
return {
stdout: refResult.stdout,
stderr: refResult.stderr,
summary: summary,
};
});
}
/**
* Destroy deletes all resources in a stack, leaving all history and configuration intact.
*
* @param opts Options to customize the behavior of the destroy.
*/
destroy(opts) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const args = ["destroy", "--yes", "--skip-preview"];
if (opts) {
if (opts.message) {
args.push("--message", opts.message);
}
if (opts.target) {
for (const tURN of opts.target) {
args.push("--target", tURN);
}
}
if (opts.targetDependents) {
args.push("--target-dependents");
}
if (opts.parallel) {
args.push("--parallel", opts.parallel.toString());
}
if (opts.userAgent) {
args.push("--exec-agent", opts.userAgent);
}
if (opts.color) {
args.push("--color", opts.color);
}
}
let logPromise;
let logFile;
// Set up event log tailing
if ((_a = opts) === null || _a === void 0 ? void 0 : _a.onEvent) {
const onEvent = opts.onEvent;
logFile = createLogFile("destroy");
args.push("--event-log", logFile);
logPromise = this.readLines(logFile, (event) => {
onEvent(event);
});
}
const kind = this.workspace.program ? execKind.inline : execKind.local;
args.push("--exec-kind", kind);
const desPromise = this.runPulumiCmd(args, (_b = opts) === null || _b === void 0 ? void 0 : _b.onOutput);
const [desResult, logResult] = yield Promise.all([desPromise, logPromise]);
yield cleanUp(logFile, logResult);
const summary = yield this.info();
return {
stdout: desResult.stdout,
stderr: desResult.stderr,
summary: summary,
};
});
}
/**
* Returns the config value associated with the specified key.
*
* @param key The key to use for the config lookup
*/
getConfig(key) {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.getConfig(this.name, key);
});
}
/**
* Returns the full config map associated with the stack in the Workspace.
*/
getAllConfig() {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.getAllConfig(this.name);
});
}
/**
* Sets a config key-value pair on the Stack in the associated Workspace.
*
* @param key The key to set.
* @param value The config value to set.
*/
setConfig(key, value) {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.setConfig(this.name, key, value);
});
}
/**
* Sets all specified config values on the stack in the associated Workspace.
*
* @param config The map of config key-value pairs to set.
*/
setAllConfig(config) {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.setAllConfig(this.name, config);
});
}
/**
* Removes the specified config key from the Stack in the associated Workspace.
*
* @param key The config key to remove.
*/
removeConfig(key) {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.removeConfig(this.name, key);
});
}
/**
* Removes the specified config keys from the Stack in the associated Workspace.
*
* @param keys The config keys to remove.
*/
removeAllConfig(keys) {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.removeAllConfig(this.name, keys);
});
}
/**
* Gets and sets the config map used with the last update.
*/
refreshConfig() {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.refreshConfig(this.name);
});
}
/**
* Gets the current set of Stack outputs from the last Stack.up().
*/
outputs() {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.stackOutputs(this.name);
});
}
/**
* Returns a list summarizing all previous and current results from Stack lifecycle operations
* (up/preview/refresh/destroy).
*/
history(pageSize, page) {
return __awaiter(this, void 0, void 0, function* () {
const args = ["stack", "history", "--json", "--show-secrets"];
if (pageSize) {
if (!page || page < 1) {
page = 1;
}
args.push("--page-size", Math.floor(pageSize).toString(), "--page", Math.floor(page).toString());
}
const result = yield this.runPulumiCmd(args);
return JSON.parse(result.stdout, (key, value) => {
if (key === "startTime" || key === "endTime") {
return new Date(value);
}
return value;
});
});
}
info() {
return __awaiter(this, void 0, void 0, function* () {
const history = yield this.history(1 /*pageSize*/);
if (!history || history.length === 0) {
return undefined;
}
return history[0];
});
}
/**
* Cancel stops a stack's currently running update. It returns an error if no update is currently running.
* Note that this operation is _very dangerous_, and may leave the stack in an inconsistent state
* if a resource operation was pending when the update was canceled.
* This command is not supported for local backends.
*/
cancel() {
return __awaiter(this, void 0, void 0, function* () {
yield this.runPulumiCmd(["cancel", "--yes"]);
});
}
/**
* exportStack exports the deployment state of the stack.
* This can be combined with Stack.importStack to edit a stack's state (such as recovery from failed deployments).
*/
exportStack() {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.exportStack(this.name);
});
}
/**
* importStack imports the specified deployment state into a pre-existing stack.
* This can be combined with Stack.exportStack to edit a stack's state (such as recovery from failed deployments).
*
* @param state the stack state to import.
*/
importStack(state) {
return __awaiter(this, void 0, void 0, function* () {
return this.workspace.importStack(this.name, state);
});
}
runPulumiCmd(args, onOutput) {
return __awaiter(this, void 0, void 0, function* () {
let envs = {
"PULUMI_DEBUG_COMMANDS": "true",
};
const pulumiHome = this.workspace.pulumiHome;
if (pulumiHome) {
envs["PULUMI_HOME"] = pulumiHome;
}
envs = Object.assign(Object.assign({}, envs), this.workspace.envVars);
const additionalArgs = yield this.workspace.serializeArgsForOp(this.name);
args = [...args, "--stack", this.name, ...additionalArgs];
const result = yield cmd_1.runPulumiCmd(args, this.workspace.workDir, envs, onOutput);
yield this.workspace.postCommandCallback(this.name);
return result;
});
}
}
exports.Stack = Stack;
/**
* Returns a stack name formatted with the greatest possible specificity:
* org/project/stack or user/project/stack
* Using this format avoids ambiguity in stack identity guards creating or selecting the wrong stack.
* Note that filestate backends (local file, S3, Azure Blob) do not support stack names in this
* format, and instead only use the stack name without an org/user or project to qualify it.
* See: https://github.com/pulumi/pulumi/issues/2522
*
* @param org The org (or user) that contains the Stack.
* @param project The project that parents the Stack.
* @param stack The name of the Stack.
*/
function fullyQualifiedStackName(org, project, stack) {
return `${org}/${project}/${stack}`;
}
exports.fullyQualifiedStackName = fullyQualifiedStackName;
const execKind = {
local: "auto.local",
inline: "auto.inline",
};
const delay = (duration) => new Promise(resolve => setTimeout(resolve, duration));
const createLogFile = (command) => {
const logDir = fs.mkdtempSync(upath.joinSafe(os.tmpdir(), `automation-logs-${command}-`));
const logFile = upath.joinSafe(logDir, "eventlog.txt");
// just open/close the file to make sure it exists when we start polling.
fs.closeSync(fs.openSync(logFile, "w"));
return logFile;
};
const cleanUp = (logFile, rl) => __awaiter(void 0, void 0, void 0, function* () {
if (rl) {
// stop tailing
yield rl.tail.quit();
// close the readline interface
rl.rl.close();
}
if (logFile) {
// remove the logfile
if (fs.rm) {
// remove with Node JS 15.X+
fs.rm(path.dirname(logFile), { recursive: true }, () => { return; });
}
else {
// remove with Node JS 14.X
fs.rmdir(path.dirname(logFile), { recursive: true }, () => { return; });
}
}
});
//# sourceMappingURL=stack.js.map |
FYI, I think the preview function probably needs to be fixed as well: const prePromise = this.runPulumiCmd(args, (_a = opts) === null || _a === void 0 ? void 0 : _a.onOutput);
let preResult;
let rlResult;
try {
// TODO: This line in the preview function also probably needs to be fixed:
[preResult, rlResult] = yield Promise.all([prePromise, rlPromise]);
}
catch (e) {
didError = true;
throw e;
}
finally {
onExit(didError);
yield cleanUp(logFile, rlResult);
} EDIT: I updated the stack.js file above to try and fix for preview as well. |
The fix from this PR should be released in 3.27.0 later today |
Same fix as in #9097 but for the preview command.
Thank you @mikhailshilkov! |
Description
Fixes #9065: uncaught error "ENOENT: no such file or directory" when an error occurs during the stack up
Checklist
Suggestion of where to write it ?