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 <jlterry@amazon.com>
This commit is contained in:
Justin Terry
2022-01-26 14:19:28 -08:00
parent 52471721fd
commit de3d9993f5
5 changed files with 525 additions and 5 deletions

View File

@@ -218,6 +218,7 @@ func WithProcessArgs(args ...string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.Args = args
s.Process.CommandLine = ""
return nil
}
}
@@ -347,17 +348,19 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
return err
}
var (
ociimage v1.Image
config v1.ImageConfig
imageConfigBytes []byte
ociimage v1.Image
config v1.ImageConfig
)
switch ic.MediaType {
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
var err error
imageConfigBytes, err = content.ReadBlob(ctx, image.ContentStore(), ic)
if err != nil {
return err
}
if err := json.Unmarshal(p, &ociimage); err != nil {
if err := json.Unmarshal(imageConfigBytes, &ociimage); err != nil {
return err
}
config = ociimage.Config
@@ -393,12 +396,72 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
// even if there is no specified user in the image config
return WithAdditionalGIDs("root")(ctx, client, c, s)
} else if s.Windows != nil {
// imageExtended is a superset of the oci Image struct that changes
// the Config type to be imageConfigExtended in order to add the
// ability to deserialize `ArgsEscaped` which is not an OCI field,
// but is supported by Docker built images.
type imageExtended struct {
Config struct {
ArgsEscaped bool `json:"ArgsEscaped,omitempty"`
}
}
// Deserialize the extended image format for Windows.
var ociImageExtended imageExtended
if err := json.Unmarshal(imageConfigBytes, &ociImageExtended); err != nil {
return err
}
argsEscaped := ociImageExtended.Config.ArgsEscaped
s.Process.Env = replaceOrAppendEnvValues(config.Env, s.Process.Env)
// To support Docker ArgsEscaped on Windows we need to combine the
// image Entrypoint & (Cmd Or User Args) while taking into account
// if Docker has already escaped them in the image config. When
// Docker sets `ArgsEscaped==true` in the config it has pre-escaped
// either Entrypoint or Cmd or both. Cmd should always be treated as
// arguments appended to Entrypoint unless:
//
// 1. Entrypoint does not exist, in which case Cmd[0] is the
// executable.
//
// 2. The user overrides the Cmd with User Args when activating the
// container in which case those args should be appended to the
// Entrypoint if it exists.
//
// To effectively do this we need to know if the arguments came from
// the user or if the arguments came from the image config when
// ArgsEscaped==true. In this case we only want to escape the
// additional user args when forming the complete CommandLine. This
// is safe in both cases of Entrypoint or Cmd being set because
// Docker will always escape them to an array of length one. Thus in
// both cases it is the "executable" portion of the command.
//
// In the case ArgsEscaped==false, Entrypoint or Cmd will contain
// any number of entries that are all unescaped and can simply be
// combined (potentially overwriting Cmd with User Args if present)
// and forwarded the container start as an Args array.
cmd := config.Cmd
cmdFromImage := true
if len(args) > 0 {
cmd = args
cmdFromImage = false
}
cmd = append(config.Entrypoint, cmd...)
if len(cmd) == 0 {
return errors.New("no arguments specified")
}
if argsEscaped && (len(config.Entrypoint) > 0 || cmdFromImage) {
s.Process.Args = nil
s.Process.CommandLine = cmd[0]
if len(cmd) > 1 {
s.Process.CommandLine += " " + escapeAndCombineArgs(cmd[1:])
}
} else {
s.Process.Args = cmd
s.Process.CommandLine = ""
}
s.Process.Args = append(config.Entrypoint, cmd...)
s.Process.Cwd = config.WorkingDir
s.Process.User = specs.User{