Skip to content

Commit a1e54d6

Browse files
committed
support for Docker contexts to connect to the Docker engine; use --crt-context global flag or DOCKER_CONTEXT env var
Signed-off-by: Kyle Quest <kcq.public@gmail.com>
1 parent a2a53d2 commit a1e54d6

File tree

8 files changed

+356
-3
lines changed

8 files changed

+356
-3
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ Global options:
326326
- `--log` - log file to store logs
327327
- `--host` - Docker host address or socket (prefix with `tcp://` or `unix://`)
328328
- `--crt-connection` - Container runtime connection (for non-Docker runtimes / for Docker user --host)
329+
- `--crt-context` - Container runtime context name if supported (for Docker similar to setting '--context' or DOCKER_CONTEXT)
329330
- `--tls` - use TLS connecting to Docker
330331
- `--tls-verify` - do TLS verification
331332
- `--tls-cert-path` - path to TLS cert files
@@ -999,7 +1000,9 @@ If the Docker environment variables are configured to use TLS and to verify the
9991000

10001001
`mint --tls-verify=false build my/sample-node-app-multi`
10011002

1002-
You can override all Docker connection options using these flags: `--host`, `--tls`, `--tls-verify`, `--tls-cert-path`. These flags correspond to the standard Docker options (and the environment variables). Note that you can also use the `--host` flag (similar to `DOCKER_HOST`) to point to a Unix socket (e.g., `--host=unix:///var/run/docker.sock`).
1003+
You can override all Docker connection options using these flags: `--host`, `--tls`, `--tls-verify`, `--tls-cert-path`, `--crt-context`. These flags correspond to the standard Docker options (and the environment variables). Note that you can also use the `--host` flag (similar to `DOCKER_HOST`) to point to a Unix socket (e.g., `--host=unix:///var/run/docker.sock`).
1004+
1005+
The `--crt-context` flag is currently supported with the Docker runtime and it's similar to using the `--context` flag or `DOCKER_CONTEXT`. Note that Mint will use `DOCKER_CONTEXT` if it's configured.
10031006

10041007
If you want to use TLS with verification:
10051008

