Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 0d32841

Browse files
authored
Merge pull request #479 from simonferquel/docker-desktop-hook
Rewrite Docker-Desktop contexts
2 parents 9110103 + c0ca8ee commit 0d32841

File tree

6 files changed

+311
-8
lines changed

6 files changed

+311
-8
lines changed

internal/commands/cnab.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ type bindMount struct {
3333
const defaultSocketPath string = "/var/run/docker.sock"
3434

3535
func prepareCredentialSet(contextName string, contextStore store.Store, b *bundle.Bundle, namedCredentialsets []string) (map[string]string, error) {
36+
// docker desktop contexts require some rewriting for being used within a container
37+
contextStore = dockerDesktopAwareStore{Store: contextStore}
3638
creds := map[string]string{}
3739
for _, file := range namedCredentialsets {
3840
if _, err := os.Stat(file); err != nil {
@@ -193,20 +195,22 @@ func requiredClaimBindMount(c claim.Claim, targetContextName string, dockerCli c
193195
specifiedOrchestrator = rawOrchestrator.(string)
194196
}
195197

196-
return requiredBindMount(targetContextName, specifiedOrchestrator, dockerCli)
198+
return requiredBindMount(targetContextName, specifiedOrchestrator, dockerCli.ContextStore())
197199
}
198200

199-
func requiredBindMount(targetContextName string, targetOrchestrator string, dockerCli command.Cli) (bindMount, error) {
201+
func requiredBindMount(targetContextName string, targetOrchestrator string, s store.Store) (bindMount, error) {
200202
if targetOrchestrator == "kubernetes" {
201203
return bindMount{}, nil
202204
}
203205

204-
// TODO:smarter handling of default context required
205206
if targetContextName == "" {
206-
return bindMount{true, defaultSocketPath}, nil
207+
targetContextName = "default"
207208
}
208209

209-
ctxMeta, err := dockerCli.ContextStore().GetContextMetadata(targetContextName)
210+
// in case of docker desktop, we want to rewrite the context in cases where it targets the local swarm or Kubernetes
211+
s = &dockerDesktopAwareStore{Store: s}
212+
213+
ctxMeta, err := s.GetContextMetadata(targetContextName)
210214
if err != nil {
211215
return bindMount{}, err
212216
}

internal/commands/cnab_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import (
44
"testing"
55

66
"github.com/docker/cli/cli/command"
7+
cliflags "github.com/docker/cli/cli/flags"
78
"gotest.tools/assert"
89
)
910

1011
func TestRequiresBindMount(t *testing.T) {
1112
dockerCli, err := command.NewDockerCli()
1213
assert.NilError(t, err)
14+
dockerCli.Initialize(cliflags.NewClientOptions())
1315

1416
testCases := []struct {
1517
name string
@@ -39,7 +41,7 @@ func TestRequiresBindMount(t *testing.T) {
3941

4042
for _, testCase := range testCases {
4143
t.Run(testCase.name, func(t *testing.T) {
42-
result, err := requiredBindMount(testCase.targetContextName, testCase.targetOrchestrator, dockerCli)
44+
result, err := requiredBindMount(testCase.targetContextName, testCase.targetOrchestrator, dockerCli.ContextStore())
4345
if testCase.expectedError == "" {
4446
assert.NilError(t, err)
4547
} else {

internal/commands/dockerdesktop.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"runtime"
7+
8+
"github.com/pkg/errors"
9+
10+
"github.com/docker/cli/cli/context/docker"
11+
"github.com/docker/cli/cli/context/kubernetes"
12+
"github.com/docker/cli/cli/context/store"
13+
"github.com/docker/docker/client"
14+
apiv1 "k8s.io/api/core/v1"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
17+
)
18+
19+
type dockerDesktopHostProvider func() (string, bool)
20+
21+
func defaultDockerDesktopHostProvider() (string, bool) {
22+
switch runtime.GOOS {
23+
case "windows", "darwin":
24+
default:
25+
// platforms other than windows or mac can't be Docker Desktop
26+
return "", false
27+
}
28+
return client.DefaultDockerHost, true
29+
}
30+
31+
type dockerDesktopLinuxKitIPProvider func() (string, error)
32+
33+
type dockerDesktopDockerEndpointRewriter struct {
34+
defaultHostProvider dockerDesktopHostProvider
35+
}
36+
37+
func (r *dockerDesktopDockerEndpointRewriter) rewrite(ep *docker.EndpointMeta) {
38+
defaultHost, isDockerDesktop := r.defaultHostProvider()
39+
if !isDockerDesktop {
40+
return
41+
}
42+
// on docker desktop, any context with host="" or host=<default host> should be rewritten as host="unix:///var/run/docker.sock" (docker socket path within the linuxkit VM)
43+
if ep.Host == "" || ep.Host == defaultHost {
44+
ep.Host = "unix:///var/run/docker.sock"
45+
}
46+
}
47+
48+
type dockerDesktopKubernetesEndpointRewriter struct {
49+
defaultHostProvider dockerDesktopHostProvider
50+
linuxKitIPProvider dockerDesktopLinuxKitIPProvider
51+
}
52+
53+
func (r *dockerDesktopKubernetesEndpointRewriter) rewrite(ep *kubernetes.EndpointMeta) {
54+
// any error while rewriting makes as if no rewriting rule applies
55+
if _, isDockerDesktop := r.defaultHostProvider(); !isDockerDesktop {
56+
return
57+
}
58+
// if the kube endpoint host points to localhost or 127.0.0.1, we need to rewrite it to whatever is linuxkit VM IP is (with port 6443)
59+
hostURL, err := url.Parse(ep.Host)
60+
if err != nil {
61+
return
62+
}
63+
hostName := hostURL.Hostname()
64+
switch hostName {
65+
case "localhost", "127.0.0.1":
66+
default:
67+
// we are on a context targeting a remote Kubernetes cluster, nothing to rewrite
68+
return
69+
}
70+
ip, err := r.linuxKitIPProvider()
71+
if err != nil {
72+
return
73+
}
74+
ep.Host = fmt.Sprintf("https://%s:6443", ip)
75+
}
76+
77+
func makeLinuxkitIPProvider(contextName string, s store.Store) dockerDesktopLinuxKitIPProvider {
78+
return func() (string, error) {
79+
clientCfg, err := kubernetes.ConfigFromContext(contextName, s)
80+
if err != nil {
81+
return "", err
82+
}
83+
restCfg, err := clientCfg.ClientConfig()
84+
if err != nil {
85+
return "", err
86+
}
87+
coreClient, err := v1.NewForConfig(restCfg)
88+
if err != nil {
89+
return "", err
90+
}
91+
nodes, err := coreClient.Nodes().List(metav1.ListOptions{})
92+
if err != nil {
93+
return "", err
94+
}
95+
if len(nodes.Items) == 0 {
96+
return "", errors.New("no node found")
97+
}
98+
for _, address := range nodes.Items[0].Status.Addresses {
99+
if address.Type == apiv1.NodeInternalIP {
100+
return address.Address, nil
101+
}
102+
}
103+
return "", errors.New("no ip found")
104+
}
105+
}
106+
107+
func rewriteContextIfDockerDesktop(meta *store.ContextMetadata, s store.Store) {
108+
// errors are treated as "don't rewrite"
109+
rewriter := dockerDesktopDockerEndpointRewriter{
110+
defaultHostProvider: defaultDockerDesktopHostProvider,
111+
}
112+
dockerEp, err := docker.EndpointFromContext(*meta)
113+
if err != nil {
114+
return
115+
}
116+
rewriter.rewrite(&dockerEp)
117+
meta.Endpoints[docker.DockerEndpoint] = dockerEp
118+
kubeEp := kubernetes.EndpointFromContext(*meta)
119+
if kubeEp == nil {
120+
return
121+
}
122+
kubeRewriter := dockerDesktopKubernetesEndpointRewriter{
123+
defaultHostProvider: defaultDockerDesktopHostProvider,
124+
linuxKitIPProvider: makeLinuxkitIPProvider(meta.Name, s),
125+
}
126+
kubeRewriter.rewrite(kubeEp)
127+
meta.Endpoints[kubernetes.KubernetesEndpoint] = *kubeEp
128+
}
129+
130+
type dockerDesktopAwareStore struct {
131+
store.Store
132+
}
133+
134+
func (s dockerDesktopAwareStore) ListContexts() ([]store.ContextMetadata, error) {
135+
contexts, err := s.Store.ListContexts()
136+
if err != nil {
137+
return nil, err
138+
}
139+
for ix, c := range contexts {
140+
rewriteContextIfDockerDesktop(&c, s.Store)
141+
contexts[ix] = c
142+
}
143+
return contexts, nil
144+
}
145+
146+
func (s dockerDesktopAwareStore) GetContextMetadata(name string) (store.ContextMetadata, error) {
147+
context, err := s.Store.GetContextMetadata(name)
148+
if err != nil {
149+
return store.ContextMetadata{}, err
150+
}
151+
rewriteContextIfDockerDesktop(&context, s.Store)
152+
return context, nil
153+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package commands
2+
3+
import (
4+
"testing"
5+
6+
"github.com/docker/cli/cli/context"
7+
"github.com/docker/cli/cli/context/docker"
8+
"github.com/docker/cli/cli/context/kubernetes"
9+
"github.com/pkg/errors"
10+
"gotest.tools/assert"
11+
)
12+
13+
var (
14+
noDesktopProvider = func() (string, bool) {
15+
return "", false
16+
}
17+
desktopProvider = func() (string, bool) {
18+
return "unix:///test", true
19+
}
20+
)
21+
22+
func TestDockerDesktopDockerEndpointRewriter(t *testing.T) {
23+
cases := []struct {
24+
name string
25+
hostProvider dockerDesktopHostProvider
26+
currentHost string
27+
expectedHost string
28+
}{
29+
{
30+
name: "no-desktop",
31+
hostProvider: noDesktopProvider,
32+
currentHost: "",
33+
expectedHost: "",
34+
},
35+
{
36+
name: "no-desktop-custom-host",
37+
hostProvider: noDesktopProvider,
38+
currentHost: "test",
39+
expectedHost: "test",
40+
},
41+
{
42+
name: "desktop-empty-host",
43+
hostProvider: desktopProvider,
44+
currentHost: "",
45+
expectedHost: "unix:///var/run/docker.sock",
46+
},
47+
{
48+
name: "desktop-default-host",
49+
hostProvider: desktopProvider,
50+
currentHost: "unix:///test",
51+
expectedHost: "unix:///var/run/docker.sock",
52+
},
53+
{
54+
name: "desktop-custom-host",
55+
hostProvider: desktopProvider,
56+
currentHost: "test",
57+
expectedHost: "test",
58+
},
59+
}
60+
61+
for _, c := range cases {
62+
t.Run(c.name, func(t *testing.T) {
63+
testee := dockerDesktopDockerEndpointRewriter{
64+
defaultHostProvider: c.hostProvider,
65+
}
66+
ep := docker.EndpointMeta{
67+
Host: c.currentHost,
68+
}
69+
testee.rewrite(&ep)
70+
assert.Check(t, ep.Host == c.expectedHost)
71+
})
72+
}
73+
}
74+
75+
func TestDockerDesktopKubernetesEndpointRewriter(t *testing.T) {
76+
cases := []struct {
77+
name string
78+
hostProvider dockerDesktopHostProvider
79+
ipProvider dockerDesktopLinuxKitIPProvider
80+
currentHost string
81+
expectedHost string
82+
}{
83+
{
84+
name: "no-desktop",
85+
hostProvider: noDesktopProvider,
86+
currentHost: "https://localhost:6443",
87+
expectedHost: "https://localhost:6443",
88+
},
89+
{
90+
name: "no-desktop-custom-host",
91+
hostProvider: noDesktopProvider,
92+
currentHost: "https://custom:6443",
93+
expectedHost: "https://custom:6443",
94+
},
95+
{
96+
name: "desktop-localhost",
97+
hostProvider: desktopProvider,
98+
currentHost: "https://localhost:4242",
99+
expectedHost: "https://42.42.42.42:6443",
100+
},
101+
{
102+
name: "desktop-127.0.0.01",
103+
hostProvider: desktopProvider,
104+
currentHost: "https://127.0.0.1:4242",
105+
expectedHost: "https://42.42.42.42:6443",
106+
},
107+
{
108+
name: "desktop-custom-host",
109+
hostProvider: desktopProvider,
110+
currentHost: "https://custom:6443",
111+
expectedHost: "https://custom:6443",
112+
},
113+
{
114+
name: "no-rewrite-on-error",
115+
hostProvider: desktopProvider,
116+
ipProvider: func() (string, error) {
117+
return "", errors.New("boom")
118+
},
119+
currentHost: "https://127.0.0.1:4242",
120+
expectedHost: "https://127.0.0.1:4242",
121+
},
122+
}
123+
124+
for _, c := range cases {
125+
t.Run(c.name, func(t *testing.T) {
126+
ipProvider := c.ipProvider
127+
if ipProvider == nil {
128+
ipProvider = func() (string, error) {
129+
return "42.42.42.42", nil
130+
}
131+
}
132+
testee := dockerDesktopKubernetesEndpointRewriter{
133+
defaultHostProvider: c.hostProvider,
134+
linuxKitIPProvider: ipProvider,
135+
}
136+
ep := kubernetes.EndpointMeta{
137+
EndpointMetaBase: context.EndpointMetaBase{
138+
Host: c.currentHost,
139+
},
140+
}
141+
testee.rewrite(&ep)
142+
assert.Check(t, ep.Host == c.expectedHost)
143+
})
144+
}
145+
}

internal/commands/install.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
7373
return errors.New("with-registry-auth is not supported at the moment")
7474
}
7575
targetContext := getTargetContext(opts.targetContext, dockerCli.CurrentContext())
76-
bind, err := requiredBindMount(targetContext, opts.orchestrator, dockerCli)
76+
bind, err := requiredBindMount(targetContext, opts.orchestrator, dockerCli.ContextStore())
7777
if err != nil {
7878
return err
7979
}

internal/store/store.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ func storePath(ref reference.Named) (string, error) {
3333
if err != nil {
3434
return "", err
3535
}
36-
3736
storeDir := filepath.Join(baseDir, filepath.FromSlash(name))
3837

3938
// We rely here on _ not being valid in a name meaning there can be no clashes due to nesting of repositories.

0 commit comments

Comments
 (0)