pulumi/sdk/nodejs/tests/automation/remoteWorkspace.spec.ts

489 lines
16 KiB
TypeScript

// Copyright 2016-2022, 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.
import assert from "assert";
import {
fullyQualifiedStackName,
isFullyQualifiedStackName,
LocalWorkspace,
LocalWorkspaceOptions,
RemoteGitAuthArgs,
RemoteGitProgramArgs,
RemoteStack,
RemoteWorkspace,
RemoteWorkspaceOptions,
} from "../../automation";
import { getTestOrg, getTestSuffix } from "./util";
const testRepo = "https://github.com/pulumi/test-repo.git";
describe("RemoteWorkspace", () => {
describe("remote cmd args", () => {
const tests: {
name: string;
opts: LocalWorkspaceOptions;
expected: string[];
}[] = [
{
name: "empty",
opts: {},
expected: [],
},
{
name: "just remote",
opts: {
remote: true,
},
expected: ["--remote"],
},
{
name: "url",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
},
},
expected: ["--remote", "foo"],
},
{
name: "path",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
projectPath: "mypath",
},
},
expected: ["--remote", "foo", "--remote-git-repo-dir", "mypath"],
},
{
name: "branch",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
branch: "mybranch",
},
},
expected: ["--remote", "foo", "--remote-git-branch", "mybranch"],
},
{
name: "commit",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
commitHash: "mycommit",
},
},
expected: ["--remote", "foo", "--remote-git-commit", "mycommit"],
},
{
name: "auth access token",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
auth: {
personalAccessToken: "mytoken",
},
},
},
expected: ["--remote", "foo", "--remote-git-auth-access-token", "mytoken"],
},
{
name: "auth ssh key",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
auth: {
sshPrivateKey: "mykey",
},
},
},
expected: ["--remote", "foo", "--remote-git-auth-ssh-private-key", "mykey"],
},
{
name: "auth ssh key path",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
auth: {
sshPrivateKeyPath: "mykeypath",
},
},
},
expected: ["--remote", "foo", "--remote-git-auth-ssh-private-key-path", "mykeypath"],
},
{
name: "auth ssh password",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
auth: {
username: "myuser",
password: "mypass",
},
},
},
expected: [
"--remote",
"foo",
"--remote-git-auth-password",
"mypass",
"--remote-git-auth-username",
"myuser",
],
},
{
name: "env",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
},
remoteEnvVars: {
foo: "bar",
},
},
expected: ["--remote", "foo", "--remote-env", "foo=bar"],
},
{
name: "env secret",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
},
remoteEnvVars: {
foo: { secret: "bar" },
},
},
expected: ["--remote", "foo", "--remote-env-secret", "foo=bar"],
},
{
name: "pre-run command",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
},
remotePreRunCommands: ["whoami"],
},
expected: ["--remote", "foo", "--remote-pre-run-command", "whoami"],
},
{
name: "skip install dependencies",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
url: "foo",
},
remoteSkipInstallDependencies: true,
},
expected: ["--remote", "foo", "--remote-skip-install-dependencies"],
},
{
name: "inherit settings",
opts: {
remote: true,
remoteGitProgramArgs: {
stackName: "stack",
},
remoteInheritSettings: true,
},
expected: ["--remote", "--remote-inherit-settings"],
},
];
tests.forEach((test) => {
it(`${test.name}`, async () => {
const ws = await LocalWorkspace.create(test.opts);
const actual = ws.remoteArgs();
assert.deepStrictEqual(actual, test.expected);
});
});
});
describe("selectStack", () => {
describe("throws appropriate errors", () => testErrors(RemoteWorkspace.selectStack));
});
describe("createStack", () => {
describe("throws appropriate errors", () => testErrors(RemoteWorkspace.createStack));
it(`runs through the stack lifecycle`, async function () {
// This test requires the service with access to Pulumi Deployments.
// Set PULUMI_ACCESS_TOKEN to an access token with access to Pulumi Deployments
// and set PULUMI_TEST_DEPLOYMENTS_API to any value to enable the test.
if (!process.env.PULUMI_ACCESS_TOKEN) {
this.skip();
return;
}
if (!process.env.PULUMI_TEST_DEPLOYMENTS_API) {
this.skip();
return;
}
await testLifecycle(RemoteWorkspace.createStack);
});
});
describe("createOrSelectStack", () => {
describe("throws appropriate errors", () => testErrors(RemoteWorkspace.createOrSelectStack));
it(`runs through the stack lifecycle`, async function () {
// This test requires the service with access to Pulumi Deployments.
// Set PULUMI_ACCESS_TOKEN to an access token with access to Pulumi Deployments
// and set PULUMI_TEST_DEPLOYMENTS_API to any value to enable the test.
if (!process.env.PULUMI_ACCESS_TOKEN) {
this.skip();
return;
}
if (!process.env.PULUMI_TEST_DEPLOYMENTS_API) {
this.skip();
return;
}
await testLifecycle(RemoteWorkspace.createOrSelectStack);
});
});
});
function testErrors(fn: (args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions) => Promise<RemoteStack>) {
const stack = "owner/project/stack";
const tests: {
name: string;
stackName: string;
url: string;
branch?: string;
commitHash?: string;
auth?: RemoteGitAuthArgs;
error: string;
}[] = [
{
name: "stack empty",
stackName: "",
url: "",
error: `stack name "" must be fully qualified.`,
},
{
name: "stack just name",
stackName: "name",
url: "",
error: `stack name "name" must be fully qualified.`,
},
{
name: "stack just name & owner",
stackName: "owner/name",
url: "",
error: `stack name "owner/name" must be fully qualified.`,
},
{
name: "stack just sep",
stackName: "/",
url: "",
error: `stack name "/" must be fully qualified.`,
},
{
name: "stack just two seps",
stackName: "//",
url: "",
error: `stack name "//" must be fully qualified.`,
},
{
name: "stack just three seps",
stackName: "///",
url: "",
error: `stack name "///" must be fully qualified.`,
},
{
name: "stack invalid",
stackName: "owner/project/stack/wat",
url: "",
error: `stack name "owner/project/stack/wat" must be fully qualified.`,
},
{
name: "no url",
stackName: stack,
url: "",
error: `url is required if inheritSettings is not set.`,
},
{
name: "no branch or commit",
stackName: stack,
url: testRepo,
error: `either branch or commitHash is required if inheritSettings is not set.`,
},
{
name: "both branch and commit",
stackName: stack,
url: testRepo,
branch: "branch",
commitHash: "commit",
error: `branch and commitHash cannot both be specified.`,
},
{
name: "both ssh private key and path",
stackName: stack,
url: testRepo,
branch: "branch",
auth: {
sshPrivateKey: "key",
sshPrivateKeyPath: "path",
},
error: `sshPrivateKey and sshPrivateKeyPath cannot both be specified.`,
},
];
tests.forEach((test) => {
it(`${test.name}`, async () => {
const { stackName, url, branch, commitHash, auth } = test;
await assert.rejects(
async () => {
await fn({ stackName, url, branch, commitHash, auth });
},
{
message: test.error,
},
);
});
});
}
async function testLifecycle(fn: (args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions) => Promise<RemoteStack>) {
const stackName = fullyQualifiedStackName(getTestOrg(), "go_remote_proj", `int_test${getTestSuffix()}`);
const stack = await fn(
{
stackName,
url: testRepo,
branch: "refs/heads/master",
projectPath: "goproj",
},
{
preRunCommands: [
`pulumi config set bar abc --stack ${stackName}`,
`pulumi config set --secret buzz secret --stack ${stackName}`,
],
skipInstallDependencies: true,
},
);
// pulumi up
const upRes = await stack.up();
assert.strictEqual(Object.keys(upRes.outputs).length, 3);
assert.strictEqual(upRes.outputs["exp_static"].value, "foo");
assert.strictEqual(upRes.outputs["exp_static"].secret, false);
assert.strictEqual(upRes.outputs["exp_cfg"].value, "abc");
assert.strictEqual(upRes.outputs["exp_cfg"].secret, false);
assert.strictEqual(upRes.outputs["exp_secret"].value, "secret");
assert.strictEqual(upRes.outputs["exp_secret"].secret, true);
assert.strictEqual(upRes.summary.kind, "update");
assert.strictEqual(upRes.summary.result, "succeeded");
// pulumi preview
const preRes = await stack.preview();
assert.strictEqual(preRes.changeSummary.same, 1);
// pulumi refresh
const refRes = await stack.refresh();
assert.strictEqual(refRes.summary.kind, "refresh");
assert.strictEqual(refRes.summary.result, "succeeded");
// pulumi destroy
const destroyRes = await stack.destroy();
assert.strictEqual(destroyRes.summary.kind, "destroy");
assert.strictEqual(destroyRes.summary.result, "succeeded");
await (await LocalWorkspace.create({})).removeStack(stackName);
}
describe("isFullyQualifiedStackName", () => {
const tests = [
{
name: "fully qualified",
input: "owner/project/stack",
expected: true,
},
{
name: "undefined",
input: undefined,
expected: false,
},
{
name: "null",
input: null,
expected: false,
},
{
name: "empty",
input: "",
expected: false,
},
{
name: "name",
input: "name",
expected: false,
},
{
name: "name & owner",
input: "owner/name",
expected: false,
},
{
name: "sep",
input: "/",
expected: false,
},
{
name: "two seps",
input: "//",
expected: false,
},
{
name: "three seps",
input: "///",
expected: false,
},
{
name: "invalid",
input: "owner/project/stack/wat",
expected: false,
},
];
tests.forEach((test) => {
it(`${test.name}`, () => {
const actual = isFullyQualifiedStackName(test.input!);
assert.strictEqual(actual, test.expected);
});
});
});