Skip to content

Commit 7f334d3

Browse files
arm64btonistiigi
authored andcommitted
Initial support for OCI multi-platform image
Add the OCI spec compatible image support in client side. Signed-off-by: Dennis Chen <[email protected]>
1 parent 35193c0 commit 7f334d3

9 files changed

Lines changed: 28 additions & 81 deletions

File tree

api/server/router/build/build_routes.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"strings"
1515
"sync"
1616

17+
"github.com/containerd/containerd/platforms"
1718
"github.com/docker/docker/api/server/httputils"
1819
"github.com/docker/docker/api/types"
1920
"github.com/docker/docker/api/types/backend"
@@ -23,7 +24,6 @@ import (
2324
"github.com/docker/docker/pkg/ioutils"
2425
"github.com/docker/docker/pkg/progress"
2526
"github.com/docker/docker/pkg/streamformatter"
26-
"github.com/docker/docker/pkg/system"
2727
"github.com/docker/go-units"
2828
"github.com/pkg/errors"
2929
"github.com/sirupsen/logrus"
@@ -72,11 +72,13 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
7272
options.RemoteContext = r.FormValue("remote")
7373
if versions.GreaterThanOrEqualTo(version, "1.32") {
7474
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
7881
}
79-
options.Platform = p.OS
8082
}
8183

8284
if r.Form.Get("shmsize") != "" {

api/server/router/image/image_routes.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@ import (
44
"context"
55
"encoding/base64"
66
"encoding/json"
7-
"fmt"
87
"net/http"
98
"strconv"
109
"strings"
1110

11+
"github.com/containerd/containerd/platforms"
1212
"github.com/docker/docker/api/server/httputils"
1313
"github.com/docker/docker/api/types"
1414
"github.com/docker/docker/api/types/filters"
1515
"github.com/docker/docker/api/types/versions"
1616
"github.com/docker/docker/errdefs"
1717
"github.com/docker/docker/pkg/ioutils"
1818
"github.com/docker/docker/pkg/streamformatter"
19-
"github.com/docker/docker/pkg/system"
2019
"github.com/docker/docker/registry"
2120
specs "github.com/opencontainers/image-spec/specs-go/v1"
2221
"github.com/pkg/errors"
@@ -45,9 +44,12 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
4544
version := httputils.VersionFromContext(ctx)
4645
if versions.GreaterThanOrEqualTo(version, "1.32") {
4746
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)
47+
if len(strings.TrimSpace(apiPlatform)) != 0 {
48+
sp, err := platforms.Parse(apiPlatform)
49+
if err != nil {
50+
return err
51+
}
52+
platform = &sp
5153
}
5254
}
5355

api/types/client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/docker/docker/api/types/container"
99
"github.com/docker/docker/api/types/filters"
1010
"github.com/docker/go-units"
11+
specs "github.com/opencontainers/image-spec/specs-go/v1"
1112
)
1213

1314
// CheckpointCreateOptions holds parameters to create a checkpoint from a container
@@ -180,7 +181,7 @@ type ImageBuildOptions struct {
180181
ExtraHosts []string // List of extra hosts
181182
Target string
182183
SessionID string
183-
Platform string
184+
Platform specs.Platform
184185
// Version specifies the version of the unerlying builder to use
185186
Version BuilderVersion
186187
// BuildID is an optional identifier that can be passed together with the

builder/dockerfile/builder.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,6 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
104104
source = src
105105
}
106106

107-
os := ""
108-
apiPlatform := system.ParsePlatform(config.Options.Platform)
109-
if apiPlatform.OS != "" {
110-
os = apiPlatform.OS
111-
}
112-
config.Options.Platform = os
113-
114107
builderOptions := builderOptions{
115108
Options: config.Options,
116109
ProgressWriter: config.ProgressWriter,

builder/dockerfile/copy.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/docker/docker/pkg/streamformatter"
2525
"github.com/docker/docker/pkg/system"
2626
"github.com/docker/docker/pkg/urlutil"
27+
specs "github.com/opencontainers/image-spec/specs-go/v1"
2728
"github.com/pkg/errors"
2829
)
2930

@@ -72,7 +73,7 @@ type copier struct {
7273
source builder.Source
7374
pathCache pathCache
7475
download sourceDownloader
75-
platform string
76+
platform specs.Platform
7677
// for cleanup. TODO: having copier.cleanup() is error prone and hard to
7778
// follow. Code calling performCopy should manage the lifecycle of its params.
7879
// Copier should take override source as input, not imageMount.
@@ -95,8 +96,8 @@ func (o *copier) createCopyInstruction(args []string, cmdName string) (copyInstr
9596
last := len(args) - 1
9697

9798
// Work in platform-specific filepath semantics
98-
inst.dest = fromSlash(args[last], o.platform)
99-
separator := string(separator(o.platform))
99+
inst.dest = fromSlash(args[last], o.platform.OS)
100+
separator := string(separator(o.platform.OS))
100101
infos, err := o.getCopyInfosForSourcePaths(args[0:last], inst.dest)
101102
if err != nil {
102103
return inst, errors.Wrapf(err, "%s failed", cmdName)

builder/dockerfile/dispatchers.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"sort"
1515
"strings"
1616

17+
"github.com/containerd/containerd/platforms"
1718
"github.com/docker/docker/api"
1819
"github.com/docker/docker/api/types/container"
1920
"github.com/docker/docker/api/types/strslice"
@@ -151,9 +152,11 @@ func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error
151152
//
152153
func initializeStage(d dispatchRequest, cmd *instructions.Stage) error {
153154
d.builder.imageProber.Reset()
154-
if err := system.ValidatePlatform(&cmd.Platform); err != nil {
155+
//TODO(@arm64b): Leave the sanity check of the spec platform to the containerd code
156+
if err := platforms.ValidatePlatform(&cmd.Platform); err != nil {
155157
return err
156158
}
159+
157160
image, err := d.getFromImage(d.shlex, cmd.BaseName, cmd.Platform.OS)
158161
if err != nil {
159162
return err
@@ -223,10 +226,10 @@ func (d *dispatchRequest) getOsFromFlagsAndStage(stageOS string) string {
223226
switch {
224227
case stageOS != "":
225228
return stageOS
226-
case d.builder.options.Platform != "":
229+
case d.builder.options.Platform.OS != "":
227230
// Note this is API "platform", but by this point, as the daemon is not
228231
// multi-arch aware yet, it is guaranteed to only hold the OS part here.
229-
return d.builder.options.Platform
232+
return d.builder.options.Platform.OS
230233
default:
231234
return "" // Auto-select
232235
}

builder/dockerfile/internals.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf
456456
// is too small for builder scenarios where many users are
457457
// using RUN statements to install large amounts of data.
458458
// 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" {
460460
hc.StorageOpt = make(map[string]string)
461461
hc.StorageOpt["size"] = "127GB"
462462
}

image/tarexport/load.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,6 @@ func checkCompatibleOS(imageOS string) error {
422422
return fmt.Errorf("cannot load %s image on %s", imageOS, runtime.GOOS)
423423
}
424424
// 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
428426
return nil
429427
}

pkg/system/lcow.go

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,9 @@
11
package system // import "github.com/docker/docker/pkg/system"
22

33
import (
4-
"fmt"
54
"runtime"
6-
"strings"
7-
8-
specs "github.com/opencontainers/image-spec/specs-go/v1"
95
)
106

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-
607
// IsOSSupported determines if an operating system is supported by the host
618
func IsOSSupported(os string) bool {
629
if strings.EqualFold(runtime.GOOS, os) {

0 commit comments

Comments
 (0)