update error strategy to be more idiomatic

This commit is contained in:
evanboyle 2020-10-08 11:07:35 -07:00 committed by Evan Boyle
parent 97e1d25802
commit 7b766924ec
3 changed files with 52 additions and 33 deletions

View file

@ -14,7 +14,7 @@
import * as childProcess from "child_process";
import { CommandError } from "./errors";
import { createCommandError } from "./errors";
/** @internal */
export class CommandResult {
@ -73,13 +73,13 @@ export function runPulumiCmd(
const resCode = code !== null ? code : unknownErrCode;
const result = new CommandResult(stdout, stderr, resCode);
if (code !== 0) {
return reject(new CommandError(result));
return reject(createCommandError(result));
}
return resolve(result);
});
proc.on("error", (err) => {
const result = new CommandResult(stdout, stderr, unknownErrCode, err);
return reject(new CommandError(result));
return reject(createCommandError(result));
});
});
}

View file

@ -16,9 +16,6 @@ import { CommandResult } from "./cmd";
/**
* CommandError is an error resulting from invocation of a Pulumi Command.
* This is an opaque error that provides utility functions to detect specific error cases
* such as concurrent stack updates (`isConcurrentUpdateError`). If you'd like to detect an additional
* error case that isn't currently covered, please file an issue: https://github.com/pulumi/pulumi/issues/new
* @alpha
*/
export class CommandError extends Error {
@ -27,30 +24,52 @@ export class CommandError extends Error {
super(commandResult.toString());
this.name = "CommandError";
}
/**
* Returns true if the error was a result of a conflicting update locking the stack.
*
* @returns a boolean indicating if this failure was due to a conflicting concurrent update on the stack.
*/
isConcurrentUpdateError() {
return this.commandResult.stderr.indexOf("[409] Conflict: Another update is currently in progress.") >= 0;
}
/**
* Returns true if the error was the result of selecting a stack that does not exist.
*
* @returns a boolean indicating if this failure was the result of selecting a stack that does not exist.
*/
isSelectStack404Error() {
const exp = new RegExp("no stack named.*found");
return exp.test(this.commandResult.stderr);
}
/**
* Returns true if the error was a result of creating a stack that already exists.
*
* @returns a boolean indicating if the error was a result of creating a stack that already exists.
*/
isCreateStack409Error() {
const exp = new RegExp("stack.*already exists");
return exp.test(this.commandResult.stderr);
}
/**
* ConcurrentUpdateError is thrown when attempting to update a stack that already has an update in progress.
*/
export class ConcurrentUpdateError extends CommandError {
/** @internal */
constructor(commandResult: CommandResult) {
super(commandResult);
this.name = "ConcurrentUpdateError";
}
}
/**
* StackNotFoundError is thrown when attempting to select a stack that does not exist.
*/
export class StackNotFoundError extends CommandError {
/** @internal */
constructor(commandResult: CommandResult) {
super(commandResult);
this.name = "StackNotFoundError";
}
}
/**
* StackAlreadyExistsError is thrown when attempting to create a stack that already exists.
*/
export class StackAlreadyExistsError extends CommandError {
/** @internal */
constructor(commandResult: CommandResult) {
super(commandResult);
this.name = "StackAlreadyExistsError";
}
}
const notFoundRegex = new RegExp("no stack named.*found");
const alreadyExistsRegex = new RegExp("stack.*already exists");
const conflictText = "[409] Conflict: Another update is currently in progress.";
/** @internal */
export function createCommandError(result: CommandResult): CommandError {
const stderr = result.stderr;
return (
notFoundRegex.test(stderr) ? new StackNotFoundError(result) :
alreadyExistsRegex.test(stderr) ? new StackAlreadyExistsError(result) :
stderr.indexOf(conflictText) >= 0 ? new ConcurrentUpdateError(result) :
new CommandError(result)
);
}

View file

@ -16,7 +16,7 @@ import * as grpc from "@grpc/grpc-js";
import { CommandResult, runPulumiCmd } from "./cmd";
import { ConfigMap, ConfigValue } from "./config";
import { CommandError } from "./errors";
import { StackAlreadyExistsError } from "./errors";
import { LanguageServer, maxRPCMessageSize } from "./server";
import { PulumiFn, Workspace } from "./workspace";
@ -92,7 +92,7 @@ export class Stack {
return this;
case "createOrSelect":
this.ready = workspace.createStack(name).catch((err) => {
if (err instanceof CommandError && err.isCreateStack409Error()) {
if (err instanceof StackAlreadyExistsError) {
return workspace.selectStack(name);
}
throw err;