pulumi/pkg/cmd/pulumi/deployment_settings_config.go

888 lines
26 KiB
Go

// 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"
)
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, 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, 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())
}
return initStackDeploymentCmd(d, gitSSHPrivateKeyPath, gitSSHPrivateKeyValue)
}),
}
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
}
// 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
}
if 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 {
useGitHub = d.Prompts.AskForConfirmation(
"A GitHub repository was detected, 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 := "No 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"
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)
fmt.Println()
fmt.Println(d.DisplayOptions.Color.Colorize(configureMsg))
fmt.Println()
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
}