pulumi/sdk/nodejs/automation/localWorkspace.ts

1095 lines
43 KiB
TypeScript
Raw Normal View History

// Copyright 2016-2023, 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.
2020-09-14 14:55:06 +00:00
import * as fs from "fs";
import * as yaml from "js-yaml";
import * as os from "os";
import * as semver from "semver";
2020-09-14 14:55:06 +00:00
import * as upath from "upath";
import { CommandResult, PulumiCommand } from "./cmd";
2020-09-18 02:33:13 +00:00
import { ConfigMap, ConfigValue } from "./config";
import { minimumVersion } from "./minimumVersion";
import { ProjectSettings } from "./projectSettings";
import { RemoteGitProgramArgs } from "./remoteWorkspace";
import { OutputMap, Stack } from "./stack";
import { StackSettings, stackSettingsSerDeKeys } from "./stackSettings";
import { TagMap } from "./tag";
import { Deployment, PluginInfo, PulumiFn, StackSummary, WhoAmIResult, Workspace } from "./workspace";
2020-09-14 14:55:06 +00:00
const SKIP_VERSION_CHECK_VAR = "PULUMI_AUTOMATION_API_SKIP_VERSION_CHECK";
2020-10-07 07:25:36 +00:00
/**
* LocalWorkspace is a default implementation of the Workspace interface.
* A Workspace is the execution context containing a single Pulumi project, a program,
* and multiple stacks. Workspaces are used to manage the execution environment,
* providing various utilities such as plugin installation, environment configuration
* ($PULUMI_HOME), and creation, deletion, and listing of Stacks.
* LocalWorkspace relies on Pulumi.yaml and Pulumi.<stack>.yaml as the intermediate format
* for Project and Stack settings. Modifying ProjectSettings will
* alter the Workspace Pulumi.yaml file, and setting config on a Stack will modify the Pulumi.<stack>.yaml file.
* This is identical to the behavior of Pulumi CLI driven workspaces.
*
* @alpha
*/
2020-09-18 02:33:13 +00:00
export class LocalWorkspace implements Workspace {
2020-10-07 07:25:36 +00:00
/**
* The working directory to run Pulumi CLI commands
*/
readonly workDir: string;
2020-10-07 07:25:36 +00:00
/**
* The directory override for CLI metadata if set.
2020-10-08 02:00:03 +00:00
* This customizes the location of $PULUMI_HOME where metadata is stored and plugins are installed.
2020-10-07 07:25:36 +00:00
*/
readonly pulumiHome?: string;
2020-10-07 07:25:36 +00:00
/**
* The secrets provider to use for encryption and decryption of stack secrets.
* See: https://www.pulumi.com/docs/intro/concepts/secrets/#available-encryption-providers
2020-10-07 07:25:36 +00:00
*/
readonly secretsProvider?: string;
private _pulumiCommand?: PulumiCommand;
/**
* The underlying Pulumi CLI.
*/
public get pulumiCommand(): PulumiCommand {
if (this._pulumiCommand === undefined) {
throw new Error("Failed to get Pulumi CLI");
}
return this._pulumiCommand;
}
2020-10-07 07:25:36 +00:00
/**
2020-10-08 02:00:03 +00:00
* The inline program `PulumiFn` to be used for Preview/Update operations if any.
* If none is specified, the stack will refer to ProjectSettings for this information.
2020-10-07 07:25:36 +00:00
*/
2020-10-08 18:12:43 +00:00
program?: PulumiFn;
2020-10-07 07:25:36 +00:00
/**
* Environment values scoped to the current workspace. These will be supplied to every Pulumi command.
*/
2020-10-08 18:12:43 +00:00
envVars: { [key: string]: string };
private _pulumiVersion?: semver.SemVer;
/**
* The version of the underlying Pulumi CLI/Engine.
*
* @returns A string representation of the version, if available. `null` otherwise.
*/
public get pulumiVersion(): string {
if (this._pulumiVersion === undefined) {
throw new Error(`Failed to get Pulumi version`);
}
return this._pulumiVersion.toString();
}
private ready: Promise<any[]>;
/**
* Whether the workspace is a remote workspace.
*/
private remote?: boolean;
/**
* Remote Git source info.
*/
private remoteGitProgramArgs?: RemoteGitProgramArgs;
/**
* An optional list of arbitrary commands to run before the remote Pulumi operation is invoked.
*/
private remotePreRunCommands?: string[];
/**
* The environment variables to pass along when running remote Pulumi operations.
*/
private remoteEnvVars?: { [key: string]: string | { secret: string } };
/**
* Whether to skip the default dependency installation step.
*/
private remoteSkipInstallDependencies?: boolean;
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
/**
* Whether to inherit the deployment settings set on the stack.
*/
private remoteInheritSettings?: boolean;
2020-10-07 07:25:36 +00:00
/**
* Creates a workspace using the specified options. Used for maximal control and customization
* of the underlying environment before any stacks are created or selected.
*
* @param opts Options used to configure the Workspace
*/
2020-10-08 18:12:43 +00:00
static async create(opts: LocalWorkspaceOptions): Promise<LocalWorkspace> {
const ws = new LocalWorkspace(opts);
await ws.ready;
return ws;
}
2020-10-07 07:25:36 +00:00
/**
* Creates a Stack with a LocalWorkspace utilizing the local Pulumi CLI program from the specified workDir.
* This is a way to create drivers on top of pre-existing Pulumi programs. This Workspace will pick up
* any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml).
*
* @param args A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that already exists on disk.
2020-10-07 07:25:36 +00:00
* @param opts Additional customizations to be applied to the Workspace.
*/
2020-10-08 18:12:43 +00:00
static async createStack(args: LocalProgramArgs, opts?: LocalWorkspaceOptions): Promise<Stack>;
2020-10-07 07:25:36 +00:00
/**
* Creates a Stack with a LocalWorkspace utilizing the specified inline (in process) Pulumi program.
* This program is fully debuggable and runs in process. If no Project option is specified, default project settings
* will be created on behalf of the user. Similarly, unless a `workDir` option is specified, the working directory
* will default to a new temporary directory provided by the OS.
*
* @param args A set of arguments to initialize a Stack with and inline `PulumiFn` program that runs in process.
2020-10-07 07:25:36 +00:00
* @param opts Additional customizations to be applied to the Workspace.
*/
2020-10-08 18:12:43 +00:00
static async createStack(args: InlineProgramArgs, opts?: LocalWorkspaceOptions): Promise<Stack>;
static async createStack(args: InlineProgramArgs | LocalProgramArgs, opts?: LocalWorkspaceOptions): Promise<Stack> {
if (isInlineProgramArgs(args)) {
return await this.inlineSourceStackHelper(args, Stack.create, opts);
} else if (isLocalProgramArgs(args)) {
return await this.localSourceStackHelper(args, Stack.create, opts);
}
throw new Error(`unexpected args: ${args}`);
}
2020-10-07 07:25:36 +00:00
/**
* Selects a Stack with a LocalWorkspace utilizing the local Pulumi CLI program from the specified workDir.
* This is a way to create drivers on top of pre-existing Pulumi programs. This Workspace will pick up
* any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml).
*
* @param args A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that already exists on disk.
2020-10-07 07:25:36 +00:00
* @param opts Additional customizations to be applied to the Workspace.
*/
2020-10-08 18:12:43 +00:00
static async selectStack(args: LocalProgramArgs, opts?: LocalWorkspaceOptions): Promise<Stack>;
2020-10-07 07:25:36 +00:00
/**
* Selects an existing Stack with a LocalWorkspace utilizing the specified inline (in process) Pulumi program.
* This program is fully debuggable and runs in process. If no Project option is specified, default project settings
* will be created on behalf of the user. Similarly, unless a `workDir` option is specified, the working directory
* will default to a new temporary directory provided by the OS.
*
* @param args A set of arguments to initialize a Stack with and inline `PulumiFn` program that runs in process.
2020-10-07 07:25:36 +00:00
* @param opts Additional customizations to be applied to the Workspace.
*/
2020-10-08 18:12:43 +00:00
static async selectStack(args: InlineProgramArgs, opts?: LocalWorkspaceOptions): Promise<Stack>;
static async selectStack(args: InlineProgramArgs | LocalProgramArgs, opts?: LocalWorkspaceOptions): Promise<Stack> {
if (isInlineProgramArgs(args)) {
return await this.inlineSourceStackHelper(args, Stack.select, opts);
} else if (isLocalProgramArgs(args)) {
return await this.localSourceStackHelper(args, Stack.select, opts);
}
throw new Error(`unexpected args: ${args}`);
}
2020-10-07 07:25:36 +00:00
/**
* Creates or selects an existing Stack with a LocalWorkspace utilizing the specified inline (in process) Pulumi CLI program.
* This program is fully debuggable and runs in process. If no Project option is specified, default project settings
* will be created on behalf of the user. Similarly, unless a `workDir` option is specified, the working directory
* will default to a new temporary directory provided by the OS.
*
* @param args A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that already exists on disk.
2020-10-07 07:25:36 +00:00
* @param opts Additional customizations to be applied to the Workspace.
*/
2020-10-08 18:12:43 +00:00
static async createOrSelectStack(args: LocalProgramArgs, opts?: LocalWorkspaceOptions): Promise<Stack>;
2020-10-07 07:25:36 +00:00
/**
* Creates or selects an existing Stack with a LocalWorkspace utilizing the specified inline Pulumi CLI program.
* This program is fully debuggable and runs in process. If no Project option is specified, default project settings will be created
* on behalf of the user. Similarly, unless a `workDir` option is specified, the working directory will default
* to a new temporary directory provided by the OS.
*
* @param args A set of arguments to initialize a Stack with and inline `PulumiFn` program that runs in process.
2020-10-07 07:25:36 +00:00
* @param opts Additional customizations to be applied to the Workspace.
*/
2020-10-08 18:12:43 +00:00
static async createOrSelectStack(args: InlineProgramArgs, opts?: LocalWorkspaceOptions): Promise<Stack>;
static async createOrSelectStack(
args: InlineProgramArgs | LocalProgramArgs,
opts?: LocalWorkspaceOptions,
): Promise<Stack> {
if (isInlineProgramArgs(args)) {
return await this.inlineSourceStackHelper(args, Stack.createOrSelect, opts);
} else if (isLocalProgramArgs(args)) {
return await this.localSourceStackHelper(args, Stack.createOrSelect, opts);
}
throw new Error(`unexpected args: ${args}`);
}
private static async localSourceStackHelper(
args: LocalProgramArgs,
initFn: StackInitializer,
opts?: LocalWorkspaceOptions,
): Promise<Stack> {
let wsOpts = { workDir: args.workDir };
if (opts) {
wsOpts = { ...opts, workDir: args.workDir };
}
const ws = new LocalWorkspace(wsOpts);
await ws.ready;
return await initFn(args.stackName, ws);
}
private static async inlineSourceStackHelper(
args: InlineProgramArgs,
initFn: StackInitializer,
opts?: LocalWorkspaceOptions,
): Promise<Stack> {
let wsOpts: LocalWorkspaceOptions = { program: args.program };
if (opts) {
wsOpts = { ...opts, program: args.program };
}
if (!wsOpts.projectSettings) {
if (wsOpts.workDir) {
try {
// Try to load the project settings.
loadProjectSettings(wsOpts.workDir);
} catch (e) {
// If it failed to find the project settings file, set a default project.
if (e.toString().includes("failed to find project settings")) {
wsOpts.projectSettings = defaultProject(args.projectName);
} else {
throw e;
}
}
} else {
wsOpts.projectSettings = defaultProject(args.projectName);
}
}
const ws = new LocalWorkspace(wsOpts);
await ws.ready;
return await initFn(args.stackName, ws);
}
private constructor(opts?: LocalWorkspaceOptions) {
let dir = "";
let envs = {};
2020-09-14 03:28:24 +00:00
if (opts) {
const {
workDir,
pulumiHome,
program,
envVars,
secretsProvider,
remote,
remoteGitProgramArgs,
remotePreRunCommands,
remoteEnvVars,
remoteSkipInstallDependencies,
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
remoteInheritSettings,
} = opts;
if (workDir) {
// Verify that the workdir exists.
if (!fs.existsSync(workDir)) {
throw new Error(`Invalid workDir passed to local workspace: '${workDir}' does not exist`);
}
dir = workDir;
}
2020-09-14 03:28:24 +00:00
this.pulumiHome = pulumiHome;
this.program = program;
this.secretsProvider = secretsProvider;
this.remote = remote;
this.remoteGitProgramArgs = remoteGitProgramArgs;
this.remotePreRunCommands = remotePreRunCommands;
this.remoteEnvVars = { ...remoteEnvVars };
this.remoteSkipInstallDependencies = remoteSkipInstallDependencies;
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
this.remoteInheritSettings = remoteInheritSettings;
envs = { ...envVars };
}
if (!dir) {
dir = fs.mkdtempSync(upath.joinSafe(os.tmpdir(), "automation-"));
2020-09-14 03:28:24 +00:00
}
this.workDir = dir;
this.envVars = envs;
const skipVersionCheck = !!this.envVars[SKIP_VERSION_CHECK_VAR] || !!process.env[SKIP_VERSION_CHECK_VAR];
const pulumiCommand = opts?.pulumiCommand
? Promise.resolve(opts.pulumiCommand)
: PulumiCommand.get({ skipVersionCheck });
const readinessPromises: Promise<any>[] = [
pulumiCommand.then((p) => {
this._pulumiCommand = p;
if (p.version) {
this._pulumiVersion = p.version;
}
return this.checkRemoteSupport();
}),
];
if (opts?.projectSettings) {
readinessPromises.push(this.saveProjectSettings(opts.projectSettings));
}
if (opts?.stackSettings) {
for (const [name, value] of Object.entries(opts.stackSettings)) {
2020-10-07 07:25:36 +00:00
readinessPromises.push(this.saveStackSettings(name, value));
}
}
this.ready = Promise.all(readinessPromises);
2020-09-14 03:28:24 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Returns the settings object for the current project if any
* LocalWorkspace reads settings from the Pulumi.yaml in the workspace.
* A workspace can contain only a single project at a time.
*/
async projectSettings(): Promise<ProjectSettings> {
return loadProjectSettings(this.workDir);
2020-09-14 03:28:24 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Overwrites the settings object in the current project.
* There can only be a single project per workspace. Fails if new project name does not match old.
2020-10-07 07:25:36 +00:00
* LocalWorkspace writes this value to a Pulumi.yaml file in Workspace.WorkDir().
*
* @param settings The settings object to save to the Workspace.
*/
async saveProjectSettings(settings: ProjectSettings): Promise<void> {
2020-09-14 16:10:02 +00:00
let foundExt = ".yaml";
for (const ext of settingsExtensions) {
const testPath = upath.joinSafe(this.workDir, `Pulumi${ext}`);
if (fs.existsSync(testPath)) {
foundExt = ext;
break;
}
2020-09-14 16:10:02 +00:00
}
const path = upath.joinSafe(this.workDir, `Pulumi${foundExt}`);
let contents;
if (foundExt === ".json") {
contents = JSON.stringify(settings, null, 4);
} else {
contents = yaml.safeDump(settings, { skipInvalid: true });
2020-09-14 16:10:02 +00:00
}
return fs.writeFileSync(path, contents);
2020-09-14 16:10:02 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Returns the settings object for the stack matching the specified stack name if any.
* LocalWorkspace reads this from a Pulumi.<stack>.yaml file in Workspace.WorkDir().
*
* @param stackName The stack to retrieve settings from.
*/
async stackSettings(stackName: string): Promise<StackSettings> {
2020-09-16 15:39:44 +00:00
const stackSettingsName = getStackSettingsName(stackName);
for (const ext of settingsExtensions) {
2020-09-16 15:39:44 +00:00
const isJSON = ext === ".json";
const path = upath.joinSafe(this.workDir, `Pulumi.${stackSettingsName}${ext}`);
if (!fs.existsSync(path)) {
continue;
}
2020-09-16 15:39:44 +00:00
const contents = fs.readFileSync(path).toString();
let stackSettings: any;
2020-09-16 15:39:44 +00:00
if (isJSON) {
stackSettings = JSON.parse(contents);
2020-09-16 15:39:44 +00:00
}
stackSettings = yaml.safeLoad(contents) as StackSettings;
// Transform the serialized representation back to what we expect.
for (const key of stackSettingsSerDeKeys) {
if (stackSettings.hasOwnProperty(key[0])) {
stackSettings[key[1]] = stackSettings[key[0]];
delete stackSettings[key[0]];
}
}
return stackSettings as StackSettings;
2020-09-16 15:39:44 +00:00
}
throw new Error(`failed to find stack settings file in workdir: ${this.workDir}`);
2020-09-16 15:39:44 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Overwrites the settings object for the stack matching the specified stack name.
* LocalWorkspace writes this value to a Pulumi.<stack>.yaml file in Workspace.WorkDir()
*
* @param stackName The stack to operate on.
* @param settings The settings object to save.
*/
async saveStackSettings(stackName: string, settings: StackSettings): Promise<void> {
2020-09-16 15:39:44 +00:00
const stackSettingsName = getStackSettingsName(stackName);
let foundExt = ".yaml";
for (const ext of settingsExtensions) {
const testPath = upath.joinSafe(this.workDir, `Pulumi.${stackSettingsName}${ext}`);
if (fs.existsSync(testPath)) {
foundExt = ext;
break;
}
2020-09-16 15:39:44 +00:00
}
const path = upath.joinSafe(this.workDir, `Pulumi.${stackSettingsName}${foundExt}`);
[auto/nodejs] don't mutate original stack settings when saving (#14952) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description When writing out the stack settings, some keys get renamed (`secretsProvider` -> `secretsprovider`). We were mutating the stack settings object in place instead of working on a copy. Fixes https://github.com/pulumi/pulumi/issues/14003 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-19 15:24:20 +00:00
const serializeSettings = { ...settings } as any;
2020-09-16 15:39:44 +00:00
let contents;
// Transform the keys to the serialized representation that we expect.
for (const key of stackSettingsSerDeKeys) {
if (serializeSettings.hasOwnProperty(key[1])) {
serializeSettings[key[0]] = serializeSettings[key[1]];
delete serializeSettings[key[1]];
}
}
2020-09-16 15:39:44 +00:00
if (foundExt === ".json") {
contents = JSON.stringify(serializeSettings, null, 4);
} else {
contents = yaml.safeDump(serializeSettings, { skipInvalid: true });
2020-09-16 15:39:44 +00:00
}
return fs.writeFileSync(path, contents);
}
2020-10-07 07:25:36 +00:00
/**
* Creates and sets a new stack with the stack name, failing if one already exists.
*
* @param stackName The stack to create.
*/
async createStack(stackName: string): Promise<void> {
const args = ["stack", "init", stackName];
if (this.secretsProvider) {
args.push("--secrets-provider", this.secretsProvider);
}
if (this.isRemote) {
args.push("--no-select");
}
await this.runPulumiCmd(args);
}
2020-10-07 07:25:36 +00:00
/**
* Selects and sets an existing stack matching the stack name, failing if none exists.
*
* @param stackName The stack to select.
*/
async selectStack(stackName: string): Promise<void> {
// If this is a remote workspace, we don't want to actually select the stack (which would modify global state);
// but we will ensure the stack exists by calling `pulumi stack`.
const args = ["stack"];
if (!this.isRemote) {
args.push("select");
}
args.push("--stack", stackName);
await this.runPulumiCmd(args);
}
2020-10-07 07:25:36 +00:00
/**
* Deletes the stack and all associated configuration and history.
*
* @param stackName The stack to remove
*/
async removeStack(stackName: string): Promise<void> {
await this.runPulumiCmd(["stack", "rm", "--yes", stackName]);
[Automation API / Nodejs] - Environment functions (#14788) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Add nodejs automation API support for adding and removing environments for stack configuration. Fixes https://github.com/pulumi/pulumi/issues/14795 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-11 16:14:10 +00:00
}
/**
* Adds environments to the end of a stack's import list. Imported environments are merged in order
* per the ESC merge rules. The list of environments behaves as if it were the import list in an anonymous
* environment.
*
* @param stackName The stack to operate on
* @param environments The names of the environments to add to the stack's configuration
*/
async addEnvironments(stackName: string, ...environments: string[]): Promise<void> {
let ver = this._pulumiVersion;
if (ver === undefined) {
// Assume an old version. Doesn't really matter what this is as long as it's pre-3.95.
ver = semver.parse("3.0.0")!;
}
// 3.95 added this command (https://github.com/pulumi/pulumi/releases/tag/v3.95.0)
if (ver.compare("3.95.0") < 0) {
throw new Error(`addEnvironments requires Pulumi version >= 3.95.0`);
}
await this.runPulumiCmd(["config", "env", "add", ...environments, "--stack", stackName, "--yes"]);
}
Automation API support for listing environments (#14995) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Automation API support for `pulumi config env ls` Fixes https://github.com/pulumi/pulumi/issues/14797 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-22 05:18:14 +00:00
/**
* Returns the list of environments associated with the specified stack name.
*
* @param stackName The stack to operate on
*/
async listEnvironments(stackName: string): Promise<string[]> {
let ver = this._pulumiVersion;
if (ver === undefined) {
// Assume an old version. Doesn't really matter what this is as long as it's pre-3.99.
ver = semver.parse("3.0.0")!;
}
// 3.99 added this command (https://github.com/pulumi/pulumi/releases/tag/v3.99.0)
if (ver.compare("3.99.0") < 0) {
throw new Error(`listEnvironments requires Pulumi version >= 3.99.0`);
}
const result = await this.runPulumiCmd(["config", "env", "ls", "--stack", stackName, "--json"]);
return JSON.parse(result.stdout);
}
[Automation API / Nodejs] - Environment functions (#14788) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Add nodejs automation API support for adding and removing environments for stack configuration. Fixes https://github.com/pulumi/pulumi/issues/14795 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-11 16:14:10 +00:00
/**
* Removes an environment from a stack's import list.
*
* @param stackName The stack to operate on
* @param environment The name of the environment to remove from the stack's configuration
*/
async removeEnvironment(stackName: string, environment: string): Promise<void> {
let ver = this._pulumiVersion;
if (ver === undefined) {
// Assume an old version. Doesn't really matter what this is as long as it's pre-3.95.
ver = semver.parse("3.0.0")!;
}
// 3.95 added this command (https://github.com/pulumi/pulumi/releases/tag/v3.95.0)
if (ver.compare("3.95.0") < 0) {
throw new Error(`removeEnvironments requires Pulumi version >= 3.95.0`);
}
await this.runPulumiCmd(["config", "env", "rm", environment, "--stack", stackName, "--yes"]);
}
2020-10-07 07:25:36 +00:00
/**
* Returns the value associated with the specified stack name and key,
* scoped to the current workspace. LocalWorkspace reads this config from the matching Pulumi.stack.yaml file.
*
* @param stackName The stack to read config from
* @param key The key to use for the config lookup
* @param path The key contains a path to a property in a map or list to get
2020-10-07 07:25:36 +00:00
*/
async getConfig(stackName: string, key: string, path?: boolean): Promise<ConfigValue> {
const args = ["config", "get"];
if (path) {
args.push("--path");
}
args.push(key, "--json", "--stack", stackName);
const result = await this.runPulumiCmd(args);
return JSON.parse(result.stdout);
2020-09-18 15:07:49 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Returns the config map for the specified stack name, scoped to the current workspace.
* LocalWorkspace reads this config from the matching Pulumi.stack.yaml file.
*
* @param stackName The stack to read config from
*/
2020-09-18 15:07:49 +00:00
async getAllConfig(stackName: string): Promise<ConfigMap> {
const result = await this.runPulumiCmd(["config", "--show-secrets", "--json", "--stack", stackName]);
return JSON.parse(result.stdout);
2020-09-18 15:07:49 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Sets the specified key-value pair on the provided stack name.
* LocalWorkspace writes this value to the matching Pulumi.<stack>.yaml file in Workspace.WorkDir().
*
* @param stackName The stack to operate on
* @param key The config key to set
* @param value The value to set
* @param path The key contains a path to a property in a map or list to set
2020-10-07 07:25:36 +00:00
*/
async setConfig(stackName: string, key: string, value: ConfigValue, path?: boolean): Promise<void> {
const args = ["config", "set"];
if (path) {
args.push("--path");
}
2020-09-19 21:40:23 +00:00
const secretArg = value.secret ? "--secret" : "--plaintext";
args.push(key, "--stack", stackName, secretArg, "--non-interactive", "--", value.value);
await this.runPulumiCmd(args);
2020-09-18 15:07:49 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Sets all values in the provided config map for the specified stack name.
* LocalWorkspace writes the config to the matching Pulumi.<stack>.yaml file in Workspace.WorkDir().
*
* @param stackName The stack to operate on
* @param config The `ConfigMap` to upsert against the existing config
* @param path The keys contain a path to a property in a map or list to set
2020-10-07 07:25:36 +00:00
*/
async setAllConfig(stackName: string, config: ConfigMap, path?: boolean): Promise<void> {
const args = ["config", "set-all", "--stack", stackName];
if (path) {
args.push("--path");
}
2020-09-18 15:07:49 +00:00
for (const [key, value] of Object.entries(config)) {
const secretArg = value.secret ? "--secret" : "--plaintext";
args.push(secretArg, `${key}=${value.value}`);
2020-09-18 15:07:49 +00:00
}
await this.runPulumiCmd(args);
2020-09-18 15:07:49 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Removes the specified key-value pair on the provided stack name.
* It will remove any matching values in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
*
* @param stackName The stack to operate on
* @param key The config key to remove
* @param path The key contains a path to a property in a map or list to remove
2020-10-07 07:25:36 +00:00
*/
async removeConfig(stackName: string, key: string, path?: boolean): Promise<void> {
const args = ["config", "rm", key, "--stack", stackName];
if (path) {
args.push("--path");
}
await this.runPulumiCmd(args);
2020-09-18 15:07:49 +00:00
}
2020-10-07 07:25:36 +00:00
/**
*
* Removes all values in the provided key list for the specified stack name
* It will remove any matching values in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
*
* @param stackName The stack to operate on
* @param keys The list of keys to remove from the underlying config
* @param path The keys contain a path to a property in a map or list to remove
2020-10-07 07:25:36 +00:00
*/
async removeAllConfig(stackName: string, keys: string[], path?: boolean): Promise<void> {
const args = ["config", "rm-all", "--stack", stackName];
if (path) {
args.push("--path");
}
args.push(...keys);
await this.runPulumiCmd(args);
2020-09-18 02:33:13 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Gets and sets the config map used with the last update for Stack matching stack name.
* It will overwrite all configuration in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
*
* @param stackName The stack to refresh
*/
2020-09-18 15:07:49 +00:00
async refreshConfig(stackName: string): Promise<ConfigMap> {
await this.runPulumiCmd(["config", "refresh", "--force", "--stack", stackName]);
2020-09-18 15:07:49 +00:00
return this.getAllConfig(stackName);
2020-09-18 02:33:13 +00:00
}
/**
* Returns the value associated with the specified stack name and key,
* scoped to the LocalWorkspace.
*
* @param stackName The stack to read tag metadata from.
* @param key The key to use for the tag lookup.
*/
async getTag(stackName: string, key: string): Promise<string> {
const result = await this.runPulumiCmd(["stack", "tag", "get", key, "--stack", stackName]);
return result.stdout.trim();
}
/**
* Sets the specified key-value pair on the provided stack name.
*
* @param stackName The stack to operate on.
* @param key The tag key to set.
* @param value The tag value to set.
*/
async setTag(stackName: string, key: string, value: string): Promise<void> {
await this.runPulumiCmd(["stack", "tag", "set", key, value, "--stack", stackName]);
}
/**
* Removes the specified key-value pair on the provided stack name.
*
* @param stackName The stack to operate on.
* @param key The tag key to remove.
*/
async removeTag(stackName: string, key: string): Promise<void> {
await this.runPulumiCmd(["stack", "tag", "rm", key, "--stack", stackName]);
}
/**
* Returns the tag map for the specified tag name, scoped to the current LocalWorkspace.
*
* @param stackName The stack to read tag metadata from.
*/
async listTags(stackName: string): Promise<TagMap> {
const result = await this.runPulumiCmd(["stack", "tag", "ls", "--json", "--stack", stackName]);
return JSON.parse(result.stdout);
}
2020-10-07 07:25:36 +00:00
/**
* Returns the currently authenticated user.
*/
async whoAmI(): Promise<WhoAmIResult> {
let ver = this._pulumiVersion;
if (ver === undefined) {
// Assume an old version. Doesn't really matter what this is as long as it's pre-3.58.
ver = semver.parse("3.0.0")!;
}
// 3.58 added the --json flag (https://github.com/pulumi/pulumi/releases/tag/v3.58.0)
if (ver.compare("3.58.0") >= 0) {
const result = await this.runPulumiCmd(["whoami", "--json"]);
return JSON.parse(result.stdout);
} else {
const result = await this.runPulumiCmd(["whoami"]);
return { user: result.stdout.trim() };
}
2020-09-18 02:33:13 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Returns a summary of the currently selected stack, if any.
*/
async stack(): Promise<StackSummary | undefined> {
const stacks = await this.listStacks();
for (const stack of stacks) {
if (stack.current) {
return stack;
}
}
return undefined;
2020-09-18 02:33:13 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Returns all Stacks created under the current Project.
* This queries underlying backend and may return stacks not present in the Workspace (as Pulumi.<stack>.yaml files).
*/
async listStacks(): Promise<StackSummary[]> {
const result = await this.runPulumiCmd(["stack", "ls", "--json"]);
return JSON.parse(result.stdout);
2020-09-18 02:33:13 +00:00
}
/**
* Installs a plugin in the Workspace, for example to use cloud providers like AWS or GCP.
*
* @param name the name of the plugin.
* @param version the version of the plugin e.g. "v1.0.0".
* @param kind the kind of plugin, defaults to "resource"
*/
async installPlugin(name: string, version: string, kind = "resource"): Promise<void> {
await this.runPulumiCmd(["plugin", "install", kind, name, version]);
}
/**
* Installs a plugin in the Workspace, from a third party server.
*
* @param name the name of the plugin.
* @param version the version of the plugin e.g. "v1.0.0".
* @param server the server to install the plugin from
*/
async installPluginFromServer(name: string, version: string, server: string): Promise<void> {
await this.runPulumiCmd(["plugin", "install", "resource", name, version, "--server", server]);
}
/**
* Removes a plugin from the Workspace matching the specified name and version.
*
* @param name the optional name of the plugin.
* @param versionRange optional semver range to check when removing plugins matching the given name
* e.g. "1.0.0", ">1.0.0".
* @param kind he kind of plugin, defaults to "resource".
*/
async removePlugin(name?: string, versionRange?: string, kind = "resource"): Promise<void> {
const args = ["plugin", "rm", kind];
if (name) {
args.push(name);
}
if (versionRange) {
args.push(versionRange);
}
args.push("--yes");
await this.runPulumiCmd(args);
}
/**
* Returns a list of all plugins installed in the Workspace.
*/
async listPlugins(): Promise<PluginInfo[]> {
const result = await this.runPulumiCmd(["plugin", "ls", "--json"]);
return JSON.parse(result.stdout, (key, value) => {
if (key === "installTime" || key === "lastUsedTime") {
return new Date(value);
}
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> {
const result = await this.runPulumiCmd(["stack", "export", "--show-secrets", "--stack", stackName]);
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> {
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, "--stack", stackName]);
fs.unlinkSync(filepath);
}
/**
* Gets the current set of Stack outputs from the last Stack.up().
* @param stackName the name of the stack.
*/
async stackOutputs(stackName: string): Promise<OutputMap> {
// TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/6050
const maskedResult = await this.runPulumiCmd(["stack", "output", "--json", "--stack", stackName]);
const plaintextResult = await this.runPulumiCmd([
"stack",
"output",
"--json",
"--show-secrets",
"--stack",
stackName,
]);
const maskedOuts = JSON.parse(maskedResult.stdout);
const plaintextOuts = JSON.parse(plaintextResult.stdout);
const outputs: OutputMap = {};
for (const [key, value] of Object.entries(plaintextOuts)) {
const secret = maskedOuts[key] === "[secret]";
outputs[key] = { value, secret };
}
return outputs;
}
2020-10-07 07:25:36 +00:00
/**
* serializeArgsForOp is hook to provide additional args to every CLI commands before they are executed.
* Provided with stack name,
* returns a list of args to append to an invoked command ["--config=...", ]
* LocalWorkspace does not utilize this extensibility point.
*/
async serializeArgsForOp(_: string): Promise<string[]> {
2020-10-07 07:25:36 +00:00
// LocalWorkspace does not utilize this extensibility point.
2020-10-08 02:00:03 +00:00
return [];
}
2020-10-07 07:25:36 +00:00
/**
* postCommandCallback is a hook executed after every command. Called with the stack name.
* An extensibility point to perform workspace cleanup (CLI operations may create/modify a Pulumi.stack.yaml)
* LocalWorkspace does not utilize this extensibility point.
*/
async postCommandCallback(_: string): Promise<void> {
2020-10-07 07:25:36 +00:00
// LocalWorkspace does not utilize this extensibility point.
return;
}
private async checkRemoteSupport() {
const optOut = !!this.envVars[SKIP_VERSION_CHECK_VAR] || !!process.env[SKIP_VERSION_CHECK_VAR];
// If remote was specified, ensure the CLI supports it.
if (!optOut && this.isRemote) {
// See if `--remote` is present in `pulumi preview --help`'s output.
const previewResult = await this.runPulumiCmd(["preview", "--help"]);
const previewOutput = previewResult.stdout.trim();
if (!previewOutput.includes("--remote")) {
throw new Error("The Pulumi CLI does not support remote operations. Please upgrade.");
}
}
}
private async runPulumiCmd(args: string[]): Promise<CommandResult> {
2020-09-19 21:40:23 +00:00
let envs: { [key: string]: string } = {};
if (this.pulumiHome) {
envs["PULUMI_HOME"] = this.pulumiHome;
}
if (this.isRemote) {
envs["PULUMI_EXPERIMENTAL"] = "true";
}
envs = { ...envs, ...this.envVars };
return this.pulumiCommand.run(args, this.workDir, envs);
2020-09-16 15:39:44 +00:00
}
/** @internal */
get isRemote(): boolean {
return !!this.remote;
}
/** @internal */
remoteArgs(): string[] {
const args: string[] = [];
if (!this.isRemote) {
return args;
}
args.push("--remote");
if (this.remoteGitProgramArgs) {
const { url, projectPath, branch, commitHash, auth } = this.remoteGitProgramArgs;
if (url) {
args.push(url);
}
if (projectPath) {
args.push("--remote-git-repo-dir", projectPath);
}
if (branch) {
args.push("--remote-git-branch", branch);
}
if (commitHash) {
args.push("--remote-git-commit", commitHash);
}
if (auth) {
const { personalAccessToken, sshPrivateKey, sshPrivateKeyPath, password, username } = auth;
if (personalAccessToken) {
args.push("--remote-git-auth-access-token", personalAccessToken);
}
if (sshPrivateKey) {
args.push("--remote-git-auth-ssh-private-key", sshPrivateKey);
}
if (sshPrivateKeyPath) {
args.push("--remote-git-auth-ssh-private-key-path", sshPrivateKeyPath);
}
if (password) {
args.push("--remote-git-auth-password", password);
}
if (username) {
args.push("--remote-git-auth-username", username);
}
}
}
for (const key of Object.keys(this.remoteEnvVars ?? {})) {
const val = this.remoteEnvVars![key];
if (typeof val === "string") {
args.push("--remote-env", `${key}=${val}`);
} else if ("secret" in val) {
args.push("--remote-env-secret", `${key}=${val.secret}`);
} else {
throw new Error(`unexpected env value '${val}' for key '${key}'`);
}
}
for (const command of this.remotePreRunCommands ?? []) {
args.push("--remote-pre-run-command", command);
}
if (this.remoteSkipInstallDependencies) {
args.push("--remote-skip-install-dependencies");
}
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
if (this.remoteInheritSettings) {
args.push("--remote-inherit-settings");
}
return args;
}
2020-09-14 03:28:24 +00:00
}
2020-10-07 07:25:36 +00:00
/**
* Description of a stack backed by an inline (in process) Pulumi program.
*/
export interface InlineProgramArgs {
2020-10-07 07:25:36 +00:00
/**
* The name of the associated Stack
*/
stackName: string;
2020-10-07 07:25:36 +00:00
/**
* The name of the associated project
*/
projectName: string;
2020-10-07 07:25:36 +00:00
/**
2020-10-08 02:00:03 +00:00
* The inline (in process) Pulumi program to use with Update and Preview operations.
2020-10-07 07:25:36 +00:00
*/
program: PulumiFn;
}
2020-10-07 07:25:36 +00:00
/**
* Description of a stack backed by pre-existing local Pulumi CLI program.
*/
export interface LocalProgramArgs {
stackName: string;
workDir: string;
}
2020-10-07 07:25:36 +00:00
/**
* Extensibility options to configure a LocalWorkspace; e.g: settings to seed
* and environment variables to pass through to every command.
*/
export interface LocalWorkspaceOptions {
2020-10-07 07:25:36 +00:00
/**
* The directory to run Pulumi commands and read settings (Pulumi.yaml and Pulumi.<stack>.yaml).
2020-10-07 07:25:36 +00:00
*/
workDir?: string;
2020-10-07 07:25:36 +00:00
/**
* The directory to override for CLI metadata
*/
pulumiHome?: string;
/**
* The underlying Pulumi CLI.
*/
pulumiCommand?: PulumiCommand;
2020-10-07 07:25:36 +00:00
/**
2020-10-08 02:00:03 +00:00
* The inline program `PulumiFn` to be used for Preview/Update operations if any.
* If none is specified, the stack will refer to ProjectSettings for this information.
2020-10-07 07:25:36 +00:00
*/
program?: PulumiFn;
2020-10-07 07:25:36 +00:00
/**
* Environment values scoped to the current workspace. These will be supplied to every Pulumi command.
*/
envVars?: { [key: string]: string };
2020-10-07 07:25:36 +00:00
/**
* The secrets provider to use for encryption and decryption of stack secrets.
* See: https://www.pulumi.com/docs/intro/concepts/secrets/#available-encryption-providers
2020-10-07 07:25:36 +00:00
*/
secretsProvider?: string;
2020-10-07 07:25:36 +00:00
/**
* The settings object for the current project.
*/
projectSettings?: ProjectSettings;
2020-10-07 07:25:36 +00:00
/**
* A map of Stack names and corresponding settings objects.
*/
stackSettings?: { [key: string]: StackSettings };
/**
* Indicates that the workspace is a remote workspace.
*
* @internal
*/
remote?: boolean;
/**
* The remote Git source info.
*
* @internal
*/
remoteGitProgramArgs?: RemoteGitProgramArgs;
/**
* An optional list of arbitrary commands to run before a remote Pulumi operation is invoked.
*
* @internal
*/
remotePreRunCommands?: string[];
/**
* The environment variables to pass along when running remote Pulumi operations.
*
* @internal
*/
remoteEnvVars?: { [key: string]: string | { secret: string } };
/**
* Whether to skip the default dependency installation step.
*
* @internal
*/
remoteSkipInstallDependencies?: boolean;
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
/**
* Whether to inherit deployment settings from the stack.
*
* @internal
*/
remoteInheritSettings?: boolean;
}
2020-09-14 14:55:06 +00:00
2020-10-07 07:25:36 +00:00
/**
* Returns true if the provided `args` object satisfies the `LocalProgramArgs` interface.
*
* @param args The args object to evaluate
*/
function isLocalProgramArgs(args: LocalProgramArgs | InlineProgramArgs): args is LocalProgramArgs {
return (args as LocalProgramArgs).workDir !== undefined;
}
2020-10-07 07:25:36 +00:00
/**
* Returns true if the provided `args` object satisfies the `InlineProgramArgs` interface.
*
* @param args The args object to evaluate
*/
function isInlineProgramArgs(args: LocalProgramArgs | InlineProgramArgs): args is InlineProgramArgs {
return (args as InlineProgramArgs).projectName !== undefined && (args as InlineProgramArgs).program !== undefined;
}
const settingsExtensions = [".yaml", ".yml", ".json"];
2020-09-16 15:39:44 +00:00
function getStackSettingsName(name: string): string {
2020-09-16 15:39:44 +00:00
const parts = name.split("/");
if (parts.length < 1) {
return name;
}
return parts[parts.length - 1];
}
type StackInitializer = (name: string, workspace: Workspace) => Promise<Stack>;
function defaultProject(projectName: string) {
const settings: ProjectSettings = { name: projectName, runtime: "nodejs", main: process.cwd() };
return settings;
}
function loadProjectSettings(workDir: string) {
for (const ext of settingsExtensions) {
const isJSON = ext === ".json";
const path = upath.joinSafe(workDir, `Pulumi${ext}`);
if (!fs.existsSync(path)) {
continue;
}
const contents = fs.readFileSync(path).toString();
if (isJSON) {
return JSON.parse(contents);
}
return yaml.safeLoad(contents) as ProjectSettings;
}
throw new Error(`failed to find project settings file in workdir: ${workDir}`);
}