From 1f8289752bf23a59c15f41a98068a508afaff936 Mon Sep 17 00:00:00 2001 From: Kanishk Date: Fri, 17 Apr 2026 15:41:48 +0530 Subject: [PATCH 1/4] RTECO-1049 - switch between podman and docker --- buildtools/cli.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/buildtools/cli.go b/buildtools/cli.go index 60058bc2d..b281c7109 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -1044,6 +1044,17 @@ func goCmdVerification(c *cli.Context) (string, error) { return configFilePath, nil } +// resolveContainerManagerType returns the container manager to use when running 'jf docker' subcommands. +// It defaults to Docker but can be overridden to Podman via the JFROG_CLI_CONTAINER_MANAGER env var. +// This allows users running Podman (or the podman-docker shim) to keep using 'jf docker ...' in their +// existing pipelines without switching to 'jf rt podman-*'. +func resolveContainerManagerType() containerutils.ContainerManagerType { + if strings.EqualFold(os.Getenv("JFROG_CLI_CONTAINER_MANAGER"), "podman") { + return containerutils.Podman + } + return containerutils.DockerClient +} + func dockerCmd(c *cli.Context) error { args := cliutils.ExtractCommand(c) var cmd, cmdArg string @@ -1095,7 +1106,7 @@ func pullCmd(c *cli.Context, image string) error { if err != nil { return err } - PullCommand := container.NewPullCommand(containerutils.DockerClient) + PullCommand := container.NewPullCommand(resolveContainerManagerType()) PullCommand.SetCmdParams(filteredDockerArgs).SetSkipLogin(skipLogin).SetImageTag(image).SetServerDetails(rtDetails).SetBuildConfiguration(buildConfiguration) supported, err := PullCommand.IsGetRepoSupported() if err != nil { @@ -1119,7 +1130,7 @@ func pushCmd(c *cli.Context, image string) (err error) { return } printDeploymentView := log.IsStdErrTerminal() - pushCommand := container.NewPushCommand(containerutils.DockerClient) + pushCommand := container.NewPushCommand(resolveContainerManagerType()) pushCommand.SetThreads(threads).SetDetailedSummary(detailedSummary || printDeploymentView).SetCmdParams(filteredDockerArgs).SetSkipLogin(skipLogin).SetBuildConfiguration(buildConfiguration).SetServerDetails(rtDetails).SetValidateSha(validateSha).SetImageTag(image) supported, err := pushCommand.IsGetRepoSupported() if err != nil { @@ -1442,7 +1453,7 @@ func dockerNativeCmd(c *cli.Context) error { if err != nil { return err } - cm := containerutils.NewManager(containerutils.DockerClient) + cm := containerutils.NewManager(resolveContainerManagerType()) return cm.RunNativeCmd(cleanArgs) } From 700fc0efba663bb198fcdfba9f46d747ed0ebd5f Mon Sep 17 00:00:00 2001 From: Kanishk Date: Fri, 17 Apr 2026 16:38:13 +0530 Subject: [PATCH 2/4] Enhance container manager detection in 'jf docker' commands to support Podman. Implemented a new function to check if the local 'docker' CLI is backed by Podman, allowing seamless usage for users without daemon-socket access. --- buildtools/cli.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/buildtools/cli.go b/buildtools/cli.go index b281c7109..6cdf4135d 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -1045,16 +1045,32 @@ func goCmdVerification(c *cli.Context) (string, error) { } // resolveContainerManagerType returns the container manager to use when running 'jf docker' subcommands. -// It defaults to Docker but can be overridden to Podman via the JFROG_CLI_CONTAINER_MANAGER env var. -// This allows users running Podman (or the podman-docker shim) to keep using 'jf docker ...' in their -// existing pipelines without switching to 'jf rt podman-*'. +// +// If the local 'docker' binary reports Podman in its version output (i.e. the podman-docker shim +// or native podman aliased as docker), treat it as Podman so 'jf docker ...' works transparently +// for Podman users without daemon-socket access. Otherwise default to Docker. +// +// Detection is intentionally conservative: only a positive "Podman" signal from 'docker version' +// switches behavior. Real Docker installations are unaffected. func resolveContainerManagerType() containerutils.ContainerManagerType { - if strings.EqualFold(os.Getenv("JFROG_CLI_CONTAINER_MANAGER"), "podman") { + if isPodmanBackedDockerCli() { + log.Debug("Detected Podman-backed 'docker' CLI. Routing 'jf docker' subcommands through Podman.") return containerutils.Podman } return containerutils.DockerClient } +// isPodmanBackedDockerCli returns true if the local 'docker' binary is actually Podman +// (either via the podman-docker shim or an alias). Any error or missing binary returns false. +func isPodmanBackedDockerCli() bool { + cmd := exec.Command("docker", "version") + out, err := cmd.CombinedOutput() + if err != nil { + return false + } + return strings.Contains(strings.ToLower(string(out)), "podman") +} + func dockerCmd(c *cli.Context) error { args := cliutils.ExtractCommand(c) var cmd, cmdArg string From 8426e1bde0468da9d459eb16ec1955cfcad8d623 Mon Sep 17 00:00:00 2001 From: Kanishk Date: Fri, 17 Apr 2026 17:35:27 +0530 Subject: [PATCH 3/4] added unit tests --- buildtools/cli.go | 6 +++++- buildtools/cli_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/buildtools/cli.go b/buildtools/cli.go index 6cdf4135d..85f7074fc 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -1044,6 +1044,10 @@ func goCmdVerification(c *cli.Context) (string, error) { return configFilePath, nil } +// podmanDetector is indirected through a package-level variable so tests can +// replace the real 'docker version' probe with a deterministic stub. +var podmanDetector = isPodmanBackedDockerCli + // resolveContainerManagerType returns the container manager to use when running 'jf docker' subcommands. // // If the local 'docker' binary reports Podman in its version output (i.e. the podman-docker shim @@ -1053,7 +1057,7 @@ func goCmdVerification(c *cli.Context) (string, error) { // Detection is intentionally conservative: only a positive "Podman" signal from 'docker version' // switches behavior. Real Docker installations are unaffected. func resolveContainerManagerType() containerutils.ContainerManagerType { - if isPodmanBackedDockerCli() { + if podmanDetector() { log.Debug("Detected Podman-backed 'docker' CLI. Routing 'jf docker' subcommands through Podman.") return containerutils.Podman } diff --git a/buildtools/cli_test.go b/buildtools/cli_test.go index e41a18a93..2978183cd 100644 --- a/buildtools/cli_test.go +++ b/buildtools/cli_test.go @@ -4,6 +4,7 @@ import ( "errors" "testing" + containerutils "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/ocicontainer" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" securityDocs "github.com/jfrog/jfrog-cli-security/cli/docs" "github.com/stretchr/testify/assert" @@ -302,3 +303,26 @@ func overrideCommandAction(commands []cli.Command, name string, action cli.Actio } return newCommands } + +// TestResolveContainerManagerType verifies the dispatcher routes to Podman +// only when the detector reports a Podman-backed 'docker' CLI. +func TestResolveContainerManagerType(t *testing.T) { + tests := []struct { + name string + podmanFound bool + want containerutils.ContainerManagerType + }{ + {name: "podman detected -> route to podman", podmanFound: true, want: containerutils.Podman}, + {name: "no podman detected -> default docker", podmanFound: false, want: containerutils.DockerClient}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + original := podmanDetector + podmanDetector = func() bool { return tc.podmanFound } + defer func() { podmanDetector = original }() + + assert.Equal(t, tc.want, resolveContainerManagerType()) + }) + } +} From 07e24848c050482f07c0402eb98bc51c03053d43 Mon Sep 17 00:00:00 2001 From: Kanishk Date: Fri, 17 Apr 2026 23:37:17 +0530 Subject: [PATCH 4/4] added env based routing for container managers --- buildtools/cli.go | 27 +++++++++++++++++++++------ buildtools/cli_test.go | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/buildtools/cli.go b/buildtools/cli.go index 85f7074fc..5325b53a7 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -1044,19 +1044,34 @@ func goCmdVerification(c *cli.Context) (string, error) { return configFilePath, nil } +// containerManagerEnvVar lets users force the container manager used by 'jf docker' +// subcommands, bypassing auto-detection. Accepted values (case-insensitive): "docker", "podman". +const containerManagerEnvVar = "JFROG_CLI_CONTAINER_MANAGER" + // podmanDetector is indirected through a package-level variable so tests can // replace the real 'docker version' probe with a deterministic stub. -var podmanDetector = isPodmanBackedDockerCli +var podmanDetector = dockerIsPodman // resolveContainerManagerType returns the container manager to use when running 'jf docker' subcommands. // -// If the local 'docker' binary reports Podman in its version output (i.e. the podman-docker shim -// or native podman aliased as docker), treat it as Podman so 'jf docker ...' works transparently -// for Podman users without daemon-socket access. Otherwise default to Docker. +// Resolution order: +// 1. Explicit override via the JFROG_CLI_CONTAINER_MANAGER env var ("docker" or "podman"). +// 2. Auto-detection: if the local 'docker' binary reports Podman in its version output +// (i.e. the podman-docker shim or native podman aliased as docker), treat it as Podman +// so 'jf docker ...' works transparently for Podman users without daemon-socket access. +// 3. Default: Docker. // // Detection is intentionally conservative: only a positive "Podman" signal from 'docker version' // switches behavior. Real Docker installations are unaffected. func resolveContainerManagerType() containerutils.ContainerManagerType { + switch strings.ToLower(strings.TrimSpace(os.Getenv(containerManagerEnvVar))) { + case "podman": + log.Debug(containerManagerEnvVar + "=podman. Routing 'jf docker' subcommands through Podman.") + return containerutils.Podman + case "docker": + log.Debug(containerManagerEnvVar + "=docker. Routing 'jf docker' subcommands through Docker.") + return containerutils.DockerClient + } if podmanDetector() { log.Debug("Detected Podman-backed 'docker' CLI. Routing 'jf docker' subcommands through Podman.") return containerutils.Podman @@ -1064,9 +1079,9 @@ func resolveContainerManagerType() containerutils.ContainerManagerType { return containerutils.DockerClient } -// isPodmanBackedDockerCli returns true if the local 'docker' binary is actually Podman +// dockerIsPodman returns true if the local 'docker' binary is actually Podman // (either via the podman-docker shim or an alias). Any error or missing binary returns false. -func isPodmanBackedDockerCli() bool { +func dockerIsPodman() bool { cmd := exec.Command("docker", "version") out, err := cmd.CombinedOutput() if err != nil { diff --git a/buildtools/cli_test.go b/buildtools/cli_test.go index 2978183cd..c03c912b4 100644 --- a/buildtools/cli_test.go +++ b/buildtools/cli_test.go @@ -2,6 +2,7 @@ package buildtools import ( "errors" + "os" "testing" containerutils "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/ocicontainer" @@ -304,23 +305,47 @@ func overrideCommandAction(commands []cli.Command, name string, action cli.Actio return newCommands } -// TestResolveContainerManagerType verifies the dispatcher routes to Podman -// only when the detector reports a Podman-backed 'docker' CLI. +// TestResolveContainerManagerType verifies the resolution order: +// 1. JFROG_CLI_CONTAINER_MANAGER env override (docker/podman, case-insensitive). +// 2. Auto-detection via the Podman probe. +// 3. Default to Docker. func TestResolveContainerManagerType(t *testing.T) { tests := []struct { name string + envValue string // "" means unset + envUnset bool podmanFound bool want containerutils.ContainerManagerType }{ - {name: "podman detected -> route to podman", podmanFound: true, want: containerutils.Podman}, - {name: "no podman detected -> default docker", podmanFound: false, want: containerutils.DockerClient}, + {name: "env=podman forces podman even when detector says docker", envValue: "podman", want: containerutils.Podman}, + {name: "env=docker forces docker even when detector says podman", envValue: "docker", podmanFound: true, want: containerutils.DockerClient}, + {name: "env=PODMAN is case-insensitive", envValue: "PODMAN", want: containerutils.Podman}, + {name: "env= with whitespace is trimmed", envValue: " podman ", want: containerutils.Podman}, + {name: "unknown env value falls through to detector (podman)", envValue: "rkt", podmanFound: true, want: containerutils.Podman}, + {name: "unknown env value falls through to detector (docker)", envValue: "rkt", podmanFound: false, want: containerutils.DockerClient}, + {name: "unset env + detector finds podman -> podman", envUnset: true, podmanFound: true, want: containerutils.Podman}, + {name: "unset env + detector finds docker -> docker", envUnset: true, podmanFound: false, want: containerutils.DockerClient}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - original := podmanDetector + originalEnv, envWasSet := os.LookupEnv(containerManagerEnvVar) + if tc.envUnset { + require.NoError(t, os.Unsetenv(containerManagerEnvVar)) + } else { + require.NoError(t, os.Setenv(containerManagerEnvVar, tc.envValue)) + } + defer func() { + if envWasSet { + _ = os.Setenv(containerManagerEnvVar, originalEnv) + } else { + _ = os.Unsetenv(containerManagerEnvVar) + } + }() + + originalDetector := podmanDetector podmanDetector = func() bool { return tc.podmanFound } - defer func() { podmanDetector = original }() + defer func() { podmanDetector = originalDetector }() assert.Equal(t, tc.want, resolveContainerManagerType()) })