Initial support for OCI multi-platform image

Add the OCI spec compatible image support in client side.

Signed-off-by: Dennis Chen <dennis.chen@arm.com>
This commit is contained in:
Dennis Chen 2018-06-26 15:39:25 +08:00 committed by Tonis Tiigi
parent 35193c0e7d
commit 7f334d3acf
9 changed files with 28 additions and 81 deletions

View file

@ -14,6 +14,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/containerd/containerd/platforms"
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/backend"
@ -23,7 +24,6 @@ import (
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/system"
"github.com/docker/go-units" "github.com/docker/go-units"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -72,11 +72,13 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
options.RemoteContext = r.FormValue("remote") options.RemoteContext = r.FormValue("remote")
if versions.GreaterThanOrEqualTo(version, "1.32") { if versions.GreaterThanOrEqualTo(version, "1.32") {
apiPlatform := r.FormValue("platform") apiPlatform := r.FormValue("platform")
p := system.ParsePlatform(apiPlatform) if len(strings.TrimSpace(apiPlatform)) != 0 {
if err := system.ValidatePlatform(p); err != nil { sp, err := platforms.Parse(apiPlatform)
return nil, errdefs.InvalidParameter(errors.Errorf("invalid platform: %s", err)) if err != nil {
return nil, err
}
options.Platform = sp
} }
options.Platform = p.OS
} }
if r.Form.Get("shmsize") != "" { if r.Form.Get("shmsize") != "" {

View file

@ -4,11 +4,11 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"github.com/containerd/containerd/platforms"
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@ -16,7 +16,6 @@ import (
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
specs "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -45,9 +44,12 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
version := httputils.VersionFromContext(ctx) version := httputils.VersionFromContext(ctx)
if versions.GreaterThanOrEqualTo(version, "1.32") { if versions.GreaterThanOrEqualTo(version, "1.32") {
apiPlatform := r.FormValue("platform") apiPlatform := r.FormValue("platform")
platform = system.ParsePlatform(apiPlatform) if len(strings.TrimSpace(apiPlatform)) != 0 {
if err = system.ValidatePlatform(platform); err != nil { sp, err := platforms.Parse(apiPlatform)
err = fmt.Errorf("invalid platform: %s", err) if err != nil {
return err
}
platform = &sp
} }
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/go-units" "github.com/docker/go-units"
specs "github.com/opencontainers/image-spec/specs-go/v1"
) )
// CheckpointCreateOptions holds parameters to create a checkpoint from a container // CheckpointCreateOptions holds parameters to create a checkpoint from a container
@ -180,7 +181,7 @@ type ImageBuildOptions struct {
ExtraHosts []string // List of extra hosts ExtraHosts []string // List of extra hosts
Target string Target string
SessionID string SessionID string
Platform string Platform specs.Platform
// Version specifies the version of the unerlying builder to use // Version specifies the version of the unerlying builder to use
Version BuilderVersion Version BuilderVersion
// BuildID is an optional identifier that can be passed together with the // BuildID is an optional identifier that can be passed together with the

View file

@ -104,13 +104,6 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
source = src source = src
} }
os := ""
apiPlatform := system.ParsePlatform(config.Options.Platform)
if apiPlatform.OS != "" {
os = apiPlatform.OS
}
config.Options.Platform = os
builderOptions := builderOptions{ builderOptions := builderOptions{
Options: config.Options, Options: config.Options,
ProgressWriter: config.ProgressWriter, ProgressWriter: config.ProgressWriter,

View file

@ -24,6 +24,7 @@ import (
"github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/pkg/urlutil"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -72,7 +73,7 @@ type copier struct {
source builder.Source source builder.Source
pathCache pathCache pathCache pathCache
download sourceDownloader download sourceDownloader
platform string platform specs.Platform
// for cleanup. TODO: having copier.cleanup() is error prone and hard to // for cleanup. TODO: having copier.cleanup() is error prone and hard to
// follow. Code calling performCopy should manage the lifecycle of its params. // follow. Code calling performCopy should manage the lifecycle of its params.
// Copier should take override source as input, not imageMount. // 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 last := len(args) - 1
// Work in platform-specific filepath semantics // Work in platform-specific filepath semantics
inst.dest = fromSlash(args[last], o.platform) inst.dest = fromSlash(args[last], o.platform.OS)
separator := string(separator(o.platform)) separator := string(separator(o.platform.OS))
infos, err := o.getCopyInfosForSourcePaths(args[0:last], inst.dest) infos, err := o.getCopyInfosForSourcePaths(args[0:last], inst.dest)
if err != nil { if err != nil {
return inst, errors.Wrapf(err, "%s failed", cmdName) return inst, errors.Wrapf(err, "%s failed", cmdName)

View file

@ -14,6 +14,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/containerd/containerd/platforms"
"github.com/docker/docker/api" "github.com/docker/docker/api"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice" "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 { func initializeStage(d dispatchRequest, cmd *instructions.Stage) error {
d.builder.imageProber.Reset() 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 return err
} }
image, err := d.getFromImage(d.shlex, cmd.BaseName, cmd.Platform.OS) image, err := d.getFromImage(d.shlex, cmd.BaseName, cmd.Platform.OS)
if err != nil { if err != nil {
return err return err
@ -223,10 +226,10 @@ func (d *dispatchRequest) getOsFromFlagsAndStage(stageOS string) string {
switch { switch {
case stageOS != "": case stageOS != "":
return 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 // 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. // 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: default:
return "" // Auto-select return "" // Auto-select
} }

View file

@ -456,7 +456,7 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf
// is too small for builder scenarios where many users are // is too small for builder scenarios where many users are
// using RUN statements to install large amounts of data. // using RUN statements to install large amounts of data.
// Use 127GB as that's the default size of a VHD in Hyper-V. // 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 = make(map[string]string)
hc.StorageOpt["size"] = "127GB" hc.StorageOpt["size"] = "127GB"
} }

View file

@ -422,8 +422,6 @@ func checkCompatibleOS(imageOS string) error {
return fmt.Errorf("cannot load %s image on %s", imageOS, runtime.GOOS) return fmt.Errorf("cannot load %s image on %s", imageOS, runtime.GOOS)
} }
// Finally, check the image OS is supported for the platform. // Finally, check the image OS is supported for the platform.
if err := system.ValidatePlatform(system.ParsePlatform(imageOS)); err != nil { // TODO(@arm64b): Leave this sanity check to the containerd code in the future
return fmt.Errorf("cannot load %s image on %s: %s", imageOS, runtime.GOOS, err)
}
return nil return nil
} }

View file

@ -1,62 +1,9 @@
package system // import "github.com/docker/docker/pkg/system" package system // import "github.com/docker/docker/pkg/system"
import ( import (
"fmt"
"runtime" "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 // IsOSSupported determines if an operating system is supported by the host
func IsOSSupported(os string) bool { func IsOSSupported(os string) bool {
if strings.EqualFold(runtime.GOOS, os) { if strings.EqualFold(runtime.GOOS, os) {