Skip to content
Merged
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
52 changes: 49 additions & 3 deletions buildtools/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,52 @@ 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 = dockerIsPodman

// resolveContainerManagerType returns the container manager to use when running 'jf docker' subcommands.
//
// 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
}
return containerutils.DockerClient
}
Comment thread
fluxxBot marked this conversation as resolved.

// 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 dockerIsPodman() bool {
cmd := exec.Command("docker", "version")
Comment thread
bhanurp marked this conversation as resolved.
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
Expand Down Expand Up @@ -1109,7 +1155,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 {
Expand All @@ -1133,7 +1179,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 {
Expand Down Expand Up @@ -1456,7 +1502,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)
}

Expand Down
49 changes: 49 additions & 0 deletions buildtools/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package buildtools

import (
"errors"
"os"
"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"
Expand Down Expand Up @@ -302,3 +304,50 @@ func overrideCommandAction(commands []cli.Command, name string, action cli.Actio
}
return newCommands
}

// 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: "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) {
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 = originalDetector }()

assert.Equal(t, tc.want, resolveContainerManagerType())
})
}
}
Loading