Skip to content

Commit 0380fbf

Browse files
author
John Howard
committed
LCOW: API: Add platform to /images/create and /build
Signed-off-by: John Howard <[email protected]> This PR has the API changes described in #34617. Specifically, it adds an HTTP header "X-Requested-Platform" which is a JSON-encoded OCI Image-spec `Platform` structure. In addition, it renames (almost all) uses of a string variable platform (and associated) methods/functions to os. This makes it much clearer to disambiguate with the swarm "platform" which is really os/arch. This is a stepping stone to getting the daemon towards fully multi-platform/arch-aware, and makes it clear when "operating system" is being referred to rather than "platform" which is misleadingly used - sometimes in the swarm meaning, but more often as just the operating system.
1 parent b8571fd commit 0380fbf

82 files changed

Lines changed: 622 additions & 510 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/server/httputils/httputils.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package httputils
22

33
import (
4+
"encoding/json"
5+
"fmt"
46
"io"
57
"mime"
68
"net/http"
9+
"runtime"
710
"strings"
811

12+
"github.com/docker/docker/api/types/versions"
13+
"github.com/docker/docker/pkg/system"
14+
specs "github.com/opencontainers/image-spec/specs-go/v1"
915
"github.com/pkg/errors"
1016
"github.com/sirupsen/logrus"
1117
"golang.org/x/net/context"
@@ -109,3 +115,27 @@ func matchesContentType(contentType, expectedType string) bool {
109115
}
110116
return err == nil && mimetype == expectedType
111117
}
118+
119+
// GetRequestedPlatform extracts an optional platform structure from an HTTP request header
120+
func GetRequestedPlatform(ctx context.Context, r *http.Request) (*specs.Platform, error) {
121+
platform := &specs.Platform{}
122+
version := VersionFromContext(ctx)
123+
if versions.GreaterThanOrEqualTo(version, "1.32") {
124+
requestedPlatform := r.Header.Get("X-Requested-Platform")
125+
if requestedPlatform != "" {
126+
if err := json.Unmarshal([]byte(requestedPlatform), platform); err != nil {
127+
return nil, fmt.Errorf("invalid X-Requested-Platform header: %s", err)
128+
}
129+
}
130+
if err := system.ValidatePlatform(platform); err != nil {
131+
return nil, err
132+
}
133+
}
134+
if platform.OS == "" {
135+
platform.OS = runtime.GOOS
136+
}
137+
if platform.Architecture == "" {
138+
platform.Architecture = runtime.GOARCH
139+
}
140+
return platform, nil
141+
}

api/server/router/build/build_routes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
8787
return nil, validationError{fmt.Errorf("The daemon on this platform does not support setting security options on build")}
8888
}
8989

90+
platform, err := httputils.GetRequestedPlatform(ctx, r)
91+
if err != nil {
92+
return nil, err
93+
}
94+
options.Platform = *platform
95+
9096
var buildUlimits = []*units.Ulimit{}
9197
ulimitsJSON := r.FormValue("ulimits")
9298
if ulimitsJSON != "" {

api/server/router/image/image_routes.go

Lines changed: 32 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"encoding/json"
66
"io"
77
"net/http"
8-
"runtime"
98
"strconv"
109
"strings"
1110

@@ -17,8 +16,8 @@ import (
1716
"github.com/docker/docker/api/types/versions"
1817
"github.com/docker/docker/pkg/ioutils"
1918
"github.com/docker/docker/pkg/streamformatter"
20-
"github.com/docker/docker/pkg/system"
2119
"github.com/docker/docker/registry"
20+
specs "github.com/opencontainers/image-spec/specs-go/v1"
2221
"github.com/pkg/errors"
2322
"golang.org/x/net/context"
2423
)
@@ -76,78 +75,46 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
7675
}
7776

7877
var (
79-
image = r.Form.Get("fromImage")
80-
repo = r.Form.Get("repo")
81-
tag = r.Form.Get("tag")
82-
message = r.Form.Get("message")
83-
err error
84-
output = ioutils.NewWriteFlusher(w)
78+
image = r.Form.Get("fromImage")
79+
repo = r.Form.Get("repo")
80+
tag = r.Form.Get("tag")
81+
message = r.Form.Get("message")
82+
err error
83+
output = ioutils.NewWriteFlusher(w)
84+
platform = &specs.Platform{}
8585
)
8686
defer output.Close()
8787

88-
// TODO @jhowardmsft LCOW Support: Eventually we will need an API change
89-
// so that platform comes from (for example) r.Form.Get("platform"). For
90-
// the initial implementation, we assume that the platform is the
91-
// runtime OS of the host. It will also need a validation function such
92-
// as below which should be called after getting it from the API.
93-
//
94-
// Ensures the requested platform is valid and normalized
95-
//func validatePlatform(req string) (string, error) {
96-
// req = strings.ToLower(req)
97-
// if req == "" {
98-
// req = runtime.GOOS // default to host platform
99-
// }
100-
// valid := []string{runtime.GOOS}
101-
//
102-
// if system.LCOWSupported() {
103-
// valid = append(valid, "linux")
104-
// }
105-
//
106-
// for _, item := range valid {
107-
// if req == item {
108-
// return req, nil
109-
// }
110-
// }
111-
// return "", fmt.Errorf("invalid platform requested: %s", req)
112-
//}
113-
//
114-
// And in the call-site:
115-
// if platform, err = validatePlatform(platform); err != nil {
116-
// return err
117-
// }
118-
platform := runtime.GOOS
119-
if system.LCOWSupported() {
120-
platform = "linux"
121-
}
122-
12388
w.Header().Set("Content-Type", "application/json")
12489

