From ad195ec2413625445ebc65cd554728b2fa4dc807 Mon Sep 17 00:00:00 2001 From: Samuel K Date: Wed, 13 May 2026 12:30:39 -0500 Subject: [PATCH] feat(driver): guard BuildKit strategy against Podman runtime Podman doesn't support the Docker BuildKit session API. Detect Podman via DockerHelper.IsPodman() at strategy selection time and force the CLI build (docker buildx) strategy, preventing BuildKit session failures when running under Podman. --- pkg/driver/docker/build.go | 6 ++ pkg/driver/docker/build_test.go | 111 ++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 pkg/driver/docker/build_test.go 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) + } +}