@@ -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 {
0 commit comments