Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ cli/azd/extensions/microsoft.azd.concurx/concurx
cli/azd/extensions/microsoft.azd.concurx/concurx.exe
cli/azd/extensions/azure.appservice/azureappservice
cli/azd/extensions/azure.appservice/azureappservice.exe

.squad/
9 changes: 9 additions & 0 deletions cli/azd/cmd/testdata/TestFigSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1844,6 +1844,15 @@ const completionSpec: Fig.Spec = {
},
],
},
{
name: ['--timeout'],
description: 'Maximum time in seconds for azd to wait for each service deployment. This stops azd from waiting but does not cancel the Azure-side deployment. (default: 1200)',
args: [
{
name: 'timeout',
},
],
},
],
args: {
name: 'service',
Expand Down
1 change: 1 addition & 0 deletions cli/azd/cmd/testdata/TestUsage-azd-deploy.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Flags
--all : Deploys all services that are listed in azure.yaml
-e, --environment string : The name of the environment to use.
--from-package string : Deploys the packaged service located at the provided path. Supports zipped file packages (file path) or container images (image tag).
--timeout int : Maximum time in seconds for azd to wait for each service deployment. This stops azd from waiting but does not cancel the Azure-side deployment. (default: 1200)

Global Flags
-C, --cwd string : Sets the current working directory.
Expand Down
83 changes: 82 additions & 1 deletion cli/azd/internal/cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ package cmd

import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"strconv"
"strings"
"time"

Expand All @@ -34,11 +36,15 @@ import (
type DeployFlags struct {
ServiceName string
All bool
Timeout int
fromPackage string
flagSet *pflag.FlagSet
global *internal.GlobalCommandOptions
*internal.EnvFlag
}

const defaultDeployTimeoutSeconds = 1200

func (d *DeployFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandOptions) {
d.BindNonCommon(local, global)
d.bindCommon(local, global)
Expand All @@ -62,6 +68,7 @@ func (d *DeployFlags) BindNonCommon(
func (d *DeployFlags) bindCommon(local *pflag.FlagSet, global *internal.GlobalCommandOptions) {
d.EnvFlag = &internal.EnvFlag{}
d.EnvFlag.Bind(local, global)
d.flagSet = local

local.BoolVar(
&d.All,
Expand All @@ -76,6 +83,16 @@ func (d *DeployFlags) bindCommon(local *pflag.FlagSet, global *internal.GlobalCo
//nolint:lll
"Deploys the packaged service located at the provided path. Supports zipped file packages (file path) or container images (image tag).",
)
local.IntVar(
&d.Timeout,
"timeout",
defaultDeployTimeoutSeconds,
fmt.Sprintf(
"Maximum time in seconds for azd to wait for each service deployment. This stops azd from waiting "+
"but does not cancel the Azure-side deployment. (default: %d)",
defaultDeployTimeoutSeconds,
),
)
}

func (d *DeployFlags) SetCommon(envFlag *internal.EnvFlag) {
Expand All @@ -91,11 +108,21 @@ func NewDeployFlags(cmd *cobra.Command, global *internal.GlobalCommandOptions) *

func NewDeployFlagsFromEnvAndOptions(envFlag *internal.EnvFlag, global *internal.GlobalCommandOptions) *DeployFlags {
return &DeployFlags{
Timeout: defaultDeployTimeoutSeconds,
EnvFlag: envFlag,
global: global,
}
}

func (d *DeployFlags) timeoutChanged() bool {
if d.flagSet == nil {
return false
}

timeoutFlag := d.flagSet.Lookup("timeout")
return timeoutFlag != nil && timeoutFlag.Changed
}

func NewDeployCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "deploy <service>",
Expand Down Expand Up @@ -241,6 +268,10 @@ func (da *DeployAction) Run(ctx context.Context) (*actions.ActionResult, error)
}

deployResults := map[string]*project.ServiceDeployResult{}
deployTimeout, err := da.resolveDeployTimeout()
if err != nil {
return nil, err
}

err = da.projectConfig.Invoke(ctx, project.ProjectEventDeploy, projectEventArgs, func() error {
for _, svc := range stableServices {
Expand Down Expand Up @@ -303,18 +334,45 @@ func (da *DeployAction) Run(ctx context.Context) (*actions.ActionResult, error)
return err
}

deployCtx, deployCancel := context.WithTimeout(ctx, deployTimeout)
defer deployCancel()

deployResult, err := async.RunWithProgress(
func(deployProgress project.ServiceProgress) {
progressMessage := fmt.Sprintf("Deploying service %s (%s)", svc.Name, deployProgress.Message)
da.console.ShowSpinner(ctx, progressMessage, input.Step)
},
func(progress *async.Progress[project.ServiceProgress]) (*project.ServiceDeployResult, error) {
return da.serviceManager.Deploy(ctx, svc, serviceContext, progress)
return da.serviceManager.Deploy(deployCtx, svc, serviceContext, progress)
},
)

if err != nil {
da.console.StopSpinner(ctx, stepMessage, input.StepFailed)
if deployCtx.Err() == context.DeadlineExceeded {
warnMsg := fmt.Sprintf(
"Deployment of service '%s' exceeded the azd wait timeout."+
" azd has stopped waiting, but the deployment may"+
" still be running in Azure.",
svc.Name,
)
da.console.MessageUxItem(ctx, &ux.WarningMessage{
Description: warnMsg,
Hints: []string{
"Check the Azure Portal for current deployment status.",
"Increase timeout with --timeout flag or AZD_DEPLOY_TIMEOUT env var.",
},
})

return fmt.Errorf(
"deployment of service '%s' timed out after %d seconds. To increase, use --timeout flag "+
"or AZD_DEPLOY_TIMEOUT env var. Note: azd has stopped "+
"waiting, but the deployment may still be running in Azure. Check the Azure Portal for "+
"current deployment status.",
svc.Name,
int(deployTimeout.Seconds()),
)
}
return err
}

Expand Down Expand Up @@ -379,6 +437,29 @@ func (da *DeployAction) Run(ctx context.Context) (*actions.ActionResult, error)
}, nil
}

func (da *DeployAction) resolveDeployTimeout() (time.Duration, error) {
if da.flags.timeoutChanged() {
if da.flags.Timeout <= 0 {
return 0, errors.New("invalid value for --timeout: must be greater than 0 seconds")
}

return time.Duration(da.flags.Timeout) * time.Second, nil
}

if envVal, ok := os.LookupEnv("AZD_DEPLOY_TIMEOUT"); ok {
seconds, err := strconv.Atoi(envVal)
if err != nil {
return 0, fmt.Errorf("invalid AZD_DEPLOY_TIMEOUT value '%s': must be an integer number of seconds", envVal)
}
if seconds <= 0 {
return 0, fmt.Errorf("invalid AZD_DEPLOY_TIMEOUT value '%d': must be greater than 0 seconds", seconds)
}
return time.Duration(seconds) * time.Second, nil
}

return time.Duration(defaultDeployTimeoutSeconds) * time.Second, nil
}

func GetCmdDeployHelpDescription(*cobra.Command) string {
return generateCmdHelpDescription("Deploy application to Azure.", []string{
formatHelpNote(
Expand Down
Loading
Loading