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