LCOW: API change JSON header to string POST parameter

Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
John Howard 2017-09-13 12:49:04 -07:00
parent 0380fbff37
commit d98ecf2d6c
27 changed files with 157 additions and 189 deletions

View File

@ -1,17 +1,11 @@
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"
@ -115,27 +109,3 @@ 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
}

View File

@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"os"
"runtime"
"strconv"
"strings"
@ -20,6 +21,7 @@ import (
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/system"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -67,6 +69,24 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
options.Squash = httputils.BoolValue(r, "squash")
options.Target = r.FormValue("target")
options.RemoteContext = r.FormValue("remote")
if versions.GreaterThanOrEqualTo(version, "1.32") {
// TODO @jhowardmsft. The following environment variable is an interim
// measure to allow the daemon to have a default platform if omitted by
// the client. This allows LCOW and WCOW to work with a down-level CLI
// for a short period of time, as the CLI changes can't be merged
// until after the daemon changes have been merged. Once the CLI is
// updated, this can be removed. PR for CLI is currently in
// https://github.com/docker/cli/pull/474.
apiPlatform := r.FormValue("platform")
if system.LCOWSupported() && apiPlatform == "" {
apiPlatform = os.Getenv("LCOW_API_PLATFORM_IF_OMITTED")
}
p := system.ParsePlatform(apiPlatform)
if err := system.ValidatePlatform(p); err != nil {
return nil, validationError{fmt.Errorf("invalid platform: %s", err)}
}
options.Platform = p.OS
}
if r.Form.Get("shmsize") != "" {
shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
@ -87,12 +107,6 @@ 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 != "" {

View File

@ -3,8 +3,10 @@ package image
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
@ -16,6 +18,7 @@ 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"
@ -70,6 +73,7 @@ func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *
// Creates an image from Pull or from Import
func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(r); err != nil {
return err
}
@ -87,7 +91,25 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
w.Header().Set("Content-Type", "application/json")
platform, err = httputils.GetRequestedPlatform(ctx, r)
version := httputils.VersionFromContext(ctx)
if versions.GreaterThanOrEqualTo(version, "1.32") {
// TODO @jhowardmsft. The following environment variable is an interim
// measure to allow the daemon to have a default platform if omitted by
// the client. This allows LCOW and WCOW to work with a down-level CLI
// for a short period of time, as the CLI changes can't be merged
// until after the daemon changes have been merged. Once the CLI is
// updated, this can be removed. PR for CLI is currently in
// https://github.com/docker/cli/pull/474.
apiPlatform := r.FormValue("platform")
if system.LCOWSupported() && apiPlatform == "" {
apiPlatform = os.Getenv("LCOW_API_PLATFORM_IF_OMITTED")
}
platform = system.ParsePlatform(apiPlatform)
if err = system.ValidatePlatform(platform); err != nil {
err = fmt.Errorf("invalid platform: %s", err)
}
}
if err == nil {
if image != "" { //pull
metaHeaders := map[string][]string{}

View File

@ -6181,17 +6181,9 @@ 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"
}
```
- name: "platform"
in: "query"
description: "Platform in the format os[/arch[/variant]]"
type: "string"
default: ""
responses:
@ -6275,17 +6267,9 @@ 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"
}
```
- name: "platform"
in: "query"
description: "Platform in the format os[/arch[/variant]]"
type: "string"
default: ""
tags: ["Image"]

View File

@ -8,7 +8,6 @@ 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
@ -180,7 +179,7 @@ type ImageBuildOptions struct {
ExtraHosts []string // List of extra hosts
Target string
SessionID string
Platform specs.Platform
Platform string
}
// ImageBuildResponse holds information
@ -193,8 +192,8 @@ type ImageBuildResponse struct {
// ImageCreateOptions holds information to create images.
type ImageCreateOptions struct {
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.
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
}
// ImageImportSource holds source information for ImageImport
@ -205,9 +204,10 @@ type ImageImportSource struct {
// ImageImportOptions holds information to import images from the client host.
type ImageImportOptions struct {
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
Message string // Message is the message to tag the image with
Changes []string // Changes are the raw changes to apply to this image
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
Message string // Message is the message to tag the image with
Changes []string // Changes are the raw changes to apply to this image
Platform string // Platform is the target platform of the image
}
// ImageListOptions holds parameters to filter the list of images with.
@ -228,7 +228,7 @@ type ImagePullOptions struct {
All bool
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
PrivilegeFunc RequestPrivilegeFunc
Platform specs.Platform
Platform string
}
// RequestPrivilegeFunc is a function interface that

View File

@ -15,6 +15,7 @@ import (
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/go-connections/nat"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// RootFS returns Image's RootFS description including the layer IDs.
@ -327,7 +328,7 @@ type ContainerJSONBase struct {
Name string
RestartCount int
Driver string
OS string
Platform specs.Platform
MountLabel string
ProcessLabel string
AppArmorProfile string

View File

@ -20,6 +20,7 @@ import (
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/system"
"github.com/moby/buildkit/session"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -102,15 +103,16 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
}
os := runtime.GOOS
optionsPlatform := system.ParsePlatform(config.Options.Platform)
if dockerfile.OS != "" {
if config.Options.Platform.OS != "" && config.Options.Platform.OS != dockerfile.OS {
if optionsPlatform.OS != "" && optionsPlatform.OS != dockerfile.OS {
return nil, fmt.Errorf("invalid platform")
}
os = dockerfile.OS
} else if config.Options.Platform.OS != "" {
os = config.Options.Platform.OS
} else if optionsPlatform.OS != "" {
os = optionsPlatform.OS
}
config.Options.Platform.OS = os
config.Options.Platform = os
dockerfile.OS = os
builderOptions := builderOptions{

View File

@ -82,7 +82,7 @@ func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, i
pathCache: req.builder.pathCache,
download: download,
imageSource: imageSource,
platform: req.builder.options.Platform.OS,
platform: req.builder.options.Platform,
}
}

View File

@ -194,11 +194,6 @@ func dispatchTriggeredOnBuild(d dispatchRequest, triggers []string) error {
return nil
}
// scratchImage is used as a token for the empty base image. It uses buildStage
// as a convenient implementation of builder.Image, but is not actually a
// buildStage.
var scratchImage builder.Image = &image.Image{}
func (d *dispatchRequest) getExpandedImageName(shlex *ShellLex, name string) (string, error) {
substitutionArgs := []string{}
for key, value := range d.state.buildArgs.GetAllMeta() {
@ -223,8 +218,9 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
imageImage := &image.Image{}
imageImage.OS = runtime.GOOS
if runtime.GOOS == "windows" {
switch d.builder.options.Platform.OS {
case "windows":
optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
switch optionsOS {
case "windows", "":
return nil, errors.New("Windows does not support FROM scratch")
case "linux":
if !system.LCOWSupported() {
@ -232,7 +228,7 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
}
imageImage.OS = "linux"
default:
return nil, errors.Errorf("operating system %q is not supported", d.builder.options.Platform.OS)
return nil, errors.Errorf("operating system %q is not supported", optionsOS)
}
}
return builder.Image(imageImage), nil
@ -264,7 +260,8 @@ func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error {
func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
runConfig := d.state.runConfig
var err error
runConfig.WorkingDir, err = normalizeWorkdir(d.builder.options.Platform.OS, runConfig.WorkingDir, c.Path)
optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
runConfig.WorkingDir, err = normalizeWorkdir(optionsOS, runConfig.WorkingDir, c.Path)
if err != nil {
return err
}
@ -280,7 +277,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
}
comment := "WORKDIR " + runConfig.WorkingDir
runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.builder.options.Platform.OS))
runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, optionsOS))
containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd)
if err != nil || containerID == "" {
return err
@ -313,7 +310,8 @@ func resolveCmdLine(cmd instructions.ShellDependantCmdLine, runConfig *container
func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
stateRunConfig := d.state.runConfig
cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.builder.options.Platform.OS)
optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, optionsOS)
buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)
saveCmd := cmdFromArgs
@ -390,7 +388,8 @@ func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.S
//
func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
runConfig := d.state.runConfig
cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS)
runConfig.Cmd = cmd
// set config as already being escaped, this prevents double escaping on windows
runConfig.ArgsEscaped = true
@ -433,7 +432,8 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand)
//
func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error {
runConfig := d.state.runConfig
cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS)
runConfig.Entrypoint = cmd
if !d.state.cmdSet {
runConfig.Cmd = nil

View File

@ -14,7 +14,6 @@ import (
"github.com/docker/docker/builder/dockerfile/instructions"
"github.com/docker/docker/pkg/system"
"github.com/docker/go-connections/nat"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -23,13 +22,13 @@ func newBuilderWithMockBackend() *Builder {
mockBackend := &MockBackend{}
ctx := context.Background()
b := &Builder{
options: &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
options: &types.ImageBuildOptions{Platform: runtime.GOOS},
docker: mockBackend,
Stdout: new(bytes.Buffer),
clientCtx: ctx,
disableCommit: true,
imageSources: newImageSources(ctx, builderOptions{
Options: &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
Options: &types.ImageBuildOptions{Platform: runtime.GOOS},
Backend: mockBackend,
}),
imageProber: newImageProber(mockBackend, nil, runtime.GOOS, false),

View File

@ -34,7 +34,8 @@ import (
func dispatch(d dispatchRequest, cmd instructions.Command) error {
if c, ok := cmd.(instructions.PlatformSpecific); ok {
err := c.CheckPlatform(d.builder.options.Platform.OS)
optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
err := c.CheckPlatform(optionsOS)
if err != nil {
return validationError{err}
}

View File

@ -5,6 +5,7 @@ import (
"github.com/docker/docker/builder"
"github.com/docker/docker/builder/remotecontext"
dockerimage "github.com/docker/docker/image"
"github.com/docker/docker/pkg/system"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
@ -30,11 +31,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
pullOption = backend.PullOptionPreferLocal
}
}
optionsPlatform := system.ParsePlatform(options.Options.Platform)
return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
PullOption: pullOption,
AuthConfig: options.Options.AuthConfigs,
Output: options.ProgressWriter.Output,
OS: options.Options.Platform.OS,
OS: optionsPlatform.OS,
})
}

View File

@ -83,7 +83,8 @@ func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
return errors.New("Please provide a source image with `from` prior to commit")
}
runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, b.options.Platform.OS))
optionsPlatform := system.ParsePlatform(b.options.Platform)
runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, optionsPlatform.OS))
hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
if err != nil || hit {
return err
@ -122,7 +123,8 @@ func (b *Builder) commitContainer(dispatchState *dispatchState, id string, conta
}
func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runConfig *container.Config) error {
newLayer, err := imageMount.Layer().Commit(b.options.Platform.OS)
optionsPlatform := system.ParsePlatform(b.options.Platform)
newLayer, err := imageMount.Layer().Commit(optionsPlatform.OS)
if err != nil {
return err
}
@ -170,9 +172,10 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest)
// TODO: should this have been using origPaths instead of srcHash in the comment?
optionsPlatform := system.ParsePlatform(b.options.Platform)
runConfigWithCommentCmd := copyRunConfig(
state.runConfig,
withCmdCommentString(commentStr, b.options.Platform.OS))
withCmdCommentString(commentStr, optionsPlatform.OS))
hit, err := b.probeCache(state, runConfigWithCommentCmd)
if err != nil || hit {
return err
@ -183,7 +186,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
return errors.Wrapf(err, "failed to get destination image %q", state.imageID)
}
destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.options.Platform.OS)
destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.options.Platform)
if err != nil {
return err
}
@ -463,13 +466,15 @@ func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *contai
}
// Set a log config to override any default value set on the daemon
hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.OS)
optionsPlatform := system.ParsePlatform(b.options.Platform)
container, err := b.containerManager.Create(runConfig, hostConfig, optionsPlatform.OS)
return container.ID, err
}
func (b *Builder) create(runConfig *container.Config) (string, error) {
hostConfig := hostConfigFromOptions(b.options)
container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.OS)
optionsPlatform := system.ParsePlatform(b.options.Platform)
container, err := b.containerManager.Create(runConfig, hostConfig, optionsPlatform.OS)
if err != nil {
return "", err
}

