// 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 { LocalWorkspace, LocalWorkspaceOptions } from "./localWorkspace"; import { RemoteStack } from "./remoteStack"; import { Stack } from "./stack"; /** * {@link RemoteWorkspace} is the execution context containing a single remote * Pulumi project. */ export class RemoteWorkspace { /** * Creates a stack backed by a {@link RemoteWorkspace} with source code from * the specified Git repository. Pulumi operations on the stack (preview, * update, refresh, and destroy) are performed remotely. * * @param args * A set of arguments to initialize a {@link RemoteStack} with a remote * Pulumi program from a Git repository. * @param opts * Additional customizations to be applied to the Workspace. */ static async createStack(args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions): Promise<RemoteStack> { const ws = await createLocalWorkspace(args, opts); const stack = await Stack.create(args.stackName, ws); return RemoteStack.create(stack); } /** * Selects an existing stack backed by a {@link RemoteWorkspace} with source * code from the specified Git repository. Pulumi operations on the stack * (preview, update, refresh, and destroy) are performed remotely. * * @param args * A set of arguments to initialize a {@link RemoteStack} with a remote * Pulumi program from a Git repository. * @param opts * Additional customizations to be applied to the Workspace. */ static async selectStack(args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions): Promise<RemoteStack> { const ws = await createLocalWorkspace(args, opts); const stack = await Stack.select(args.stackName, ws); return RemoteStack.create(stack); } /** * Creates or selects an existing stack backed by a {@link RemoteWorkspace} * with source code from the specified Git repository. Pulumi operations on * the stack (preview, update, refresh, and destroy) are performed remotely. * * @param args * A set of arguments to initialize a RemoteStack with a remote Pulumi program from a Git repository. * @param opts * Additional customizations to be applied to the Workspace. */ static async createOrSelectStack(args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions): Promise<RemoteStack> { const ws = await createLocalWorkspace(args, opts); const stack = await Stack.createOrSelect(args.stackName, ws); return RemoteStack.create(stack); } private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function } /** * Description of a stack backed by a remote Pulumi program in a Git repository. */ export interface RemoteGitProgramArgs { /** * The associated stack name. */ stackName: string; /** * The URL of the repository. */ url?: string; /** * An optional path relative to the repo root specifying location of the Pulumi program. */ projectPath?: string; /** * An optional branch to checkout. */ branch?: string; /** * Optional commit to checkout. */ commitHash?: string; /** * Authentication options for the repository. */ auth?: RemoteGitAuthArgs; } /** * Authentication options that can be specified for a private Git repository. * There are three different authentication paths: * * - A Personal access token * - An SSH private key (and its optional passphrase) * - Username and password (basic authentication) * * Only one authentication path is valid. */ export interface RemoteGitAuthArgs { /** * The absolute path to a private key to be used for access to the Git repository. */ sshPrivateKeyPath?: string; /** * A string containing the contents of a private key to be used for access * to the Git repository. */ sshPrivateKey?: string; /** * The password that pairs with a username as part of basic authentication, * or the passphrase to be used with an SSH private key. */ password?: string; /** * A Git personal access token, to be used in replacement of a password. */ personalAccessToken?: string; /** * The username to use when authenticating to a Git repository with basic * authentication. */ username?: string; } /** * Extensibility options to configure a {@link RemoteWorkspace.} */ export interface RemoteWorkspaceOptions { /** * Environment values scoped to the remote workspace. These will be passed * to remote operations. */ envVars?: { [key: string]: string | { secret: string } }; /** * An optional list of arbitrary commands to run before a remote Pulumi * operation is invoked. */ preRunCommands?: string[]; /** * Whether to skip the default dependency installation step. Defaults to * false. */ skipInstallDependencies?: boolean; /** * Whether to inherit the deployment settings set on the stack. Defaults to * false. */ inheritSettings?: boolean; } async function createLocalWorkspace( args: RemoteGitProgramArgs, opts?: RemoteWorkspaceOptions, ): Promise<LocalWorkspace> { if (!isFullyQualifiedStackName(args.stackName)) { throw new Error(`stack name "${args.stackName}" must be fully qualified.`); } if (!args.url && !opts?.inheritSettings) { throw new Error("url is required if inheritSettings is not set."); } if (args.branch && args.commitHash) { throw new Error("branch and commitHash cannot both be specified."); } if (!args.branch && !args.commitHash && !opts?.inheritSettings) { throw new Error("either branch or commitHash is required if inheritSettings is not set."); } if (args.auth) { if (args.auth.sshPrivateKey && args.auth.sshPrivateKeyPath) { throw new Error("sshPrivateKey and sshPrivateKeyPath cannot both be specified."); } } const localOpts: LocalWorkspaceOptions = { remote: true, remoteGitProgramArgs: args, remoteEnvVars: opts?.envVars, remotePreRunCommands: opts?.preRunCommands, remoteSkipInstallDependencies: opts?.skipInstallDependencies, remoteInheritSettings: opts?.inheritSettings, }; return await LocalWorkspace.create(localOpts); } /** * @internal * Exported only so it can be tested. */ export function isFullyQualifiedStackName(stackName: string): boolean { if (!stackName) { return false; } const split = stackName.split("/"); return split.length === 3 && !!split[0] && !!split[1] && !!split[2]; }