Skip to content

Commit f0dc029

Browse files
jterry75Kirtana Ashok
authored andcommitted
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]> (cherry picked from commit de3d999) Signed-off-by: Kirtana Ashok <[email protected]>
1 parent 373c2d8 commit f0dc029

5 files changed

Lines changed: 525 additions & 5 deletions

File tree

oci/spec_opts.go

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ func WithProcessArgs(args ...string) SpecOpts {
228228
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
229229
setProcess(s)
230230
s.Process.Args = args
231+
s.Process.CommandLine = ""
231232
return nil
232233
}
233234
}
@@ -357,17 +358,19 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
357358
return err
358359
}
359360
var (
360-
ociimage v1.Image
361-
config v1.ImageConfig
361+
imageConfigBytes []byte
362+
ociimage v1.Image
363+
config v1.ImageConfig
362364
)
363365
switch ic.MediaType {
364366
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
365-
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
367+
var err error
368+
imageConfigBytes, err = content.ReadBlob(ctx, image.ContentStore(), ic)
366369
if err != nil {
367370
return err
368371
}
369372

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

413476
s.Process.Cwd = config.WorkingDir
414477
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)