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:
parent
35193c0e7d
commit
7f334d3acf
9 changed files with 28 additions and 81 deletions
|
@ -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") != "" {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue