Skip to content

Commit de3d999

Browse files
committed
Adds support for Windows ArgsEscaped images
Adds support for Windows container images built by Docker that contain the ArgsEscaped boolean in the ImageConfig. This is a non-OCI entry that tells the runtime that the Entrypoint and/or Cmd are a single element array with the args pre-escaped into a single CommandLine that should be passed directly to Windows rather than passed as an args array which will be additionally escaped. Signed-off-by: Justin Terry <[email protected]>
1 parent 5247172 commit de3d999

File tree

5 files changed

+525
-5
lines changed

5 files changed

+525
-5
lines changed

oci/spec_opts.go

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ func WithProcessArgs(args ...string) SpecOpts {
218218
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
219219
setProcess(s)
220220
s.Process.Args = args
221+
s.Process.CommandLine = ""
221222
return nil
222223
}
223224
}
@@ -347,17 +348,19 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
347348
return err
348349
}
349350
var (
350-
ociimage v1.Image
351-
config v1.ImageConfig
351+
imageConfigBytes []byte
352+
ociimage v1.Image
353+
config v1.ImageConfig
352354
)
353355
switch ic.MediaType {
354356
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
355-
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
357+
var err error
358+
imageConfigBytes, err = content.ReadBlob(ctx, image.ContentStore(), ic)
356359
if err != nil {
357360
return err
358361
}
359362

360-
if err := json.Unmarshal(p, &ociimage); err != nil {
363+
if err := json.Unmarshal(imageConfigBytes, &ociimage); err != nil {
361364
return err
362365
}
363366
config = ociimage.Config
@@ -393,12 +396,72 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
393396
// even if there is no specified user in the image config
394397
return WithAdditionalGIDs("root")(ctx, client, c, s)
395398
} else if s.Windows != nil {
399+
// imageExtended is a superset of the oci Image struct that changes
400+
// the Config type to be imageConfigExtended in order to add the
401+
// ability to deserialize `ArgsEscaped` which is not an OCI field,
402+
// but is supported by Docker built images.
403+
type imageExtended struct {
404+
Config struct {
405+
ArgsEscaped bool `json:"ArgsEscaped,omitempty"`
406+
}
407+
}
408+
// Deserialize the extended image format for Windows.
409+
var ociImageExtended imageExtended
410+
if err := json.Unmarshal(imageConfigBytes, &ociImageExtended); err != nil {
411+
return err
412+
}
413+
argsEscaped := ociImageExtended.Config.ArgsEscaped
414+
396415
s.Process.Env = replaceOrAppendEnvValues(config.Env, s.Process.Env)
416+
417+
// To support Docker ArgsEscaped on Windows we need to combine the
418+
// image Entrypoint & (Cmd Or User Args) while taking into account
419+
// if Docker has already escaped them in the image config. When
420+
// Docker sets `ArgsEscaped==true` in the config it has pre-escaped
421+
// either Entrypoint or Cmd or both. Cmd should always be treated as
422+
// arguments appended to Entrypoint unless:
423+
//
424+
// 1. Entrypoint does not exist, in which case Cmd[0] is the
425+
// executable.
426+
//
427+
// 2. The user overrides the Cmd with User Args when activating the
428+
// container in which case those args should be appended to the
429+
// Entrypoint if it exists.
430+
//
431+
// To effectively do this we need to know if the arguments came from
432+
// the user or if the arguments came from the image config when
433+
// ArgsEscaped==true. In this case we only want to escape the
434+
// additional user args when forming the complete CommandLine. This
435+
// is safe in both cases of Entrypoint or Cmd being set because
436+
// Docker will always escape them to an array of length one. Thus in
437+
// both cases it is the "executable" portion of the command.
438+
//
439+
// In the case ArgsEscaped==false, Entrypoint or Cmd will contain
440+
// any number of entries that are all unescaped and can simply be
441+
// combined (potentially overwriting Cmd with User Args if present)
442+
// and forwarded the container start as an Args array.
397443
cmd := config.Cmd
444+
cmdFromImage := true
398445
if len(args) > 0 {
399446
cmd = args
447+
cmdFromImage = false
448+
}
449+
450+
cmd = append(config.Entrypoint, cmd...)
451+
if len(cmd) == 0 {
452+
return errors.New("no arguments specified")
453+
}
454+
455+
if argsEscaped && (len(config.Entrypoint) > 0 || cmdFromImage) {
456+
s.Process.Args = nil
457+
s.Process.CommandLine = cmd[0]
458+
if len(cmd) > 1 {
459+
s.Process.CommandLine += " " + escapeAndCombineArgs(cmd[1:])
460+
}
461+
} else {
462+
s.Process.Args = cmd
463+
s.Process.CommandLine = ""
400464
}
401-
s.Process.Args = append(config.Entrypoint, cmd...)
402465

403466
s.Process.Cwd = config.WorkingDir
404467
s.Process.User = specs.User{

oci/spec_opts_linux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,7 @@ func WithRdt(closID, l3CacheSchema, memBwSchema string) SpecOpts {
153153
return nil
154154
}
155155
}
156+
157+
func escapeAndCombineArgs(args []string) string {
158+
panic("not supported")
159+
}

oci/spec_opts_unix.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,7 @@ func WithCPUCFS(quota int64, period uint64) SpecOpts {
5757
return nil
5858
}
5959
}
60+
61+
func escapeAndCombineArgs(args []string) string {
62+
panic("not supported")
63+
}

oci/spec_opts_windows.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ package oci
1919
import (
2020
"context"
2121
"errors"
22+
"strings"
2223

2324
"github.com/containerd/containerd/containers"
25+
2426
specs "github.com/opencontainers/runtime-spec/specs-go"
27+
"golang.org/x/sys/windows"
2528
)
2629

2730
// WithWindowsCPUCount sets the `Windows.Resources.CPU.Count` section to the
@@ -89,3 +92,11 @@ func WithWindowsNetworkNamespace(ns string) SpecOpts {
8992
return nil
9093
}
9194
}
95+
96+
func escapeAndCombineArgs(args []string) string {
97+
escaped := make([]string, len(args))
98+
for i, a := range args {
99+
escaped[i] = windows.EscapeArg(a)
100+
}
101+
return strings.Join(escaped, " ")
102+
}

0 commit comments

Comments
 (0)