diff --git a/pkg/driver/docker/build.go b/pkg/driver/docker/build.go index 3042813bb..91c6d95e4 100644 --- a/pkg/driver/docker/build.go +++ b/pkg/driver/docker/build.go @@ -272,6 +272,12 @@ type buildOrchestrator struct { } func (o *buildOrchestrator) selectStrategy(options provider.BuildOptions) buildStrategy { + // Podman doesn't support the Docker BuildKit session API, so force CLI build. + if o.driver.Docker.IsPodman() { + log.Debugf("podman detected, forcing docker buildx strategy") + return &dockerBuildxStrategy{driver: o.driver} + } + builder := o.driver.Docker.Builder // Select docker buildx if configured and not forcing internal buildkit diff --git a/pkg/driver/docker/build_test.go b/pkg/driver/docker/build_test.go new file mode 100644 index 000000000..896b9e728 --- /dev/null +++ b/pkg/driver/docker/build_test.go @@ -0,0 +1,111 @@ +package docker + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/devsy-org/devsy/pkg/docker" + "github.com/devsy-org/devsy/pkg/provider" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func writeHelperScript(t *testing.T, dir, name, output string) string { + t.Helper() + path := filepath.Join(dir, name) + content := "#!/bin/sh\necho '" + output + "'\n" + //nolint:gosec // test helper script needs exec bit + require.NoError(t, os.WriteFile(path, []byte(content), 0o755)) + return path +} + +func TestSelectStrategy_DockerRuntime_AllowsBuildKit(t *testing.T) { + tmp := t.TempDir() + bin := writeHelperScript(t, tmp, "docker-fake", "Docker version 24.0.7, build afdd53b") + + helper := &docker.DockerHelper{ + DockerCommand: bin, + Builder: docker.DockerBuilderBuildKit, + } + d := &dockerDriver{Docker: helper} + o := &buildOrchestrator{driver: d} + + strategy := o.selectStrategy(provider.BuildOptions{}) + + assert.IsType(t, &buildkitStrategy{}, strategy) + assert.Equal(t, "internal buildkit", strategy.name()) +} + +func TestSelectStrategy_PodmanRuntime_ForcesCLIBuild(t *testing.T) { + tmp := t.TempDir() + bin := writeHelperScript(t, tmp, "podman-fake", "podman version 4.9.3") + + helper := &docker.DockerHelper{ + DockerCommand: bin, + Builder: docker.DockerBuilderBuildKit, + } + d := &dockerDriver{Docker: helper} + o := &buildOrchestrator{driver: d} + + strategy := o.selectStrategy(provider.BuildOptions{ + CLIOptions: provider.CLIOptions{ForceInternalBuildKit: true}, + }) + + assert.IsType(t, &dockerBuildxStrategy{}, strategy) + assert.Equal(t, "docker buildx build", strategy.name()) +} + +func TestSelectStrategy_PodmanRuntime_IgnoresBuilderConfig(t *testing.T) { + tmp := t.TempDir() + bin := writeHelperScript(t, tmp, "podman-fake", "podman version 4.9.3") + + builders := []docker.DockerBuilder{ + docker.DockerBuilderDefault, + docker.DockerBuilderBuildX, + docker.DockerBuilderBuildKit, + } + + for _, builder := range builders { + t.Run(builder.String(), func(t *testing.T) { + helper := &docker.DockerHelper{ + DockerCommand: bin, + Builder: builder, + } + d := &dockerDriver{Docker: helper} + o := &buildOrchestrator{driver: d} + + strategy := o.selectStrategy(provider.BuildOptions{}) + + assert.IsType(t, &dockerBuildxStrategy{}, strategy, + "Podman should always use CLI build regardless of builder config %q", builder) + }) + } +} + +func TestSelectStrategy_DockerRuntime_BuildxWhenAvailable(t *testing.T) { + if _, err := exec.LookPath("docker"); err != nil { + t.Skip("docker not available") + } + + helper := &docker.DockerHelper{ + DockerCommand: "docker", + Builder: docker.DockerBuilderDefault, + } + d := &dockerDriver{Docker: helper} + o := &buildOrchestrator{driver: d} + + strategy := o.selectStrategy(provider.BuildOptions{}) + + // With real Docker, buildx is typically available, so expect buildx strategy. + // If buildx isn't installed, it falls back to buildkit — both are valid for Docker. + switch strategy.(type) { + case *dockerBuildxStrategy: + // Docker with buildx available + case *buildkitStrategy: + // Docker without buildx — still valid, not Podman + default: + t.Fatalf("unexpected strategy type: %T", strategy) + } +}