Skip to content
Open
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
81 changes: 79 additions & 2 deletions cli/azd/pkg/project/project_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import (
"errors"
"fmt"
"os"
"slices"
"strings"

"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/internal/tracing"
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
Expand Down Expand Up @@ -205,6 +208,12 @@ func (pm *projectManager) EnsureFrameworkTools(
return nil
}

// svcToolInfo tracks whether a service's target required Docker.
type svcToolInfo struct {
svc *ServiceConfig
needsDocker bool
}

func (pm *projectManager) EnsureServiceTargetTools(
ctx context.Context,
projectConfig *ProjectConfig,
Expand All @@ -217,6 +226,8 @@ func (pm *projectManager) EnsureServiceTargetTools(
return err
}

var svcTools []svcToolInfo

for _, svc := range servicesStable {
if serviceFilterFn != nil && !serviceFilterFn(svc) {
continue
Expand All @@ -227,17 +238,83 @@ func (pm *projectManager) EnsureServiceTargetTools(
return fmt.Errorf("getting service target: %w", err)
}

serviceTargetTools := serviceTarget.RequiredExternalTools(ctx, svc)
requiredTools = append(requiredTools, serviceTargetTools...)
targetTools := serviceTarget.RequiredExternalTools(ctx, svc)
requiredTools = append(requiredTools, targetTools...)

needsDocker := false
for _, tool := range targetTools {
if tool.Name() == "Docker" {
needsDocker = true
break
}
}
svcTools = append(svcTools, svcToolInfo{svc: svc, needsDocker: needsDocker})
}

if err := tools.EnsureInstalled(ctx, tools.Unique(requiredTools)...); err != nil {
if toolErr, ok := errors.AsType[*tools.MissingToolErrors](err); ok {
if suggestion := suggestRemoteBuild(svcTools, toolErr); suggestion != nil {
return suggestion
}
}
return err
}

return nil
}

// suggestRemoteBuild checks if Docker is in the missing tools list and whether any
// services that required it could use remote builds instead. Only services whose
// service target actually listed Docker as required are included in the suggestion.
func suggestRemoteBuild(
svcTools []svcToolInfo,
toolErr *tools.MissingToolErrors,
) *internal.ErrorWithSuggestion {
if !slices.Contains(toolErr.ToolNames, "Docker") {
return nil
}

// Find services that actually required Docker (per their service target)
// and could use remoteBuild instead.
var remoteBuildCapable []string
for _, info := range svcTools {
if !info.needsDocker {
continue
}
remoteBuildCapable = append(remoteBuildCapable, info.svc.Name)
}

if len(remoteBuildCapable) == 0 {
return nil
}

// Check whether the container runtime is not installed or just not running
errMsg := toolErr.Error()
isNotRunning := strings.Contains(errMsg, "is not running")

serviceList := strings.Join(remoteBuildCapable, ", ")
var suggestion string
if isNotRunning {
suggestion = fmt.Sprintf(
"Services [%s] can build on Azure instead of locally.\n"+
"Set 'remoteBuild: true' under the 'docker:' section for each service in azure.yaml,\n"+
"or start your container runtime (Docker/Podman).",
serviceList)
} else {
suggestion = fmt.Sprintf(
"Services [%s] can build on Azure instead of locally.\n"+
"Set 'remoteBuild: true' under the 'docker:' section for each service in azure.yaml,\n"+
"or install Docker (https://aka.ms/azure-dev/docker-install)\n"+
"or Podman (https://aka.ms/azure-dev/podman-install).",
serviceList)
}

return &internal.ErrorWithSuggestion{
Err: toolErr,
Suggestion: suggestion,
}
}

func (pm *projectManager) EnsureRestoreTools(
ctx context.Context,
projectConfig *ProjectConfig,
Expand Down
122 changes: 122 additions & 0 deletions cli/azd/pkg/project/project_manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package project

import (
"fmt"
"testing"

"github.com/azure/azure-dev/cli/azd/pkg/tools"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_suggestRemoteBuild(t *testing.T) {
dockerMissing := &tools.MissingToolErrors{
ToolNames: []string{"Docker"},
Errs: []error{fmt.Errorf("neither docker nor podman is installed")},
}
dockerNotRunning := &tools.MissingToolErrors{
ToolNames: []string{"Docker"},
Errs: []error{fmt.Errorf("the Docker service is not running, please start it")},
}
bicepMissing := &tools.MissingToolErrors{
ToolNames: []string{"bicep"},
Errs: []error{assert.AnError},
}

tests := []struct {
name string
svcTools []svcToolInfo
toolErr *tools.MissingToolErrors
wantSuggestion bool
wantContains string
}{
{
name: "Service_needing_Docker_suggests",
svcTools: []svcToolInfo{
{svc: &ServiceConfig{Name: "api"}, needsDocker: true},
},
toolErr: dockerMissing,
wantSuggestion: true,
wantContains: "api",
},
{
name: "Multiple_services_lists_all",
svcTools: []svcToolInfo{
{svc: &ServiceConfig{Name: "api"}, needsDocker: true},
{svc: &ServiceConfig{Name: "web"}, needsDocker: true},
},
toolErr: dockerMissing,
wantSuggestion: true,
wantContains: "api, web",
},
{
name: "Service_not_needing_Docker_no_suggestion",
svcTools: []svcToolInfo{
{svc: &ServiceConfig{Name: "api"}, needsDocker: false},
},
toolErr: dockerMissing,
wantSuggestion: false,
},
{
name: "Non_Docker_tool_missing_no_suggestion",
svcTools: []svcToolInfo{
{svc: &ServiceConfig{Name: "api"}, needsDocker: true},
},
toolErr: bicepMissing,
wantSuggestion: false,
},
{
name: "Mixed_services_only_Docker_ones",
svcTools: []svcToolInfo{
{svc: &ServiceConfig{Name: "api"}, needsDocker: true},
{svc: &ServiceConfig{Name: "web"}, needsDocker: false},
{svc: &ServiceConfig{Name: "worker"}, needsDocker: true},
},
toolErr: dockerMissing,
wantSuggestion: true,
wantContains: "api, worker",
},
{
name: "Docker_not_running_suggests_start",
svcTools: []svcToolInfo{
{svc: &ServiceConfig{Name: "api"}, needsDocker: true},
},
toolErr: dockerNotRunning,
wantSuggestion: true,
wantContains: "start your container runtime",
},
{
name: "Docker_not_installed_suggests_install",
svcTools: []svcToolInfo{
{svc: &ServiceConfig{Name: "api"}, needsDocker: true},
},
toolErr: dockerMissing,
wantSuggestion: true,
wantContains: "install Docker",
},
{
name: "Empty_services_no_suggestion",
svcTools: []svcToolInfo{},
toolErr: dockerMissing,
wantSuggestion: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := suggestRemoteBuild(tt.svcTools, tt.toolErr)

if !tt.wantSuggestion {
assert.Nil(t, result)
return
}

require.NotNil(t, result)
assert.Contains(t, result.Suggestion, tt.wantContains)
assert.Contains(t, result.Suggestion, "remoteBuild")
})
}
}
28 changes: 28 additions & 0 deletions cli/azd/resources/error_suggestions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,34 @@ rules:
- url: "https://learn.microsoft.com/azure/developer/azure-developer-cli/reference#azd-auth-login"
title: "azd auth login reference"

# ============================================================================
# Tool Missing Errors — Docker / Container Runtime
# ============================================================================

- errorType: "MissingToolErrors"
patterns:
- "is not running"
message: "The container runtime (Docker/Podman) is not running."
suggestion: >-
Start your container runtime, or build on Azure instead by setting
'remoteBuild: true' under the 'docker:' section for each service in azure.yaml.

- errorType: "MissingToolErrors"
patterns:
- "Docker"
message: "No container runtime (Docker/Podman) is installed."
suggestion: >-
If your services use Container Apps or AKS, you can build on Azure instead
of locally. Set 'remoteBuild: true' under the 'docker:' section for each
service in azure.yaml. Otherwise, install Docker or Podman.
links:
- url: "https://aka.ms/azure-dev/docker-install"
title: "Install Docker"
- url: "https://aka.ms/azure-dev/podman-install"
title: "Install Podman"
- url: "https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-schema"
title: "azure.yaml schema reference"

# ============================================================================
# Text Pattern Rules — Specific patterns first
# These are fallbacks for errors without typed Go structs.
Expand Down
Loading