pulumi/pkg/cmd/pulumi/deployment_settings_config.go

954 lines
28 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2016-2024, 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.
package main
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"github.com/pulumi/pulumi/pkg/v3/backend"
"github.com/pulumi/pulumi/pkg/v3/backend/display"
pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/gitutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
"github.com/spf13/cobra"
)
const (
optYes = "Yes"
optNo = "No"
optOidcAws = "Enable AWS integration"
optOidcAzure = "Enable Azure integration"
optOidcGcp = "Enable Google Cloud integration"
optGit = "Git"
optExecutorImage = "Executor image"
optAdvancedSettings = "Advanced settings"
optPreviewPr = "Run previews for pull requests"
optUpdatePushes = "Run updates for pushed commits"
optPrTemplate = "Use this stack as a template for pull request stacks"
optNoAuthentication = "No authentication"
optUserPass = "Username/Password"
optSSH = "SSH key"
optSkipDepsInstall = "Skip automatic dependency installation step"
optSkipIntermediateDeployments = "Skip intermediate deployments"
)
var errAbortCmd = errors.New("abort")
func newDeploymentCmd() *cobra.Command {
cmd := &cobra.Command{
// This is temporarily hidden while we iterate over the new set of commands,
// we will remove before releasing these new set of features.
Hidden: true,
Use: "deployment",
Short: "Manage stack deployments on Pulumi Cloud",
Long: "Manage stack deployments on Pulumi Cloud.\n" +
"\n" +
"Use this command to trigger deployment jobs and manage deployment settings.",
Args: cmdutil.NoArgs,
Run: runCmdFunc(func(cmd *cobra.Command, args []string) error {
return cmd.Help()
}),
}
cmd.PersistentFlags().StringVar(
&stackDeploymentConfigFile, "config-file", "",
"Override the file name where the deployment settings are specified. Default is Pulumi.[stack].deploy.yaml")
cmd.AddCommand(newDeploymentSettingsCmd())
cmd.AddCommand(newDeploymentRunCmd())
return cmd
}
func newDeploymentSettingsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "settings",
Args: cmdutil.NoArgs,
Short: "Manage stack deployment settings",
Long: "Manage stack deployment settings\n" +
"\n" +
"Use this command to manage a stack's deployment settings like\n" +
"generating the deployment file, updating secrets or pushing the\n" +
"updated settings to Pulumi Cloud.",
Run: runCmdFunc(func(cmd *cobra.Command, args []string) error {
return cmd.Help()
}),
}
cmd.AddCommand(newDeploymentSettingsInitCmd())
cmd.AddCommand(newDeploymentSettingsPullCmd())
cmd.AddCommand(newDeploymentSettingsUpdateCmd())
cmd.AddCommand(newDeploymentSettingsDestroyCmd())
cmd.AddCommand(newDeploymentSettingsEnvCmd())
cmd.AddCommand(newDeploymentSettingsConfigureCmd())
return cmd
}
type deploymentSettingsCommandDependencies struct {
DisplayOptions *display.Options
Stack backend.Stack
Deployment *workspace.ProjectStackDeployment
Backend backend.Backend
Interactive bool
Ctx context.Context
Prompts prompts
WorkDir string
}
func initializeDeploymentSettingsCmd(
ctx context.Context, ws pkgWorkspace.Context, stack string,
) (*deploymentSettingsCommandDependencies, error) {
interactive := cmdutil.Interactive()
displayOpts := display.Options{
Color: cmdutil.GetGlobalColorization(),
IsInteractive: interactive,
}
project, _, err := ws.ReadProject()
if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
return nil, err
}
be, err := currentBackend(ctx, ws, DefaultLoginManager, project, displayOpts)
if err != nil {
return nil, err
}
if !be.SupportsDeployments() {
unsupportedBackendMsg := fmt.Sprintf("Backends of type %q do not support managed deployments.\n\n"+
"Create a Pulumi Cloud account to get started, learn more about pulumi deployments here: "+
"https://www.pulumi.com/docs/pulumi-cloud/deployments/",
be.Name())
unsupportedBackendMsg = colors.Highlight(unsupportedBackendMsg,
fmt.Sprintf("Backends of type %q do not support managed deployments", be.Name()),
colors.SpecError+colors.Bold)
unsupportedBackendMsg = colors.Highlight(unsupportedBackendMsg, "Pulumi Cloud", colors.BrightCyan+colors.Bold)
unsupportedBackendMsg = colors.Highlight(unsupportedBackendMsg,
"https://www.pulumi.com/docs/pulumi-cloud/deployments/", colors.BrightBlue+colors.Underline+colors.Bold)
fmt.Println()
fmt.Println(displayOpts.Color.Colorize(unsupportedBackendMsg))
fmt.Println()
return nil, fmt.Errorf("unable to manage stack deployments for backend type: %s",
be.Name())
}
s, err := requireStack(ctx, ws, DefaultLoginManager, stack, stackOfferNew|stackSetCurrent, displayOpts)
if err != nil {
return nil, err
}
sd, err := loadProjectStackDeployment(s)
if err != nil {
return nil, err
}
wd, err := os.Getwd()
if err != nil {
return nil, err
}
return &deploymentSettingsCommandDependencies{
DisplayOptions: &displayOpts,
Stack: s,
Deployment: sd,
Backend: be,
Interactive: interactive,
Ctx: ctx,
Prompts: promptHandlers{},
WorkDir: wd,
}, nil
}
func newDeploymentSettingsInitCmd() *cobra.Command {
var force bool
var stack string
var gitSSHPrivateKeyPath string
var gitSSHPrivateKeyValue string
cmd := &cobra.Command{
Use: "init",
SuggestFor: []string{"new", "create"},
Args: cmdutil.ExactArgs(0),
Short: "Initialize the stack's deployment.yaml file",
Long: "",
Run: runCmdFunc(func(cmd *cobra.Command, args []string) error {
d, err := initializeDeploymentSettingsCmd(cmd.Context(), pkgWorkspace.Instance, stack)
if err != nil {
return err
}
if d.Deployment != nil && !force {
return fmt.Errorf("Deployment settings already configured for stack %q. Rerun for a "+
"different stack by using --stack, update it by using the \"configure\" command or by "+
"editing the file manually; or use --force", d.Stack.Ref())
}
err = initStackDeploymentCmd(d, gitSSHPrivateKeyPath, gitSSHPrivateKeyValue)
switch {
case errors.Is(err, errAbortCmd):
return nil
case err != nil:
return err
}
return nil
}),
}
cmd.PersistentFlags().StringVar(
&gitSSHPrivateKeyPath, "git-auth-ssh-private-key-path", "",
"Git SSH private key path")
cmd.PersistentFlags().StringVar(
&gitSSHPrivateKeyValue, "git-auth-ssh-private-key", "",
"Git SSH private key")
cmd.PersistentFlags().StringVarP(
&stack, "stack", "s", "",
"The name of the stack to operate on. Defaults to the current stack")
cmd.PersistentFlags().BoolVarP(
&force, "force", "f", false,
"Forces content to be generated even if it is already configured")
return cmd
}
func initStackDeploymentCmd(
d *deploymentSettingsCommandDependencies, gitSSHPrivateKeyPath string, gitSSHPrivateKeyValue string,
) error {
d.Deployment = &workspace.ProjectStackDeployment{
DeploymentSettings: apitype.DeploymentSettings{},
}
err := configureGit(d, gitSSHPrivateKeyPath, gitSSHPrivateKeyValue)
if err != nil {
return err
}
err = configureImageRepository(d)
if err != nil {
return err
}
err = configureAdvancedSettings(d)
if err != nil {
return err
}
oidcExplanationMsg := "\nPulumi supports OpenID Connect (OIDC) integration across various services by " +
"leveraging signed, short-lived tokens and eliminating the necessity for hardcoded " +
"cloud provider credentials and facilitates the exchange of these tokens for " +
"short-term credentials.\n"
oidcExplanationMsg = colors.Highlight(oidcExplanationMsg, "Pulumi", colors.SpecHeadline)
oidcExplanationMsg = colors.Highlight(oidcExplanationMsg, "OpenID Connect (OIDC)", colors.SpecInfo)
d.Prompts.Print(d.DisplayOptions.Color.Colorize(oidcExplanationMsg))
// For non interactive execution, we skip oidc configuration
option := d.Prompts.PromptUserSkippable(
!d.Interactive,
"Do you want to configure an OpenID Connect integration?",
[]string{
optNo,
optOidcAws,
optOidcAzure,
optOidcGcp,
},
optNo,
d.DisplayOptions.Color)
switch option {
case optOidcAws:
err = configureOidcAws(d)
case optOidcAzure:
err = configureOidcAzure(d)
case optOidcGcp:
err = configureOidcGCP(d)
}
if err != nil {
return err
}
return saveProjectStackDeployment(d.Deployment, d.Stack)
}
func newDeploymentSettingsConfigureCmd() *cobra.Command {
var stack string
var gitSSHPrivateKeyPath string
var gitSSHPrivateKeyValue string
cmd := &cobra.Command{
Use: "configure",
Args: cmdutil.ExactArgs(0),
Short: "Updates stack's deployment settings secrets",
Long: "",
Run: runCmdFunc(func(cmd *cobra.Command, args []string) error {
if !cmdutil.Interactive() {
return errors.New("configure command is only supported in interactive mode")
}
d, err := initializeDeploymentSettingsCmd(cmd.Context(), pkgWorkspace.Instance, stack)
if err != nil {
return err
}
if d.Deployment == nil {
return errors.New("Deployment file not initialized, please run `pulumi deployment settings init` instead")
}
option := d.Prompts.PromptUser(
"Configure",
[]string{
optGit,
optExecutorImage,
optAdvancedSettings,
optOidcAws,
optOidcAzure,
optOidcGcp,
},
optGit,
d.DisplayOptions.Color)
switch option {
case optGit:
err = configureGit(d, gitSSHPrivateKeyPath, gitSSHPrivateKeyValue)
case optExecutorImage:
err = configureImageRepository(d)
case optAdvancedSettings:
err = configureAdvancedSettings(d)
case optOidcAws:
err = configureOidcAws(d)
case optOidcAzure:
err = configureOidcAzure(d)
case optOidcGcp:
err = configureOidcGCP(d)
default:
return nil
}
switch {
case errors.Is(err, errAbortCmd):
return nil
case err != nil:
return err
}
err = saveProjectStackDeployment(d.Deployment, d.Stack)
if err != nil {
return err
}
return nil
}),
}
cmd.PersistentFlags().StringVarP(
&gitSSHPrivateKeyPath, "git-auth-ssh-private-key-path", "k", "",
"Private key path")
cmd.PersistentFlags().StringVar(
&gitSSHPrivateKeyValue, "git-auth-ssh-private-key", "",
"Git SSH private key")
cmd.PersistentFlags().StringVarP(
&stack, "stack", "s", "",
"The name of the stack to operate on. Defaults to the current stack")
return cmd
}
func configureGit(d *deploymentSettingsCommandDependencies, gitSSHPrivateKeyPath string, gitSSHPrivateKeyValue string,
) error {
sd := d.Deployment
if sd.DeploymentSettings.SourceContext == nil {
sd.DeploymentSettings.SourceContext = &apitype.SourceContext{}
}
if sd.DeploymentSettings.SourceContext.Git == nil {
sd.DeploymentSettings.SourceContext.Git = &apitype.SourceContextGit{}
}
rl, err := newRepoLookup(d.WorkDir)
if err != nil {
return err
}
var defaultRepoDir string
if sd.DeploymentSettings.SourceContext.Git.RepoDir != "" {
// we convert the directory to use the native path separator to keep it consistent with the environment
defaultRepoDir = filepath.FromSlash(sd.DeploymentSettings.SourceContext.Git.RepoDir)
} else {
defaultRepoDir, err = rl.GetRootDirectory(d.WorkDir)
if err != nil {
return err
}
}
repoDir, err := d.Prompts.PromptForValue(!d.Interactive, "Repository directory",
defaultRepoDir, false, ValidateRelativeDirectory(rl.GetRepoRoot()), *d.DisplayOptions)
if err != nil {
return err
}
// we have to convert non unix to use the unix path separator
sd.DeploymentSettings.SourceContext.Git.RepoDir = filepath.ToSlash(repoDir)
var branchName string
if sd.DeploymentSettings.SourceContext.Git.Branch != "" {
branchName = sd.DeploymentSettings.SourceContext.Git.Branch
} else {
branchName = rl.GetBranchName()
}
branchName, err = d.Prompts.PromptForValue(!d.Interactive, "Branch name",
branchName, false, ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
sd.DeploymentSettings.SourceContext.Git.Branch = branchName
remoteURL, err := rl.RemoteURL()
if err != nil {
return err
}
remoteURL, err = d.Prompts.PromptForValue(!d.Interactive, "Repository URL",
remoteURL, false, ValidateGitURL, *d.DisplayOptions)
if err != nil {
return err
}
vcsInfo, err := gitutil.TryGetVCSInfo(remoteURL)
var useGitHub bool
if err == nil {
useGitHub = vcsInfo.Kind == gitutil.GitHubHostName
} else {
// we failed to parse the remote URL, we default to a non-github repo
useGitHub = false
}
if useGitHub {
integration, err := d.Backend.GetGHAppIntegration(d.Ctx, d.Stack)
if err != nil {
return err
}
if !integration.Installed {
useGitHub = false
ghAppExplanationTitle := "\nPulumis GitHub app is not installed\n\n"
ghAppExplanationMsg := "Pulumis GitHub app displays the results of Pulumi stack update previews in " +
"pull requests and enables automatic stack deployments via Pulumi Deployments. " +
"Once installed and configured, it will show you any potential infrastructure " +
"changes on Pull Requests and commit checks. You can also configure git push to " +
"deploy workflows that update your stacks whenever a pull request is merged.\n\n" +
"To install the App follow the instructions at: " +
"https://www.pulumi.com/docs/iac/packages-and-automation/continuous-delivery/github-app/\n"
ghAppExplanationTitle = colors.Highlight(ghAppExplanationTitle,
"Pulumis GitHub app is not installed", colors.SpecWarning)
ghAppExplanationMsg = colors.Highlight(ghAppExplanationMsg, "Pulumis GitHub app", colors.SpecHeadline)
ghAppExplanationMsg = colors.Highlight(ghAppExplanationMsg, "Pulumi Deployments", colors.SpecHeadline)
d.Prompts.Print(d.DisplayOptions.Color.Colorize(ghAppExplanationTitle + ghAppExplanationMsg))
confirm := d.Prompts.AskForConfirmation("Do you want to continue without using the Pulumi's GitHub app?",
d.DisplayOptions.Color, true, false)
if !confirm {
return errAbortCmd
}
}
}
if useGitHub {
ghAppExplanationMsg := "\nPulumis GitHub app displays the results of Pulumi stack update previews in " +
"pull requests and enables automatic stack deployments via Pulumi Deployments. " +
"Once installed and configured, it will show you any potential infrastructure " +
"changes on Pull Requests and commit checks. You can also configure git push to " +
"deploy workflows that update your stacks whenever a pull request is merged.\n"
ghAppExplanationMsg = colors.Highlight(ghAppExplanationMsg, "Pulumis GitHub app", colors.SpecHeadline)
ghAppExplanationMsg = colors.Highlight(ghAppExplanationMsg, "Pulumi Deployments", colors.SpecHeadline)
d.Prompts.Print(d.DisplayOptions.Color.Colorize(ghAppExplanationMsg))
useGitHub = d.Prompts.AskForConfirmation(
"Do you want to use the Pulumi GitHub App?",
d.DisplayOptions.Color, true, !d.Interactive)
}
if useGitHub {
err := configureGitHubRepo(d, vcsInfo)
if err != nil {
return err
}
} else {
err := configureBareGitRepo(d, remoteURL, gitSSHPrivateKeyPath, gitSSHPrivateKeyValue)
if err != nil {
return err
}
}
return nil
}
func configureGitHubRepo(d *deploymentSettingsCommandDependencies, vcsInfo *gitutil.VCSInfo) error {
sd := d.Deployment
if sd.DeploymentSettings.GitHub == nil {
sd.DeploymentSettings.GitHub = &apitype.DeploymentSettingsGitHub{}
}
sd.DeploymentSettings.GitHub.Repository = fmt.Sprintf("%s/%s", vcsInfo.Owner, vcsInfo.Repo)
var defaults []string
if sd.DeploymentSettings.GitHub.PreviewPullRequests {
defaults = append(defaults, optPreviewPr)
}
if sd.DeploymentSettings.GitHub.DeployCommits {
defaults = append(defaults, optUpdatePushes)
}
if sd.DeploymentSettings.GitHub.PullRequestTemplate {
defaults = append(defaults, optPrTemplate)
}
if len(defaults) == 0 {
defaults = []string{
optPreviewPr,
optUpdatePushes,
}
}
// For non interactive execution, it automatically accepts the default values
options := d.Prompts.PromptUserMultiSkippable(
!d.Interactive,
"GitHub configuration",
[]string{
optPreviewPr,
optUpdatePushes,
optPrTemplate,
},
defaults,
d.DisplayOptions.Color)
sd.DeploymentSettings.GitHub.PreviewPullRequests = slices.Contains(options, optPreviewPr)
sd.DeploymentSettings.GitHub.DeployCommits = slices.Contains(options, optUpdatePushes)
sd.DeploymentSettings.GitHub.PullRequestTemplate = slices.Contains(options, optPrTemplate)
return nil
}
func configureBareGitRepo(d *deploymentSettingsCommandDependencies,
remoteURL string, gitSSHPrivateKeyPath string, gitSSHPrivateKeyValue string,
) error {
sd := d.Deployment
sd.DeploymentSettings.SourceContext.Git.RepoURL = remoteURL
option := d.Prompts.PromptUser(
"What kind of authentication does the repository use?",
[]string{
optUserPass,
optSSH,
optNoAuthentication,
},
optUserPass,
d.DisplayOptions.Color)
switch option {
case optUserPass:
return configureGitPassword(d)
case optSSH:
return configureGitSSH(d, gitSSHPrivateKeyPath, gitSSHPrivateKeyValue)
case optNoAuthentication:
sd.DeploymentSettings.SourceContext.Git.GitAuth = nil
}
return nil
}
func configureGitPassword(d *deploymentSettingsCommandDependencies) error {
var username string
var password string
var err error
sd := d.Deployment
if sd.DeploymentSettings.SourceContext == nil {
sd.DeploymentSettings.SourceContext = &apitype.SourceContext{}
}
if sd.DeploymentSettings.SourceContext.Git == nil {
sd.DeploymentSettings.SourceContext.Git = &apitype.SourceContextGit{}
}
if sd.DeploymentSettings.SourceContext.Git.GitAuth == nil {
sd.DeploymentSettings.SourceContext.Git.GitAuth = &apitype.GitAuthConfig{}
}
if sd.DeploymentSettings.SourceContext.Git.GitAuth.BasicAuth == nil {
sd.DeploymentSettings.SourceContext.Git.GitAuth.BasicAuth = &apitype.BasicAuth{}
}
if sd.DeploymentSettings.SourceContext.Git.GitAuth.BasicAuth.UserName.Value != "" &&
!sd.DeploymentSettings.SourceContext.Git.GitAuth.BasicAuth.UserName.Secret {
username = sd.DeploymentSettings.SourceContext.Git.GitAuth.BasicAuth.UserName.Value
}
username, err = d.Prompts.PromptForValue(false, "Git username",
username, false, ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
password, err = d.Prompts.PromptForValue(false, "Git password",
password, true, ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
secret, err := d.Backend.EncryptStackDeploymentSettingsSecret(d.Ctx, d.Stack, password)
if err != nil {
return err
}
sd.DeploymentSettings.SourceContext.Git.GitAuth = &apitype.GitAuthConfig{
BasicAuth: &apitype.BasicAuth{
UserName: apitype.SecretValue{Value: username},
Password: *secret,
},
}
return nil
}
func configureGitSSH(
d *deploymentSettingsCommandDependencies, gitSSHPrivateKeyPath string, gitSSHPrivateKeyValue string,
) error {
if gitSSHPrivateKeyPath == "" && gitSSHPrivateKeyValue == "" {
configureMsg := "\nNo SSH private key was provided, run `pulumi deployment settings " +
"configure` with the `--git-auth-ssh-private-key` or `--git-auth-ssh-private-key-path` flag set\n"
configureMsg = colors.Highlight(configureMsg, "No SSH private key was provided", colors.SpecError+colors.Bold)
configureMsg = colors.Highlight(configureMsg, "pulumi deployment settings configure", colors.BrightBlue+colors.Bold)
configureMsg = colors.Highlight(configureMsg, "--git-auth-ssh-private-key", colors.BrightBlue+colors.Bold)
configureMsg = colors.Highlight(configureMsg, "--git-auth-ssh-private-key-path", colors.BrightBlue+colors.Bold)
d.Prompts.Print(d.DisplayOptions.Color.Colorize(configureMsg))
return nil
}
privateKey := gitSSHPrivateKeyValue
if privateKey == "" {
value, err := os.ReadFile(gitSSHPrivateKeyPath)
if err != nil {
return err
}
privateKey = string(value)
}
secret, err := d.Backend.EncryptStackDeploymentSettingsSecret(d.Ctx, d.Stack, privateKey)
if err != nil {
return err
}
sd := d.Deployment
if sd.DeploymentSettings.SourceContext == nil {
sd.DeploymentSettings.SourceContext = &apitype.SourceContext{}
}
if sd.DeploymentSettings.SourceContext.Git == nil {
sd.DeploymentSettings.SourceContext.Git = &apitype.SourceContextGit{}
}
sd.DeploymentSettings.SourceContext.Git.GitAuth = &apitype.GitAuthConfig{
SSHAuth: &apitype.SSHAuth{
SSHPrivateKey: *secret,
},
}
var password string
password, err = d.Prompts.PromptForValue(false, "(Optional) Private key password", password, true,
ValidateShortInput, *d.DisplayOptions)
if err != nil {
return err
}
if password != "" {
secret, err := d.Backend.EncryptStackDeploymentSettingsSecret(d.Ctx, d.Stack, password)
if err != nil {
return err
}
sd.DeploymentSettings.SourceContext.Git.GitAuth.SSHAuth.Password = secret
}
return nil
}
func configureOidcAws(d *deploymentSettingsCommandDependencies) error {
sd := d.Deployment
if sd.DeploymentSettings.Operation == nil {
sd.DeploymentSettings.Operation = &apitype.OperationContext{}
}
if sd.DeploymentSettings.Operation.OIDC == nil {
sd.DeploymentSettings.Operation.OIDC = &apitype.OperationContextOIDCConfiguration{}
}
if sd.DeploymentSettings.Operation.OIDC.AWS == nil {
sd.DeploymentSettings.Operation.OIDC.AWS = &apitype.OperationContextAWSOIDCConfiguration{}
}
roleARN, err := d.Prompts.PromptForValue(false, "AWS role ARN", sd.DeploymentSettings.Operation.OIDC.AWS.RoleARN,
false, ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
sessionName, err := d.Prompts.PromptForValue(false, "AWS session name",
sd.DeploymentSettings.Operation.OIDC.AWS.SessionName, false,
ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
sd.DeploymentSettings.Operation.OIDC.AWS.RoleARN = roleARN
sd.DeploymentSettings.Operation.OIDC.AWS.SessionName = sessionName
return nil
}
func configureOidcGCP(d *deploymentSettingsCommandDependencies) error {
sd := d.Deployment
if sd.DeploymentSettings.Operation == nil {
sd.DeploymentSettings.Operation = &apitype.OperationContext{}
}
if sd.DeploymentSettings.Operation.OIDC == nil {
sd.DeploymentSettings.Operation.OIDC = &apitype.OperationContextOIDCConfiguration{}
}
if sd.DeploymentSettings.Operation.OIDC.GCP == nil {
sd.DeploymentSettings.Operation.OIDC.GCP = &apitype.OperationContextGCPOIDCConfiguration{}
}
projectID, err := d.Prompts.PromptForValue(false, "GCP project id",
sd.DeploymentSettings.Operation.OIDC.GCP.ProjectID, false, ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
providerID, err := d.Prompts.PromptForValue(false, "GCP provider id",
sd.DeploymentSettings.Operation.OIDC.GCP.ProviderID, false, ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
workloadPoolID, err := d.Prompts.PromptForValue(false, "GCP identity provider id",
sd.DeploymentSettings.Operation.OIDC.GCP.WorkloadPoolID, false,
ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
serviceAccount, err := d.Prompts.PromptForValue(false, "GCP service account email address",
sd.DeploymentSettings.Operation.OIDC.GCP.ServiceAccount, false,
ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
sd.DeploymentSettings.Operation.OIDC.GCP.ProjectID = projectID
sd.DeploymentSettings.Operation.OIDC.GCP.ProviderID = providerID
sd.DeploymentSettings.Operation.OIDC.GCP.WorkloadPoolID = workloadPoolID
sd.DeploymentSettings.Operation.OIDC.GCP.ServiceAccount = serviceAccount
return nil
}
func configureOidcAzure(d *deploymentSettingsCommandDependencies) error {
sd := d.Deployment
if sd.DeploymentSettings.Operation == nil {
sd.DeploymentSettings.Operation = &apitype.OperationContext{}
}
if sd.DeploymentSettings.Operation.OIDC == nil {
sd.DeploymentSettings.Operation.OIDC = &apitype.OperationContextOIDCConfiguration{}
}
if sd.DeploymentSettings.Operation.OIDC.Azure == nil {
sd.DeploymentSettings.Operation.OIDC.Azure = &apitype.OperationContextAzureOIDCConfiguration{}
}
clientID, err := d.Prompts.PromptForValue(false, "Azure client ID",
sd.DeploymentSettings.Operation.OIDC.Azure.ClientID, false, ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
tenantID, err := d.Prompts.PromptForValue(false, "Azure tenant ID",
sd.DeploymentSettings.Operation.OIDC.Azure.TenantID, false, ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
subscriptionID, err := d.Prompts.PromptForValue(false, "Azure subscription ID",
sd.DeploymentSettings.Operation.OIDC.Azure.SubscriptionID, false,
ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
sd.DeploymentSettings.Operation.OIDC.Azure.ClientID = clientID
sd.DeploymentSettings.Operation.OIDC.Azure.TenantID = tenantID
sd.DeploymentSettings.Operation.OIDC.Azure.SubscriptionID = subscriptionID
return nil
}
func configureAdvancedSettings(d *deploymentSettingsCommandDependencies) error {
sd := d.Deployment
var defaults []string
if sd.DeploymentSettings.Operation == nil {
sd.DeploymentSettings.Operation = &apitype.OperationContext{}
}
if sd.DeploymentSettings.Operation.Options == nil {
sd.DeploymentSettings.Operation.Options = &apitype.OperationContextOptions{}
}
if sd.DeploymentSettings.Operation.Options.SkipInstallDependencies {
defaults = append(defaults, optSkipDepsInstall)
}
if sd.DeploymentSettings.Operation.Options.SkipIntermediateDeployments {
defaults = append(defaults, optSkipIntermediateDeployments)
}
if len(defaults) == 0 {
defaults = []string{
optSkipIntermediateDeployments,
}
}
// For non interactive execution, it automatically accepts the default values
options := d.Prompts.PromptUserMultiSkippable(
!d.Interactive,
"Advanced settings",
[]string{
optSkipIntermediateDeployments,
optSkipDepsInstall,
},
defaults,
d.DisplayOptions.Color)
sd.DeploymentSettings.Operation.Options.
SkipInstallDependencies = slices.Contains(options, optSkipDepsInstall)
sd.DeploymentSettings.Operation.Options.
SkipIntermediateDeployments = slices.Contains(options, optSkipIntermediateDeployments)
return nil
}
func configureImageRepository(d *deploymentSettingsCommandDependencies) error {
sd := d.Deployment
// for non interactive runs, we default to false
confirm := d.Prompts.AskForConfirmation("Do you want to use a custom executor image?",
d.DisplayOptions.Color, false, !d.Interactive)
if !confirm {
sd.DeploymentSettings.Executor = nil
return nil
}
if sd.DeploymentSettings.Executor == nil {
sd.DeploymentSettings.Executor = &apitype.ExecutorContext{}
}
if sd.DeploymentSettings.Executor.ExecutorImage == nil {
sd.DeploymentSettings.Executor.ExecutorImage = &apitype.DockerImage{}
}
if sd.DeploymentSettings.Executor.ExecutorImage.Credentials == nil {
sd.DeploymentSettings.Executor.ExecutorImage.Credentials = &apitype.DockerImageCredentials{}
}
imageReference, err := d.Prompts.PromptForValue(false, "Image reference",
sd.DeploymentSettings.Executor.ExecutorImage.Reference, false,
ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
username, err := d.Prompts.PromptForValue(false, "(Optional) Image repository username",
sd.DeploymentSettings.Executor.ExecutorImage.Credentials.Username, false,
ValidateShortInput, *d.DisplayOptions)
if err != nil {
return err
}
sd.DeploymentSettings.Executor.ExecutorImage = &apitype.DockerImage{
Reference: imageReference,
}
if username == "" {
return nil
}
password, err := d.Prompts.PromptForValue(false, "Image repository password", "", true,
ValidateShortInputNonEmpty, *d.DisplayOptions)
if err != nil {
return err
}
secret, err := d.Backend.EncryptStackDeploymentSettingsSecret(d.Ctx, d.Stack, password)
if err != nil {
return err
}
sd.DeploymentSettings.Executor.ExecutorImage.Credentials = &apitype.DockerImageCredentials{
Username: username,
Password: *secret,
}
return nil
}