View File

@ -7,12 +7,12 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/system"
)
// ImageBuild sends request to the daemon to build images.
@ -31,18 +31,11 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
}
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
// TODO @jhowardmsft: system.IsPlatformEmpty is a temporary function. We need to move
// (in the reasonably short future) to a package which supports all the platform
// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
if !system.IsPlatformEmpty(options.Platform) {
if options.Platform != "" {
if err := cli.NewVersionError("1.32", "platform"); err != nil {
return types.ImageBuildResponse{}, err
}
platformJSON, err := json.Marshal(options.Platform)
if err != nil {
return types.ImageBuildResponse{}, err
}
headers.Add("X-Requested-Platform", string(platformJSON[:]))
query.Set("platform", options.Platform)
}
headers.Set("Content-Type", "application/x-tar")
@ -138,5 +131,8 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
if options.SessionID != "" {
query.Set("session", options.SessionID)
}
if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform))
}
return query, nil
}

View File

@ -1,16 +1,14 @@
package client
import (
"encoding/json"
"io"
"net/url"
"strings"
"golang.org/x/net/context"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/system"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageCreate creates a new image based in the parent options.
@ -24,25 +22,17 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
query := url.Values{}
query.Set("fromImage", reference.FamiliarName(ref))
query.Set("tag", getAPITagFromNamedRef(ref))
resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform))
}
resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
if err != nil {
return nil, err
}
return resp.body, nil
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string, platform specs.Platform) (serverResponse, error) {
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
// TODO @jhowardmsft: system.IsPlatformEmpty is a temporary function. We need to move
// (in the reasonably short future) to a package which supports all the platform
// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
if !system.IsPlatformEmpty(platform) {
platformJSON, err := json.Marshal(platform)
if err != nil {
return serverResponse{}, err
}
headers["X-Requested-Platform"] = []string{string(platformJSON[:])}
}
return cli.post(ctx, "/images/create", query, nil, headers)
}

