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:
John Howard
2017-10-06 11:44:18 -07:00
parent 318e1046f2
commit 35db73fa01
82 changed files with 622 additions and 510 deletions
@@ -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() {
+26
View File
@@ -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:
+1 -1
View File
@@ -40,5 +40,5 @@ type GetImageAndLayerOptions struct {
PullOption PullOption
AuthConfig map[string]types.AuthConfig
Output io.Writer
Platform string
OS string
}
+5 -5
View File
@@ -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
-1
View File
@@ -16,7 +16,6 @@ type ContainerCreateConfig struct {
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
AdjustCPUShares bool
Platform string
}
// ContainerRmConfig holds arguments for the container remove
+1 -1
View File
@@ -327,7 +327,7 @@ type ContainerJSONBase struct {
Name string
RestartCount int
Driver string
Platform string
OS string
MountLabel string
ProcessLabel string
AppArmorProfile string