Browse code

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>

Dennis Chen authored on 2018/06/26 16:39:25
Showing 9 changed files
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"strings"
15 15
 	"sync"
16 16
 
17
+	"github.com/containerd/containerd/platforms"
17 18
 	"github.com/docker/docker/api/server/httputils"
18 19
 	"github.com/docker/docker/api/types"
19 20
 	"github.com/docker/docker/api/types/backend"
... ...
@@ -23,7 +24,6 @@ import (
23 23
 	"github.com/docker/docker/pkg/ioutils"
24 24
 	"github.com/docker/docker/pkg/progress"
25 25
 	"github.com/docker/docker/pkg/streamformatter"
26
-	"github.com/docker/docker/pkg/system"
27 26
 	"github.com/docker/go-units"
28 27
 	"github.com/pkg/errors"
29 28
 	"github.com/sirupsen/logrus"
... ...
@@ -72,11 +72,13 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
72 72
 	options.RemoteContext = r.FormValue("remote")
73 73
 	if versions.GreaterThanOrEqualTo(version, "1.32") {
74 74
 		apiPlatform := r.FormValue("platform")
75
-		p := system.ParsePlatform(apiPlatform)
76
-		if err := system.ValidatePlatform(p); err != nil {
77
-			return nil, errdefs.InvalidParameter(errors.Errorf("invalid platform: %s", err))
75
+		if len(strings.TrimSpace(apiPlatform)) != 0 {
76
+			sp, err := platforms.Parse(apiPlatform)
77
+			if err != nil {
78
+				return nil, err
79
+			}
80
+			options.Platform = sp
78 81
 		}
79
-		options.Platform = p.OS
80 82
 	}
81 83
 
82 84
 	if r.Form.Get("shmsize") != "" {
... ...
@@ -4,11 +4,11 @@ import (
4 4
 	"context"
5 5
 	"encoding/base64"
6 6
 	"encoding/json"
7
-	"fmt"
8 7
 	"net/http"
9 8
 	"strconv"
10 9
 	"strings"
11 10
 
11
+	"github.com/containerd/containerd/platforms"
12 12
 	"github.com/docker/docker/api/server/httputils"
13 13
 	"github.com/docker/docker/api/types"
14 14
 	"github.com/docker/docker/api/types/filters"
... ...
@@ -16,7 +16,6 @@ import (
16 16
 	"github.com/docker/docker/errdefs"
17 17
 	"github.com/docker/docker/pkg/ioutils"
18 18
 	"github.com/docker/docker/pkg/streamformatter"
19
-	"github.com/docker/docker/pkg/system"
20 19
 	"github.com/docker/docker/registry"
21 20
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
22 21
 	"github.com/pkg/errors"
... ...
@@ -45,9 +44,12 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
45 45
 	version := httputils.VersionFromContext(ctx)
46 46
 	if versions.GreaterThanOrEqualTo(version, "1.32") {
47 47
 		apiPlatform := r.FormValue("platform")
48
-		platform = system.ParsePlatform(apiPlatform)
49
-		if err = system.ValidatePlatform(platform); err != nil {
50
-			err = fmt.Errorf("invalid platform: %s", err)
48
+		if len(strings.TrimSpace(apiPlatform)) != 0 {
49
+			sp, err := platforms.Parse(apiPlatform)
50
+			if err != nil {
51
+				return err
52
+			}
53
+			platform = &sp
51 54
 		}
52 55
 	}
53 56
 
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/docker/docker/api/types/container"
9 9
 	"github.com/docker/docker/api/types/filters"
10 10
 	"github.com/docker/go-units"
11
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
11 12
 )
12 13
 
13 14
 // CheckpointCreateOptions holds parameters to create a checkpoint from a container
... ...
@@ -180,7 +181,7 @@ type ImageBuildOptions struct {
180 180
 	ExtraHosts  []string // List of extra hosts
181 181
 	Target      string
182 182
 	SessionID   string
183
-	Platform    string
183
+	Platform    specs.Platform
184 184
 	// Version specifies the version of the unerlying builder to use
185 185
 	Version BuilderVersion
186 186
 	// 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) (
104 104
 		source = src
105 105
 	}
106 106
 
107
-	os := ""
108
-	apiPlatform := system.ParsePlatform(config.Options.Platform)
109
-	if apiPlatform.OS != "" {
110
-		os = apiPlatform.OS
111
-	}
112
-	config.Options.Platform = os
113
-
114 107
 	builderOptions := builderOptions{
115 108
 		Options:        config.Options,
116 109
 		ProgressWriter: config.ProgressWriter,
... ...
@@ -24,6 +24,7 @@ import (
24 24
 	"github.com/docker/docker/pkg/streamformatter"
25 25
 	"github.com/docker/docker/pkg/system"
26 26
 	"github.com/docker/docker/pkg/urlutil"
27
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
27 28
 	"github.com/pkg/errors"
28 29
 )
29 30
 
... ...
@@ -72,7 +73,7 @@ type copier struct {
72 72
 	source      builder.Source
73 73
 	pathCache   pathCache
74 74
 	download    sourceDownloader
75
-	platform    string
75
+	platform    specs.Platform
76 76
 	// for cleanup. TODO: having copier.cleanup() is error prone and hard to
77 77
 	// follow. Code calling performCopy should manage the lifecycle of its params.
78 78
 	// Copier should take override source as input, not imageMount.
... ...
@@ -95,8 +96,8 @@ func (o *copier) createCopyInstruction(args []string, cmdName string) (copyInstr
95 95
 	last := len(args) - 1
96 96
 
97 97
 	// Work in platform-specific filepath semantics
98
-	inst.dest = fromSlash(args[last], o.platform)
99
-	separator := string(separator(o.platform))
98
+	inst.dest = fromSlash(args[last], o.platform.OS)
99
+	separator := string(separator(o.platform.OS))
100 100
 	infos, err := o.getCopyInfosForSourcePaths(args[0:last], inst.dest)
101 101
 	if err != nil {
102 102
 		return inst, errors.Wrapf(err, "%s failed", cmdName)
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"sort"
15 15
 	"strings"
16 16
 
17
+	"github.com/containerd/containerd/platforms"
17 18
 	"github.com/docker/docker/api"
18 19
 	"github.com/docker/docker/api/types/container"
19 20
 	"github.com/docker/docker/api/types/strslice"
... ...
@@ -151,9 +152,11 @@ func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error
151 151
 //
152 152
 func initializeStage(d dispatchRequest, cmd *instructions.Stage) error {
153 153
 	d.builder.imageProber.Reset()
154
-	if err := system.ValidatePlatform(&cmd.Platform); err != nil {
154
+	//TODO(@arm64b): Leave the sanity check of the spec platform to the containerd code
155
+	if err := platforms.ValidatePlatform(&cmd.Platform); err != nil {
155 156
 		return err
156 157
 	}
158
+
157 159
 	image, err := d.getFromImage(d.shlex, cmd.BaseName, cmd.Platform.OS)
158 160
 	if err != nil {
159 161
 		return err
... ...
@@ -223,10 +226,10 @@ func (d *dispatchRequest) getOsFromFlagsAndStage(stageOS string) string {
223 223
 	switch {
224 224
 	case stageOS != "":
225 225
 		return stageOS
226
-	case d.builder.options.Platform != "":
226
+	case d.builder.options.Platform.OS != "":
227 227
 		// Note this is API "platform", but by this point, as the daemon is not
228 228
 		// multi-arch aware yet, it is guaranteed to only hold the OS part here.
229
-		return d.builder.options.Platform
229
+		return d.builder.options.Platform.OS
230 230
 	default:
231 231
 		return "" // Auto-select
232 232
 	}
... ...
@@ -456,7 +456,7 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf
456 456
 	// is too small for builder scenarios where many users are
457 457
 	// using RUN statements to install large amounts of data.
458 458
 	// Use 127GB as that's the default size of a VHD in Hyper-V.
459
-	if runtime.GOOS == "windows" && options.Platform == "windows" {
459
+	if runtime.GOOS == "windows" && options.Platform.OS == "windows" {
460 460
 		hc.StorageOpt = make(map[string]string)
461 461
 		hc.StorageOpt["size"] = "127GB"
462 462
 	}
... ...
@@ -422,8 +422,6 @@ func checkCompatibleOS(imageOS string) error {
422 422
 		return fmt.Errorf("cannot load %s image on %s", imageOS, runtime.GOOS)
423 423
 	}
424 424
 	// Finally, check the image OS is supported for the platform.
425
-	if err := system.ValidatePlatform(system.ParsePlatform(imageOS)); err != nil {
426
-		return fmt.Errorf("cannot load %s image on %s: %s", imageOS, runtime.GOOS, err)
427
-	}
425
+	// TODO(@arm64b): Leave this sanity check to the containerd code in the future
428 426
 	return nil
429 427
 }
... ...
@@ -1,62 +1,9 @@
1 1
 package system // import "github.com/docker/docker/pkg/system"
2 2
 
3 3
 import (
4
-	"fmt"
5 4
 	"runtime"
6
-	"strings"
7
-
8
-	specs "github.com/opencontainers/image-spec/specs-go/v1"
9 5
 )
10 6
 
11
-// ValidatePlatform determines if a platform structure is valid.
12
-// TODO This is a temporary function - can be replaced by parsing from
13
-// https://github.com/containerd/containerd/pull/1403/files at a later date.
14
-// @jhowardmsft
15
-func ValidatePlatform(platform *specs.Platform) error {
16
-	platform.Architecture = strings.ToLower(platform.Architecture)
17
-	platform.OS = strings.ToLower(platform.OS)
18
-	// Based on https://github.com/moby/moby/pull/34642#issuecomment-330375350, do
19
-	// not support anything except operating system.
20
-	if platform.Architecture != "" {
21
-		return fmt.Errorf("invalid platform architecture %q", platform.Architecture)
22
-	}
23
-	if platform.OS != "" {
24
-		if !(platform.OS == runtime.GOOS || (LCOWSupported() && platform.OS == "linux")) {
25
-			return fmt.Errorf("invalid platform os %q", platform.OS)
26
-		}
27
-	}
28
-	if len(platform.OSFeatures) != 0 {
29
-		return fmt.Errorf("invalid platform osfeatures %q", platform.OSFeatures)
30
-	}
31
-	if platform.OSVersion != "" {
32
-		return fmt.Errorf("invalid platform osversion %q", platform.OSVersion)
33
-	}
34
-	if platform.Variant != "" {
35
-		return fmt.Errorf("invalid platform variant %q", platform.Variant)
36
-	}
37
-	return nil
38
-}
39
-
40
-// ParsePlatform parses a platform string in the format os[/arch[/variant]
41
-// into an OCI image-spec platform structure.
42
-// TODO This is a temporary function - can be replaced by parsing from
43
-// https://github.com/containerd/containerd/pull/1403/files at a later date.
44
-// @jhowardmsft
45
-func ParsePlatform(in string) *specs.Platform {
46
-	p := &specs.Platform{}
47
-	elements := strings.SplitN(strings.ToLower(in), "/", 3)
48
-	if len(elements) == 3 {
49
-		p.Variant = elements[2]
50
-	}
51
-	if len(elements) >= 2 {
52
-		p.Architecture = elements[1]
53
-	}
54
-	if len(elements) >= 1 {
55
-		p.OS = elements[0]
56
-	}
57
-	return p
58
-}
59
-
60 7
 // IsOSSupported determines if an operating system is supported by the host
61 8
 func IsOSSupported(os string) bool {
62 9
 	if strings.EqualFold(runtime.GOOS, os) {