Skip to content

Commit 219d3fe

Browse files
authored
Merge pull request #5858 from stevvooe/sjd/include-docker-socket
run: flag to include the docker socket
2 parents 1adc158 + 1a502e9 commit 219d3fe

6 files changed

Lines changed: 146 additions & 13 deletions

File tree

cli/command/container/create.go

Lines changed: 141 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
package container
22

33
import (
4+
"archive/tar"
5+
"bytes"
46
"context"
57
"fmt"
68
"io"
79
"net/netip"
810
"os"
11+
"path"
12+
"strings"
913

1014
"github.com/containerd/platforms"
1115
"github.com/distribution/reference"
1216
"github.com/docker/cli/cli"
1317
"github.com/docker/cli/cli/command"
1418
"github.com/docker/cli/cli/command/completion"
1519
"github.com/docker/cli/cli/command/image"
20+
"github.com/docker/cli/cli/config/configfile"
21+
"github.com/docker/cli/cli/config/types"
1622
"github.com/docker/cli/cli/internal/jsonstream"
1723
"github.com/docker/cli/cli/streams"
1824
"github.com/docker/cli/cli/trust"
1925
"github.com/docker/cli/opts"
2026
"github.com/docker/docker/api/types/container"
2127
imagetypes "github.com/docker/docker/api/types/image"
28+
"github.com/docker/docker/api/types/mount"
2229
"github.com/docker/docker/api/types/versions"
30+
"github.com/docker/docker/client"
2331
"github.com/docker/docker/errdefs"
2432
specs "github.com/opencontainers/image-spec/specs-go/v1"
2533
"github.com/pkg/errors"
@@ -35,11 +43,12 @@ const (
3543
)
3644

3745
type createOptions struct {
38-
name string
39-
platform string
40-
untrusted bool
41-
pull string // always, missing, never
42-
quiet bool
46+
name string
47+
platform string
48+
untrusted bool
49+
pull string // always, missing, never
50+
quiet bool
51+
useAPISocket bool
4352
}
4453

4554
// NewCreateCommand creates a new cobra.Command for `docker create`
@@ -70,6 +79,8 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
7079
flags.StringVar(&options.name, "name", "", "Assign a name to the container")
7180
flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before creating ("`+PullImageAlways+`", "|`+PullImageMissing+`", "`+PullImageNever+`")`)
7281
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
82+
flags.BoolVarP(&options.useAPISocket, "use-api-socket", "", false, "Bind mount Docker API socket and required auth")
83+
flags.SetAnnotation("use-api-socket", "experimentalCLI", nil) // Marks flag as experimental for now.
7384

7485
// Add an explicit help that doesn't have a `-h` to prevent the conflict
7586
// with hostname
@@ -179,20 +190,20 @@ func (cid *cidFile) Write(id string) error {
179190
return nil
180191
}
181192

182-
func newCIDFile(path string) (*cidFile, error) {
183-
if path == "" {
193+
func newCIDFile(cidPath string) (*cidFile, error) {
194+
if cidPath == "" {
184195
return &cidFile{}, nil
185196
}
186-
if _, err := os.Stat(path); err == nil {
187-
return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", path)
197+
if _, err := os.Stat(cidPath); err == nil {
198+
return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", cidPath)
188199
}
189200

190-
f, err := os.Create(path)
201+
f, err := os.Create(cidPath)
191202
if err != nil {
192203
return nil, errors.Wrap(err, "failed to create the container ID file")
193204
}
194205

195-
return &cidFile{path: path, file: f}, nil
206+
return &cidFile{path: cidPath, file: f}, nil
196207
}
197208

198209
//nolint:gocyclo
@@ -239,6 +250,73 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
239250
return nil
240251
}
241252

253+
const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
254+
var apiSocketCreds map[string]types.AuthConfig
255+
256+
if options.useAPISocket {
257+
// We'll create two new mounts to handle this flag:
258+
// 1. Mount the actual docker socket.
259+
// 2. A synthezised ~/.docker/config.json with resolved tokens.
260+
261+
socket := dockerCli.DockerEndpoint().Host
262+
if !strings.HasPrefix(socket, "unix://") {
263+
return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
264+
}
265+
socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
266+
267+
containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
268+
Type: mount.TypeBind,
269+
Source: socket,
270+
Target: "/var/run/docker.sock",
271+
BindOptions: &mount.BindOptions{},
272+
})
273+
274+
/*
275+
276+
Ideally, we'd like to copy the config into a tmpfs but unfortunately,
277+
the mounts won't be in place until we start the container. This can
278+
leave around the config if the container doesn't get deleted.
279+
280+
We are using the most compose-secret-compatible approach,
281+
which is implemented at
282+
https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
283+
284+
// Prepare a tmpfs mount for our credentials so they go away after the
285+
// container exits. We'll copy into this mount after the container is
286+
// created.
287+
containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
288+
Type: mount.TypeTmpfs,
289+
Target: "/docker/",
290+
TmpfsOptions: &mount.TmpfsOptions{
291+
SizeBytes: 1 << 20, // only need a small partition
292+
Mode: 0o600,
293+
},
294+
})
295+
*/
296+
297+
var envvarPresent bool
298+
for _, envvar := range containerCfg.Config.Env {
299+
if strings.HasPrefix(envvar, "DOCKER_CONFIG=") {
300+
envvarPresent = true
301+
}
302+
}
303+
304+
// If the DOCKER_CONFIG env var is already present, we assume the client knows
305+
// what they're doing and don't inject the creds.
306+
if !envvarPresent {
307+
// Set our special little location for the config file.
308+
containerCfg.Config.Env = append(containerCfg.Config.Env,
309+
"DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
310+
311+
// Resolve this here for later, ensuring we error our before we create the container.
312+
creds, err := dockerCli.ConfigFile().GetAllCredentials()
313+
if err != nil {
314+
return "", fmt.Errorf("resolving credentials failed: %w", err)
315+
}
316+
apiSocketCreds = creds // inject these after container creation.
317+
}
318+
}
319+
242320
var platform *specs.Platform
243321
// Engine API version 1.41 first introduced the option to specify platform on
244322
// create. It will produce an error if you try to set a platform on older API
@@ -286,11 +364,25 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
286364
if warn := localhostDNSWarning(*hostConfig); warn != "" {
287365
response.Warnings = append(response.Warnings, warn)
288366
}
367+
368+
containerID = response.ID
289369
for _, w := range response.Warnings {
290370
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
291371
}
292-
err = containerIDFile.Write(response.ID)
293-
return response.ID, err
372+
err = containerIDFile.Write(containerID)
373+
374+
if options.useAPISocket && apiSocketCreds != nil {
375+
// Create a new config file with just the auth.
376+
newConfig := &configfile.ConfigFile{
377+
AuthConfigs: apiSocketCreds,
378+
}
379+
380+
if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
381+
return "", fmt.Errorf("injecting docker config.json into container failed: %w", err)
382+
}
383+
}
384+
385+
return containerID, err
294386
}
295387

296388
// check the DNS settings passed via --dns against localhost regexp to warn if
@@ -321,3 +413,39 @@ func validatePullOpt(val string) error {
321413
)
322414
}
323415
}
416+
417+
// copyDockerConfigIntoContainer takes the client configuration and copies it
418+
// into the container.
419+
//
420+
// The path should be an absolute path in the container, commonly
421+
// /root/.docker/config.json.
422+
func copyDockerConfigIntoContainer(ctx context.Context, dockerAPI client.APIClient, containerID string, configPath string, config *configfile.ConfigFile) error {
423+
var configBuf bytes.Buffer
424+
if err := config.SaveToWriter(&configBuf); err != nil {
425+
return fmt.Errorf("saving creds: %w", err)
426+
}
427+
428+
// We don't need to get super fancy with the tar creation.
429+
var tarBuf bytes.Buffer
430+
tarWriter := tar.NewWriter(&tarBuf)
431+
tarWriter.WriteHeader(&tar.Header{
432+
Name: configPath,
433+
Size: int64(configBuf.Len()),
434+
Mode: 0o600,
435+
})
436+
437+
if _, err := io.Copy(tarWriter, &configBuf); err != nil {
438+
return fmt.Errorf("writing config to tar file for config copy: %w", err)
439+
}
440+
441+
if err := tarWriter.Close(); err != nil {
442+
return fmt.Errorf("closing tar for config copy failed: %w", err)
443+
}
444+
445+
if err := dockerAPI.CopyToContainer(ctx, containerID, "/",
446+
&tarBuf, container.CopyToContainerOptions{}); err != nil {
447+
return fmt.Errorf("copying config.json into container failed: %w", err)
448+
}
449+
450+
return nil
451+
}

cli/command/container/run.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
6060
flags.StringVar(&options.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
6161
flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before running ("`+PullImageAlways+`", "`+PullImageMissing+`", "`+PullImageNever+`")`)
6262
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
63+
flags.BoolVarP(&options.createOptions.useAPISocket, "use-api-socket", "", false, "Bind mount Docker API socket and required auth")
6364