pkg/app/master/command/cliflags.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
FlagTLSCertPath = "tls-cert-path"
3030
FlagHost = "host"
3131
FlagCRTConnection = "crt-connection"
32+
FlagCRTContext = "crt-context"
3233
FlagStatePath = "state-path"
3334
FlagInContainer = "in-container"
3435
FlagArchiveState = "archive-state"
@@ -58,6 +59,7 @@ const (
5859
FlagAPIVersionUsage = "Container runtime API version"
5960
FlagHostUsage = "Docker host address or socket (prefix with 'tcp://' or 'unix://')"
6061
FlagCRTConnectionUsage = "Container runtime connection (for non-Docker runtimes / for Docker user --host)"
62+
FlagCRTContextUsage = "Container runtime context name if supported (for Docker similar to setting '--context' or DOCKER_CONTEXT)"
6163
FlagStatePathUsage = "app state base path"
6264
FlagInContainerUsage = "app is running in a container"
6365
FlagArchiveStateUsage = "archive app state to the selected Docker volume (default volume - mint-state). By default, enabled when app is running in a container (disabled otherwise). Set it to 'off' to disable explicitly."
@@ -394,6 +396,12 @@ func GlobalFlags() []cli.Flag {
394396
Usage: FlagCRTConnectionUsage,
395397
EnvVars: []string{"DSLIM_CRT_CONN"},
396398
},
399+
&cli.StringFlag{
400+
Name: FlagCRTContext,
401+
Value: "",
402+
Usage: FlagCRTContextUsage,
403+
EnvVars: []string{"DSLIM_CRT_CTX"},
404+
},
397405
&cli.StringFlag{
398406
Name: FlagStatePath,
399407
Value: "",

pkg/app/master/command/clifvgetter.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,10 @@ func UpdateGlobalFlagValues(appOpts *config.AppOptions, values *GenericParams) *
392392
values.CRTConnection = *appOpts.Global.CRTConnection
393393
}
394394

395+
if appOpts.Global.CRTContext != nil {
396+
values.CRTContext = *appOpts.Global.CRTContext
397+
}
398+
395399
if appOpts.Global.UseTLS != nil {
396400
values.ClientConfig.UseTLS = *appOpts.Global.UseTLS
397401
}
@@ -429,6 +433,7 @@ func GlobalFlagValues(ctx *cli.Context) *GenericParams {
429433
StatePath: ctx.String(FlagStatePath),
430434
ReportLocation: ctx.String(FlagCommandReport),
431435
CRTConnection: ctx.String(FlagCRTConnection),
436+
CRTContext: ctx.String(FlagCRTContext),
432437
}
433438

434439
if values.ReportLocation == "off" {
@@ -444,16 +449,23 @@ func GlobalFlagValues(ctx *cli.Context) *GenericParams {
444449
}
445450

446451
func GetDockerClientConfig(ctx *cli.Context) *config.DockerClient {
452+
const op = "commands.GetDockerClientConfig"
453+
447454
config := &config.DockerClient{
455+
Context: ctx.String(FlagCRTContext),
456+
Host: ctx.String(FlagHost),
448457
APIVersion: ctx.String(FlagAPIVersion),
449458
UseTLS: ctx.Bool(FlagUseTLS),
450459
VerifyTLS: ctx.Bool(FlagVerifyTLS),
451460
TLSCertPath: ctx.String(FlagTLSCertPath),
452-
Host: ctx.String(FlagHost),
453461
Env: map[string]string{},
454462
}
455463

456464
getEnv := func(name string) {
465+
log.WithFields(log.Fields{
466+
"op": op,
467+
"var": name,
468+
}).Trace("loading")
457469
if value, exists := os.LookupEnv(name); exists {
458470
config.Env[name] = value
459471
}

pkg/app/master/command/cliprompt.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ var GlobalFlagSuggestions = []prompt.Suggest{
365365
{Text: FullFlagName(FlagTLSCertPath), Description: FlagTLSCertPathUsage},
366366
{Text: FullFlagName(FlagHost), Description: FlagHostUsage},
367367
{Text: FullFlagName(FlagCRTConnection), Description: FlagCRTConnectionUsage},
368+
{Text: FullFlagName(FlagCRTContext), Description: FlagCRTContextUsage},
368369
{Text: FullFlagName(FlagAPIVersion), Description: FlagAPIVersionUsage},
369370
{Text: FullFlagName(FlagArchiveState), Description: FlagArchiveStateUsage},
370371
{Text: FullFlagName(FlagInContainer), Description: FlagInContainerUsage},

pkg/app/master/command/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type GenericParams struct {
7272
IsDSImage bool
7373
ArchiveState string
7474
CRTConnection string
75+
CRTContext string
7576
ClientConfig *config.DockerClient
7677
}
7778

pkg/app/master/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type GlobalAppOptions struct {
4141
StatePath *string `json:"state_path,omitempty"`
4242
ReportLocation *string `json:"report_location,omitempty"`
4343
CRTConnection *string `json:"crt_connection,omitempty"`
44+
CRTContext *string `json:"crt_context,omitempty"`
4445
ArchiveState *string `json:"archive_state,omitempty"`
4546
}
4647

@@ -145,6 +146,7 @@ type DockerClient struct {
145146
TLSCertPath string
146147
Host string
147148
APIVersion string
149+
Context string
148150
Env map[string]string
149151
}
150152

pkg/crt/docker/dockerclient/client.go

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"os"
7+
"os/user"
78
"path/filepath"
89
"strings"
910

@@ -17,6 +18,8 @@ import (
1718
)
1819

1920
const (
21+
EnvDockerConfig = "DOCKER_CONFIG"
22+
EnvDockerContext = "DOCKER_CONTEXT"
2023
EnvDockerAPIVer = "DOCKER_API_VERSION"
2124
EnvDockerHost = "DOCKER_HOST"
2225
EnvDockerTLSVerify = "DOCKER_TLS_VERIFY"
@@ -31,14 +34,34 @@ var EnvVarNames = []string{
3134
EnvDockerTLSVerify,
3235
EnvDockerCertPath,
3336
EnvDockerAPIVer,
37+
EnvDockerContext,
3438
}
3539

3640
var (
3741
ErrNoDockerInfo = errors.New("no docker info")
3842
)
3943

44+
func UserHomeDir() string {
45+
output, err := os.UserHomeDir()
46+
if err != nil {
47+
log.Debugf("dockerclient.UserHomeDir: os.UserHomeDir error - %v", err)
48+
}
49+
50+
if output != "" {
51+
return output
52+
}
53+
54+
info, err := user.Current()
55+
if err == nil {
56+
return info.HomeDir
57+
}
58+
59+
log.Debugf("dockerclient.UserHomeDir: user.Current error - %v", err)
60+
return ""
61+
}
62+
4063
func UserDockerSocket() string {
41-
home, _ := os.UserHomeDir()
64+
home := UserHomeDir()
4265
return filepath.Join(home, unixUserSocketSuffix)
4366
}
4467

@@ -182,6 +205,62 @@ func New(config *config.DockerClient) (*docker.Client, error) {
182205
return docker.NewVersionedTLSClientFromBytes(host, cert, key, ca, apiVersion)
183206
}
184207

208+
//NOTE:
209+
//go-dockerclient doesn't support DOCKER_CONTEXT natively
210+
//so we need to lookup the context first to extract its connection info
211+
var currentDockerContext string
212+
if dcf, err := ReadConfigFile(ConfigFilePath()); err == nil {
213+
currentDockerContext = dcf.CurrentContext
214+
log.Debugf("dockerclient.New: currentDockerContext - '%s'", currentDockerContext)
215+
} else {
216+
log.Debugf("dockerclient.New: ReadConfigFile error - %v", err)
217+
}
218+
219+
contextName := config.Context
220+
if contextName == "" {
221+
contextName = config.Env[EnvDockerContext]
222+
}
223+
if contextName == "" &&
224+
currentDockerContext != "" &&
225+
currentDockerContext != DefaultContextName {
226+
contextName = currentDockerContext
227+
}
228+
229+
//note: don't use context host if the host parameter is specified explicitly
230+
var contextHost string
231+
var contextVerifyTLS bool
232+
if contextName != "" {
233+
log.Debugf("dockerclient.New: contextName - '%s' - loading contexts", contextName)
234+
cmList, err := ListContextsMetadata(ContextsMetaDir())
235+
if err == nil {
236+
var targetContext *DockerContextMetadata
237+
for _, cm := range cmList {
238+
if cm.Name == contextName {
239+
targetContext = cm
240+
break
241+
}
242+
}
243+
244+
if targetContext != nil {
245+
info := targetContext.Endpoint()
246+
if info != nil {
247+
contextHost = info.Host
248+
if info.SkipTLSVerify {
249+
contextVerifyTLS = false
250+
} else {
251+
contextVerifyTLS = true
252+
}
253+
} else {
254+
log.Debugf("dockerclient.New: endpoint in target context ('%s') not found - %+v", contextName, targetContext)
255+
}
256+
} else {
257+
log.Debugf("dockerclient.New: target context ('%s') not found - %+v", contextName, cmList)
258+
}
259+
} else {
260+
log.Debugf("dockerclient.New: ListContextsMetadata error - %v", err)
261+
}
262+
}
263+
185264
switch {
186265
case config.Host != "" &&
187266
config.UseTLS &&
@@ -268,6 +347,43 @@ func New(config *config.DockerClient) (*docker.Client, error) {
268347
client.SkipServerVersionCheck = true
269348
}
270349

350+
case config.Host == "" && config.Env[EnvDockerHost] == "" && contextHost != "":
351+
log.Debugf("dockerclient.New: new Docker client - from context ('%s') contextVerifyTLS=%v", contextHost, contextVerifyTLS)
352+
if strings.HasPrefix(contextHost, "/") ||
353+
strings.HasPrefix(contextHost, "unix://") {
354+
355+
socketPath := strings.TrimPrefix(contextHost, "unix://")
356+
if !HasSocket(socketPath) {
357+
log.Errorf("dockerclient.New: new Docker client - from context ('%s') - no unix socket => '%s'", contextHost, socketPath)
358+
return nil, ErrNoDockerInfo
359+
}
360+
361+
socketInfo, err := getSocketInfo(socketPath)
362+
if err != nil {
363+
return nil, err
364+
}
365+
366+
socketInfo.Address = fmt.Sprintf("unix://%s", socketPath)
367+
368+
if socketInfo.CanRead == false || socketInfo.CanWrite == false {
369+
return nil, fmt.Errorf("insufficient socket permissions (can_read=%v can_write=%v)", socketInfo.CanRead, socketInfo.CanWrite)
370+
}
371+
372+
config.Host = socketInfo.Address
373+
client, err = docker.NewVersionedClient(config.Host, config.APIVersion)
374+
if err != nil {
375+
return nil, err
376+
}
377+
378+
if config.APIVersion != "" {
379+
client.SkipServerVersionCheck = true
380+
}
381+
382+
log.Debugf("dockerclient.New: new Docker client - from context ('%s') - [7]", contextHost)
383+
} else {
384+
log.Debugf("dockerclient.New: new Docker client - from context - non-unix socket host (%s) [todo]", contextHost)
385+
}
386+
271387
case config.Host == "" && config.Env[EnvDockerHost] == "":
272388
socketInfo, err := GetUnixSocketAddr()
273389
if err != nil {

0 commit comments

Comments
 (0)