From 7b766924ec5547344faba69b90026bbaacf401ce Mon Sep 17 00:00:00 2001 From: evanboyle Date: Thu, 8 Oct 2020 11:07:35 -0700 Subject: [PATCH] update error strategy to be more idiomatic --- sdk/nodejs/x/automation/cmd.ts | 6 +-- sdk/nodejs/x/automation/errors.ts | 75 +++++++++++++++++++------------ sdk/nodejs/x/automation/stack.ts | 4 +- 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/sdk/nodejs/x/automation/cmd.ts b/sdk/nodejs/x/automation/cmd.ts index ce1b25256..a16cd8d88 100644 --- a/sdk/nodejs/x/automation/cmd.ts +++ b/sdk/nodejs/x/automation/cmd.ts @@ -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)); }); }); } diff --git a/sdk/nodejs/x/automation/errors.ts b/sdk/nodejs/x/automation/errors.ts index 08ec7894e..0ba7a7185 100644 --- a/sdk/nodejs/x/automation/errors.ts +++ b/sdk/nodejs/x/automation/errors.ts @@ -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) + ); +} diff --git a/sdk/nodejs/x/automation/stack.ts b/sdk/nodejs/x/automation/stack.ts index 5af1ee734..a4798a7b5 100644 --- a/sdk/nodejs/x/automation/stack.ts +++ b/sdk/nodejs/x/automation/stack.ts @@ -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;