6465
// Add an explicit help that doesn't have a `-h` to prevent the conflict
6566
// with hostname

docs/reference/commandline/container_create.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Create a new container
104104
| `--tmpfs` | `list` | | Mount a tmpfs directory |
105105
| `-t`, `--tty` | `bool` | | Allocate a pseudo-TTY |
106106
| `--ulimit` | `ulimit` | | Ulimit options |
107+
| `--use-api-socket` | `bool` | | Bind mount Docker API socket and required auth |
107108
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
108109
| `--userns` | `string` | | User namespace to use |
109110
| `--uts` | `string` | | UTS namespace to use |

docs/reference/commandline/container_run.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Create and run a new container from an image
107107
| [`--tmpfs`](#tmpfs) | `list` | | Mount a tmpfs directory |
108108
| [`-t`](#tty), [`--tty`](#tty) | `bool` | | Allocate a pseudo-TTY |
109109
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
110+
| `--use-api-socket` | `bool` | | Bind mount Docker API socket and required auth |
110111
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
111112
| [`--userns`](#userns) | `string` | | User namespace to use |
112113
| [`--uts`](#uts) | `string` | | UTS namespace to use |

docs/reference/commandline/create.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Create a new container
104104
| `--tmpfs` | `list` | | Mount a tmpfs directory |
105105
| `-t`, `--tty` | `bool` | | Allocate a pseudo-TTY |
106106
| `--ulimit` | `ulimit` | | Ulimit options |
107+
| `--use-api-socket` | `bool` | | Bind mount Docker API socket and required auth |
107108
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
108109
| `--userns` | `string` | | User namespace to use |
109110
| `--uts` | `string` | | UTS namespace to use |

docs/reference/commandline/run.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Create and run a new container from an image
107107
| `--tmpfs` | `list` | | Mount a tmpfs directory |
108108
| `-t`, `--tty` | `bool` | | Allocate a pseudo-TTY |
109109
| `--ulimit` | `ulimit` | | Ulimit options |
110+
| `--use-api-socket` | `bool` | | Bind mount Docker API socket and required auth |
110111
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
111112
| `--userns` | `string` | | User namespace to use |
112113
| `--uts` | `string` | | UTS namespace to use |

0 commit comments

Comments
 (0)