View File

@ -3,6 +3,7 @@ package client
import (
"io"
"net/url"
"strings"
"golang.org/x/net/context"
@ -25,6 +26,9 @@ func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSour
query.Set("repo", ref)
query.Set("tag", options.Tag)
query.Set("message", options.Message)
if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform))
}
for _, change := range options.Changes {
query.Add("changes", change)
}

View File

@ -4,12 +4,12 @@ import (
"io"
"net/http"
"net/url"
"strings"
"golang.org/x/net/context"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/system"
)
// ImagePull requests the docker host to pull an image from a remote registry.
@ -31,30 +31,17 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options types.I
if !options.All {
query.Set("tag", getAPITagFromNamedRef(ref))
}
// TODO 1: Extend to include "and the platform is supported by the daemon".
// This is dependent on https://github.com/moby/moby/pull/34628 though,
// and the daemon returning the set of platforms it supports via the _ping
// API endpoint.
//
// TODO 2: system.IsPlatformEmpty is a temporary function. We need to move
// (in the reasonably short future) to a package which supports all the platform
// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
//
// @jhowardmsft.
if !system.IsPlatformEmpty(options.Platform) {
if err := cli.NewVersionError("1.32", "platform"); err != nil {
return nil, err
}
if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform))
}
resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
newAuthHeader, privilegeErr := options.PrivilegeFunc()
if privilegeErr != nil {
return nil, privilegeErr
}
resp, err = cli.tryImageCreate(ctx, query, newAuthHeader, options.Platform)
resp, err = cli.tryImageCreate(ctx, query, newAuthHeader)
}
if err != nil {
return nil, err

View File

@ -66,7 +66,7 @@ func (container *Container) BuildHostnameFile() error {
func (container *Container) NetworkMounts() []Mount {
var mounts []Mount
shared := container.HostConfig.NetworkMode.IsContainer()
parser := volume.NewParser(container.Platform)
parser := volume.NewParser(container.OS)
if container.ResolvConfPath != "" {
if _, err := os.Stat(container.ResolvConfPath); err != nil {
logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
@ -195,7 +195,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro
// IpcMounts returns the list of IPC mounts
func (container *Container) IpcMounts() []Mount {
var mounts []Mount
parser := volume.NewParser(container.Platform)
parser := volume.NewParser(container.OS)
if container.HasMountFor("/dev/shm") {
return mounts
@ -429,7 +429,7 @@ func copyOwnership(source, destination string) error {
// TmpfsMounts returns the list of tmpfs mounts
func (container *Container) TmpfsMounts() ([]Mount, error) {
parser := volume.NewParser(container.Platform)
parser := volume.NewParser(container.OS)
var mounts []Mount
for dest, data := range container.HostConfig.Tmpfs {
mounts = append(mounts, Mount{

View File

@ -12,7 +12,7 @@ import (
// cannot be configured with a read-only rootfs.
func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
var toVolume bool
parser := volume.NewParser(container.Platform)
parser := volume.NewParser(container.OS)
for _, mnt := range container.MountPoints {
if toVolume = parser.HasResource(mnt, absPath); toVolume {
if mnt.RW {

View File

@ -13,6 +13,7 @@ import (
"github.com/docker/docker/daemon/network"
volumestore "github.com/docker/docker/volume/store"
"github.com/docker/go-connections/nat"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// ContainerInspect returns low-level information about a
@ -171,7 +172,7 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
Name: container.Name,
RestartCount: container.RestartCount,
Driver: container.Driver,
OS: container.OS,
Platform: specs.Platform{OS: container.OS},
MountLabel: container.MountLabel,
ProcessLabel: container.ProcessLabel,
ExecIDs: container.GetExecIDs(),

View File

@ -12,6 +12,7 @@ import (
"time"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/distribution/metadata"
"github.com/docker/docker/distribution/xfer"
@ -68,7 +69,9 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, platform strin
return nil
}
func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error {
// Note use auth.Scope rather than reference.Named due to this warning causing Jenkins CI to fail:
// warning: ref can be github.com/docker/docker/vendor/github.com/docker/distribution/registry/client/auth.Scope (interfacer)
func (p *v1Puller) pullRepository(ctx context.Context, ref auth.Scope) error {
progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.Name.Name())
tagged, isTagged := ref.(reference.NamedTagged)

View File

@ -510,7 +510,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
// Early bath if the requested OS doesn't match that of the configuration.
// This avoids doing the download, only to potentially fail later.
if !strings.EqualFold(string(configOS), requestedOS) {
if !strings.EqualFold(configOS, requestedOS) {
return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
}
@ -651,7 +651,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
}
if configJSON == nil {
configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
if err == nil && configRootFS == nil {
err = errRootFSInvalid
}
@ -723,7 +723,7 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), os, runtime.GOARCH)
manifestMatches := filterManifests(mfstList.Manifests)
manifestMatches := filterManifests(mfstList.Manifests, os)
if len(manifestMatches) == 0 {
errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)

View File

@ -16,13 +16,13 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
return blobs.Open(ctx, ld.digest)
}
func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist.ManifestDescriptor {
func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
var matches []manifestlist.ManifestDescriptor
for _, manifestDescriptor := range manifests {
if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
matches = append(matches, manifestDescriptor)
logrus.Debugf("found match for %s/%s with media type %s, digest %s", runtime.GOOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
}
}
return matches

View File

@ -62,29 +62,28 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
return rsc, err
}
func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist.ManifestDescriptor {
version := system.GetOSVersion()
// TODO @jhowardmsft LCOW Support: Need to remove the hard coding in LCOW mode.
lookingForOS := runtime.GOOS
osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
if system.LCOWSupported() {
lookingForOS = "linux"
osVersion = ""
func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
osVersion := ""
if os == "windows" {
version := system.GetOSVersion()
osVersion = fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
logrus.Debugf("will only match entries with version %s", osVersion)
}
var matches []manifestlist.ManifestDescriptor
for _, manifestDescriptor := range manifests {
if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == lookingForOS {
if lookingForOS == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
if os == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
logrus.Debugf("skipping %s", manifestDescriptor.Platform.OSVersion)
continue
}
matches = append(matches, manifestDescriptor)
logrus.Debugf("found match for %s/%s with media type %s, digest %s", lookingForOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
}
}
sort.Stable(manifestsByVersion(matches))
if os == "windows" {
sort.Stable(manifestsByVersion(matches))
}
return matches
}

View File

@ -4,6 +4,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"runtime"
"strings"
"testing"

View File

@ -62,7 +62,7 @@ func TestImage(t *testing.T) {
Domainname: "domain",
User: "root",
}
platform := runtime.GOOS
os := runtime.GOOS
img := &Image{
V1Image: V1Image{
@ -73,19 +73,19 @@ func TestImage(t *testing.T) {
assert.Equal(t, cid, img.ImageID())
assert.Equal(t, cid, img.ID().String())
assert.Equal(t, platform, img.Platform())
assert.Equal(t, os, img.OperatingSystem())
assert.Equal(t, config, img.RunConfig())
}
func TestImagePlatformNotEmpty(t *testing.T) {
platform := "platform"
func TestImageOSNotEmpty(t *testing.T) {
os := "os"
img := &Image{
V1Image: V1Image{
OS: platform,
OS: os,
},
OSVersion: "osversion",
}
assert.Equal(t, platform, img.Platform())
assert.Equal(t, os, img.OperatingSystem())
}
func TestNewChildImageFromImageWithRootFS(t *testing.T) {

View File

@ -8,21 +8,6 @@ import (
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// IsPlatformEmpty determines if an OCI image-spec platform structure is not populated.
// TODO This is a temporary function - can be replaced by parsing from
// https://github.com/containerd/containerd/pull/1403/files at a later date.
// @jhowardmsft
func IsPlatformEmpty(platform specs.Platform) bool {
if platform.Architecture == "" &&
platform.OS == "" &&
len(platform.OSFeatures) == 0 &&
platform.OSVersion == "" &&
platform.Variant == "" {
return true
}
return false
}
// ValidatePlatform determines if a platform structure is valid.
// TODO This is a temporary function - can be replaced by parsing from
// https://github.com/containerd/containerd/pull/1403/files at a later date.
@ -30,7 +15,9 @@ func IsPlatformEmpty(platform specs.Platform) bool {
func ValidatePlatform(platform *specs.Platform) error {
platform.Architecture = strings.ToLower(platform.Architecture)
platform.OS = strings.ToLower(platform.OS)
if platform.Architecture != "" && platform.Architecture != runtime.GOARCH {
// Based on https://github.com/moby/moby/pull/34642#issuecomment-330375350, do
// not support anything except operating system.
if platform.Architecture != "" {
return fmt.Errorf("invalid platform architecture %q", platform.Architecture)
}
if platform.OS != "" {