From 7f334d3acfd7bfde900e16e393662587b9ff74a1 Mon Sep 17 00:00:00 2001 From: Dennis Chen Date: Tue, 26 Jun 2018 15:39:25 +0800 Subject: [PATCH] Initial support for OCI multi-platform image Add the OCI spec compatible image support in client side. Signed-off-by: Dennis Chen --- api/server/router/build/build_routes.go | 12 +++--- api/server/router/image/image_routes.go | 12 +++--- api/types/client.go | 3 +- builder/dockerfile/builder.go | 7 ---- builder/dockerfile/copy.go | 7 ++-- builder/dockerfile/dispatchers.go | 9 +++-- builder/dockerfile/internals.go | 2 +- image/tarexport/load.go | 4 +- pkg/system/lcow.go | 53 ------------------------- 9 files changed, 28 insertions(+), 81 deletions(-) diff --git a/api/server/router/build/build_routes.go b/api/server/router/build/build_routes.go index 2d73e9b1f3..0dd2b1d8f9 100644 --- a/api/server/router/build/build_routes.go +++ b/api/server/router/build/build_routes.go @@ -14,6 +14,7 @@ import ( "strings" "sync" + "github.com/containerd/containerd/platforms" "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" @@ -23,7 +24,6 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/docker/pkg/system" "github.com/docker/go-units" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -72,11 +72,13 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui options.RemoteContext = r.FormValue("remote") if versions.GreaterThanOrEqualTo(version, "1.32") { apiPlatform := r.FormValue("platform") - p := system.ParsePlatform(apiPlatform) - if err := system.ValidatePlatform(p); err != nil { - return nil, errdefs.InvalidParameter(errors.Errorf("invalid platform: %s", err)) + if len(strings.TrimSpace(apiPlatform)) != 0 { + sp, err := platforms.Parse(apiPlatform) + if err != nil { + return nil, err + } + options.Platform = sp } - options.Platform = p.OS } if r.Form.Get("shmsize") != "" { diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index 8e32d0292e..f359df27f8 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -4,11 +4,11 @@ import ( "context" "encoding/base64" "encoding/json" - "fmt" "net/http" "strconv" "strings" + "github.com/containerd/containerd/platforms" "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" @@ -16,7 +16,6 @@ import ( "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/docker/pkg/system" "github.com/docker/docker/registry" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -45,9 +44,12 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite version := httputils.VersionFromContext(ctx) if versions.GreaterThanOrEqualTo(version, "1.32") { apiPlatform := r.FormValue("platform") - platform = system.ParsePlatform(apiPlatform) - if err = system.ValidatePlatform(platform); err != nil { - err = fmt.Errorf("invalid platform: %s", err) + if len(strings.TrimSpace(apiPlatform)) != 0 { + sp, err := platforms.Parse(apiPlatform) + if err != nil { + return err + } + platform = &sp } } diff --git a/api/types/client.go b/api/types/client.go index 3df8d23368..cc10a98a58 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -8,6 +8,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/go-units" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) // CheckpointCreateOptions holds parameters to create a checkpoint from a container @@ -180,7 +181,7 @@ type ImageBuildOptions struct { ExtraHosts []string // List of extra hosts Target string SessionID string - Platform string + Platform specs.Platform // Version specifies the version of the unerlying builder to use Version BuilderVersion // BuildID is an optional identifier that can be passed together with the diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go index d5d2de8180..b585347079 100644 --- a/builder/dockerfile/builder.go +++ b/builder/dockerfile/builder.go @@ -104,13 +104,6 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) ( source = src } - os := "" - apiPlatform := system.ParsePlatform(config.Options.Platform) - if apiPlatform.OS != "" { - os = apiPlatform.OS - } - config.Options.Platform = os - builderOptions := builderOptions{ Options: config.Options, ProgressWriter: config.ProgressWriter, diff --git a/builder/dockerfile/copy.go b/builder/dockerfile/copy.go index 43f40b62f9..17d2b5bde9 100644 --- a/builder/dockerfile/copy.go +++ b/builder/dockerfile/copy.go @@ -24,6 +24,7 @@ import ( "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/urlutil" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -72,7 +73,7 @@ type copier struct { source builder.Source pathCache pathCache download sourceDownloader - platform string + platform specs.Platform // for cleanup. TODO: having copier.cleanup() is error prone and hard to // follow. Code calling performCopy should manage the lifecycle of its params. // Copier should take override source as input, not imageMount. @@ -95,8 +96,8 @@ func (o *copier) createCopyInstruction(args []string, cmdName string) (copyInstr last := len(args) - 1 // Work in platform-specific filepath semantics - inst.dest = fromSlash(args[last], o.platform) - separator := string(separator(o.platform)) + inst.dest = fromSlash(args[last], o.platform.OS) + separator := string(separator(o.platform.OS)) infos, err := o.getCopyInfosForSourcePaths(args[0:last], inst.dest) if err != nil { return inst, errors.Wrapf(err, "%s failed", cmdName) diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go index 4a71b6765e..65d93139f1 100644 --- a/builder/dockerfile/dispatchers.go +++ b/builder/dockerfile/dispatchers.go @@ -14,6 +14,7 @@ import ( "sort" "strings" + "github.com/containerd/containerd/platforms" "github.com/docker/docker/api" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/strslice" @@ -151,9 +152,11 @@ func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error // func initializeStage(d dispatchRequest, cmd *instructions.Stage) error { d.builder.imageProber.Reset() - if err := system.ValidatePlatform(&cmd.Platform); err != nil { + //TODO(@arm64b): Leave the sanity check of the spec platform to the containerd code + if err := platforms.ValidatePlatform(&cmd.Platform); err != nil { return err } + image, err := d.getFromImage(d.shlex, cmd.BaseName, cmd.Platform.OS) if err != nil { return err @@ -223,10 +226,10 @@ func (d *dispatchRequest) getOsFromFlagsAndStage(stageOS string) string { switch { case stageOS != "": return stageOS - case d.builder.options.Platform != "": + case d.builder.options.Platform.OS != "": // Note this is API "platform", but by this point, as the daemon is not // multi-arch aware yet, it is guaranteed to only hold the OS part here. - return d.builder.options.Platform + return d.builder.options.Platform.OS default: return "" // Auto-select } diff --git a/builder/dockerfile/internals.go b/builder/dockerfile/internals.go index 88e75a2179..e23c328001 100644 --- a/builder/dockerfile/internals.go +++ b/builder/dockerfile/internals.go @@ -456,7 +456,7 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf // is too small for builder scenarios where many users are // using RUN statements to install large amounts of data. // Use 127GB as that's the default size of a VHD in Hyper-V. - if runtime.GOOS == "windows" && options.Platform == "windows" { + if runtime.GOOS == "windows" && options.Platform.OS == "windows" { hc.StorageOpt = make(map[string]string) hc.StorageOpt["size"] = "127GB" } diff --git a/image/tarexport/load.go b/image/tarexport/load.go index c89dd08f93..0416e28b81 100644 --- a/image/tarexport/load.go +++ b/image/tarexport/load.go @@ -422,8 +422,6 @@ func checkCompatibleOS(imageOS string) error { return fmt.Errorf("cannot load %s image on %s", imageOS, runtime.GOOS) } // Finally, check the image OS is supported for the platform. - if err := system.ValidatePlatform(system.ParsePlatform(imageOS)); err != nil { - return fmt.Errorf("cannot load %s image on %s: %s", imageOS, runtime.GOOS, err) - } + // TODO(@arm64b): Leave this sanity check to the containerd code in the future return nil } diff --git a/pkg/system/lcow.go b/pkg/system/lcow.go index dc8ef9f96d..786811df74 100644 --- a/pkg/system/lcow.go +++ b/pkg/system/lcow.go @@ -1,62 +1,9 @@ package system // import "github.com/docker/docker/pkg/system" import ( - "fmt" "runtime" - "strings" - - specs "github.com/opencontainers/image-spec/specs-go/v1" ) -// ValidatePlatform determines if a platform structure is valid. -// TODO This is a temporary function - can be replaced by parsing from -// https://github.com/containerd/containerd/pull/1403/files at a later date. -// @jhowardmsft -func ValidatePlatform(platform *specs.Platform) error { - platform.Architecture = strings.ToLower(platform.Architecture) - platform.OS = strings.ToLower(platform.OS) - // Based on https://github.com/moby/moby/pull/34642#issuecomment-330375350, do - // not support anything except operating system. - if platform.Architecture != "" { - return fmt.Errorf("invalid platform architecture %q", platform.Architecture) - } - if platform.OS != "" { - if !(platform.OS == runtime.GOOS || (LCOWSupported() && platform.OS == "linux")) { - return fmt.Errorf("invalid platform os %q", platform.OS) - } - } - if len(platform.OSFeatures) != 0 { - return fmt.Errorf("invalid platform osfeatures %q", platform.OSFeatures) - } - if platform.OSVersion != "" { - return fmt.Errorf("invalid platform osversion %q", platform.OSVersion) - } - if platform.Variant != "" { - return fmt.Errorf("invalid platform variant %q", platform.Variant) - } - return nil -} - -// ParsePlatform parses a platform string in the format os[/arch[/variant] -// into an OCI image-spec platform structure. -// TODO This is a temporary function - can be replaced by parsing from -// https://github.com/containerd/containerd/pull/1403/files at a later date. -// @jhowardmsft -func ParsePlatform(in string) *specs.Platform { - p := &specs.Platform{} - elements := strings.SplitN(strings.ToLower(in), "/", 3) - if len(elements) == 3 { - p.Variant = elements[2] - } - if len(elements) >= 2 { - p.Architecture = elements[1] - } - if len(elements) >= 1 { - p.OS = elements[0] - } - return p -} - // IsOSSupported determines if an operating system is supported by the host func IsOSSupported(os string) bool { if strings.EqualFold(runtime.GOOS, os) {