LCOW: API: Add platform to /images/create and /build
Signed-off-by: John Howard <jhoward@microsoft.com> This PR has the API changes described in https://github.com/moby/moby/issues/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. Upstream-commit: 0380fbff37922cadf294851b1546f4c212c7f364 Component: engine
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
package httputils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
@@ -109,3 +115,27 @@ func matchesContentType(contentType, expectedType string) bool {
|
||||
}
|
||||
return err == nil && mimetype == expectedType
|
||||
}
|
||||
|
||||
// GetRequestedPlatform extracts an optional platform structure from an HTTP request header
|
||||
func GetRequestedPlatform(ctx context.Context, r *http.Request) (*specs.Platform, error) {
|
||||
platform := &specs.Platform{}
|
||||
version := VersionFromContext(ctx)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.32") {
|
||||
requestedPlatform := r.Header.Get("X-Requested-Platform")
|
||||
if requestedPlatform != "" {
|
||||
if err := json.Unmarshal([]byte(requestedPlatform), platform); err != nil {
|
||||
return nil, fmt.Errorf("invalid X-Requested-Platform header: %s", err)
|
||||
}
|
||||
}
|
||||
if err := system.ValidatePlatform(platform); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if platform.OS == "" {
|
||||
platform.OS = runtime.GOOS
|
||||
}
|
||||
if platform.Architecture == "" {
|
||||
platform.Architecture = runtime.GOARCH
|
||||
}
|
||||
return platform, nil
|
||||
}
|
||||
|
||||
@@ -87,6 +87,12 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
|
||||
return nil, validationError{fmt.Errorf("The daemon on this platform does not support setting security options on build")}
|
||||
}
|
||||
|
||||
platform, err := httputils.GetRequestedPlatform(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options.Platform = *platform
|
||||
|
||||
var buildUlimits = []*units.Ulimit{}
|
||||
ulimitsJSON := r.FormValue("ulimits")
|
||||
if ulimitsJSON != "" {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -17,8 +16,8 @@ import (
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/registry"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@@ -76,78 +75,46 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
|
||||
}
|
||||
|
||||
var (
|
||||
image = r.Form.Get("fromImage")
|
||||
repo = r.Form.Get("repo")
|
||||
tag = r.Form.Get("tag")
|
||||
message = r.Form.Get("message")
|
||||
err error
|
||||
output = ioutils.NewWriteFlusher(w)
|
||||
image = r.Form.Get("fromImage")
|
||||
repo = r.Form.Get("repo")
|
||||
tag = r.Form.Get("tag")
|
||||
message = r.Form.Get("message")
|
||||
err error
|
||||
output = ioutils.NewWriteFlusher(w)
|
||||
platform = &specs.Platform{}
|
||||
)
|
||||
defer output.Close()
|
||||
|
||||
// TODO @jhowardmsft LCOW Support: Eventually we will need an API change
|
||||
// so that platform comes from (for example) r.Form.Get("platform"). For
|
||||
// the initial implementation, we assume that the platform is the
|
||||
// runtime OS of the host. It will also need a validation function such
|
||||
// as below which should be called after getting it from the API.
|
||||
//
|
||||
// Ensures the requested platform is valid and normalized
|
||||
//func validatePlatform(req string) (string, error) {
|
||||
// req = strings.ToLower(req)
|
||||
// if req == "" {
|
||||
// req = runtime.GOOS // default to host platform
|
||||
// }
|
||||
// valid := []string{runtime.GOOS}
|
||||
//
|
||||
// if system.LCOWSupported() {
|
||||
// valid = append(valid, "linux")
|
||||
// }
|
||||
//
|
||||
// for _, item := range valid {
|
||||
// if req == item {
|
||||
// return req, nil
|
||||
// }
|
||||
// }
|
||||
// return "", fmt.Errorf("invalid platform requested: %s", req)
|
||||
//}
|
||||
//
|
||||
// And in the call-site:
|
||||
// if platform, err = validatePlatform(platform); err != nil {
|
||||
// return err
|
||||
// }
|
||||
platform := runtime.GOOS
|
||||
if system.LCOWSupported() {
|
||||
platform = "linux"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if image != "" { //pull
|
||||
metaHeaders := map[string][]string{}
|
||||
for k, v := range r.Header {
|
||||
if strings.HasPrefix(k, "X-Meta-") {
|
||||
metaHeaders[k] = v
|
||||
platform, err = httputils.GetRequestedPlatform(ctx, r)
|
||||
if err == nil {
|
||||
if image != "" { //pull
|
||||
metaHeaders := map[string][]string{}
|
||||
for k, v := range r.Header {
|
||||
if strings.HasPrefix(k, "X-Meta-") {
|
||||
metaHeaders[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
||||
authConfig := &types.AuthConfig{}
|
||||
if authEncoded != "" {
|
||||
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
||||
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
|
||||
// for a pull it is not an error if no auth was given
|
||||
// to increase compatibility with the existing api it is defaulting to be empty
|
||||
authConfig = &types.AuthConfig{}
|
||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
||||
authConfig := &types.AuthConfig{}
|
||||
if authEncoded != "" {
|
||||
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
||||
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
|
||||
// for a pull it is not an error if no auth was given
|
||||
// to increase compatibility with the existing api it is defaulting to be empty
|
||||
authConfig = &types.AuthConfig{}
|
||||
}
|
||||
}
|
||||
err = s.backend.PullImage(ctx, image, tag, platform.OS, metaHeaders, authConfig, output)
|
||||
} else { //import
|
||||
src := r.Form.Get("fromSrc")
|
||||
// 'err' MUST NOT be defined within this block, we need any error
|
||||
// generated from the download to be available to the output
|
||||
// stream processing below
|
||||
err = s.backend.ImportImage(src, repo, platform.OS, tag, message, r.Body, output, r.Form["changes"])
|
||||
}
|
||||
|
||||
err = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output)
|
||||
} else { //import
|
||||
src := r.Form.Get("fromSrc")
|
||||
// 'err' MUST NOT be defined within this block, we need any error
|
||||
// generated from the download to be available to the output
|
||||
// stream processing below
|
||||
err = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"])
|
||||
}
|
||||
if err != nil {
|
||||
if !output.Flushed() {
|
||||
|
||||
@@ -6181,6 +6181,19 @@ paths:
|
||||
|
||||
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.
|
||||
type: "string"
|
||||
- name: "X-Requested-Platform"
|
||||
in: "header"
|
||||
description: |
|
||||
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:
|
||||
|
||||
```
|
||||
{
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
}
|
||||
```
|
||||
type: "string"
|
||||
default: ""
|
||||
responses:
|
||||
200:
|
||||
description: "no error"
|
||||
@@ -6262,6 +6275,19 @@ paths:
|
||||
in: "header"
|
||||
description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
|
||||
type: "string"
|
||||
- name: "X-Requested-Platform"
|
||||
in: "header"
|
||||
description: |
|
||||
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:
|
||||
|
||||
```
|
||||
{
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
}
|
||||
```
|
||||
type: "string"
|
||||
default: ""
|
||||
tags: ["Image"]
|
||||
/images/{name}/json:
|
||||
get:
|
||||
|
||||
@@ -40,5 +40,5 @@ type GetImageAndLayerOptions struct {
|
||||
PullOption PullOption
|
||||
AuthConfig map[string]types.AuthConfig
|
||||
Output io.Writer
|
||||
Platform string
|
||||
OS string
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
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
|
||||
@@ -179,10 +180,7 @@ type ImageBuildOptions struct {
|
||||
ExtraHosts []string // List of extra hosts
|
||||
Target string
|
||||
SessionID string
|
||||
|
||||
// TODO @jhowardmsft LCOW Support: This will require extending to include
|
||||
// `Platform string`, but is omitted for now as it's hard-coded temporarily
|
||||
// to avoid API changes.
|
||||
Platform specs.Platform
|
||||
}
|
||||
|
||||
// ImageBuildResponse holds information
|
||||
@@ -195,7 +193,8 @@ type ImageBuildResponse struct {
|
||||
|
||||
// ImageCreateOptions holds information to create images.
|
||||
type ImageCreateOptions struct {
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
|
||||
Platform specs.Platform // Platform is the target platform of the image if it needs to be pulled from the registry.
|
||||
}
|
||||
|
||||
// ImageImportSource holds source information for ImageImport
|
||||
@@ -229,6 +228,7 @@ type ImagePullOptions struct {
|
||||
All bool
|
||||
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
|
||||
PrivilegeFunc RequestPrivilegeFunc
|
||||
Platform specs.Platform
|
||||
}
|
||||
|
||||
// RequestPrivilegeFunc is a function interface that
|
||||
|
||||
@@ -16,7 +16,6 @@ type ContainerCreateConfig struct {
|
||||
HostConfig *container.HostConfig
|
||||
NetworkingConfig *network.NetworkingConfig
|
||||
AdjustCPUShares bool
|
||||
Platform string
|
||||
}
|
||||
|
||||
// ContainerRmConfig holds arguments for the container remove
|
||||
|
||||
@@ -327,7 +327,7 @@ type ContainerJSONBase struct {
|
||||
Name string
|
||||
RestartCount int
|
||||
Driver string
|
||||
Platform string
|
||||
OS string
|
||||
MountLabel string
|
||||
ProcessLabel string
|
||||
AppArmorProfile string
|
||||
|
||||
Reference in New Issue
Block a user