From 8ec3080e14b86d6bd9e7b332f958aa7521876a6e Mon Sep 17 00:00:00 2001 From: Jacques Nadeau Date: Wed, 16 Jul 2025 11:34:45 -1000 Subject: [PATCH] Work around Docker Desktop's macOS socket location change Docker Desktop 4.13.0+ moved the socket from /var/run/docker.sock to ~/.docker/run/docker.sock on macOS, but the Docker client library still defaults to the old location for all Unix systems. Add platform-specific logic to use the correct default on macOS when --daemon-host is not specified. This makes skopeo work more reliable out of the box for new users. Signed-off-by: Jacques Nadeau --- cmd/skopeo/utils.go | 23 ++++++++++++++- cmd/skopeo/utils_darwin_test.go | 33 ++++++++++++++++++++++ cmd/skopeo/utils_test.go | 50 ++++++++++++++++++++++----------- 3 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 cmd/skopeo/utils_darwin_test.go diff --git a/cmd/skopeo/utils.go b/cmd/skopeo/utils.go index 3f40f5ee..f383725b 100644 --- a/cmd/skopeo/utils.go +++ b/cmd/skopeo/utils.go @@ -6,6 +6,8 @@ import ( "fmt" "io" "os" + "path/filepath" + "runtime" "strings" "time" @@ -32,7 +34,20 @@ import ( "golang.org/x/term" ) -// errorShouldDisplayUsage is a subtype of error used by command handlers to indicate that the command’s help should be included. +// getDefaultDockerSocket returns the platform-specific default Docker socket path +func getDefaultDockerSocket() string { + if runtime.GOOS == "darwin" { + // On macOS, use the user-specific Docker socket location + if home := os.Getenv("HOME"); home != "" { + return "unix://" + filepath.Join(home, ".docker/run/docker.sock") + } + } + // For all other platforms or if HOME is not set, return empty string + // to use the default from the Docker client library + return "" +} + +// errorShouldDisplayUsage is a subtype of error used by command handlers to indicate that the command's help should be included. type errorShouldDisplayUsage struct { error } @@ -204,6 +219,12 @@ func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) { ctx.OCISharedBlobDirPath = opts.sharedBlobDir ctx.AuthFilePath = opts.shared.authFilePath ctx.DockerDaemonHost = opts.dockerDaemonHost + // Set platform-specific default for Docker daemon host if not explicitly set + if ctx.DockerDaemonHost == "" { + if defaultSocket := getDefaultDockerSocket(); defaultSocket != "" { + ctx.DockerDaemonHost = defaultSocket + } + } ctx.DockerDaemonCertPath = opts.dockerCertPath if opts.authFilePath.Present() { ctx.AuthFilePath = opts.authFilePath.Value() diff --git a/cmd/skopeo/utils_darwin_test.go b/cmd/skopeo/utils_darwin_test.go new file mode 100644 index 00000000..46c78faf --- /dev/null +++ b/cmd/skopeo/utils_darwin_test.go @@ -0,0 +1,33 @@ +//go:build darwin + +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestGetDefaultDockerSocketDarwin(t *testing.T) { + // Save original HOME + originalHome := os.Getenv("HOME") + defer func() { + os.Setenv("HOME", originalHome) + }() + + // Test with HOME set + testHome := "/Users/test" + os.Setenv("HOME", testHome) + expected := "unix://" + filepath.Join(testHome, ".docker/run/docker.sock") + result := getDefaultDockerSocket() + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } + + // Test with HOME unset + os.Unsetenv("HOME") + result = getDefaultDockerSocket() + if result != "" { + t.Errorf("Expected empty string when HOME is unset, got %s", result) + } +} diff --git a/cmd/skopeo/utils_test.go b/cmd/skopeo/utils_test.go index bcfa5497..e03efd3f 100644 --- a/cmd/skopeo/utils_test.go +++ b/cmd/skopeo/utils_test.go @@ -4,6 +4,8 @@ import ( "bytes" "errors" "os" + "path/filepath" + "runtime" "testing" "github.com/containers/image/v5/copy" @@ -16,6 +18,21 @@ import ( "github.com/stretchr/testify/require" ) +// newExpectedSystemContext creates a SystemContext with expected defaults for tests, +// accounting for platform-specific behavior like macOS Docker socket location +func newExpectedSystemContext() *types.SystemContext { + ctx := &types.SystemContext{ + DockerRegistryUserAgent: defaultUserAgent, + } + // On macOS, DockerDaemonHost gets set to the platform-specific default + if runtime.GOOS == "darwin" { + if home := os.Getenv("HOME"); home != "" { + ctx.DockerDaemonHost = "unix://" + filepath.Join(home, ".docker/run/docker.sock") + } + } + return ctx +} + func TestNoteCloseFailure(t *testing.T) { const description = "description" @@ -73,9 +90,7 @@ func TestImageOptionsNewSystemContext(t *testing.T) { opts := fakeImageOptions(t, "dest-", true, []string{}, []string{}) res, err := opts.newSystemContext() require.NoError(t, err) - assert.Equal(t, &types.SystemContext{ - DockerRegistryUserAgent: defaultUserAgent, - }, res) + assert.Equal(t, newExpectedSystemContext(), res) // Set everything to non-default values. opts = fakeImageOptions(t, "dest-", true, []string{ @@ -148,9 +163,7 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) { opts := fakeImageDestOptions(t, "dest-", true, []string{}, []string{}) res, err := opts.newSystemContext() require.NoError(t, err) - assert.Equal(t, &types.SystemContext{ - DockerRegistryUserAgent: defaultUserAgent, - }, res) + assert.Equal(t, newExpectedSystemContext(), res) authFile := "/tmp/auth.json" // Make sure when REGISTRY_AUTH_FILE is set the auth file is used @@ -162,10 +175,9 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) { }) res, err = opts.newSystemContext() require.NoError(t, err) - assert.Equal(t, &types.SystemContext{ - AuthFilePath: authFile, - DockerRegistryUserAgent: defaultUserAgent, - }, res) + expected := newExpectedSystemContext() + expected.AuthFilePath = authFile + assert.Equal(t, expected, res) // Set everything to non-default values. opts = fakeImageDestOptions(t, "dest-", true, []string{ @@ -255,10 +267,9 @@ func TestImageOptionsUsernamePassword(t *testing.T) { assert.Error(t, err) } else { require.NoError(t, err) - assert.Equal(t, &types.SystemContext{ - DockerRegistryUserAgent: defaultUserAgent, - DockerAuthConfig: command.expectedAuthConfig, - }, res) + expected := newExpectedSystemContext() + expected.DockerAuthConfig = command.expectedAuthConfig + assert.Equal(t, expected, res) } } } @@ -508,9 +519,16 @@ func TestImageOptionsAuthfileOverride(t *testing.T) { res, err := opts.newSystemContext() require.NoError(t, err) - assert.Equal(t, &types.SystemContext{ + expected := &types.SystemContext{ AuthFilePath: testCase.expectedAuthfilePath, DockerRegistryUserAgent: defaultUserAgent, - }, res) + } + // On macOS, DockerDaemonHost gets set to the platform-specific default + if runtime.GOOS == "darwin" { + if home := os.Getenv("HOME"); home != "" { + expected.DockerDaemonHost = "unix://" + filepath.Join(home, ".docker/run/docker.sock") + } + } + assert.Equal(t, expected, res) } }