125-
if image != "" { //pull
126-
metaHeaders := map[string][]string{}
127-
for k, v := range r.Header {
128-
if strings.HasPrefix(k, "X-Meta-") {
129-
metaHeaders[k] = v
90+
platform, err = httputils.GetRequestedPlatform(ctx, r)
91+
if err == nil {
92+
if image != "" { //pull
93+
metaHeaders := map[string][]string{}
94+
for k, v := range r.Header {
95+
if strings.HasPrefix(k, "X-Meta-") {
96+
metaHeaders[k] = v
97+
}
13098
}
131-
}
13299

133-
authEncoded := r.Header.Get("X-Registry-Auth")
134-
authConfig := &types.AuthConfig{}
135-
if authEncoded != "" {
136-
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
137-
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
138-
// for a pull it is not an error if no auth was given
139-
// to increase compatibility with the existing api it is defaulting to be empty
140-
authConfig = &types.AuthConfig{}
100+
authEncoded := r.Header.Get("X-Registry-Auth")
101+
authConfig := &types.AuthConfig{}
102+
if authEncoded != "" {
103+
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
104+
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
105+
// for a pull it is not an error if no auth was given
106+
// to increase compatibility with the existing api it is defaulting to be empty
107+
authConfig = &types.AuthConfig{}
108+
}
141109
}
110+
err = s.backend.PullImage(ctx, image, tag, platform.OS, metaHeaders, authConfig, output)
111+
} else { //import
112+
src := r.Form.Get("fromSrc")
113+
// 'err' MUST NOT be defined within this block, we need any error
114+
// generated from the download to be available to the output
115+
// stream processing below
116+
err = s.backend.ImportImage(src, repo, platform.OS, tag, message, r.Body, output, r.Form["changes"])
142117
}
143-
144-
err = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output)
145-
} else { //import
146-
src := r.Form.Get("fromSrc")
147-
// 'err' MUST NOT be defined within this block, we need any error
148-
// generated from the download to be available to the output
149-
// stream processing below
150-
err = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"])
151118
}
152119
if err != nil {
153120
if !output.Flushed() {

api/swagger.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6181,6 +6181,19 @@ paths:
61816181
61826182
Only the registry domain name (and port if not the default 443) are required. However, for legacy reasons, the Docker Hub registry must be specified with both a `https://` prefix and a `/v1/` suffix even though Docker will prefer to use the v2 registry API.
61836183
type: "string"
6184+
- name: "X-Requested-Platform"
6185+
in: "header"
6186+
description: |
6187+
This is a JSON object representing an OCI image-spec `Platform` object. It is used to request a platform in the case that the engine supports multiple platforms. For example:
6188+
6189+
```
6190+
{
6191+
"architecture": "amd64",
6192+
"os": "linux"
6193+
}
6194+
```
6195+
type: "string"
6196+
default: ""
61846197
responses:
61856198
200:
61866199
description: "no error"
@@ -6262,6 +6275,19 @@ paths:
62626275
in: "header"
62636276
description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
62646277
type: "string"
6278+
- name: "X-Requested-Platform"
6279+
in: "header"
6280+
description: |
6281+
This is a JSON object representing an OCI image-spec `Platform` object. It is used to request a platform in the case that the engine supports multiple platforms. For example:
6282+
6283+
```
6284+
{
6285+
"architecture": "amd64",
6286+
"os": "linux"
6287+
}
6288+
```
6289+
type: "string"
6290+
default: ""
62656291
tags: ["Image"]
62666292
/images/{name}/json:
62676293
get:

api/types/backend/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,5 @@ type GetImageAndLayerOptions struct {
4040
PullOption PullOption
4141
AuthConfig map[string]types.AuthConfig
4242
Output io.Writer
43-
Platform string
43+
OS string
4444
}

api/types/client.go

Lines changed: 5 additions & 5 deletions
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
units "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
@@ -179,10 +180,7 @@ type ImageBuildOptions struct {
179180
ExtraHosts []string // List of extra hosts
180181
Target string
181182
SessionID string
182-
183-
// TODO @jhowardmsft LCOW Support: This will require extending to include
184-
// `Platform string`, but is omitted for now as it's hard-coded temporarily
185-
// to avoid API changes.
183+
Platform specs.Platform
186184
}
187185

188186
// ImageBuildResponse holds information
@@ -195,7 +193,8 @@ type ImageBuildResponse struct {
195193

196194
// ImageCreateOptions holds information to create images.
197195
type ImageCreateOptions struct {
198-
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
196+
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
197+
Platform specs.Platform // Platform is the target platform of the image if it needs to be pulled from the registry.
199198
}
200199

201200
// ImageImportSource holds source information for ImageImport
@@ -229,6 +228,7 @@ type ImagePullOptions struct {
229228
All bool
230229
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
231230
PrivilegeFunc RequestPrivilegeFunc
231+
Platform specs.Platform
232232
}
233233

234234
// RequestPrivilegeFunc is a function interface that

api/types/configs.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ type ContainerCreateConfig struct {
1616
HostConfig *container.HostConfig
1717
NetworkingConfig *network.NetworkingConfig
1818
AdjustCPUShares bool
19-
Platform string
2019
}
2120

2221
// ContainerRmConfig holds arguments for the container remove

api/types/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ type ContainerJSONBase struct {
327327
Name string
328328
RestartCount int
329329
Driver string
330-
Platform string
330+
OS string
331331
MountLabel string
332332
ProcessLabel string
333333
AppArmorProfile string

builder/builder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ type Image interface {
9595
ImageID() string
9696
RunConfig() *container.Config
9797
MarshalJSON() ([]byte, error)
98+
OperatingSystem() string
9899
}
99100

100101
// ReleaseableLayer is an image layer that can be mounted and released

0 commit comments

Comments
 (0)