[automation-api/nodejs] - Support recovery workflow (#6038)

This commit is contained in:
Komal 2021-01-04 16:45:57 -08:00 committed by GitHub
parent 9d228721b6
commit 8e8129012e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 128 additions and 9 deletions

View file

@ -9,7 +9,10 @@ CHANGELOG
- [CLI] Allow `pulumi console` to accept a stack name
[#6031](https://github.com/pulumi/pulumi/pull/6031)
- [CLI] Add a confirmation promt when using `pulumi policy rm`
- Support recovery workflow (import/export/cancel) in NodeJS Automation API.
[#6038](https://github.com/pulumi/pulumi/pull/6038)
- [CLI] Add a confirmation prompt when using `pulumi policy rm`
[#6034](https://github.com/pulumi/pulumi/pull/6034)
- [CLI] Ensure errors with the Pulumi credentials file

View file

@ -16,10 +16,7 @@ import * as assert from "assert";
import * as upath from "upath";
import { Config } from "../../index";
import { ConfigMap } from "../../x/automation/config";
import { LocalWorkspace } from "../../x/automation/localWorkspace";
import { ProjectSettings } from "../../x/automation/projectSettings";
import { Stack } from "../../x/automation/stack";
import { ConfigMap, LocalWorkspace, ProjectSettings, Stack } from "../../x/automation";
import { asyncTest } from "../util";
describe("LocalWorkspace", () => {
@ -95,7 +92,7 @@ describe("LocalWorkspace", () => {
const secretKey = normalizeConfigKey("secret", projectName);
try {
const empty = await stack.getConfig(plainKey);
await stack.getConfig(plainKey);
} catch (error) {
caught++;
}
@ -241,6 +238,40 @@ describe("LocalWorkspace", () => {
await stack.workspace.removeStack(stackName);
}));
it(`imports and exports stacks`, asyncTest(async() => {
const program = async () => {
const config = new Config();
return {
exp_static: "foo",
exp_cfg: config.get("bar"),
exp_secret: config.getSecret("buzz"),
};
};
const stackName = `int_test${getTestSuffix()}`;
const projectName = "import_export_node";
const stack = await LocalWorkspace.createStack({ stackName, projectName, program });
try {
await stack.setAllConfig({
"bar": { value: "abc" },
"buzz": { value: "secret", secret: true },
});
await stack.up();
// export stack
const state = await stack.exportStack();
// import stack
await stack.importStack(state);
const configVal = await stack.getConfig("bar");
assert.strictEqual(configVal.value, "abc");
} finally {
const destroyRes = await stack.destroy();
assert.strictEqual(destroyRes.summary.kind, "destroy");
assert.strictEqual(destroyRes.summary.result, "succeeded");
await stack.workspace.removeStack(stackName);
}
}));
});
const getTestSuffix = () => {

View file

@ -22,7 +22,7 @@ import { ConfigMap, ConfigValue } from "./config";
import { ProjectSettings } from "./projectSettings";
import { Stack } from "./stack";
import { StackSettings } from "./stackSettings";
import { PluginInfo, PulumiFn, StackSummary, WhoAmIResult, Workspace } from "./workspace";
import { Deployment, PluginInfo, PulumiFn, StackSummary, WhoAmIResult, Workspace } from "./workspace";
/**
* LocalWorkspace is a default implementation of the Workspace interface.
@ -491,6 +491,33 @@ export class LocalWorkspace implements Workspace {
return value;
});
}
/**
* exportStack exports the deployment state of the stack.
* This can be combined with Workspace.importStack to edit a stack's state (such as recovery from failed deployments).
*
* @param stackName the name of the stack.
*/
async exportStack(stackName: string): Promise<Deployment> {
await this.selectStack(stackName);
const result = await this.runPulumiCmd(["stack", "export", "--show-secrets"]);
return JSON.parse(result.stdout);
}
/**
* importStack imports the specified deployment state into a pre-existing stack.
* This can be combined with Workspace.exportStack to edit a stack's state (such as recovery from failed deployments).
*
* @param stackName the name of the stack.
* @param state the stack state to import.
*/
async importStack(stackName: string, state: Deployment): Promise<void> {
await this.selectStack(stackName);
const randomSuffix = Math.floor(100000 + Math.random() * 900000);
const filepath = upath.joinSafe(os.tmpdir(), `automation-${randomSuffix}`);
const contents = JSON.stringify(state, null, 4);
fs.writeFileSync(filepath, contents);
await this.runPulumiCmd(["stack", "import", "--file", filepath]);
fs.unlinkSync(filepath);
}
/**
* serializeArgsForOp is hook to provide additional args to every CLI commands before they are executed.
* Provided with stack name,

View file

@ -18,7 +18,7 @@ import { CommandResult, runPulumiCmd } from "./cmd";
import { ConfigMap, ConfigValue } from "./config";
import { StackAlreadyExistsError } from "./errors";
import { LanguageServer, maxRPCMessageSize } from "./server";
import { PulumiFn, Workspace } from "./workspace";
import { Deployment, PulumiFn, Workspace } from "./workspace";
const langrpc = require("../../proto/language_grpc_pb.js");
@ -421,6 +421,35 @@ export class Stack {
}
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.
*/
async cancel(): Promise<void> {
await this.workspace.selectStack(this.name);
await 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).
*/
async exportStack(): Promise<Deployment> {
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.
*/
async importStack(state: Deployment): Promise<void> {
return this.workspace.importStack(this.name, state);
}
private async runPulumiCmd(args: string[], onOutput?: (out: string) => void): Promise<CommandResult> {
let envs: { [key: string]: string } = {};
const pulumiHome = this.workspace.pulumiHome;

View file

@ -187,7 +187,21 @@ export interface Workspace {
* Returns a list of all plugins installed in the Workspace.
*/
listPlugins(): Promise<PluginInfo[]>;
// TODO import/export
/**
* exportStack exports the deployment state of the stack.
* This can be combined with Workspace.importStack to edit a stack's state (such as recovery from failed deployments).
*
* @param stackName the name of the stack.
*/
exportStack(stackName: string): Promise<Deployment>;
/**
* importStack imports the specified deployment state into a pre-existing stack.
* This can be combined with Workspace.exportStack to edit a stack's state (such as recovery from failed deployments).
*
* @param stackName the name of the stack.
* @param state the stack state to import.
*/
importStack(stackName: string, state: Deployment): Promise<void>;
}
/**
@ -202,6 +216,21 @@ export interface StackSummary {
url?: string;
}
/**
* Deployment encapsulates the state of a stack deployment.
*/
export interface Deployment {
/**
* Version indicates the schema of the encoded deployment.
*/
version: number;
/**
* The pulumi deployment.
*/
// TODO: Expand type to encapsulate deployment.
deployment: any;
}
/**
* A Pulumi program as an inline function (in process).
*/