Skip to content

Commit 2ec2d1d

Browse files
CopilotJeffreyCA
authored andcommitted
Add error suggestion message when package fails due to containerd
1 parent 6a917d0 commit 2ec2d1d

File tree

4 files changed

+119
-0
lines changed

4 files changed

+119
-0
lines changed

cli/azd/.vscode/cspell-azd-dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ conditionalize
7777
consolesize
7878
containerapp
7979
containerapps
80+
containerd
8081
contoso
8182
createdby
8283
csharpapp

cli/azd/pkg/project/framework_service_docker.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,20 @@ func (p *dockerProject) packBuild(
559559
}
560560
}
561561

562+
// Provide better error message for containerd-related issues
563+
if strings.Contains(err.Error(), "failed to write image") && strings.Contains(err.Error(), "No such image") {
564+
isContainerdEnabled, containerdErr := p.docker.IsContainerdEnabled(ctx)
565+
if containerdErr != nil {
566+
log.Printf("warning: failed to detect containerd status: %v", containerdErr)
567+
} else if isContainerdEnabled {
568+
return nil, &internal.ErrorWithSuggestion{
569+
Err: err,
570+
Suggestion: "Suggestion: disable containerd image store: " +
571+
output.WithLinkFormat("https://docs.docker.com/desktop/features/containerd"),
572+
}
573+
}
574+
}
575+
562576
return nil, err
563577
}
564578

cli/azd/pkg/tools/docker/docker.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,19 @@ func (d *Cli) Name() string {
282282
return "Docker"
283283
}
284284

285+
// IsContainerdEnabled checks if Docker is using containerd as the image store
286+
func (d *Cli) IsContainerdEnabled(ctx context.Context) (bool, error) {
287+
result, err := d.executeCommand(ctx, "", "system", "info", "--format", "{{.DriverStatus}}")
288+
if err != nil {
289+
return false, fmt.Errorf("checking docker driver status: %w", err)
290+
}
291+
292+
driverStatus := strings.TrimSpace(result.Stdout)
293+
294+
// Check for containerd snapshotter which indicates containerd image store is enabled
295+
return strings.Contains(driverStatus, "io.containerd.snapshotter.v1"), nil
296+
}
297+
285298
func (d *Cli) executeCommand(ctx context.Context, cwd string, args ...string) (exec.RunResult, error) {
286299
runArgs := exec.NewRunArgs("docker", args...).
287300
WithCwd(cwd)

cli/azd/pkg/tools/docker/docker_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,3 +596,94 @@ func TestSplitDockerImage(t *testing.T) {
596596
})
597597
}
598598
}
599+
600+
func TestIsContainerdEnabled(t *testing.T) {
601+
tests := []struct {
602+
name string
603+
systemInfoOut string
604+
systemInfoErr error
605+
expectedEnabled bool
606+
expectError bool
607+
}{
608+
{
609+
name: "containerd enabled",
610+
systemInfoOut: ` [
611+
["driver", "overlay2"],
612+
["snapshotter", "io.containerd.snapshotter.v1.overlayfs"]
613+
]`,
614+
expectedEnabled: true,
615+
expectError: false,
616+
},
617+
{
618+
name: "containerd not enabled",
619+
systemInfoOut: ` [
620+
["driver", "overlay2"],
621+
["backing filesystem", "extfs"]
622+
]`,
623+
expectedEnabled: false,
624+
expectError: false,
625+
},
626+
{
627+
name: "empty driver status",
628+
systemInfoOut: "",
629+
expectedEnabled: false,
630+
expectError: false,
631+
},
632+
{
633+
name: "whitespace only output",
634+
systemInfoOut: " \n\t ",
635+
expectedEnabled: false,
636+
expectError: false,
637+
},
638+
{
639+
name: "containerd string present in other context",
640+
systemInfoOut: ` [
641+
["some unrelated field", "io.containerd.snapshotter.v1.something"],
642+
["driver", "overlay2"]
643+
]`,
644+
expectedEnabled: true,
645+
expectError: false,
646+
},
647+
{
648+
name: "command execution error",
649+
systemInfoErr: errors.New("docker daemon not running"),
650+
expectedEnabled: false,
651+
expectError: true,
652+
},
653+
}
654+
655+
for _, tt := range tests {
656+
t.Run(tt.name, func(t *testing.T) {
657+
mockContext := mocks.NewMockContext(context.Background())
658+
docker := NewCli(mockContext.CommandRunner)
659+
660+
mockContext.CommandRunner.When(func(args exec.RunArgs, command string) bool {
661+
return strings.Contains(command, "docker system info")
662+
}).RespondFn(func(args exec.RunArgs) (exec.RunResult, error) {
663+
require.Equal(t, "docker", args.Cmd)
664+
require.Equal(t, []string{"system", "info", "--format", "{{.DriverStatus}}"}, args.Args)
665+
666+
if tt.systemInfoErr != nil {
667+
return exec.RunResult{}, tt.systemInfoErr
668+
}
669+
670+
return exec.RunResult{
671+
Stdout: tt.systemInfoOut,
672+
Stderr: "",
673+
ExitCode: 0,
674+
}, nil
675+
})
676+
677+
enabled, err := docker.IsContainerdEnabled(context.Background())
678+
679+
if tt.expectError {
680+
require.Error(t, err)
681+
require.Contains(t, err.Error(), "checking docker driver status")
682+
} else {
683+
require.NoError(t, err)
684+
}
685+
686+
require.Equal(t, tt.expectedEnabled, enabled)
687+
})
688+
}
689+
}

0 commit comments

Comments
 (0)