Merge pull request #33241 from Microsoft/jjh/multi-layerstore

LCOW: Support most operations excluding remote filesystem
This commit is contained in:
John Stephens 2017-06-21 15:45:23 -07:00 коммит произвёл GitHub
Родитель 795a2fdbcb fe7b4d8fcd
Коммит 930e689668
138 изменённых файлов: 2833 добавлений и 797 удалений

Просмотреть файл

@ -126,7 +126,7 @@ func MatchesContentType(contentType, expectedType string) bool {
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
// otherwise generates a new one
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700)
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700, "")
if err != nil {
return nil, err
}

Просмотреть файл

@ -17,7 +17,7 @@ import (
// ImageComponent provides an interface for working with images
type ImageComponent interface {
SquashImage(from string, to string) (string, error)
TagImageWithReference(image.ID, reference.Named) error
TagImageWithReference(image.ID, string, reference.Named) error
}
// Backend provides build functionality to the API router

Просмотреть файл

@ -3,9 +3,11 @@ package build
import (
"fmt"
"io"
"runtime"
"github.com/docker/distribution/reference"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/system"
"github.com/pkg/errors"
)
@ -33,7 +35,12 @@ func NewTagger(backend ImageComponent, stdout io.Writer, names []string) (*Tagge
// TagImages creates image tags for the imageID
func (bt *Tagger) TagImages(imageID image.ID) error {
for _, rt := range bt.repoAndTags {
if err := bt.imageComponent.TagImageWithReference(imageID, rt); err != nil {
// TODO @jhowardmsft LCOW support. Will need revisiting.
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
if err := bt.imageComponent.TagImageWithReference(imageID, platform, rt); err != nil {
return err
}
fmt.Fprintf(bt.stdout, "Successfully tagged %s\n", reference.FamiliarString(rt))

Просмотреть файл

@ -35,12 +35,12 @@ type imageBackend interface {
type importExportBackend interface {
LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error
ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error
ImportImage(src string, repository, platform string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error
ExportImage(names []string, outStream io.Writer) error
}
type registryBackend interface {
PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
}

Просмотреть файл

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"runtime"
"strconv"
"strings"
@ -17,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"
"golang.org/x/net/context"
)
@ -85,6 +87,41 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
)
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 runtime.GOOS == "windows" && 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 platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
w.Header().Set("Content-Type", "application/json")
if image != "" { //pull
@ -106,13 +143,13 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
}
}
err = s.backend.PullImage(ctx, image, tag, metaHeaders, authConfig, output)
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, tag, message, r.Body, output, r.Form["changes"])
err = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"])
}
if err != nil {
if !output.Flushed() {

Просмотреть файл

@ -40,4 +40,5 @@ type GetImageAndLayerOptions struct {
PullOption PullOption
AuthConfig map[string]types.AuthConfig
Output io.Writer
Platform string
}

Просмотреть файл

@ -178,6 +178,10 @@ type ImageBuildOptions struct {
SecurityOpt []string
ExtraHosts []string // List of extra hosts
Target string
// TODO @jhowardmsft LCOW Support: This will require extending to include
// `Platform string`, but is ommited for now as it's hard-coded temporarily
// to avoid API changes.
}
// ImageBuildResponse holds information

Просмотреть файл

@ -16,6 +16,7 @@ type ContainerCreateConfig struct {
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
AdjustCPUShares bool
Platform string
}
// ContainerRmConfig holds arguments for the container remove

Просмотреть файл

@ -320,6 +320,7 @@ type ContainerJSONBase struct {
Name string
RestartCount int
Driver string
Platform string
MountLabel string
ProcessLabel string
AppArmorProfile string

Просмотреть файл

@ -43,7 +43,7 @@ type Backend interface {
// ContainerCreateWorkdir creates the workdir
ContainerCreateWorkdir(containerID string) error
CreateImage(config []byte, parent string) (Image, error)
CreateImage(config []byte, parent string, platform string) (Image, error)
ImageCacheBuilder
}
@ -78,7 +78,7 @@ type Result struct {
// ImageCacheBuilder represents a generator for stateful image cache.
type ImageCacheBuilder interface {
// MakeImageCache creates a stateful image cache.
MakeImageCache(cacheFrom []string) ImageCache
MakeImageCache(cacheFrom []string, platform string) ImageCache
}
// ImageCache abstracts an image cache.
@ -100,6 +100,6 @@ type Image interface {
type ReleaseableLayer interface {
Release() error
Mount() (string, error)
Commit() (ReleaseableLayer, error)
Commit(platform string) (ReleaseableLayer, error)
DiffID() layer.DiffID
}

Просмотреть файл

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"io/ioutil"
"runtime"
"strings"
"github.com/Sirupsen/logrus"
@ -20,6 +21,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/pkg/errors"
"golang.org/x/net/context"
"golang.org/x/sync/syncmap"
@ -73,13 +75,24 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
}()
}
// TODO @jhowardmsft LCOW support - this will require rework to allow both linux and Windows simultaneously.
// This is an interim solution to hardcode to linux if LCOW is turned on.
if dockerfile.Platform == "" {
dockerfile.Platform = runtime.GOOS
if dockerfile.Platform == "windows" && system.LCOWSupported() {
dockerfile.Platform = "linux"
}
}
builderOptions := builderOptions{
Options: config.Options,
ProgressWriter: config.ProgressWriter,
Backend: bm.backend,
PathCache: bm.pathCache,
Archiver: bm.archiver,
Platform: dockerfile.Platform,
}
return newBuilder(ctx, builderOptions).build(source, dockerfile)
}
@ -90,6 +103,7 @@ type builderOptions struct {
ProgressWriter backend.ProgressWriter
PathCache pathCache
Archiver *archive.Archiver
Platform string
}
// Builder is a Dockerfile builder
@ -113,14 +127,32 @@ type Builder struct {
pathCache pathCache
containerManager *containerManager
imageProber ImageProber
// TODO @jhowardmft LCOW Support. This will be moved to options at a later
// stage, however that cannot be done now as it affects the public API
// if it were.
platform string
}
// newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
// TODO @jhowardmsft LCOW support: Eventually platform can be moved into the builder
// options, however, that would be an API change as it shares types.ImageBuildOptions.
func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
config := options.Options
if config == nil {
config = new(types.ImageBuildOptions)
}
// @jhowardmsft LCOW Support. For the time being, this is interim. Eventually
// will be moved to types.ImageBuildOptions, but it can't for now as that would
// be an API change.
if options.Platform == "" {
options.Platform = runtime.GOOS
}
if options.Platform == "windows" && system.LCOWSupported() {
options.Platform = "linux"
}
b := &Builder{
clientCtx: clientCtx,
options: config,
@ -134,9 +166,11 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
buildStages: newBuildStages(),
imageSources: newImageSources(clientCtx, options),
pathCache: options.PathCache,
imageProber: newImageProber(options.Backend, config.CacheFrom, config.NoCache),
imageProber: newImageProber(options.Backend, config.CacheFrom, options.Platform, config.NoCache),
containerManager: newContainerManager(options.Backend),
platform: options.Platform,
}
return b
}
@ -267,6 +301,17 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
return nil, err
}
// TODO @jhowardmsft LCOW support. For now, if LCOW enabled, switch to linux.
// Also explicitly set the platform. Ultimately this will be in the builder
// options, but we can't do that yet as it would change the API.
if dockerfile.Platform == "" {
dockerfile.Platform = runtime.GOOS
}
if dockerfile.Platform == "windows" && system.LCOWSupported() {
dockerfile.Platform = "linux"
}
b.platform = dockerfile.Platform
// ensure that the commands are valid
for _, n := range dockerfile.AST.Children {
if !validCommitCommands[n.Value] {

Просмотреть файл

@ -2,4 +2,6 @@
package dockerfile
var defaultShell = []string{"/bin/sh", "-c"}
func defaultShellForPlatform(platform string) []string {
return []string{"/bin/sh", "-c"}
}

Просмотреть файл

@ -1,3 +1,8 @@
package dockerfile
var defaultShell = []string{"cmd", "/S", "/C"}
func defaultShellForPlatform(platform string) []string {
if platform == "linux" {
return []string{"/bin/sh", "-c"}
}
return []string{"cmd", "/S", "/C"}
}

Просмотреть файл

@ -28,10 +28,11 @@ func newContainerManager(docker builder.ExecBackend) *containerManager {
}
// Create a container
func (c *containerManager) Create(runConfig *container.Config, hostConfig *container.HostConfig) (container.ContainerCreateCreatedBody, error) {
func (c *containerManager) Create(runConfig *container.Config, hostConfig *container.HostConfig, platform string) (container.ContainerCreateCreatedBody, error) {
container, err := c.backend.ContainerCreate(types.ContainerCreateConfig{
Config: runConfig,
HostConfig: hostConfig,
Platform: platform,
})
if err != nil {
return container, err

Просмотреть файл

@ -26,6 +26,7 @@ import (
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/system"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
)
@ -274,10 +275,12 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err
localOnly = true
}
// Windows cannot support a container with no base image.
// Windows cannot support a container with no base image unless it is LCOW.
if name == api.NoBaseImageSpecifier {
if runtime.GOOS == "windows" {
return nil, errors.New("Windows does not support FROM scratch")
if b.platform == "windows" || (b.platform != "windows" && !system.LCOWSupported()) {
return nil, errors.New("Windows does not support FROM scratch")
}
}
return scratchImage, nil
}
@ -400,7 +403,7 @@ func workdir(req dispatchRequest) error {
}
comment := "WORKDIR " + runConfig.WorkingDir
runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment))
runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, req.builder.platform))
containerID, err := req.builder.probeAndCreate(req.state, runConfigWithCommentCmd)
if err != nil || containerID == "" {
return err
@ -418,7 +421,7 @@ func workdir(req dispatchRequest) error {
// the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under
// Windows, in the event there is only one argument The difference in processing:
//
// RUN echo hi # sh -c echo hi (Linux)
// RUN echo hi # sh -c echo hi (Linux and LCOW)
// RUN echo hi # cmd /S /C echo hi (Windows)
// RUN [ "echo", "hi" ] # echo hi
//
@ -434,7 +437,7 @@ func run(req dispatchRequest) error {
stateRunConfig := req.state.runConfig
args := handleJSONArgs(req.args, req.attributes)
if !req.attributes["json"] {
args = append(getShell(stateRunConfig), args...)
args = append(getShell(stateRunConfig, req.builder.platform), args...)
}
cmdFromArgs := strslice.StrSlice(args)
buildArgs := req.builder.buildArgs.FilterAllowed(stateRunConfig.Env)
@ -519,7 +522,7 @@ func cmd(req dispatchRequest) error {
runConfig := req.state.runConfig
cmdSlice := handleJSONArgs(req.args, req.attributes)
if !req.attributes["json"] {
cmdSlice = append(getShell(runConfig), cmdSlice...)
cmdSlice = append(getShell(runConfig, req.builder.platform), cmdSlice...)
}
runConfig.Cmd = strslice.StrSlice(cmdSlice)
@ -671,7 +674,7 @@ func entrypoint(req dispatchRequest) error {
runConfig.Entrypoint = nil
default:
// ENTRYPOINT echo hi
runConfig.Entrypoint = strslice.StrSlice(append(getShell(runConfig), parsed[0]))
runConfig.Entrypoint = strslice.StrSlice(append(getShell(runConfig, req.builder.platform), parsed[0]))
}
// when setting the entrypoint if a CMD was not explicitly set then

Просмотреть файл

@ -63,7 +63,7 @@ func newBuilderWithMockBackend() *Builder {
Backend: mockBackend,
}),
buildStages: newBuildStages(),
imageProber: newImageProber(mockBackend, nil, false),
imageProber: newImageProber(mockBackend, nil, runtime.GOOS, false),
containerManager: newContainerManager(mockBackend),
}
return b
@ -194,7 +194,7 @@ func TestFromScratch(t *testing.T) {
req := defaultDispatchReq(b, "scratch")
err := from(req)
if runtime.GOOS == "windows" {
if runtime.GOOS == "windows" && !system.LCOWSupported() {
assert.EqualError(t, err, "Windows does not support FROM scratch")
return
}
@ -202,7 +202,12 @@ func TestFromScratch(t *testing.T) {
require.NoError(t, err)
assert.True(t, req.state.hasFromImage())
assert.Equal(t, "", req.state.imageID)
assert.Equal(t, []string{"PATH=" + system.DefaultPathEnv}, req.state.runConfig.Env)
// Windows does not set the default path. TODO @jhowardmsft LCOW support. This will need revisiting as we get further into the implementation
expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
if runtime.GOOS == "windows" {
expected = ""
}
assert.Equal(t, []string{expected}, req.state.runConfig.Env)
}
func TestFromWithArg(t *testing.T) {
@ -469,7 +474,7 @@ func TestRunWithBuildArgs(t *testing.T) {
runConfig := &container.Config{}
origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
cmdWithShell := strslice.StrSlice(append(getShell(runConfig), "echo foo"))
cmdWithShell := strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo"))
envVars := []string{"|1", "one=two"}
cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
@ -483,10 +488,10 @@ func TestRunWithBuildArgs(t *testing.T) {
}
mockBackend := b.docker.(*MockBackend)
mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
mockBackend.makeImageCacheFunc = func(_ []string, _ string) builder.ImageCache {
return imageCache
}
b.imageProber = newImageProber(mockBackend, nil, false)
b.imageProber = newImageProber(mockBackend, nil, runtime.GOOS, false)
mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) {
return &mockImage{
id: "abcdef",

Просмотреть файл

@ -22,6 +22,7 @@ package dockerfile
import (
"bytes"
"fmt"
"runtime"
"strings"
"github.com/docker/docker/api/types/container"
@ -228,14 +229,19 @@ func (s *dispatchState) beginStage(stageName string, image builder.Image) {
}
// Add the default PATH to runConfig.ENV if one exists for the platform and there
// is no PATH set. Note that windows won't have one as it's set by HCS
// is no PATH set. Note that Windows containers on Windows won't have one as it's set by HCS
func (s *dispatchState) setDefaultPath() {
if system.DefaultPathEnv == "" {
// TODO @jhowardmsft LCOW Support - This will need revisiting later
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
if system.DefaultPathEnv(platform) == "" {
return
}
envMap := opts.ConvertKVStringsToMap(s.runConfig.Env)
if _, ok := envMap["PATH"]; !ok {
s.runConfig.Env = append(s.runConfig.Env, "PATH="+system.DefaultPathEnv)
s.runConfig.Env = append(s.runConfig.Env, "PATH="+system.DefaultPathEnv(platform))
}
}

Просмотреть файл

@ -97,6 +97,8 @@ type imageSources struct {
cache pathCache // TODO: remove
}
// TODO @jhowardmsft LCOW Support: Eventually, platform can be moved to options.Options.Platform,
// and removed from builderOptions, but that can't be done yet as it would affect the API.
func newImageSources(ctx context.Context, options builderOptions) *imageSources {
getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) {
pullOption := backend.PullOptionNoPull
@ -111,6 +113,7 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
PullOption: pullOption,
AuthConfig: options.Options.AuthConfigs,
Output: options.ProgressWriter.Output,
Platform: options.Platform,
})
}

Просмотреть файл

@ -19,13 +19,13 @@ type imageProber struct {
cacheBusted bool
}
func newImageProber(cacheBuilder builder.ImageCacheBuilder, cacheFrom []string, noCache bool) ImageProber {
func newImageProber(cacheBuilder builder.ImageCacheBuilder, cacheFrom []string, platform string, noCache bool) ImageProber {
if noCache {
return &nopProber{}
}
reset := func() builder.ImageCache {
return cacheBuilder.MakeImageCache(cacheFrom)
return cacheBuilder.MakeImageCache(cacheFrom, platform)
}
return &imageProber{cache: reset(), reset: reset}
}

Просмотреть файл

@ -25,7 +25,7 @@ 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))
runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, b.platform))
hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
if err != nil || hit {
return err
@ -65,7 +65,7 @@ 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()
newLayer, err := imageMount.Layer().Commit(b.platform)
if err != nil {
return err
}
@ -84,7 +84,7 @@ func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runC
ContainerConfig: runConfig,
DiffID: newLayer.DiffID(),
Config: copyRunConfig(state.runConfig),
})
}, parentImage.OS)
// TODO: it seems strange to marshal this here instead of just passing in the
// image struct
@ -93,7 +93,7 @@ func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runC
return errors.Wrap(err, "failed to encode image config")
}
exportedImage, err := b.docker.CreateImage(config, state.imageID)
exportedImage, err := b.docker.CreateImage(config, state.imageID, parentImage.OS)
if err != nil {
return errors.Wrapf(err, "failed to export image")
}
@ -110,7 +110,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
// TODO: should this have been using origPaths instead of srcHash in the comment?
runConfigWithCommentCmd := copyRunConfig(
state.runConfig,
withCmdCommentString(fmt.Sprintf("%s %s in %s ", inst.cmdName, srcHash, inst.dest)))
withCmdCommentString(fmt.Sprintf("%s %s in %s ", inst.cmdName, srcHash, inst.dest), b.platform))
hit, err := b.probeCache(state, runConfigWithCommentCmd)
if err != nil || hit {
return err
@ -190,9 +190,9 @@ func withCmd(cmd []string) runConfigModifier {
// withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
// why there are two almost identical versions of this.
func withCmdComment(comment string) runConfigModifier {
func withCmdComment(comment string, platform string) runConfigModifier {
return func(runConfig *container.Config) {
runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment)
runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) ", comment)
}
}
@ -200,9 +200,9 @@ func withCmdComment(comment string) runConfigModifier {
// A few instructions (workdir, copy, add) used a nop comment that is a single arg
// where as all the other instructions used a two arg comment string. This
// function implements the single arg version.
func withCmdCommentString(comment string) runConfigModifier {
func withCmdCommentString(comment string, platform string) runConfigModifier {
return func(runConfig *container.Config) {
runConfig.Cmd = append(getShell(runConfig), "#(nop) "+comment)
runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) "+comment)
}
}
@ -229,9 +229,9 @@ func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier
// getShell is a helper function which gets the right shell for prefixing the
// shell-form of RUN, ENTRYPOINT and CMD instructions
func getShell(c *container.Config) []string {
func getShell(c *container.Config, platform string) []string {
if 0 == len(c.Shell) {
return append([]string{}, defaultShell[:]...)
return append([]string{}, defaultShellForPlatform(platform)[:]...)
}
return append([]string{}, c.Shell[:]...)
}
@ -256,13 +256,13 @@ 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)
container, err := b.containerManager.Create(runConfig, hostConfig, b.platform)
return container.ID, err
}
func (b *Builder) create(runConfig *container.Config) (string, error) {
hostConfig := hostConfigFromOptions(b.options)
container, err := b.containerManager.Create(runConfig, hostConfig)
container, err := b.containerManager.Create(runConfig, hostConfig, b.platform)
if err != nil {
return "", err
}

Просмотреть файл

@ -2,6 +2,7 @@ package dockerfile
import (
"fmt"
"runtime"
"testing"
"github.com/docker/docker/api/types"
@ -97,9 +98,9 @@ func TestCopyRunConfig(t *testing.T) {
},
{
doc: "Set the command to a comment",
modifiers: []runConfigModifier{withCmdComment("comment")},
modifiers: []runConfigModifier{withCmdComment("comment", runtime.GOOS)},
expected: &container.Config{
Cmd: append(defaultShell, "#(nop) ", "comment"),
Cmd: append(defaultShellForPlatform(runtime.GOOS), "#(nop) ", "comment"),
Env: defaultEnv,
},
},

Просмотреть файл

@ -18,7 +18,7 @@ type MockBackend struct {
containerCreateFunc func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
commitFunc func(string, *backend.ContainerCommitConfig) (string, error)
getImageFunc func(string) (builder.Image, builder.ReleaseableLayer, error)
makeImageCacheFunc func(cacheFrom []string) builder.ImageCache
makeImageCacheFunc func(cacheFrom []string, platform string) builder.ImageCache
}
func (m *MockBackend) ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool, attached chan struct{}) error {
@ -71,14 +71,14 @@ func (m *MockBackend) GetImageAndReleasableLayer(ctx context.Context, refOrID st
return &mockImage{id: "theid"}, &mockLayer{}, nil
}
func (m *MockBackend) MakeImageCache(cacheFrom []string) builder.ImageCache {
func (m *MockBackend) MakeImageCache(cacheFrom []string, platform string) builder.ImageCache {
if m.makeImageCacheFunc != nil {
return m.makeImageCacheFunc(cacheFrom)
return m.makeImageCacheFunc(cacheFrom, platform)
}
return nil
}
func (m *MockBackend) CreateImage(config []byte, parent string) (builder.Image, error) {
func (m *MockBackend) CreateImage(config []byte, parent string, platform string) (builder.Image, error) {
return nil, nil
}
@ -121,7 +121,7 @@ func (l *mockLayer) Mount() (string, error) {
return "mountPath", nil
}
func (l *mockLayer) Commit() (builder.ReleaseableLayer, error) {
func (l *mockLayer) Commit(string) (builder.ReleaseableLayer, error) {
return nil, nil
}

Просмотреть файл

@ -7,11 +7,13 @@ import (
"fmt"
"io"
"regexp"
"runtime"
"strconv"
"strings"
"unicode"
"github.com/docker/docker/builder/dockerfile/command"
"github.com/docker/docker/pkg/system"
"github.com/pkg/errors"
)
@ -79,22 +81,28 @@ func (node *Node) AddChild(child *Node, startLine, endLine int) {
}
var (
dispatch map[string]func(string, *Directive) (*Node, map[string]bool, error)
tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
tokenEscapeCommand = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
tokenComment = regexp.MustCompile(`^#.*$`)
dispatch map[string]func(string, *Directive) (*Node, map[string]bool, error)
tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
tokenEscapeCommand = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
tokenPlatformCommand = regexp.MustCompile(`^#[ \t]*platform[ \t]*=[ \t]*(?P<platform>.*)$`)
tokenComment = regexp.MustCompile(`^#.*$`)
)
// DefaultEscapeToken is the default escape token
const DefaultEscapeToken = '\\'
// DefaultPlatformToken is the platform assumed for the build if not explicitly provided
var DefaultPlatformToken = runtime.GOOS
// Directive is the structure used during a build run to hold the state of
// parsing directives.
type Directive struct {
escapeToken rune // Current escape token
platformToken string // Current platform token
lineContinuationRegex *regexp.Regexp // Current line continuation regex
processingComplete bool // Whether we are done looking for directives
escapeSeen bool // Whether the escape directive has been seen
platformSeen bool // Whether the platform directive has been seen
}
// setEscapeToken sets the default token for escaping characters in a Dockerfile.
@ -107,6 +115,22 @@ func (d *Directive) setEscapeToken(s string) error {
return nil
}
// setPlatformToken sets the default platform for pulling images in a Dockerfile.
func (d *Directive) setPlatformToken(s string) error {
s = strings.ToLower(s)
valid := []string{runtime.GOOS}
if runtime.GOOS == "windows" && system.LCOWSupported() {
valid = append(valid, "linux")
}
for _, item := range valid {
if s == item {
d.platformToken = s
return nil
}
}
return fmt.Errorf("invalid PLATFORM '%s'. Must be one of %v", s, valid)
}
// possibleParserDirective looks for one or more parser directives '# escapeToken=<char>' and
// '# platform=<string>'. Parser directives must precede any builder instruction
// or other comments, and cannot be repeated.
@ -128,6 +152,23 @@ func (d *Directive) possibleParserDirective(line string) error {
}
}
// TODO @jhowardmsft LCOW Support: Eventually this check can be removed,
// but only recognise a platform token if running in LCOW mode.
if runtime.GOOS == "windows" && system.LCOWSupported() {
tpcMatch := tokenPlatformCommand.FindStringSubmatch(strings.ToLower(line))
if len(tpcMatch) != 0 {
for i, n := range tokenPlatformCommand.SubexpNames() {
if n == "platform" {
if d.platformSeen == true {
return errors.New("only one platform parser directive can be used")
}
d.platformSeen = true
return d.setPlatformToken(tpcMatch[i])
}
}
}
}
d.processingComplete = true
return nil
}
@ -136,6 +177,7 @@ func (d *Directive) possibleParserDirective(line string) error {
func NewDefaultDirective() *Directive {
directive := Directive{}
directive.setEscapeToken(string(DefaultEscapeToken))
directive.setPlatformToken(runtime.GOOS)
return &directive
}
@ -200,6 +242,7 @@ func newNodeFromLine(line string, directive *Directive) (*Node, error) {
type Result struct {
AST *Node
EscapeToken rune
Platform string
}
// Parse reads lines from a Reader, parses the lines into an AST and returns
@ -252,7 +295,7 @@ func Parse(rwc io.Reader) (*Result, error) {
}
root.AddChild(child, startLine, currentLine)
}
return &Result{AST: root, EscapeToken: d.escapeToken}, nil
return &Result{AST: root, EscapeToken: d.escapeToken, Platform: d.platformToken}, nil
}
func trimComments(src []byte) []byte {

Просмотреть файл

@ -1,6 +1,8 @@
package main
import (
"runtime"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/opts"
"github.com/spf13/pflag"
@ -33,7 +35,12 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
flags.BoolVarP(&conf.AutoRestart, "restart", "r", true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run")
flags.MarkDeprecated("restart", "Please use a restart policy on docker run")
flags.StringVarP(&conf.GraphDriver, "storage-driver", "s", "", "Storage driver to use")
// Windows doesn't support setting the storage driver - there is no choice as to which ones to use.
if runtime.GOOS != "windows" {
flags.StringVarP(&conf.GraphDriver, "storage-driver", "s", "", "Storage driver to use")
}
flags.IntVar(&conf.Mtu, "mtu", 0, "Set the containers network MTU")
flags.BoolVar(&conf.RawLogs, "raw-logs", false, "Full timestamps without ANSI coloring")
flags.Var(opts.NewListOptsRef(&conf.DNS, opts.ValidateIPAddress), "dns", "DNS server to use")

Просмотреть файл

@ -40,6 +40,7 @@ import (
"github.com/docker/docker/pkg/pidfile"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/plugin"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
@ -210,6 +211,10 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
logrus.Fatalf("Error creating middlewares: %v", err)
}
if system.LCOWSupported() {
logrus.Warnln("LCOW support is enabled - this feature is incomplete")
}
d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
if err != nil {
return fmt.Errorf("Error starting daemon: %v", err)
@ -262,12 +267,6 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
logrus.Info("Daemon has completed initialization")
logrus.WithFields(logrus.Fields{
"version": dockerversion.Version,
"commit": dockerversion.GitCommit,
"graphdriver": d.GraphDriverName(),
}).Info("Docker daemon")
cli.d = d
initRouter(api, d, c)

Просмотреть файл

@ -7,6 +7,7 @@ import (
"net"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
@ -33,6 +34,7 @@ import (
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/restartmanager"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/volume"
@ -58,9 +60,8 @@ var (
errInvalidNetwork = fmt.Errorf("invalid network settings while building port map info")
)
// CommonContainer holds the fields for a container which are
// applicable across all platforms supported by the daemon.
type CommonContainer struct {
// Container holds the structure defining a container object.
type Container struct {
StreamConfig *stream.Config
// embed for Container to support states directly.
*State `json:"State"` // Needed for Engine API version <= 1.11
@ -78,6 +79,7 @@ type CommonContainer struct {
LogPath string
Name string
Driver string
Platform string
// MountLabel contains the options for the 'mount' command
MountLabel string
ProcessLabel string
@ -95,21 +97,31 @@ type CommonContainer struct {
LogCopier *logger.Copier `json:"-"`
restartManager restartmanager.RestartManager
attachContext *attachContext
// Fields here are specific to Unix platforms
AppArmorProfile string
HostnamePath string
HostsPath string
ShmPath string
ResolvConfPath string
SeccompProfile string
NoNewPrivileges bool
// Fields here are specific to Windows
NetworkSharedContainerID string
}
// NewBaseContainer creates a new container with its
// basic configuration.
func NewBaseContainer(id, root string) *Container {
return &Container{
CommonContainer: CommonContainer{
ID: id,
State: NewState(),
ExecCommands: exec.NewStore(),
Root: root,
MountPoints: make(map[string]*volume.MountPoint),
StreamConfig: stream.NewConfig(),
attachContext: &attachContext{},
},
ID: id,
State: NewState(),
ExecCommands: exec.NewStore(),
Root: root,
MountPoints: make(map[string]*volume.MountPoint),
StreamConfig: stream.NewConfig(),
attachContext: &attachContext{},
}
}
@ -133,6 +145,13 @@ func (container *Container) FromDisk() error {
return err
}
// Ensure the platform is set if blank. Assume it is the platform of the
// host OS if not, to ensure containers created before multiple-platform
// support are migrated
if container.Platform == "" {
container.Platform = runtime.GOOS
}
if err := label.ReserveLabel(container.ProcessLabel); err != nil {
return err
}
@ -986,3 +1005,31 @@ func (container *Container) ConfigsDirPath() string {
func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) string {
return filepath.Join(container.ConfigsDirPath(), configRef.ConfigID)
}
// CreateDaemonEnvironment creates a new environment variable slice for this container.
func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
// Setup environment
// TODO @jhowardmsft LCOW Support. This will need revisiting later.
platform := container.Platform
if platform == "" {
platform = runtime.GOOS
}
env := []string{}
if runtime.GOOS != "windows" || (runtime.GOOS == "windows" && system.LCOWSupported() && platform == "linux") {
env = []string{
"PATH=" + system.DefaultPathEnv(platform),
"HOSTNAME=" + container.Config.Hostname,
}
if tty {
env = append(env, "TERM=xterm")
}
env = append(env, linkedEnv...)
}
// because the env on the container can override certain default values
// we need to replace the 'env' keys where they match and append anything
// else.
//return ReplaceOrAppendEnvValues(linkedEnv, container.Config.Env)
foo := ReplaceOrAppendEnvValues(env, container.Config.Env)
return foo
}

Просмотреть файл

@ -11,9 +11,7 @@ import (
func TestContainerStopSignal(t *testing.T) {
c := &Container{
CommonContainer: CommonContainer{
Config: &container.Config{},
},
Config: &container.Config{},
}
def, err := signal.ParseSignal(signal.DefaultStopSignal)
@ -27,9 +25,7 @@ func TestContainerStopSignal(t *testing.T) {
}
c = &Container{
CommonContainer: CommonContainer{
Config: &container.Config{StopSignal: "SIGKILL"},
},
Config: &container.Config{StopSignal: "SIGKILL"},
}
s = c.StopSignal()
if s != 9 {
@ -39,9 +35,7 @@ func TestContainerStopSignal(t *testing.T) {
func TestContainerStopTimeout(t *testing.T) {
c := &Container{
CommonContainer: CommonContainer{
Config: &container.Config{},
},
Config: &container.Config{},
}
s := c.StopTimeout()
@ -51,9 +45,7 @@ func TestContainerStopTimeout(t *testing.T) {
stopTimeout := 15
c = &Container{
CommonContainer: CommonContainer{
Config: &container.Config{StopTimeout: &stopTimeout},
},
Config: &container.Config{StopTimeout: &stopTimeout},
}
s = c.StopSignal()
if s != 15 {

Просмотреть файл

@ -26,21 +26,6 @@ const (
containerSecretMountPath = "/run/secrets"
)
// Container holds the fields specific to unixen implementations.
// See CommonContainer for standard fields common to all containers.
type Container struct {
CommonContainer
// Fields below here are platform specific.
AppArmorProfile string
HostnamePath string
HostsPath string
ShmPath string
ResolvConfPath string
SeccompProfile string
NoNewPrivileges bool
}
// ExitStatus provides exit reasons for a container.
type ExitStatus struct {
// The exit code with which the container exited.
@ -50,27 +35,6 @@ type ExitStatus struct {
OOMKilled bool
}
// CreateDaemonEnvironment returns the list of all environment variables given the list of
// environment variables related to links.
// Sets PATH, HOSTNAME and if container.Config.Tty is set: TERM.
// The defaults set here do not override the values in container.Config.Env
func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
// Setup environment
env := []string{
"PATH=" + system.DefaultPathEnv,
"HOSTNAME=" + container.Config.Hostname,
}
if tty {
env = append(env, "TERM=xterm")
}
env = append(env, linkedEnv...)
// because the env on the container can override certain default values
// we need to replace the 'env' keys where they match and append anything
// else.
env = ReplaceOrAppendEnvValues(env, container.Config.Env)
return env
}
// TrySetNetworkMount attempts to set the network mounts given a provided destination and
// the path to use for it; return true if the given destination was a network mount file
func (container *Container) TrySetNetworkMount(destination string, path string) bool {

Просмотреть файл

@ -17,29 +17,12 @@ const (
containerInternalConfigsDirPath = `C:\ProgramData\Docker\internal\configs`
)
// Container holds fields specific to the Windows implementation. See
// CommonContainer for standard fields common to all containers.
type Container struct {
CommonContainer
// Fields below here are platform specific.
NetworkSharedContainerID string
}
// ExitStatus provides exit reasons for a container.
type ExitStatus struct {
// The exit code with which the container exited.
ExitCode int
}
// CreateDaemonEnvironment creates a new environment variable slice for this container.
func (container *Container) CreateDaemonEnvironment(_ bool, linkedEnv []string) []string {
// because the env on the container can override certain default values
// we need to replace the 'env' keys where they match and append anything
// else.
return ReplaceOrAppendEnvValues(linkedEnv, container.Config.Env)
}
// UnmountIpcMounts unmounts Ipc related mounts.
// This is a NOOP on windows.
func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
@ -60,7 +43,7 @@ func (container *Container) CreateSecretSymlinks() error {
if err != nil {
return err
}
if err := system.MkdirAll(filepath.Dir(resolvedPath), 0); err != nil {
if err := system.MkdirAll(filepath.Dir(resolvedPath), 0, ""); err != nil {
return err
}
if err := os.Symlink(filepath.Join(containerInternalSecretMountPath, r.SecretID), resolvedPath); err != nil {
@ -102,7 +85,7 @@ func (container *Container) CreateConfigSymlinks() error {
if err != nil {
return err
}
if err := system.MkdirAll(filepath.Dir(resolvedPath), 0); err != nil {
if err := system.MkdirAll(filepath.Dir(resolvedPath), 0, ""); err != nil {
return err
}
if err := os.Symlink(filepath.Join(containerInternalConfigsDirPath, configRef.ConfigID), resolvedPath); err != nil {

Просмотреть файл

@ -2,6 +2,7 @@ package daemon
import (
"io"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/reference"
@ -40,7 +41,7 @@ func (rl *releaseableLayer) Mount() (string, error) {
return rl.rwLayer.Mount("")
}
func (rl *releaseableLayer) Commit() (builder.ReleaseableLayer, error) {
func (rl *releaseableLayer) Commit(platform string) (builder.ReleaseableLayer, error) {
var chainID layer.ChainID
if rl.roLayer != nil {
chainID = rl.roLayer.ChainID()
@ -51,7 +52,7 @@ func (rl *releaseableLayer) Commit() (builder.ReleaseableLayer, error) {
return nil, err
}
newLayer, err := rl.layerStore.Register(stream, chainID)
newLayer, err := rl.layerStore.Register(stream, chainID, layer.Platform(platform))
if err != nil {
return nil, err
}
@ -114,7 +115,7 @@ func newReleasableLayerForImage(img *image.Image, layerStore layer.Store) (build
}
// TODO: could this use the regular daemon PullImage ?
func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (*image.Image, error) {
func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer, platform string) (*image.Image, error) {
ref, err := reference.ParseNormalizedNamed(name)
if err != nil {
return nil, err
@ -133,7 +134,7 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
pullRegistryAuth = &resolvedConfig
}
if err := daemon.pullImageWithReference(ctx, ref, nil, pullRegistryAuth, output); err != nil {
if err := daemon.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
return nil, err
}
return daemon.GetImage(name)
@ -144,7 +145,7 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
// leaking of layers.
func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
if refOrID == "" {
layer, err := newReleasableLayerForImage(nil, daemon.layerStore)
layer, err := newReleasableLayerForImage(nil, daemon.stores[opts.Platform].layerStore)
return nil, layer, err
}
@ -155,35 +156,38 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st
}
// TODO: shouldn't we error out if error is different from "not found" ?
if image != nil {
layer, err := newReleasableLayerForImage(image, daemon.layerStore)
layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
return image, layer, err
}
}
image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output)
image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
if err != nil {
return nil, nil, err
}
layer, err := newReleasableLayerForImage(image, daemon.layerStore)
layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
return image, layer, err
}
// CreateImage creates a new image by adding a config and ID to the image store.
// This is similar to LoadImage() except that it receives JSON encoded bytes of
// an image instead of a tar archive.
func (daemon *Daemon) CreateImage(config []byte, parent string) (builder.Image, error) {
id, err := daemon.imageStore.Create(config)
func (daemon *Daemon) CreateImage(config []byte, parent string, platform string) (builder.Image, error) {
if platform == "" {
platform = runtime.GOOS
}
id, err := daemon.stores[platform].imageStore.Create(config)
if err != nil {
return nil, errors.Wrapf(err, "failed to create image")
}
if parent != "" {
if err := daemon.imageStore.SetParent(id, image.ID(parent)); err != nil {
if err := daemon.stores[platform].imageStore.SetParent(id, image.ID(parent)); err != nil {
return nil, errors.Wrapf(err, "failed to set parent %s", parent)
}
}
return daemon.imageStore.Get(id)
return daemon.stores[platform].imageStore.Get(id)
}
// IDMappings returns uid/gid mappings for the builder

Просмотреть файл

@ -7,12 +7,12 @@ import (
)
// MakeImageCache creates a stateful image cache.
func (daemon *Daemon) MakeImageCache(sourceRefs []string) builder.ImageCache {
func (daemon *Daemon) MakeImageCache(sourceRefs []string, platform string) builder.ImageCache {
if len(sourceRefs) == 0 {
return cache.NewLocal(daemon.imageStore)
return cache.NewLocal(daemon.stores[platform].imageStore)
}
cache := cache.New(daemon.imageStore)
cache := cache.New(daemon.stores[platform].imageStore)
for _, ref := range sourceRefs {
img, err := daemon.GetImage(ref)

Просмотреть файл

@ -30,7 +30,7 @@ type Backend interface {
FindNetwork(idName string) (libnetwork.Network, error)
SetupIngress(clustertypes.NetworkCreateRequest, string) (<-chan struct{}, error)
ReleaseIngress() (<-chan struct{}, error)
PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
CreateManagedContainer(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
ContainerStop(name string, seconds *int) error

Просмотреть файл

@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"runtime"
"strings"
"syscall"
"time"
@ -20,6 +21,7 @@ import (
containerpkg "github.com/docker/docker/container"
"github.com/docker/docker/daemon/cluster/convert"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
"github.com/docker/docker/pkg/system"
"github.com/docker/libnetwork"
"github.com/docker/swarmkit/agent/exec"
"github.com/docker/swarmkit/api"
@ -88,7 +90,13 @@ func (c *containerAdapter) pullImage(ctx context.Context) error {
pr, pw := io.Pipe()
metaHeaders := map[string][]string{}
go func() {
err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw)
// TODO @jhowardmsft LCOW Support: This will need revisiting as
// the stack is built up to include LCOW support for swarm.
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
err := c.backend.PullImage(ctx, c.container.image(), "", platform, metaHeaders, authConfig, pw)
pw.CloseWithError(err)
}()

Просмотреть файл

@ -38,14 +38,12 @@ func TestHealthStates(t *testing.T) {
}
c := &container.Container{
CommonContainer: container.CommonContainer{
ID: "id",
Name: "name",
Config: &containertypes.Config{
Image: "image_name",
Labels: map[string]string{
"com.docker.swarm.task.id": "id",
},
ID: "id",
Name: "name",
Config: &containertypes.Config{
Image: "image_name",
Labels: map[string]string{
"com.docker.swarm.task.id": "id",
},
},
}

Просмотреть файл

@ -164,17 +164,17 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
parent = new(image.Image)
parent.RootFS = image.NewRootFS()
} else {
parent, err = daemon.imageStore.Get(container.ImageID)
parent, err = daemon.stores[container.Platform].imageStore.Get(container.ImageID)
if err != nil {
return "", err
}
}
l, err := daemon.layerStore.Register(rwTar, parent.RootFS.ChainID())
l, err := daemon.stores[container.Platform].layerStore.Register(rwTar, parent.RootFS.ChainID(), layer.Platform(container.Platform))
if err != nil {
return "", err
}
defer layer.ReleaseAndLog(daemon.layerStore, l)
defer layer.ReleaseAndLog(daemon.stores[container.Platform].layerStore, l)
containerConfig := c.ContainerConfig
if containerConfig == nil {
@ -188,18 +188,18 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
Config: newConfig,
DiffID: l.DiffID(),
}
config, err := json.Marshal(image.NewChildImage(parent, cc))
config, err := json.Marshal(image.NewChildImage(parent, cc, container.Platform))
if err != nil {
return "", err
}
id, err := daemon.imageStore.Create(config)
id, err := daemon.stores[container.Platform].imageStore.Create(config)
if err != nil {
return "", err
}
if container.ImageID != "" {
if err := daemon.imageStore.SetParent(id, container.ImageID); err != nil {
if err := daemon.stores[container.Platform].imageStore.SetParent(id, container.ImageID); err != nil {
return "", err
}
}
@ -218,7 +218,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
return "", err
}
}
if err := daemon.TagImageWithReference(id, newTag); err != nil {
if err := daemon.TagImageWithReference(id, container.Platform, newTag); err != nil {
return "", err
}
imageRef = reference.FamiliarString(newTag)

Просмотреть файл

@ -111,7 +111,7 @@ func (daemon *Daemon) Register(c *container.Container) {
daemon.idIndex.Add(c.ID)
}
func (daemon *Daemon) newContainer(name string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
func (daemon *Daemon) newContainer(name string, platform string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
var (
id string
err error
@ -144,8 +144,8 @@ func (daemon *Daemon) newContainer(name string, config *containertypes.Config, h
base.ImageID = imgID
base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
base.Name = name
base.Driver = daemon.GraphDriverName()
base.Driver = daemon.GraphDriverName(platform)
base.Platform = platform
return base, err
}

Просмотреть файл

@ -25,7 +25,7 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
logrus.Debugf("configs: setting up config dir: %s", localPath)
// create local config root
if err := system.MkdirAllWithACL(localPath, 0); err != nil {
if err := system.MkdirAllWithACL(localPath, 0, system.SddlAdministratorsLocalSystem); err != nil {
return errors.Wrap(err, "error creating config dir")
}
@ -98,7 +98,7 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
logrus.Debugf("secrets: setting up secret dir: %s", localMountPath)
// create local secret root
if err := system.MkdirAllWithACL(localMountPath, 0); err != nil {
if err := system.MkdirAllWithACL(localMountPath, 0, system.SddlAdministratorsLocalSystem); err != nil {
return errors.Wrap(err, "error creating secret local directory")
}

Просмотреть файл

@ -19,6 +19,7 @@ import (
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/runconfig"
"github.com/opencontainers/selinux/go-selinux/label"
)
@ -75,6 +76,16 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
err error
)
// TODO: @jhowardmsft LCOW support - at a later point, can remove the hard-coding
// to force the platform to be linux.
// Default the platform if not supplied
if params.Platform == "" {
params.Platform = runtime.GOOS
}
if params.Platform == "windows" && system.LCOWSupported() {
params.Platform = "linux"
}
if params.Config.Image != "" {
img, err = daemon.GetImage(params.Config.Image)
if err != nil {
@ -82,9 +93,23 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
}
if runtime.GOOS == "solaris" && img.OS != "solaris " {
return nil, errors.New("Platform on which parent image was created is not Solaris")
return nil, errors.New("platform on which parent image was created is not Solaris")
}
imgID = img.ID()
if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
return nil, errors.New("platform on which parent image was created is not Windows")
}
}
// Make sure the platform requested matches the image
if img != nil {
if params.Platform != img.Platform() {
// Ignore this in LCOW mode. @jhowardmsft TODO - This will need revisiting later.
if !(runtime.GOOS == "windows" && system.LCOWSupported()) {
return nil, fmt.Errorf("cannot create a %s container from a %s image", params.Platform, img.Platform())
}
}
}
if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
@ -95,7 +120,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
return nil, err
}
if container, err = daemon.newContainer(params.Name, params.Config, params.HostConfig, imgID, managed); err != nil {
if container, err = daemon.newContainer(params.Name, params.Platform, params.Config, params.HostConfig, imgID, managed); err != nil {
return nil, err
}
defer func() {
@ -215,7 +240,7 @@ func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig)
func (daemon *Daemon) setRWLayer(container *container.Container) error {
var layerID layer.ChainID
if container.ImageID != "" {
img, err := daemon.imageStore.Get(container.ImageID)
img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
if err != nil {
return err
}
@ -228,7 +253,7 @@ func (daemon *Daemon) setRWLayer(container *container.Container) error {
StorageOpt: container.HostConfig.StorageOpt,
}
rwLayer, err := daemon.layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
rwLayer, err := daemon.stores[container.Platform].layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
if err != nil {
return err
}

Просмотреть файл

@ -69,43 +69,49 @@ var (
errSystemNotSupported = errors.New("The Docker daemon is not supported on this platform.")
)
type daemonStore struct {
graphDriver string
imageRoot string
imageStore image.Store
layerStore layer.Store
distributionMetadataStore dmetadata.Store
referenceStore refstore.Store
}
// Daemon holds information about the Docker daemon.
type Daemon struct {
ID string
repository string
containers container.Store
execCommands *exec.Store
referenceStore refstore.Store
downloadManager *xfer.LayerDownloadManager
uploadManager *xfer.LayerUploadManager
distributionMetadataStore dmetadata.Store
trustKey libtrust.PrivateKey
idIndex *truncindex.TruncIndex
configStore *config.Config
statsCollector *stats.Collector
defaultLogConfig containertypes.LogConfig
RegistryService registry.Service
EventsService *events.Events
netController libnetwork.NetworkController
volumes *store.VolumeStore
discoveryWatcher discovery.Reloader
root string
seccompEnabled bool
apparmorEnabled bool
shutdown bool
idMappings *idtools.IDMappings
layerStore layer.Store
imageStore image.Store
PluginStore *plugin.Store // todo: remove
pluginManager *plugin.Manager
nameIndex *registrar.Registrar
linkIndex *linkIndex
containerd libcontainerd.Client
containerdRemote libcontainerd.Remote
defaultIsolation containertypes.Isolation // Default isolation mode on Windows
clusterProvider cluster.Provider
cluster Cluster
metricsPluginListener net.Listener
ID string
repository string
containers container.Store
execCommands *exec.Store
downloadManager *xfer.LayerDownloadManager
uploadManager *xfer.LayerUploadManager
trustKey libtrust.PrivateKey
idIndex *truncindex.TruncIndex
configStore *config.Config
statsCollector *stats.Collector
defaultLogConfig containertypes.LogConfig
RegistryService registry.Service
EventsService *events.Events
netController libnetwork.NetworkController
volumes *store.VolumeStore
discoveryWatcher discovery.Reloader
root string
seccompEnabled bool
apparmorEnabled bool
shutdown bool
idMappings *idtools.IDMappings
stores map[string]daemonStore // By container target platform
PluginStore *plugin.Store // todo: remove
pluginManager *plugin.Manager
nameIndex *registrar.Registrar
linkIndex *linkIndex
containerd libcontainerd.Client
containerdRemote libcontainerd.Remote
defaultIsolation containertypes.Isolation // Default isolation mode on Windows
clusterProvider cluster.Provider
cluster Cluster
metricsPluginListener net.Listener
machineMemory uint64
@ -137,10 +143,7 @@ func (daemon *Daemon) HasExperimental() bool {
}
func (daemon *Daemon) restore() error {
var (
currentDriver = daemon.GraphDriverName()
containers = make(map[string]*container.Container)
)
containers := make(map[string]*container.Container)
logrus.Info("Loading containers: start.")
@ -158,8 +161,9 @@ func (daemon *Daemon) restore() error {
}
// Ignore the container if it does not support the current driver being used by the graph
if (container.Driver == "" && currentDriver == "aufs") || container.Driver == currentDriver {
rwlayer, err := daemon.layerStore.GetRWLayer(container.ID)
currentDriverForContainerPlatform := daemon.stores[container.Platform].graphDriver
if (container.Driver == "" && currentDriverForContainerPlatform == "aufs") || container.Driver == currentDriverForContainerPlatform {
rwlayer, err := daemon.stores[container.Platform].layerStore.GetRWLayer(container.ID)
if err != nil {
logrus.Errorf("Failed to load container mount %v: %v", id, err)
continue
@ -590,14 +594,29 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
}
if runtime.GOOS == "windows" {
if err := system.MkdirAll(filepath.Join(config.Root, "credentialspecs"), 0); err != nil && !os.IsExist(err) {
if err := system.MkdirAll(filepath.Join(config.Root, "credentialspecs"), 0, ""); err != nil && !os.IsExist(err) {
return nil, err
}
}
driverName := os.Getenv("DOCKER_DRIVER")
if driverName == "" {
driverName = config.GraphDriver
// On Windows we don't support the environment variable, or a user supplied graphdriver
// as Windows has no choice in terms of which graphdrivers to use. It's a case of
// running Windows containers on Windows - windowsfilter, running Linux containers on Windows,
// lcow. Unix platforms however run a single graphdriver for all containers, and it can
// be set through an environment variable, a daemon start parameter, or chosen through
// initialization of the layerstore through driver priority order for example.
d.stores = make(map[string]daemonStore)
if runtime.GOOS == "windows" {
d.stores["windows"] = daemonStore{graphDriver: "windowsfilter"}
if system.LCOWSupported() {
d.stores["linux"] = daemonStore{graphDriver: "lcow"}
}
} else {
driverName := os.Getenv("DOCKER_DRIVER")
if driverName == "" {
driverName = config.GraphDriver
}
d.stores[runtime.GOOS] = daemonStore{graphDriver: driverName} // May still be empty. Layerstore init determines instead.
}
d.RegistryService = registryService
@ -625,40 +644,56 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
return nil, errors.Wrap(err, "couldn't create plugin manager")
}
d.layerStore, err = layer.NewStoreFromOptions(layer.StoreOptions{
StorePath: config.Root,
MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
GraphDriver: driverName,
GraphDriverOptions: config.GraphOptions,
IDMappings: idMappings,
PluginGetter: d.PluginStore,
ExperimentalEnabled: config.Experimental,
})
if err != nil {
return nil, err
var graphDrivers []string
for platform, ds := range d.stores {
ls, err := layer.NewStoreFromOptions(layer.StoreOptions{
StorePath: config.Root,
MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
GraphDriver: ds.graphDriver,
GraphDriverOptions: config.GraphOptions,
IDMappings: idMappings,
PluginGetter: d.PluginStore,
ExperimentalEnabled: config.Experimental,
Platform: platform,
})
if err != nil {
return nil, err
}
ds.graphDriver = ls.DriverName() // As layerstore may set the driver
ds.layerStore = ls
d.stores[platform] = ds
graphDrivers = append(graphDrivers, ls.DriverName())
}
graphDriver := d.layerStore.DriverName()
imageRoot := filepath.Join(config.Root, "image", graphDriver)
// Configure and validate the kernels security support
if err := configureKernelSecuritySupport(config, graphDriver); err != nil {
if err := configureKernelSecuritySupport(config, graphDrivers); err != nil {
return nil, err
}
logrus.Debugf("Max Concurrent Downloads: %d", *config.MaxConcurrentDownloads)
d.downloadManager = xfer.NewLayerDownloadManager(d.layerStore, *config.MaxConcurrentDownloads)
lsMap := make(map[string]layer.Store)
for platform, ds := range d.stores {
lsMap[platform] = ds.layerStore
}
d.downloadManager = xfer.NewLayerDownloadManager(lsMap, *config.MaxConcurrentDownloads)
logrus.Debugf("Max Concurrent Uploads: %d", *config.MaxConcurrentUploads)
d.uploadManager = xfer.NewLayerUploadManager(*config.MaxConcurrentUploads)
ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb"))
if err != nil {
return nil, err
}
for platform, ds := range d.stores {
imageRoot := filepath.Join(config.Root, "image", ds.graphDriver)
ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb"))
if err != nil {
return nil, err
}
d.imageStore, err = image.NewImageStore(ifs, d.layerStore)
if err != nil {
return nil, err
var is image.Store
is, err = image.NewImageStore(ifs, platform, ds.layerStore)
if err != nil {
return nil, err
}
ds.imageRoot = imageRoot
ds.imageStore = is
d.stores[platform] = ds
}
// Configure the volumes driver
@ -674,27 +709,35 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
trustDir := filepath.Join(config.Root, "trust")
if err := system.MkdirAll(trustDir, 0700); err != nil {
return nil, err
}
distributionMetadataStore, err := dmetadata.NewFSMetadataStore(filepath.Join(imageRoot, "distribution"))
if err != nil {
if err := system.MkdirAll(trustDir, 0700, ""); err != nil {
return nil, err
}
eventsService := events.New()
referenceStore, err := refstore.NewReferenceStore(filepath.Join(imageRoot, "repositories.json"))
if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store repositories: %s", err)
}
for platform, ds := range d.stores {
dms, err := dmetadata.NewFSMetadataStore(filepath.Join(ds.imageRoot, "distribution"), platform)
if err != nil {
return nil, err
}
migrationStart := time.Now()
if err := v1.Migrate(config.Root, graphDriver, d.layerStore, d.imageStore, referenceStore, distributionMetadataStore); err != nil {
logrus.Errorf("Graph migration failed: %q. Your old graph data was found to be too inconsistent for upgrading to content-addressable storage. Some of the old data was probably not upgraded. We recommend starting over with a clean storage directory if possible.", err)
rs, err := refstore.NewReferenceStore(filepath.Join(ds.imageRoot, "repositories.json"), platform)
if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store repositories: %s", err)
}
ds.distributionMetadataStore = dms
ds.referenceStore = rs
d.stores[platform] = ds
// No content-addressability migration on Windows as it never supported pre-CA
if runtime.GOOS != "windows" {
migrationStart := time.Now()
if err := v1.Migrate(config.Root, ds.graphDriver, ds.layerStore, ds.imageStore, rs, dms); err != nil {
logrus.Errorf("Graph migration failed: %q. Your old graph data was found to be too inconsistent for upgrading to content-addressable storage. Some of the old data was probably not upgraded. We recommend starting over with a clean storage directory if possible.", err)
}
logrus.Infof("Graph migration to content-addressability took %.2f seconds", time.Since(migrationStart).Seconds())
}
}
logrus.Infof("Graph migration to content-addressability took %.2f seconds", time.Since(migrationStart).Seconds())
// Discovery is only enabled when the daemon is launched with an address to advertise. When
// initialized, the daemon is registered and we can store the discovery backend as it's read-only
@ -713,8 +756,6 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
d.repository = daemonRepo
d.containers = container.NewMemoryStore()
d.execCommands = exec.NewStore()
d.referenceStore = referenceStore
d.distributionMetadataStore = distributionMetadataStore
d.trustKey = trustKey
d.idIndex = truncindex.NewTruncIndex([]string{})
d.statsCollector = d.newStatsCollector(1 * time.Second)
@ -761,6 +802,22 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
engineCpus.Set(float64(info.NCPU))
engineMemory.Set(float64(info.MemTotal))
gd := ""
for platform, ds := range d.stores {
if len(gd) > 0 {
gd += ", "
}
gd += ds.graphDriver
if len(d.stores) > 1 {
gd = fmt.Sprintf("%s (%s)", gd, platform)
}
}
logrus.WithFields(logrus.Fields{
"version": dockerversion.Version,
"commit": dockerversion.GitCommit,
"graphdriver(s)": gd,
}).Info("Docker daemon")
return d, nil
}
@ -867,7 +924,7 @@ func (daemon *Daemon) Shutdown() error {
logrus.Errorf("Stop container error: %v", err)
return
}
if mountid, err := daemon.layerStore.GetMountID(c.ID); err == nil {
if mountid, err := daemon.stores[c.Platform].layerStore.GetMountID(c.ID); err == nil {
daemon.cleanupMountsByID(mountid)
}
logrus.Debugf("container stopped %s", c.ID)
@ -880,9 +937,11 @@ func (daemon *Daemon) Shutdown() error {
}
}
if daemon.layerStore != nil {
if err := daemon.layerStore.Cleanup(); err != nil {
logrus.Errorf("Error during layer Store.Cleanup(): %v", err)
for platform, ds := range daemon.stores {
if ds.layerStore != nil {
if err := ds.layerStore.Cleanup(); err != nil {
logrus.Errorf("Error during layer Store.Cleanup(): %v %s", err, platform)
}
}
}
@ -925,7 +984,7 @@ func (daemon *Daemon) Mount(container *container.Container) error {
if container.BaseFS != "" && runtime.GOOS != "windows" {
daemon.Unmount(container)
return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
daemon.GraphDriverName(), container.ID, container.BaseFS, dir)
daemon.GraphDriverName(container.Platform), container.ID, container.BaseFS, dir)
}
}
container.BaseFS = dir // TODO: combine these fields
@ -967,8 +1026,8 @@ func (daemon *Daemon) Subnets() ([]net.IPNet, []net.IPNet) {
}
// GraphDriverName returns the name of the graph driver used by the layer.Store
func (daemon *Daemon) GraphDriverName() string {
return daemon.layerStore.DriverName()
func (daemon *Daemon) GraphDriverName(platform string) string {
return daemon.stores[platform].layerStore.DriverName()
}
// prepareTempDir prepares and returns the default directory to use

Просмотреть файл

@ -353,7 +353,7 @@ func configureMaxThreads(config *Config) error {
}
// configureKernelSecuritySupport configures and validate security support for the kernel
func configureKernelSecuritySupport(config *Config, driverName string) error {
func configureKernelSecuritySupport(config *config.Config, driverNames []string) error {
return nil
}

Просмотреть файл

@ -27,38 +27,28 @@ import (
func TestGetContainer(t *testing.T) {
c1 := &container.Container{
CommonContainer: container.CommonContainer{
ID: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
Name: "tender_bardeen",
},
ID: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
Name: "tender_bardeen",
}
c2 := &container.Container{
CommonContainer: container.CommonContainer{
ID: "3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de",
Name: "drunk_hawking",
},
ID: "3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de",
Name: "drunk_hawking",
}
c3 := &container.Container{
CommonContainer: container.CommonContainer{
ID: "3cdbd1aa394fd68559fd1441d6eff2abfafdcba06e72d2febdba229008b0bf57",
Name: "3cdbd1aa",
},
ID: "3cdbd1aa394fd68559fd1441d6eff2abfafdcba06e72d2febdba229008b0bf57",
Name: "3cdbd1aa",
}
c4 := &container.Container{
CommonContainer: container.CommonContainer{
ID: "75fb0b800922abdbef2d27e60abcdfaf7fb0698b2a96d22d3354da361a6ff4a5",
Name: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
},
ID: "75fb0b800922abdbef2d27e60abcdfaf7fb0698b2a96d22d3354da361a6ff4a5",
Name: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
}
c5 := &container.Container{
CommonContainer: container.CommonContainer{
ID: "d22d69a2b8960bf7fafdcba06e72d2febdba960bf7fafdcba06e72d2f9008b060b",
Name: "d22d69a2b896",
},
ID: "d22d69a2b8960bf7fafdcba06e72d2febdba960bf7fafdcba06e72d2f9008b060b",
Name: "d22d69a2b896",
}
store := container.NewMemoryStore()
@ -184,7 +174,7 @@ func TestContainerInitDNS(t *testing.T) {
"UpdateDns":false,"Volumes":{},"VolumesRW":{},"AppliedVolumesFrom":null}`
// Container struct only used to retrieve path to config file
container := &container.Container{CommonContainer: container.CommonContainer{Root: containerPath}}
container := &container.Container{Root: containerPath}
configPath, err := container.ConfigPath()
if err != nil {
t.Fatal(err)

Просмотреть файл

@ -702,14 +702,22 @@ func overlaySupportsSelinux() (bool, error) {
}
// configureKernelSecuritySupport configures and validates security support for the kernel
func configureKernelSecuritySupport(config *config.Config, driverName string) error {
func configureKernelSecuritySupport(config *config.Config, driverNames []string) error {
if config.EnableSelinuxSupport {
if !selinuxEnabled() {
logrus.Warn("Docker could not enable SELinux on the host system")
return nil
}
if driverName == "overlay" || driverName == "overlay2" {
overlayFound := false
for _, d := range driverNames {
if d == "overlay" || d == "overlay2" {
overlayFound = true
break
}
}
if overlayFound {
// If driver is overlay or overlay2, make sure kernel
// supports selinux with overlay.
supported, err := overlaySupportsSelinux()
@ -718,7 +726,7 @@ func configureKernelSecuritySupport(config *config.Config, driverName string) er
}
if !supported {
logrus.Warnf("SELinux is not supported with the %s graph driver on this kernel", driverName)
logrus.Warnf("SELinux is not supported with the %v graph driver on this kernel", driverNames)
}
}
} else {

Просмотреть файл

@ -260,7 +260,7 @@ func checkSystem() error {
}
// configureKernelSecuritySupport configures and validate security support for the kernel
func configureKernelSecuritySupport(config *config.Config, driverName string) error {
func configureKernelSecuritySupport(config *config.Config, driverNames []string) error {
return nil
}
@ -465,7 +465,7 @@ func setupRemappedRoot(config *config.Config) (*idtools.IDMappings, error) {
func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPair) error {
config.Root = rootDir
// Create the root directory if it doesn't exists
if err := system.MkdirAllWithACL(config.Root, 0); err != nil && !os.IsExist(err) {
if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil && !os.IsExist(err) {
return err
}
return nil
@ -486,6 +486,11 @@ func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig
// conditionalMountOnStart is a platform specific helper function during the
// container start to call mount.
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
// Bail out now for Linux containers
if system.LCOWSupported() && container.Platform != "windows" {
return nil
}
// We do not mount if a Hyper-V container
if !daemon.runAsHyperVContainer(container.HostConfig) {
return daemon.Mount(container)
@ -496,6 +501,11 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
// conditionalUnmountOnCleanup is a platform specific helper function called
// during the cleanup of a container to unmount.
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
// Bail out now for Linux containers
if system.LCOWSupported() && container.Platform != "windows" {
return nil
}
// We do not unmount if a Hyper-V container
if !daemon.runAsHyperVContainer(container.HostConfig) {
return daemon.Unmount(container)

Просмотреть файл

@ -115,10 +115,10 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
// When container creation fails and `RWLayer` has not been created yet, we
// do not call `ReleaseRWLayer`
if container.RWLayer != nil {
metadata, err := daemon.layerStore.ReleaseRWLayer(container.RWLayer)
metadata, err := daemon.stores[container.Platform].layerStore.ReleaseRWLayer(container.RWLayer)
layer.LogReleaseMetadata(metadata)
if err != nil && err != layer.ErrMountDoesNotExist {
return errors.Wrapf(err, "driver %q failed to remove root filesystem for %s", daemon.GraphDriverName(), container.ID)
return errors.Wrapf(err, "driver %q failed to remove root filesystem for %s", daemon.GraphDriverName(container.Platform), container.ID)
}
}

Просмотреть файл

@ -26,11 +26,9 @@ func newDaemonWithTmpRoot(t *testing.T) (*Daemon, func()) {
func newContainerWithState(state *container.State) *container.Container {
return &container.Container{
CommonContainer: container.CommonContainer{
ID: "test",
State: state,
Config: &containertypes.Config{},
},
ID: "test",
State: state,
Config: &containertypes.Config{},
}
}

Просмотреть файл

@ -15,12 +15,12 @@ import (
"github.com/opencontainers/go-digest"
)
func (daemon *Daemon) getLayerRefs() map[layer.ChainID]int {
tmpImages := daemon.imageStore.Map()
func (daemon *Daemon) getLayerRefs(platform string) map[layer.ChainID]int {
tmpImages := daemon.stores[platform].imageStore.Map()
layerRefs := map[layer.ChainID]int{}
for id, img := range tmpImages {
dgst := digest.Digest(id)
if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
if len(daemon.stores[platform].referenceStore.References(dgst)) == 0 && len(daemon.stores[platform].imageStore.Children(id)) != 0 {
continue
}
@ -53,6 +53,7 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
}
// Get all top images with extra attributes
// TODO @jhowardmsft LCOW. This may need revisiting
allImages, err := daemon.Images(filters.NewArgs(), false, true)
if err != nil {
return nil, fmt.Errorf("failed to retrieve image list: %v", err)
@ -94,23 +95,26 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
}
// Get total layers size on disk
layerRefs := daemon.getLayerRefs()
allLayers := daemon.layerStore.Map()
var allLayersSize int64
for _, l := range allLayers {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
size, err := l.DiffSize()
if err == nil {
if _, ok := layerRefs[l.ChainID()]; ok {
allLayersSize += size
for platform := range daemon.stores {
layerRefs := daemon.getLayerRefs(platform)
allLayers := daemon.stores[platform].layerStore.Map()
var allLayersSize int64
for _, l := range allLayers {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
size, err := l.DiffSize()
if err == nil {
if _, ok := layerRefs[l.ChainID()]; ok {
allLayersSize += size
} else {
logrus.Warnf("found leaked image layer %v platform %s", l.ChainID(), platform)
}
} else {
logrus.Warnf("found leaked image layer %v", l.ChainID())
logrus.Warnf("failed to get diff size for layer %v %s", l.ChainID(), platform)
}
} else {
logrus.Warnf("failed to get diff size for layer %v", l.ChainID())
}
}
}

Просмотреть файл

@ -16,15 +16,13 @@ func TestLogContainerEventCopyLabels(t *testing.T) {
defer e.Evict(l)
container := &container.Container{
CommonContainer: container.CommonContainer{
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Image: "image_name",
Labels: map[string]string{
"node": "1",
"os": "alpine",
},
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Image: "image_name",
Labels: map[string]string{
"node": "1",
"os": "alpine",
},
},
}
@ -49,14 +47,12 @@ func TestLogContainerEventWithAttributes(t *testing.T) {
defer e.Evict(l)
container := &container.Container{
CommonContainer: container.CommonContainer{
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Labels: map[string]string{
"node": "1",
"os": "alpine",
},
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Labels: map[string]string{
"node": "1",
"os": "alpine",
},
},
}

Просмотреть файл

@ -3,6 +3,8 @@
package daemon
import (
"runtime"
"github.com/Sirupsen/logrus"
)
@ -13,17 +15,17 @@ func (daemon *Daemon) getSize(containerID string) (int64, int64) {
err error
)
rwlayer, err := daemon.layerStore.GetRWLayer(containerID)
rwlayer, err := daemon.stores[runtime.GOOS].layerStore.GetRWLayer(containerID)
if err != nil {
logrus.Errorf("Failed to compute size of container rootfs %v: %v", containerID, err)
return sizeRw, sizeRootfs
}
defer daemon.layerStore.ReleaseRWLayer(rwlayer)
defer daemon.stores[runtime.GOOS].layerStore.ReleaseRWLayer(rwlayer)
sizeRw, err = rwlayer.Size()
if err != nil {
logrus.Errorf("Driver %s couldn't return diff size of container %s: %s",
daemon.GraphDriverName(), containerID, err)
daemon.GraphDriverName(runtime.GOOS), containerID, err)
// FIXME: GetSize should return an error. Not changing it now in case
// there is a side-effect.
sizeRw = -1

Просмотреть файл

@ -0,0 +1,497 @@
// +build windows
package lcow
// Maintainer: @jhowardmsft
// Graph-driver for Linux Containers On Windows (LCOW)
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/system"
"github.com/jhowardmsft/opengcs/gogcs/client"
)
// init registers the LCOW driver to the register.
func init() {
graphdriver.Register("lcow", InitLCOW)
}
const (
// sandboxFilename is the name of the file containing a layers sandbox (read-write layer)
sandboxFilename = "sandbox.vhdx"
)
// cacheType is our internal structure representing an item in our local cache
// of things that have been mounted.
type cacheType struct {
uvmPath string // Path in utility VM
hostPath string // Path on host
refCount int // How many times its been mounted
isSandbox bool // True if a sandbox
}
// Driver represents an LCOW graph driver.
type Driver struct {
// homeDir is the hostpath where we're storing everything
homeDir string
// cachedSandboxFile is the location of the local default-sized cached sandbox
cachedSandboxFile string
// options are the graphdriver options we are initialised with
options []string
// JJH LIFETIME TODO - Remove this and move up to daemon. For now, a global service utility-VM
config client.Config
// it is safe for windows to use a cache here because it does not support
// restoring containers when the daemon dies.
// cacheMu is the mutex protection add/update/deletes to our cache
cacheMu sync.Mutex
// cache is the cache of all the IDs we've mounted/unmounted.
cache map[string]cacheType
}
// InitLCOW returns a new LCOW storage driver.
func InitLCOW(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
title := "lcowdriver: init:"
logrus.Debugf("%s %s", title, home)
d := &Driver{
homeDir: home,
options: options,
cachedSandboxFile: filepath.Join(home, "cache", sandboxFilename),
cache: make(map[string]cacheType),
}
if err := idtools.MkdirAllAs(home, 0700, 0, 0); err != nil {
return nil, fmt.Errorf("%s failed to create '%s': %v", title, home, err)
}
// Cache directory for blank sandbox so don't have to pull it from the service VM each time
if err := idtools.MkdirAllAs(filepath.Dir(d.cachedSandboxFile), 0700, 0, 0); err != nil {
return nil, fmt.Errorf("%s failed to create '%s': %v", title, home, err)
}
return d, nil
}
// startUvm starts the service utility VM if it isn't running.
// TODO @jhowardmsft. This will change before RS3 ships as we move to a model of one
// service VM globally to a service VM per container (or offline operation). However,
// for the initial bring-up of LCOW, this is acceptable.
func (d *Driver) startUvm(context string) error {
// Nothing to do if it's already running
if d.config.Uvm != nil {
return nil
}
// So we need to start it. Generate a default configuration
if err := d.config.GenerateDefault(d.options); err != nil {
return fmt.Errorf("failed to generate default gogcs configuration (%s): %s", context, err)
}
d.config.Name = "LinuxServiceVM" // TODO @jhowardmsft - This requires an in-flight platform change. Can't hard code it to this longer term
if err := d.config.Create(); err != nil {
return fmt.Errorf("failed to start utility VM (%s): %s", context, err)
}
return nil
}
// terminateUvm terminates the service utility VM if its running.
func (d *Driver) terminateUvm(context string) error {
// Nothing to do if it's not running
if d.config.Uvm == nil {
return nil
}
// FIXME: @jhowardmsft
// This isn't thread-safe yet, but will change anyway with the lifetime
// changes and multiple instances. Defering that work for now.
uvm := d.config.Uvm
d.config.Uvm = nil
if err := uvm.Terminate(); err != nil {
return fmt.Errorf("failed to terminate utility VM (%s): %s", context, err)
}
if err := uvm.WaitTimeout(time.Duration(d.config.UvmTimeoutSeconds) * time.Second); err != nil {
return fmt.Errorf("failed waiting for utility VM to terminate (%s): %s", context, err)
}
return nil
}
// String returns the string representation of a driver. This should match
// the name the graph driver has been registered with.
func (d *Driver) String() string {
return "lcow"
}
// Status returns the status of the driver.
func (d *Driver) Status() [][2]string {
return [][2]string{
{"LCOW", ""},
}
}
// Exists returns true if the given id is registered with this driver.
func (d *Driver) Exists(id string) bool {
_, err := os.Lstat(d.dir(id))
logrus.Debugf("lcowdriver: exists: id %s %t", id, err == nil)
return err == nil
}
// CreateReadWrite creates a layer that is writable for use as a container
// file system. That equates to creating a sandbox VHDx.
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
logrus.Debugf("lcowdriver: createreadwrite: id %s", id)
if err := d.startUvm("createreadwrite"); err != nil {
return err
}
if err := d.Create(id, parent, opts); err != nil {
return err
}
return d.config.CreateSandbox(filepath.Join(d.dir(id), sandboxFilename), client.DefaultSandboxSizeMB, d.cachedSandboxFile)
}
// Create creates a new read-only layer with the given id.
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
logrus.Debugf("lcowdriver: create: id %s parent: %s", id, parent)
parentChain, err := d.getLayerChain(parent)
if err != nil {
return err
}
var layerChain []string
if parent != "" {
if !d.Exists(parent) {
return fmt.Errorf("lcowdriver: cannot create read-only layer with missing parent %s", parent)
}
layerChain = []string{d.dir(parent)}
}
layerChain = append(layerChain, parentChain...)
// Make sure layers are created with the correct ACL so that VMs can access them.
layerPath := d.dir(id)
logrus.Debugf("lcowdriver: create: id %s: creating layerPath %s", id, layerPath)
// Make sure the layers are created with the correct ACL so that VMs can access them.
if err := system.MkdirAllWithACL(layerPath, 755, system.SddlNtvmAdministratorsLocalSystem); err != nil {
return err
}
if err := d.setLayerChain(id, layerChain); err != nil {
if err2 := os.RemoveAll(layerPath); err2 != nil {
logrus.Warnf("Failed to remove layer %s: %s", layerPath, err2)
}
return err
}
logrus.Debugf("lcowdriver: createreadwrite: id %s: success", id)
return nil
}
// Remove unmounts and removes the dir information.
func (d *Driver) Remove(id string) error {
logrus.Debugf("lcowdriver: remove: id %s", id)
tmpID := fmt.Sprintf("%s-removing", id)
tmpLayerPath := d.dir(tmpID)
layerPath := d.dir(id)
logrus.Debugf("lcowdriver: remove: id %s: layerPath %s", id, layerPath)
if err := os.Rename(layerPath, tmpLayerPath); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.RemoveAll(tmpLayerPath); err != nil {
return err
}
logrus.Debugf("lcowdriver: remove: id %s: layerPath %s succeeded", id, layerPath)
return nil
}
// Get returns the rootfs path for the id. It is reference counted and
// effectively can be thought of as a "mount the layer into the utility
// vm if it isn't already"
func (d *Driver) Get(id, mountLabel string) (string, error) {
dir, _, _, err := d.getEx(id)
return dir, err
}
// getEx is Get, but also returns the cache-entry and the size of the VHD
func (d *Driver) getEx(id string) (string, cacheType, int64, error) {
title := "lcowdriver: getEx"
logrus.Debugf("%s %s", title, id)
if err := d.startUvm(fmt.Sprintf("getex %s", id)); err != nil {
logrus.Debugf("%s failed to start utility vm: %s", title, err)
return "", cacheType{}, 0, err
}
// Work out what we are working on
vhdFilename, vhdSize, isSandbox, err := client.LayerVhdDetails(d.dir(id))
if err != nil {
logrus.Debugf("%s failed to get LayerVhdDetails from %s: %s", title, d.dir(id), err)
return "", cacheType{}, 0, fmt.Errorf("%s failed to open layer or sandbox VHD to open in %s: %s", title, d.dir(id), err)
}
logrus.Debugf("%s %s, size %d, isSandbox %t", title, vhdFilename, vhdSize, isSandbox)
hotAddRequired := false
d.cacheMu.Lock()
var cacheEntry cacheType
if _, ok := d.cache[id]; !ok {
// The item is not currently in the cache.
//
// Sandboxes need hot-adding in the case that there is a single global utility VM
// This will change for multiple instances with the lifetime changes.
if isSandbox {
hotAddRequired = true
}
d.cache[id] = cacheType{
uvmPath: fmt.Sprintf("/mnt/%s", id),
refCount: 1,
isSandbox: isSandbox,
hostPath: vhdFilename,
}
} else {
// Increment the reference counter in the cache.
cacheEntry = d.cache[id]
cacheEntry.refCount++
d.cache[id] = cacheEntry
}
cacheEntry = d.cache[id]
logrus.Debugf("%s %s: isSandbox %t, refCount %d", title, id, cacheEntry.isSandbox, cacheEntry.refCount)
d.cacheMu.Unlock()
if hotAddRequired {
logrus.Debugf("%s %s: Hot-Adding %s", title, id, vhdFilename)
if err := d.config.HotAddVhd(vhdFilename, cacheEntry.uvmPath); err != nil {
return "", cacheType{}, 0, fmt.Errorf("%s hot add %s failed: %s", title, vhdFilename, err)
}
}
logrus.Debugf("%s %s success. %s: %+v: size %d", title, id, d.dir(id), cacheEntry, vhdSize)
return d.dir(id), cacheEntry, vhdSize, nil
}
// Put does the reverse of get. If there are no more references to
// the layer, it unmounts it from the utility VM.
func (d *Driver) Put(id string) error {
title := "lcowdriver: put"
logrus.Debugf("%s %s", title, id)
if err := d.startUvm(fmt.Sprintf("put %s", id)); err != nil {
return err
}
d.cacheMu.Lock()
// Bad-news if unmounting something that isn't in the cache.
entry, ok := d.cache[id]
if !ok {
d.cacheMu.Unlock()
return fmt.Errorf("%s possible ref-count error, or invalid id was passed to the graphdriver. Cannot handle id %s as it's not in the cache", title, id)
}
// Are we just decrementing the reference count
if entry.refCount > 1 {
entry.refCount--
d.cache[id] = entry
logrus.Debugf("%s %s: refCount decremented to %d", title, id, entry.refCount)
d.cacheMu.Unlock()
return nil
}
// No more references, so tear it down if previously hot-added
if entry.isSandbox {
logrus.Debugf("%s %s: Hot-Removing %s", title, id, entry.hostPath)
if err := d.config.HotRemoveVhd(entry.hostPath); err != nil {
d.cacheMu.Unlock()
return fmt.Errorf("%s failed to hot-remove %s from service utility VM: %s", title, entry.hostPath, err)
}
}
// @jhowardmsft TEMPORARY FIX WHILE WAITING FOR HOT-REMOVE TO BE FIXED IN PLATFORM
//d.terminateUvm(fmt.Sprintf("put %s", id))
// Remove from the cache map.
delete(d.cache, id)
d.cacheMu.Unlock()
logrus.Debugf("%s %s: refCount 0. %s (%s) completed successfully", title, id, entry.hostPath, entry.uvmPath)
return nil
}
// Cleanup ensures the information the driver stores is properly removed.
// We use this opportunity to cleanup any -removing folders which may be
// still left if the daemon was killed while it was removing a layer.
func (d *Driver) Cleanup() error {
title := "lcowdriver: cleanup"
logrus.Debugf(title)
d.cacheMu.Lock()
for k, v := range d.cache {
logrus.Debugf("%s cache entry: %s: %+v", title, k, v)
if v.refCount > 0 {
logrus.Warnf("%s leaked %s: %+v", title, k, v)
}
}
d.cacheMu.Unlock()
items, err := ioutil.ReadDir(d.homeDir)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
// Note we don't return an error below - it's possible the files
// are locked. However, next time around after the daemon exits,
// we likely will be able to to cleanup successfully. Instead we log
// warnings if there are errors.
for _, item := range items {
if item.IsDir() && strings.HasSuffix(item.Name(), "-removing") {
if err := os.RemoveAll(filepath.Join(d.homeDir, item.Name())); err != nil {
logrus.Warnf("%s failed to cleanup %s: %s", title, item.Name(), err)
} else {
logrus.Infof("%s cleaned up %s", title, item.Name())
}
}
}
return nil
}
// Diff takes a layer (and it's parent layer which may be null, but
// is ignored by this implementation below) and returns a reader for
// a tarstream representing the layers contents. The id could be
// a read-only "layer.vhd" or a read-write "sandbox.vhdx". The semantics
// of this function dictate that the layer is already mounted.
func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
title := "lcowdriver: diff:"
logrus.Debugf("%s id %s", title, id)
if err := d.startUvm(fmt.Sprintf("diff %s", id)); err != nil {
return nil, err
}
d.cacheMu.Lock()
if _, ok := d.cache[id]; !ok {
d.cacheMu.Unlock()
return nil, fmt.Errorf("%s fail as %s is not in the cache", title, id)
}
cacheEntry := d.cache[id]
d.cacheMu.Unlock()
// Stat to get size
fileInfo, err := os.Stat(cacheEntry.hostPath)
if err != nil {
return nil, fmt.Errorf("%s failed to stat %s: %s", title, cacheEntry.hostPath, err)
}
// Then obtain the tar stream for it
logrus.Debugf("%s %s, size %d, isSandbox %t", title, cacheEntry.hostPath, fileInfo.Size(), cacheEntry.isSandbox)
tarReadCloser, err := d.config.VhdToTar(cacheEntry.hostPath, cacheEntry.uvmPath, cacheEntry.isSandbox, fileInfo.Size())
if err != nil {
return nil, fmt.Errorf("%s failed to export layer to tar stream for id: %s, parent: %s : %s", title, id, parent, err)
}
logrus.Debugf("%s id %s parent %s completed successfully", title, id, parent)
return tarReadCloser, nil
}
// ApplyDiff extracts the changeset from the given diff into the
// layer with the specified id and parent, returning the size of the
// new layer in bytes. The layer should not be mounted when calling
// this function. Another way of describing this is that ApplyDiff writes
// to a new layer (a VHD in LCOW) the contents of a tarstream it's given.
func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
logrus.Debugf("lcowdriver: applydiff: id %s", id)
if err := d.startUvm(fmt.Sprintf("applydiff %s", id)); err != nil {
return 0, err
}
return d.config.TarToVhd(filepath.Join(d.homeDir, id, "layer.vhd"), diff)
}
// Changes produces a list of changes between the specified layer
// and its parent layer. If parent is "", then all changes will be ADD changes.
// The layer should not be mounted when calling this function.
func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
logrus.Debugf("lcowdriver: changes: id %s parent %s", id, parent)
// TODO @gupta-ak. Needs implementation with assistance from service VM
return nil, nil
}
// DiffSize calculates the changes between the specified layer
// and its parent and returns the size in bytes of the changes
// relative to its base filesystem directory.
func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
logrus.Debugf("lcowdriver: diffsize: id %s", id)
// TODO @gupta-ak. Needs implementation with assistance from service VM
return 0, nil
}
// GetMetadata returns custom driver information.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
logrus.Debugf("lcowdriver: getmetadata: id %s", id)
m := make(map[string]string)
m["dir"] = d.dir(id)
return m, nil
}
// dir returns the absolute path to the layer.
func (d *Driver) dir(id string) string {
return filepath.Join(d.homeDir, filepath.Base(id))
}
// getLayerChain returns the layer chain information.
func (d *Driver) getLayerChain(id string) ([]string, error) {
jPath := filepath.Join(d.dir(id), "layerchain.json")
logrus.Debugf("lcowdriver: getlayerchain: id %s json %s", id, jPath)
content, err := ioutil.ReadFile(jPath)
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("lcowdriver: getlayerchain: %s unable to read layerchain file %s: %s", id, jPath, err)
}
var layerChain []string
err = json.Unmarshal(content, &layerChain)
if err != nil {
return nil, fmt.Errorf("lcowdriver: getlayerchain: %s failed to unmarshall layerchain file %s: %s", id, jPath, err)
}
return layerChain, nil
}
// setLayerChain stores the layer chain information on disk.
func (d *Driver) setLayerChain(id string, chain []string) error {
content, err := json.Marshal(&chain)
if err != nil {
return fmt.Errorf("lcowdriver: setlayerchain: %s failed to marshall layerchain json: %s", id, err)
}
jPath := filepath.Join(d.dir(id), "layerchain.json")
logrus.Debugf("lcowdriver: setlayerchain: id %s json %s", id, jPath)
err = ioutil.WriteFile(jPath, content, 0600)
if err != nil {
return fmt.Errorf("lcowdriver: setlayerchain: %s failed to write layerchain file: %s", id, err)
}
return nil
}

Просмотреть файл

@ -1,6 +1,7 @@
package register
import (
// register the windows graph driver
// register the windows graph drivers
_ "github.com/docker/docker/daemon/graphdriver/lcow"
_ "github.com/docker/docker/daemon/graphdriver/windows"
)

Просмотреть файл

@ -94,6 +94,10 @@ func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap)
return nil, fmt.Errorf("%s is on an ReFS volume - ReFS volumes are not supported", home)
}
if err := idtools.MkdirAllAs(home, 0700, 0, 0); err != nil {
return nil, fmt.Errorf("windowsfilter failed to create '%s': %v", home, err)
}
d := &Driver{
info: hcsshim.DriverInfo{
HomeDir: home,
@ -149,8 +153,19 @@ func (d *Driver) Status() [][2]string {
}
}
// panicIfUsedByLcow does exactly what it says.
// TODO @jhowardmsft - this is an temporary measure for the bring-up of
// Linux containers on Windows. It is a failsafe to ensure that the right
// graphdriver is used.
func panicIfUsedByLcow() {
if system.LCOWSupported() {
panic("inconsistency - windowsfilter graphdriver should not be used when in LCOW mode")
}
}
// Exists returns true if the given id is registered with this driver.
func (d *Driver) Exists(id string) bool {
panicIfUsedByLcow()
rID, err := d.resolveID(id)
if err != nil {
return false
@ -165,6 +180,7 @@ func (d *Driver) Exists(id string) bool {
// CreateReadWrite creates a layer that is writable for use as a container
// file system.
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
panicIfUsedByLcow()
if opts != nil {
return d.create(id, parent, opts.MountLabel, false, opts.StorageOpt)
}
@ -173,6 +189,7 @@ func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts
// Create creates a new read-only layer with the given id.
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
panicIfUsedByLcow()
if opts != nil {
return d.create(id, parent, opts.MountLabel, true, opts.StorageOpt)
}
@ -256,6 +273,7 @@ func (d *Driver) dir(id string) string {
// Remove unmounts and removes the dir information.
func (d *Driver) Remove(id string) error {
panicIfUsedByLcow()
rID, err := d.resolveID(id)
if err != nil {
return err
@ -337,6 +355,7 @@ func (d *Driver) Remove(id string) error {
// Get returns the rootfs path for the id. This will mount the dir at its given path.
func (d *Driver) Get(id, mountLabel string) (string, error) {
panicIfUsedByLcow()
logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel)
var dir string
@ -395,6 +414,7 @@ func (d *Driver) Get(id, mountLabel string) (string, error) {
// Put adds a new layer to the driver.
func (d *Driver) Put(id string) error {
panicIfUsedByLcow()
logrus.Debugf("WindowsGraphDriver Put() id %s", id)
rID, err := d.resolveID(id)
@ -424,7 +444,6 @@ func (d *Driver) Put(id string) error {
// We use this opportunity to cleanup any -removing folders which may be
// still left if the daemon was killed while it was removing a layer.
func (d *Driver) Cleanup() error {
items, err := ioutil.ReadDir(d.info.HomeDir)
if err != nil {
if os.IsNotExist(err) {
@ -454,6 +473,7 @@ func (d *Driver) Cleanup() error {
// layer and its parent layer which may be "".
// The layer should be mounted when calling this function
func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
panicIfUsedByLcow()
rID, err := d.resolveID(id)
if err != nil {
return
@ -490,6 +510,7 @@ func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
// and its parent layer. If parent is "", then all changes will be ADD changes.
// The layer should not be mounted when calling this function.
func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
panicIfUsedByLcow()
rID, err := d.resolveID(id)
if err != nil {
return nil, err
@ -545,6 +566,7 @@ func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
// new layer in bytes.
// The layer should not be mounted when calling this function
func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
panicIfUsedByLcow()
var layerChain []string
if parent != "" {
rPId, err := d.resolveID(parent)
@ -579,6 +601,7 @@ func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
// and its parent and returns the size in bytes of the changes
// relative to its base filesystem directory.
func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
panicIfUsedByLcow()
rPId, err := d.resolveID(parent)
if err != nil {
return
@ -600,6 +623,7 @@ func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
// GetMetadata returns custom driver information.
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
panicIfUsedByLcow()
m := make(map[string]string)
m["dir"] = d.dir(id)
return m, nil
@ -902,6 +926,7 @@ func (fg *fileGetCloserWithBackupPrivileges) Close() error {
// DiffGetter returns a FileGetCloser that can read files from the directory that
// contains files for the layer differences. Used for direct access for tar-split.
func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
panicIfUsedByLcow()
id, err := d.resolveID(id)
if err != nil {
return nil, err

Просмотреть файл

@ -19,17 +19,15 @@ func reset(c *container.Container) {
func TestNoneHealthcheck(t *testing.T) {
c := &container.Container{
CommonContainer: container.CommonContainer{
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Image: "image_name",
Healthcheck: &containertypes.HealthConfig{
Test: []string{"NONE"},
},
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Image: "image_name",
Healthcheck: &containertypes.HealthConfig{
Test: []string{"NONE"},
},
State: &container.State{},
},
State: &container.State{},
}
daemon := &Daemon{}
@ -58,12 +56,10 @@ func TestHealthStates(t *testing.T) {
}
c := &container.Container{
CommonContainer: container.CommonContainer{
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Image: "image_name",
},
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Image: "image_name",
},
}
daemon := &Daemon{

Просмотреть файл

@ -21,37 +21,43 @@ func (e ErrImageDoesNotExist) Error() string {
return fmt.Sprintf("No such image: %s", reference.FamiliarString(ref))
}
// GetImageID returns an image ID corresponding to the image referred to by
// GetImageIDAndPlatform returns an image ID and platform corresponding to the image referred to by
// refOrID.
func (daemon *Daemon) GetImageID(refOrID string) (image.ID, error) {
func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, error) {
ref, err := reference.ParseAnyReference(refOrID)
if err != nil {
return "", err
return "", "", err
}
namedRef, ok := ref.(reference.Named)
if !ok {
digested, ok := ref.(reference.Digested)
if !ok {
return "", ErrImageDoesNotExist{ref}
return "", "", ErrImageDoesNotExist{ref}
}
id := image.IDFromDigest(digested.Digest())
if _, err := daemon.imageStore.Get(id); err != nil {
return "", ErrImageDoesNotExist{ref}
for platform := range daemon.stores {
if _, err = daemon.stores[platform].imageStore.Get(id); err == nil {
return id, platform, nil
}
}
return id, nil
return "", "", ErrImageDoesNotExist{ref}
}
if id, err := daemon.referenceStore.Get(namedRef); err == nil {
return image.IDFromDigest(id), nil
for platform := range daemon.stores {
if id, err := daemon.stores[platform].referenceStore.Get(namedRef); err == nil {
return image.IDFromDigest(id), platform, nil
}
}
// deprecated: repo:shortid https://github.com/docker/docker/pull/799
if tagged, ok := namedRef.(reference.Tagged); ok {
if tag := tagged.Tag(); stringid.IsShortID(stringid.TruncateID(tag)) {
if id, err := daemon.imageStore.Search(tag); err == nil {
for _, storeRef := range daemon.referenceStore.References(id.Digest()) {
if storeRef.Name() == namedRef.Name() {
return id, nil
for platform := range daemon.stores {
if id, err := daemon.stores[platform].imageStore.Search(tag); err == nil {
for _, storeRef := range daemon.stores[platform].referenceStore.References(id.Digest()) {
if storeRef.Name() == namedRef.Name() {
return id, platform, nil
}
}
}
}
@ -59,18 +65,20 @@ func (daemon *Daemon) GetImageID(refOrID string) (image.ID, error) {
}
// Search based on ID
if id, err := daemon.imageStore.Search(refOrID); err == nil {
return id, nil
for platform := range daemon.stores {
if id, err := daemon.stores[platform].imageStore.Search(refOrID); err == nil {
return id, platform, nil
}
}
return "", ErrImageDoesNotExist{ref}
return "", "", ErrImageDoesNotExist{ref}
}
// GetImage returns an image corresponding to the image referred to by refOrID.
func (daemon *Daemon) GetImage(refOrID string) (*image.Image, error) {
imgID, err := daemon.GetImageID(refOrID)
imgID, platform, err := daemon.GetImageIDAndPlatform(refOrID)
if err != nil {
return nil, err
}
return daemon.imageStore.Get(imgID)
return daemon.stores[platform].imageStore.Get(imgID)
}

Просмотреть файл

@ -65,12 +65,12 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
start := time.Now()
records := []types.ImageDeleteResponseItem{}
imgID, err := daemon.GetImageID(imageRef)
imgID, platform, err := daemon.GetImageIDAndPlatform(imageRef)
if err != nil {
return nil, daemon.imageNotExistToErrcode(err)
}
repoRefs := daemon.referenceStore.References(imgID.Digest())
repoRefs := daemon.stores[platform].referenceStore.References(imgID.Digest())
var removedRepositoryRef bool
if !isImageIDPrefix(imgID.String(), imageRef) {
@ -94,7 +94,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
return nil, err
}
parsedRef, err = daemon.removeImageRef(parsedRef)
parsedRef, err = daemon.removeImageRef(platform, parsedRef)
if err != nil {
return nil, err
}
@ -104,7 +104,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
records = append(records, untaggedRecord)
repoRefs = daemon.referenceStore.References(imgID.Digest())
repoRefs = daemon.stores[platform].referenceStore.References(imgID.Digest())
// If a tag reference was removed and the only remaining
// references to the same repository are digest references,
@ -122,7 +122,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
remainingRefs := []reference.Named{}
for _, repoRef := range repoRefs {
if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
if _, err := daemon.removeImageRef(repoRef); err != nil {
if _, err := daemon.removeImageRef(platform, repoRef); err != nil {
return records, err
}
@ -152,12 +152,12 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
if !force {
c |= conflictSoft &^ conflictActiveReference
}
if conflict := daemon.checkImageDeleteConflict(imgID, c); conflict != nil {
if conflict := daemon.checkImageDeleteConflict(imgID, platform, c); conflict != nil {
return nil, conflict
}
for _, repoRef := range repoRefs {
parsedRef, err := daemon.removeImageRef(repoRef)
parsedRef, err := daemon.removeImageRef(platform, repoRef)
if err != nil {
return nil, err
}
@ -170,7 +170,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
}
}
if err := daemon.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef); err != nil {
if err := daemon.imageDeleteHelper(imgID, platform, &records, force, prune, removedRepositoryRef); err != nil {
return nil, err
}
@ -231,13 +231,13 @@ func (daemon *Daemon) getContainerUsingImage(imageID image.ID) *container.Contai
// repositoryRef must not be an image ID but a repository name followed by an
// optional tag or digest reference. If tag or digest is omitted, the default
// tag is used. Returns the resolved image reference and an error.
func (daemon *Daemon) removeImageRef(ref reference.Named) (reference.Named, error) {
func (daemon *Daemon) removeImageRef(platform string, ref reference.Named) (reference.Named, error) {
ref = reference.TagNameOnly(ref)
// Ignore the boolean value returned, as far as we're concerned, this
// is an idempotent operation and it's okay if the reference didn't
// exist in the first place.
_, err := daemon.referenceStore.Delete(ref)
_, err := daemon.stores[platform].referenceStore.Delete(ref)
return ref, err
}
@ -247,11 +247,11 @@ func (daemon *Daemon) removeImageRef(ref reference.Named) (reference.Named, erro
// on the first encountered error. Removed references are logged to this
// daemon's event service. An "Untagged" types.ImageDeleteResponseItem is added to the
// given list of records.
func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, records *[]types.ImageDeleteResponseItem) error {
imageRefs := daemon.referenceStore.References(imgID.Digest())
func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, platform string, records *[]types.ImageDeleteResponseItem) error {
imageRefs := daemon.stores[platform].referenceStore.References(imgID.Digest())
for _, imageRef := range imageRefs {
parsedRef, err := daemon.removeImageRef(imageRef)
parsedRef, err := daemon.removeImageRef(platform, imageRef)
if err != nil {
return err
}
@ -296,15 +296,15 @@ func (idc *imageDeleteConflict) Error() string {
// conflict is encountered, it will be returned immediately without deleting
// the image. If quiet is true, any encountered conflicts will be ignored and
// the function will return nil immediately without deleting the image.
func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDeleteResponseItem, force, prune, quiet bool) error {
func (daemon *Daemon) imageDeleteHelper(imgID image.ID, platform string, records *[]types.ImageDeleteResponseItem, force, prune, quiet bool) error {
// First, determine if this image has any conflicts. Ignore soft conflicts
// if force is true.
c := conflictHard
if !force {
c |= conflictSoft
}
if conflict := daemon.checkImageDeleteConflict(imgID, c); conflict != nil {
if quiet && (!daemon.imageIsDangling(imgID) || conflict.used) {
if conflict := daemon.checkImageDeleteConflict(imgID, platform, c); conflict != nil {
if quiet && (!daemon.imageIsDangling(imgID, platform) || conflict.used) {
// Ignore conflicts UNLESS the image is "dangling" or not being used in
// which case we want the user to know.
return nil
@ -315,18 +315,18 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
return conflict
}
parent, err := daemon.imageStore.GetParent(imgID)
parent, err := daemon.stores[platform].imageStore.GetParent(imgID)
if err != nil {
// There may be no parent
parent = ""
}
// Delete all repository tag/digest references to this image.
if err := daemon.removeAllReferencesToImageID(imgID, records); err != nil {
if err := daemon.removeAllReferencesToImageID(imgID, platform, records); err != nil {
return err
}
removedLayers, err := daemon.imageStore.Delete(imgID)
removedLayers, err := daemon.stores[platform].imageStore.Delete(imgID)
if err != nil {
return err
}
@ -346,7 +346,7 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
// either running or stopped).
// Do not force prunings, but do so quietly (stopping on any encountered
// conflicts).
return daemon.imageDeleteHelper(parent, records, false, true, true)
return daemon.imageDeleteHelper(parent, platform, records, false, true, true)
}
// checkImageDeleteConflict determines whether there are any conflicts
@ -355,9 +355,9 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
// using the image. A soft conflict is any tags/digest referencing the given
// image or any stopped container using the image. If ignoreSoftConflicts is
// true, this function will not check for soft conflict conditions.
func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType) *imageDeleteConflict {
func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, platform string, mask conflictType) *imageDeleteConflict {
// Check if the image has any descendant images.
if mask&conflictDependentChild != 0 && len(daemon.imageStore.Children(imgID)) > 0 {
if mask&conflictDependentChild != 0 && len(daemon.stores[platform].imageStore.Children(imgID)) > 0 {
return &imageDeleteConflict{
hard: true,
imgID: imgID,
@ -381,7 +381,7 @@ func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType
}
// Check if any repository tags/digest reference this image.
if mask&conflictActiveReference != 0 && len(daemon.referenceStore.References(imgID.Digest())) > 0 {
if mask&conflictActiveReference != 0 && len(daemon.stores[platform].referenceStore.References(imgID.Digest())) > 0 {
return &imageDeleteConflict{
imgID: imgID,
message: "image is referenced in multiple repositories",
@ -408,6 +408,6 @@ func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType
// imageIsDangling returns whether the given image is "dangling" which means
// that there are no repository references to the given image and it has no
// child images.
func (daemon *Daemon) imageIsDangling(imgID image.ID) bool {
return !(len(daemon.referenceStore.References(imgID.Digest())) > 0 || len(daemon.imageStore.Children(imgID)) > 0)
func (daemon *Daemon) imageIsDangling(imgID image.ID, platform string) bool {
return !(len(daemon.stores[platform].referenceStore.References(imgID.Digest())) > 0 || len(daemon.stores[platform].imageStore.Children(imgID)) > 0)
}

Просмотреть файл

@ -2,8 +2,10 @@ package daemon
import (
"io"
"runtime"
"github.com/docker/docker/image/tarexport"
"github.com/docker/docker/pkg/system"
)
// ExportImage exports a list of images to the given output stream. The
@ -12,7 +14,12 @@ import (
// the same tag are exported. names is the set of tags to export, and
// outStream is the writer which the images are written to.
func (daemon *Daemon) ExportImage(names []string, outStream io.Writer) error {
imageExporter := tarexport.NewTarExporter(daemon.imageStore, daemon.layerStore, daemon.referenceStore, daemon)
// TODO @jhowardmsft LCOW. This will need revisiting later.
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
imageExporter := tarexport.NewTarExporter(daemon.stores[platform].imageStore, daemon.stores[platform].layerStore, daemon.stores[platform].referenceStore, daemon)
return imageExporter.Save(names, outStream)
}
@ -20,6 +27,11 @@ func (daemon *Daemon) ExportImage(names []string, outStream io.Writer) error {
// complement of ImageExport. The input stream is an uncompressed tar
// ball containing images and metadata.
func (daemon *Daemon) LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
imageExporter := tarexport.NewTarExporter(daemon.imageStore, daemon.layerStore, daemon.referenceStore, daemon)
// TODO @jhowardmsft LCOW. This will need revisiting later.
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
imageExporter := tarexport.NewTarExporter(daemon.stores[platform].imageStore, daemon.stores[platform].layerStore, daemon.stores[platform].referenceStore, daemon)
return imageExporter.Load(inTar, outStream, quiet)
}

Просмотреть файл

@ -2,6 +2,7 @@ package daemon
import (
"fmt"
"runtime"
"time"
"github.com/docker/distribution/reference"
@ -18,6 +19,12 @@ func (daemon *Daemon) ImageHistory(name string) ([]*image.HistoryResponseItem, e
return nil, err
}
// If the image OS isn't set, assume it's the host OS
platform := img.OS
if platform == "" {
platform = runtime.GOOS
}
history := []*image.HistoryResponseItem{}
layerCounter := 0
@ -33,12 +40,12 @@ func (daemon *Daemon) ImageHistory(name string) ([]*image.HistoryResponseItem, e
}
rootFS.Append(img.RootFS.DiffIDs[layerCounter])
l, err := daemon.layerStore.Get(rootFS.ChainID())
l, err := daemon.stores[platform].layerStore.Get(rootFS.ChainID())
if err != nil {
return nil, err
}
layerSize, err = l.DiffSize()
layer.ReleaseAndLog(daemon.layerStore, l)
layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
if err != nil {
return nil, err
}
@ -62,7 +69,7 @@ func (daemon *Daemon) ImageHistory(name string) ([]*image.HistoryResponseItem, e
h.ID = id.String()
var tags []string
for _, r := range daemon.referenceStore.References(id.Digest()) {
for _, r := range daemon.stores[platform].referenceStore.References(id.Digest()) {
if _, ok := r.(reference.NamedTagged); ok {
tags = append(tags, reference.FamiliarString(r))
}

Просмотреть файл

@ -1,6 +1,7 @@
package daemon
import (
"runtime"
"time"
"github.com/docker/distribution/reference"
@ -17,7 +18,13 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
return nil, errors.Wrapf(err, "no such image: %s", name)
}
refs := daemon.referenceStore.References(img.ID().Digest())
// If the image OS isn't set, assume it's the host OS
platform := img.OS
if platform == "" {
platform = runtime.GOOS
}
refs := daemon.stores[platform].referenceStore.References(img.ID().Digest())
repoTags := []string{}
repoDigests := []string{}
for _, ref := range refs {
@ -33,11 +40,11 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
var layerMetadata map[string]string
layerID := img.RootFS.ChainID()
if layerID != "" {
l, err := daemon.layerStore.Get(layerID)
l, err := daemon.stores[platform].layerStore.Get(layerID)
if err != nil {
return nil, err
}
defer layer.ReleaseAndLog(daemon.layerStore, l)
defer layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
size, err = l.Size()
if err != nil {
return nil, err
@ -67,15 +74,14 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
Author: img.Author,
Config: img.Config,
Architecture: img.Architecture,
Os: img.OS,
Os: platform,
OsVersion: img.OSVersion,
Size: size,
VirtualSize: size, // TODO: field unused, deprecate
RootFS: rootFSToAPIType(img.RootFS),
}
imageInspect.GraphDriver.Name = daemon.GraphDriverName()
imageInspect.GraphDriver.Name = daemon.GraphDriverName(platform)
imageInspect.GraphDriver.Data = layerMetadata
return imageInspect, nil

Просмотреть файл

@ -2,6 +2,7 @@ package daemon
import (
"io"
"runtime"
"strings"
dist "github.com/docker/distribution"
@ -17,7 +18,7 @@ import (
// PullImage initiates a pull operation. image is the repository name to pull, and
// tag may be either empty, or indicate a specific tag to pull.
func (daemon *Daemon) PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
func (daemon *Daemon) PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
// Special case: "pull -a" may send an image name with a
// trailing :. This is ugly, but let's not break API
// compatibility.
@ -42,10 +43,10 @@ func (daemon *Daemon) PullImage(ctx context.Context, image, tag string, metaHead
}
}
return daemon.pullImageWithReference(ctx, ref, metaHeaders, authConfig, outStream)
return daemon.pullImageWithReference(ctx, ref, platform, metaHeaders, authConfig, outStream)
}
func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.Named, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
// Include a buffer so that slow client connections don't affect
// transfer performance.
progressChan := make(chan progress.Progress, 100)
@ -59,6 +60,11 @@ func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.
close(writesDone)
}()
// Default to the host OS platform in case it hasn't been populated with an explicit value.
if platform == "" {
platform = runtime.GOOS
}
imagePullConfig := &distribution.ImagePullConfig{
Config: distribution.Config{
MetaHeaders: metaHeaders,
@ -66,12 +72,13 @@ func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.
ProgressOutput: progress.ChanOutput(progressChan),
RegistryService: daemon.RegistryService,
ImageEventLogger: daemon.LogImageEvent,
MetadataStore: daemon.distributionMetadataStore,
ImageStore: distribution.NewImageConfigStoreFromStore(daemon.imageStore),
ReferenceStore: daemon.referenceStore,
MetadataStore: daemon.stores[platform].distributionMetadataStore,
ImageStore: distribution.NewImageConfigStoreFromStore(daemon.stores[platform].imageStore),
ReferenceStore: daemon.stores[platform].referenceStore,
},
DownloadManager: daemon.downloadManager,
Schema2Types: distribution.ImageTypes,
Platform: platform,
}
err := distribution.Pull(ctx, ref, imagePullConfig)

Просмотреть файл

@ -2,6 +2,7 @@ package daemon
import (
"io"
"runtime"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference"
@ -9,6 +10,7 @@ import (
"github.com/docker/docker/distribution"
progressutils "github.com/docker/docker/distribution/utils"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/system"
"golang.org/x/net/context"
)
@ -39,6 +41,12 @@ func (daemon *Daemon) PushImage(ctx context.Context, image, tag string, metaHead
close(writesDone)
}()
// TODO @jhowardmsft LCOW Support. This will require revisiting. For now, hard-code.
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
imagePushConfig := &distribution.ImagePushConfig{
Config: distribution.Config{
MetaHeaders: metaHeaders,
@ -46,12 +54,12 @@ func (daemon *Daemon) PushImage(ctx context.Context, image, tag string, metaHead
ProgressOutput: progress.ChanOutput(progressChan),
RegistryService: daemon.RegistryService,
ImageEventLogger: daemon.LogImageEvent,
MetadataStore: daemon.distributionMetadataStore,
ImageStore: distribution.NewImageConfigStoreFromStore(daemon.imageStore),
ReferenceStore: daemon.referenceStore,
MetadataStore: daemon.stores[platform].distributionMetadataStore,
ImageStore: distribution.NewImageConfigStoreFromStore(daemon.stores[platform].imageStore),
ReferenceStore: daemon.stores[platform].referenceStore,
},
ConfigMediaType: schema2.MediaTypeImageConfig,
LayerStore: distribution.NewLayerProviderFromStore(daemon.layerStore),
LayerStore: distribution.NewLayerProviderFromStore(daemon.stores[platform].layerStore),
TrustKey: daemon.trustKey,
UploadManager: daemon.uploadManager,
}

Просмотреть файл

@ -8,7 +8,7 @@ import (
// TagImage creates the tag specified by newTag, pointing to the image named
// imageName (alternatively, imageName can also be an image ID).
func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
imageID, err := daemon.GetImageID(imageName)
imageID, platform, err := daemon.GetImageIDAndPlatform(imageName)
if err != nil {
return err
}
@ -23,12 +23,12 @@ func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
}
}
return daemon.TagImageWithReference(imageID, newTag)
return daemon.TagImageWithReference(imageID, platform, newTag)
}
// TagImageWithReference adds the given reference to the image ID provided.
func (daemon *Daemon) TagImageWithReference(imageID image.ID, newTag reference.Named) error {
if err := daemon.referenceStore.AddTag(newTag, imageID.Digest(), true); err != nil {
func (daemon *Daemon) TagImageWithReference(imageID image.ID, platform string, newTag reference.Named) error {
if err := daemon.stores[platform].referenceStore.AddTag(newTag, imageID.Digest(), true); err != nil {
return err
}

Просмотреть файл

@ -3,6 +3,7 @@ package daemon
import (
"encoding/json"
"fmt"
"runtime"
"sort"
"time"
@ -14,6 +15,7 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/system"
)
var acceptedImageFilterTags = map[string]bool{
@ -34,7 +36,12 @@ func (r byCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
// Map returns a map of all images in the ImageStore
func (daemon *Daemon) Map() map[image.ID]*image.Image {
return daemon.imageStore.Map()
// TODO @jhowardmsft LCOW. This will need work to enumerate the stores for all platforms.
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
return daemon.stores[platform].imageStore.Map()
}
// Images returns a filtered list of images. filterArgs is a JSON-encoded set
@ -43,6 +50,13 @@ func (daemon *Daemon) Map() map[image.ID]*image.Image {
// named all controls whether all images in the graph are filtered, or just
// the heads.
func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
// TODO @jhowardmsft LCOW. This will need work to enumerate the stores for all platforms.
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
var (
allImages map[image.ID]*image.Image
err error
@ -61,9 +75,9 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
}
}
if danglingOnly {
allImages = daemon.imageStore.Heads()
allImages = daemon.stores[platform].imageStore.Heads()
} else {
allImages = daemon.imageStore.Map()
allImages = daemon.stores[platform].imageStore.Map()
}
var beforeFilter, sinceFilter *image.Image
@ -116,7 +130,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
layerID := img.RootFS.ChainID()
var size int64
if layerID != "" {
l, err := daemon.layerStore.Get(layerID)
l, err := daemon.stores[platform].layerStore.Get(layerID)
if err != nil {
// The layer may have been deleted between the call to `Map()` or
// `Heads()` and the call to `Get()`, so we just ignore this error
@ -127,7 +141,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
}
size, err = l.Size()
layer.ReleaseAndLog(daemon.layerStore, l)
layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
if err != nil {
return nil, err
}
@ -135,7 +149,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
newImage := newImage(img, size)
for _, ref := range daemon.referenceStore.References(id.Digest()) {
for _, ref := range daemon.stores[platform].referenceStore.References(id.Digest()) {
if imageFilters.Include("reference") {
var found bool
var matchErr error
@ -157,7 +171,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
}
}
if newImage.RepoDigests == nil && newImage.RepoTags == nil {
if all || len(daemon.imageStore.Children(id)) == 0 {
if all || len(daemon.stores[platform].imageStore.Children(id)) == 0 {
if imageFilters.Include("dangling") && !danglingOnly {
//dangling=false case, so dangling image is not needed
@ -179,7 +193,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
// lazily init variables
if imagesMap == nil {
allContainers = daemon.List()
allLayers = daemon.layerStore.Map()
allLayers = daemon.stores[platform].layerStore.Map()
imagesMap = make(map[*image.Image]*types.ImageSummary)
layerRefs = make(map[layer.ChainID]int)
}
@ -242,7 +256,16 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
// The existing image(s) is not destroyed.
// If no parent is specified, a new image with the diff of all the specified image's layers merged into a new layer that has no parents.
func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
img, err := daemon.imageStore.Get(image.ID(id))
var (
img *image.Image
err error
)
for _, ds := range daemon.stores {
if img, err = ds.imageStore.Get(image.ID(id)); err == nil {
break
}
}
if err != nil {
return "", err
}
@ -250,7 +273,7 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
var parentImg *image.Image
var parentChainID layer.ChainID
if len(parent) != 0 {
parentImg, err = daemon.imageStore.Get(image.ID(parent))
parentImg, err = daemon.stores[img.Platform()].imageStore.Get(image.ID(parent))
if err != nil {
return "", errors.Wrap(err, "error getting specified parent layer")
}
@ -260,11 +283,11 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
parentImg = &image.Image{RootFS: rootFS}
}
l, err := daemon.layerStore.Get(img.RootFS.ChainID())
l, err := daemon.stores[img.Platform()].layerStore.Get(img.RootFS.ChainID())
if err != nil {
return "", errors.Wrap(err, "error getting image layer")
}
defer daemon.layerStore.Release(l)
defer daemon.stores[img.Platform()].layerStore.Release(l)
ts, err := l.TarStreamFrom(parentChainID)
if err != nil {
@ -272,11 +295,11 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
}
defer ts.Close()
newL, err := daemon.layerStore.Register(ts, parentChainID)
newL, err := daemon.stores[img.Platform()].layerStore.Register(ts, parentChainID, layer.Platform(img.Platform()))
if err != nil {
return "", errors.Wrap(err, "error registering layer")
}
defer daemon.layerStore.Release(newL)
defer daemon.stores[img.Platform()].layerStore.Release(newL)
var newImage image.Image
newImage = *img
@ -313,7 +336,7 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
return "", errors.Wrap(err, "error marshalling image config")
}
newImgID, err := daemon.imageStore.Create(b)
newImgID, err := daemon.stores[img.Platform()].imageStore.Create(b)
if err != nil {
return "", errors.Wrap(err, "error creating new image after squash")
}

Просмотреть файл

@ -26,13 +26,18 @@ import (
// inConfig (if src is "-"), or from a URI specified in src. Progress output is
// written to outStream. Repository and tag names can optionally be given in
// the repo and tag arguments, respectively.
func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
func (daemon *Daemon) ImportImage(src string, repository, platform string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
var (
rc io.ReadCloser
resp *http.Response
newRef reference.Named
)
// Default the platform if not supplied.
if platform == "" {
platform = runtime.GOOS
}
if repository != "" {
var err error
newRef, err = reference.ParseNormalizedNamed(repository)
@ -85,12 +90,11 @@ func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string
if err != nil {
return err
}
// TODO: support windows baselayer?
l, err := daemon.layerStore.Register(inflatedLayerData, "")
l, err := daemon.stores[platform].layerStore.Register(inflatedLayerData, "", layer.Platform(platform))
if err != nil {
return err
}
defer layer.ReleaseAndLog(daemon.layerStore, l)
defer layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
created := time.Now().UTC()
imgConfig, err := json.Marshal(&image.Image{
@ -98,7 +102,7 @@ func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string
DockerVersion: dockerversion.Version,
Config: config,
Architecture: runtime.GOARCH,
OS: runtime.GOOS,
OS: platform,
Created: created,
Comment: msg,
},
@ -115,14 +119,14 @@ func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string
return err
}
id, err := daemon.imageStore.Create(imgConfig)
id, err := daemon.stores[platform].imageStore.Create(imgConfig)
if err != nil {
return err
}
// FIXME: connect with commit code and call refstore directly
if newRef != nil {
if err := daemon.TagImageWithReference(id, newRef); err != nil {
if err := daemon.TagImageWithReference(id, platform, newRef); err != nil {
return err
}
}

Просмотреть файл

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"runtime"
"strings"
"time"
"github.com/Sirupsen/logrus"
@ -77,15 +78,32 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
securityOptions = append(securityOptions, "name=userns")
}
imageCount := 0
drivers := ""
for p, ds := range daemon.stores {
imageCount += len(ds.imageStore.Map())
drivers += daemon.GraphDriverName(p)
if len(daemon.stores) > 1 {
drivers += fmt.Sprintf(" (%s) ", p)
}
}
// TODO @jhowardmsft LCOW support. For now, hard-code the platform shown for the driver status
p := runtime.GOOS
if p == "windows" && system.LCOWSupported() {
p = "linux"
}
drivers = strings.TrimSpace(drivers)
v := &types.Info{
ID: daemon.ID,
Containers: int(cRunning + cPaused + cStopped),
ContainersRunning: int(cRunning),
ContainersPaused: int(cPaused),
ContainersStopped: int(cStopped),
Images: len(daemon.imageStore.Map()),
Driver: daemon.GraphDriverName(),
DriverStatus: daemon.layerStore.DriverStatus(),
Images: imageCount,
Driver: drivers,
DriverStatus: daemon.stores[p].layerStore.DriverStatus(),
Plugins: daemon.showPluginsInfo(),
IPv4Forwarding: !sysInfo.IPv4ForwardingDisabled,
BridgeNfIptables: !sysInfo.BridgeNFCallIPTablesDisabled,

Просмотреть файл

@ -170,6 +170,7 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
Name: container.Name,
RestartCount: container.RestartCount,
Driver: container.Driver,
Platform: container.Platform,
MountLabel: container.MountLabel,
ProcessLabel: container.ProcessLabel,
ExecIDs: container.GetExecIDs(),

Просмотреть файл

@ -317,7 +317,7 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
if psFilters.Include("ancestor") {
ancestorFilter = true
psFilters.WalkValues("ancestor", func(ancestor string) error {
id, err := daemon.GetImageID(ancestor)
id, platform, err := daemon.GetImageIDAndPlatform(ancestor)
if err != nil {
logrus.Warnf("Error while looking up for image %v", ancestor)
return nil
@ -327,7 +327,7 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
return nil
}
// Then walk down the graph and put the imageIds in imagesFilter
populateImageFilterByParents(imagesFilter, id, daemon.imageStore.Children)
populateImageFilterByParents(imagesFilter, id, daemon.stores[platform].imageStore.Children)
return nil
})
}
@ -558,7 +558,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
image := container.Config.Image // if possible keep the original ref
if image != container.ImageID.String() {
id, err := daemon.GetImageID(image)
id, _, err := daemon.GetImageIDAndPlatform(image)
if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
return nil, err
}

Просмотреть файл

@ -7,11 +7,17 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/opencontainers/runtime-spec/specs-go"
)
func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
s := oci.DefaultSpec()
img, err := daemon.GetImage(string(c.ImageID))
if err != nil {
return nil, err
}
s := oci.DefaultOSSpec(img.OS)
linkedEnv, err := daemon.setupLinkedContainers(c)
if err != nil {
@ -95,7 +101,30 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
if !c.Config.ArgsEscaped {
s.Process.Args = escapeArgs(s.Process.Args)
}
s.Process.Cwd = c.Config.WorkingDir
s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
if c.Config.Tty {
s.Process.Terminal = c.Config.Tty
s.Process.ConsoleSize.Height = c.HostConfig.ConsoleSize[0]
s.Process.ConsoleSize.Width = c.HostConfig.ConsoleSize[1]
}
s.Process.User.Username = c.Config.User
if img.OS == "windows" {
daemon.createSpecWindowsFields(c, &s, isHyperV)
} else {
// TODO @jhowardmsft LCOW Support. Modify this check when running in dual-mode
if system.LCOWSupported() && img.OS == "linux" {
daemon.createSpecLinuxFields(c, &s)
}
}
return (*specs.Spec)(&s), nil
}
// Sets the Windows-specific fields of the OCI spec
func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) {
if len(s.Process.Cwd) == 0 {
// We default to C:\ to workaround the oddity of the case that the
// default directory for cmd running as LocalSystem (or
@ -106,17 +135,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
// as c:\. Hence, setting it to default of c:\ makes for consistency.
s.Process.Cwd = `C:\`
}
s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
s.Process.ConsoleSize.Height = c.HostConfig.ConsoleSize[0]
s.Process.ConsoleSize.Width = c.HostConfig.ConsoleSize[1]
s.Process.Terminal = c.Config.Tty
s.Process.User.Username = c.Config.User
// In spec.Root. This is not set for Hyper-V containers
if !isHyperV {
s.Root.Path = c.BaseFS
}
s.Root.Readonly = false // Windows does not support a read-only root filesystem
if !isHyperV {
s.Root.Path = c.BaseFS // This is not set for Hyper-V containers
}
// In s.Windows.Resources
cpuShares := uint16(c.HostConfig.CPUShares)
@ -157,7 +180,17 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
Iops: &c.HostConfig.IOMaximumIOps,
},
}
return (*specs.Spec)(&s), nil
}
// Sets the Linux-specific fields of the OCI spec
// TODO: @jhowardmsft LCOW Support. We need to do a lot more pulling in what can
// be pulled in from oci_linux.go.
func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) {
if len(s.Process.Cwd) == 0 {
s.Process.Cwd = `/`
}
s.Root.Path = "rootfs"
s.Root.Readonly = c.HostConfig.ReadonlyRootfs
}
func escapeArgs(args []string) []string {

Просмотреть файл

@ -3,6 +3,7 @@ package daemon
import (
"fmt"
"regexp"
"runtime"
"sync/atomic"
"time"
@ -14,6 +15,7 @@ import (
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/directory"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/volume"
"github.com/docker/libnetwork"
@ -157,6 +159,12 @@ func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Arg
// ImagesPrune removes unused images
func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
// TODO @jhowardmsft LCOW Support: This will need revisiting later.
platform := runtime.GOOS
if platform == "windows" && system.LCOWSupported() {
platform = "linux"
}
if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
return nil, errPruneRunning
}
@ -186,9 +194,9 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args
var allImages map[image.ID]*image.Image
if danglingOnly {
allImages = daemon.imageStore.Heads()
allImages = daemon.stores[platform].imageStore.Heads()
} else {
allImages = daemon.imageStore.Map()
allImages = daemon.stores[platform].imageStore.Map()
}
allContainers := daemon.List()
imageRefs := map[string]bool{}
@ -202,7 +210,7 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args
}
// Filter intermediary images and get their unique size
allLayers := daemon.layerStore.Map()
allLayers := daemon.stores[platform].layerStore.Map()
topImages := map[image.ID]*image.Image{}
for id, img := range allImages {
select {
@ -210,7 +218,7 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args
return nil, ctx.Err()
default:
dgst := digest.Digest(id)
if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
if len(daemon.stores[platform].referenceStore.References(dgst)) == 0 && len(daemon.stores[platform].imageStore.Children(id)) != 0 {
continue
}
if !until.IsZero() && img.Created.After(until) {
@ -241,7 +249,7 @@ deleteImagesLoop:
}
deletedImages := []types.ImageDeleteResponseItem{}
refs := daemon.referenceStore.References(dgst)
refs := daemon.stores[platform].referenceStore.References(dgst)
if len(refs) > 0 {
shouldDelete := !danglingOnly
if !shouldDelete {

Просмотреть файл

@ -207,7 +207,7 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
// FIXME: remove once reference counting for graphdrivers has been refactored
// Ensure that all the mounts are gone
if mountid, err := daemon.layerStore.GetMountID(container.ID); err == nil {
if mountid, err := daemon.stores[container.Platform].layerStore.GetMountID(container.ID); err == nil {
daemon.cleanupMountsByID(mountid)
}
}

Просмотреть файл

@ -41,7 +41,7 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
layerOpts.LayerFolderPath = m["dir"]
// Generate the layer paths of the layer options
img, err := daemon.imageStore.Get(container.ImageID)
img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
if err != nil {
return nil, fmt.Errorf("failed to graph.Get on ImageID %s - %s", container.ImageID, err)
}
@ -49,9 +49,9 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
max := len(img.RootFS.DiffIDs)
for i := 1; i <= max; i++ {
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
layerPath, err := layer.GetLayerPath(daemon.layerStore, img.RootFS.ChainID())
layerPath, err := layer.GetLayerPath(daemon.stores[container.Platform].layerStore, img.RootFS.ChainID())
if err != nil {
return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.layerStore, img.RootFS.ChainID(), err)
return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[container.Platform].layerStore, img.RootFS.ChainID(), err)
}
// Reverse order, expecting parent most first
layerOpts.LayerPaths = append([]string{layerPath}, layerOpts.LayerPaths...)

Просмотреть файл

@ -18,70 +18,67 @@ func TestBackportMountSpec(t *testing.T) {
d := Daemon{containers: container.NewMemoryStore()}
c := &container.Container{
CommonContainer: container.CommonContainer{
State: &container.State{},
MountPoints: map[string]*volume.MountPoint{
"/apple": {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
"/banana": {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true}, // named volume
"/cherry": {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true}, // RO named volume
"/dates": {Destination: "/dates", Source: "/var/lib/docker/volumes/data", Name: "data"}, // named volume nocopy
"/elderberry": {Destination: "/elderberry", Source: "/var/lib/docker/volumes/data", Name: "data"}, // masks anon vol
"/fig": {Destination: "/fig", Source: "/data", RW: true}, // RW bind
"/guava": {Destination: "/guava", Source: "/data", RW: false, Propagation: "shared"}, // RO bind + propagation
"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true}, // volumes-from
State: &container.State{},
MountPoints: map[string]*volume.MountPoint{
"/apple": {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
"/banana": {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true}, // named volume
"/cherry": {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true}, // RO named volume
"/dates": {Destination: "/dates", Source: "/var/lib/docker/volumes/data", Name: "data"}, // named volume nocopy
"/elderberry": {Destination: "/elderberry", Source: "/var/lib/docker/volumes/data", Name: "data"}, // masks anon vol
"/fig": {Destination: "/fig", Source: "/data", RW: true}, // RW bind
"/guava": {Destination: "/guava", Source: "/data", RW: false, Propagation: "shared"}, // RO bind + propagation
"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true}, // volumes-from
// partially configured mountpoint due to #32613
// specifically, `mp.Spec.Source` is not set
"/honeydew": {
Type: mounttypes.TypeVolume,
Destination: "/honeydew",
Name: "data",
Source: "/var/lib/docker/volumes/data",
Spec: mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/honeydew", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
},
// partially configured mountpoint due to #32613
// specifically, `mp.Spec.Source` is not set
"/honeydew": {
Type: mounttypes.TypeVolume,
Destination: "/honeydew",
Name: "data",
Source: "/var/lib/docker/volumes/data",
Spec: mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/honeydew", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
},
// from hostconfig.Mounts
"/jambolan": {
Type: mounttypes.TypeVolume,
Destination: "/jambolan",
Source: "/var/lib/docker/volumes/data",
RW: true,
Name: "data",
Spec: mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/jambolan", Source: "data"},
},
// from hostconfig.Mounts
"/jambolan": {
Type: mounttypes.TypeVolume,
Destination: "/jambolan",
Source: "/var/lib/docker/volumes/data",
RW: true,
Name: "data",
Spec: mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/jambolan", Source: "data"},
},
HostConfig: &containertypes.HostConfig{
Binds: []string{
"data:/banana",
"data:/cherry:ro",
"data:/dates:ro,nocopy",
"data:/elderberry:ro,nocopy",
"/data:/fig",
"/data:/guava:ro,shared",
"data:/honeydew:nocopy",
},
VolumesFrom: []string{"1:ro"},
Mounts: []mounttypes.Mount{
{Type: mounttypes.TypeVolume, Target: "/jambolan"},
},
},
HostConfig: &containertypes.HostConfig{
Binds: []string{
"data:/banana",
"data:/cherry:ro",
"data:/dates:ro,nocopy",
"data:/elderberry:ro,nocopy",
"/data:/fig",
"/data:/guava:ro,shared",
"data:/honeydew:nocopy",
},
Config: &containertypes.Config{Volumes: map[string]struct{}{
"/apple": {},
"/elderberry": {},
}},
}}
VolumesFrom: []string{"1:ro"},
Mounts: []mounttypes.Mount{
{Type: mounttypes.TypeVolume, Target: "/jambolan"},
},
},
Config: &containertypes.Config{Volumes: map[string]struct{}{
"/apple": {},
"/elderberry": {},
}},
}
d.containers.Add("1", &container.Container{
CommonContainer: container.CommonContainer{
State: &container.State{},
ID: "1",
MountPoints: map[string]*volume.MountPoint{
"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
},
HostConfig: &containertypes.HostConfig{
Binds: []string{
"data:/kumquat:ro",
},
State: &container.State{},
ID: "1",
MountPoints: map[string]*volume.MountPoint{
"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
},
HostConfig: &containertypes.HostConfig{
Binds: []string{
"data:/kumquat:ro",
},
},
})

Просмотреть файл

@ -14,6 +14,7 @@ import (
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/system"
refstore "github.com/docker/docker/reference"
"github.com/docker/docker/registry"
"github.com/docker/libtrust"
@ -58,6 +59,9 @@ type ImagePullConfig struct {
// Schema2Types is the valid schema2 configuration types allowed
// by the pull operation.
Schema2Types []string
// Platform is the requested platform of the image being pulled to ensure it can be validated
// when the host platform supports multiple image operating systems.
Platform string
}
// ImagePushConfig stores push configuration.
@ -82,7 +86,7 @@ type ImagePushConfig struct {
type ImageConfigStore interface {
Put([]byte) (digest.Digest, error)
Get(digest.Digest) ([]byte, error)
RootFSFromConfig([]byte) (*image.RootFS, error)
RootFSAndPlatformFromConfig([]byte) (*image.RootFS, layer.Platform, error)
}
// PushLayerProvider provides layers to be pushed by ChainID.
@ -108,7 +112,7 @@ type RootFSDownloadManager interface {
// returns the final rootfs.
// Given progress output to track download progress
// Returns function to release download resources
Download(ctx context.Context, initialRootFS image.RootFS, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error)
Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error)
}
type imageConfigStore struct {
@ -136,21 +140,25 @@ func (s *imageConfigStore) Get(d digest.Digest) ([]byte, error) {
return img.RawJSON(), nil
}
func (s *imageConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
func (s *imageConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
var unmarshalledConfig image.Image
if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
return nil, err
return nil, "", err
}
// fail immediately on Windows when downloading a non-Windows image
// and vice versa
if runtime.GOOS == "windows" && unmarshalledConfig.OS == "linux" {
return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
// and vice versa. Exception on Windows if Linux Containers are enabled.
if runtime.GOOS == "windows" && unmarshalledConfig.OS == "linux" && !system.LCOWSupported() {
return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
} else if runtime.GOOS != "windows" && unmarshalledConfig.OS == "windows" {
return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
}
return unmarshalledConfig.RootFS, nil
platform := ""
if runtime.GOOS == "windows" {
platform = unmarshalledConfig.OS
}
return unmarshalledConfig.RootFS, layer.Platform(platform), nil
}
type storeLayerProvider struct {

Просмотреть файл

@ -26,15 +26,17 @@ type Store interface {
type FSMetadataStore struct {
sync.RWMutex
basePath string
platform string
}
// NewFSMetadataStore creates a new filesystem-based metadata store.
func NewFSMetadataStore(basePath string) (*FSMetadataStore, error) {
func NewFSMetadataStore(basePath, platform string) (*FSMetadataStore, error) {
if err := os.MkdirAll(basePath, 0700); err != nil {
return nil, err
}
return &FSMetadataStore{
basePath: basePath,
platform: platform,
}, nil
}

Просмотреть файл

@ -3,6 +3,7 @@ package metadata
import (
"io/ioutil"
"os"
"runtime"
"testing"
"github.com/docker/docker/layer"
@ -15,7 +16,7 @@ func TestV1IDService(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
metadataStore, err := NewFSMetadataStore(tmpDir)
metadataStore, err := NewFSMetadataStore(tmpDir, runtime.GOOS)
if err != nil {
t.Fatalf("could not create metadata store: %v", err)
}

Просмотреть файл

@ -6,6 +6,7 @@ import (
"math/rand"
"os"
"reflect"
"runtime"
"testing"
"github.com/docker/docker/layer"
@ -19,7 +20,7 @@ func TestV2MetadataService(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
metadataStore, err := NewFSMetadataStore(tmpDir)
metadataStore, err := NewFSMetadataStore(tmpDir, runtime.GOOS)
if err != nil {
t.Fatalf("could not create metadata store: %v", err)
}

Просмотреть файл

@ -232,7 +232,7 @@ func (p *v1Puller) pullImage(ctx context.Context, v1ID, endpoint string, localNa
}
rootFS := image.NewRootFS()
resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, descriptors, p.config.ProgressOutput)
resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, "", descriptors, p.config.ProgressOutput)
if err != nil {
return err
}

Просмотреть файл

@ -27,6 +27,7 @@ import (
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/system"
refstore "github.com/docker/docker/reference"
"github.com/docker/docker/registry"
"github.com/opencontainers/go-digest"
@ -486,7 +487,26 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Named, unverif
descriptors = append(descriptors, layerDescriptor)
}
resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, descriptors, p.config.ProgressOutput)
// The v1 manifest itself doesn't directly contain a platform. However,
// the history does, but unfortunately that's a string, so search through
// all the history until hopefully we find one which indicates the os.
platform := runtime.GOOS
if runtime.GOOS == "windows" && system.LCOWSupported() {
type config struct {
Os string `json:"os,omitempty"`
}
for _, v := range verifiedManifest.History {
var c config
if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil {
if c.Os != "" {
platform = c.Os
break
}
}
}
}
resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, layer.Platform(platform), descriptors, p.config.ProgressOutput)
if err != nil {
return "", "", err
}
@ -556,10 +576,11 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
}()
var (
configJSON []byte // raw serialized image config
downloadedRootFS *image.RootFS // rootFS from registered layers
configRootFS *image.RootFS // rootFS from configuration
release func() // release resources from rootFS download
configJSON []byte // raw serialized image config
downloadedRootFS *image.RootFS // rootFS from registered layers
configRootFS *image.RootFS // rootFS from configuration
release func() // release resources from rootFS download
platform layer.Platform // for LCOW when registering downloaded layers
)
// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
@ -571,7 +592,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
// check to block Windows images being pulled on Linux is implemented, it
// may be necessary to perform the same type of serialisation.
if runtime.GOOS == "windows" {
configJSON, configRootFS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
configJSON, configRootFS, platform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
if err != nil {
return "", "", err
}
@ -598,7 +619,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
rootFS image.RootFS
)
downloadRootFS := *image.NewRootFS()
rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, descriptors, p.config.ProgressOutput)
rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, platform, descriptors, p.config.ProgressOutput)
if err != nil {
// Intentionally do not cancel the config download here
// as the error from config download (if there is one)
@ -616,7 +637,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
}
if configJSON == nil {
configJSON, configRootFS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
if err == nil && configRootFS == nil {
err = errRootFSInvalid
}
@ -663,16 +684,16 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
return imageID, manifestDigest, nil
}
func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, error) {
func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, layer.Platform, error) {
select {
case configJSON := <-configChan:
rootfs, err := s.RootFSFromConfig(configJSON)
rootfs, platform, err := s.RootFSAndPlatformFromConfig(configJSON)
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
return configJSON, rootfs, nil
return configJSON, rootfs, platform, nil
case err := <-errChan:
return nil, nil, err
return nil, nil, "", err
// Don't need a case for ctx.Done in the select because cancellation
// will trigger an error in p.pullSchema2ImageConfig.
}

Просмотреть файл

@ -118,7 +118,7 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id
return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err)
}
rootfs, err := p.config.ImageStore.RootFSFromConfig(imgConfig)
rootfs, _, err := p.config.ImageStore.RootFSAndPlatformFromConfig(imgConfig)
if err != nil {
return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err)
}

Просмотреть файл

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
"runtime"
"time"
"github.com/Sirupsen/logrus"
@ -22,7 +23,7 @@ const maxDownloadAttempts = 5
// registers and downloads those, taking into account dependencies between
// layers.
type LayerDownloadManager struct {
layerStore layer.Store
layerStores map[string]layer.Store
tm TransferManager
waitDuration time.Duration
}
@ -33,9 +34,9 @@ func (ldm *LayerDownloadManager) SetConcurrency(concurrency int) {
}
// NewLayerDownloadManager returns a new LayerDownloadManager.
func NewLayerDownloadManager(layerStore layer.Store, concurrencyLimit int, options ...func(*LayerDownloadManager)) *LayerDownloadManager {
func NewLayerDownloadManager(layerStores map[string]layer.Store, concurrencyLimit int, options ...func(*LayerDownloadManager)) *LayerDownloadManager {
manager := LayerDownloadManager{
layerStore: layerStore,
layerStores: layerStores,
tm: NewTransferManager(concurrencyLimit),
waitDuration: time.Second,
}
@ -94,7 +95,7 @@ type DownloadDescriptorWithRegistered interface {
// Download method is called to get the layer tar data. Layers are then
// registered in the appropriate order. The caller must call the returned
// release function once it is done with the returned RootFS object.
func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
var (
topLayer layer.Layer
topDownload *downloadTransfer
@ -104,6 +105,11 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
downloadsByKey = make(map[string]*downloadTransfer)
)
// Assume that the platform is the host OS if blank
if platform == "" {
platform = layer.Platform(runtime.GOOS)
}
rootFS := initialRootFS
for _, descriptor := range layers {
key := descriptor.Key()
@ -115,13 +121,13 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
if err == nil {
getRootFS := rootFS
getRootFS.Append(diffID)
l, err := ldm.layerStore.Get(getRootFS.ChainID())
l, err := ldm.layerStores[string(platform)].Get(getRootFS.ChainID())
if err == nil {
// Layer already exists.
logrus.Debugf("Layer already exists: %s", descriptor.ID())
progress.Update(progressOutput, descriptor.ID(), "Already exists")
if topLayer != nil {
layer.ReleaseAndLog(ldm.layerStore, topLayer)
layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
}
topLayer = l
missingLayer = false
@ -140,7 +146,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
// the stack? If so, avoid downloading it more than once.
var topDownloadUncasted Transfer
if existingDownload, ok := downloadsByKey[key]; ok {
xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload)
xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload, platform)
defer topDownload.Transfer.Release(watcher)
topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
topDownload = topDownloadUncasted.(*downloadTransfer)
@ -152,10 +158,10 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
var xferFunc DoFunc
if topDownload != nil {
xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload)
xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload, platform)
defer topDownload.Transfer.Release(watcher)
} else {
xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil)
xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil, platform)
}
topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
topDownload = topDownloadUncasted.(*downloadTransfer)
@ -165,7 +171,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
if topDownload == nil {
return rootFS, func() {
if topLayer != nil {
layer.ReleaseAndLog(ldm.layerStore, topLayer)
layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
}
}, nil
}
@ -176,7 +182,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
defer func() {
if topLayer != nil {
layer.ReleaseAndLog(ldm.layerStore, topLayer)
layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
}
}()
@ -212,11 +218,11 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
// complete before the registration step, and registers the downloaded data
// on top of parentDownload's resulting layer. Otherwise, it registers the
// layer on top of the ChainID given by parentLayer.
func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer) DoFunc {
func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer, platform layer.Platform) DoFunc {
return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
d := &downloadTransfer{
Transfer: NewTransfer(),
layerStore: ldm.layerStore,
layerStore: ldm.layerStores[string(platform)],
}
go func() {
@ -335,9 +341,9 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
src = fs.Descriptor()
}
if ds, ok := d.layerStore.(layer.DescribableStore); ok {
d.layer, err = ds.RegisterWithDescriptor(inflatedLayerData, parentLayer, src)
d.layer, err = ds.RegisterWithDescriptor(inflatedLayerData, parentLayer, platform, src)
} else {
d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer)
d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer, platform)
}
if err != nil {
select {
@ -376,11 +382,11 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
// parentDownload. This function does not log progress output because it would
// interfere with the progress reporting for sourceDownload, which has the same
// Key.
func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer) DoFunc {
func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer, platform layer.Platform) DoFunc {
return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
d := &downloadTransfer{
Transfer: NewTransfer(),
layerStore: ldm.layerStore,
layerStore: ldm.layerStores[string(platform)],
}
go func() {
@ -434,9 +440,9 @@ func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor Downloa
src = fs.Descriptor()
}
if ds, ok := d.layerStore.(layer.DescribableStore); ok {
d.layer, err = ds.RegisterWithDescriptor(layerReader, parentLayer, src)
d.layer, err = ds.RegisterWithDescriptor(layerReader, parentLayer, platform, src)
} else {
d.layer, err = d.layerStore.Register(layerReader, parentLayer)
d.layer, err = d.layerStore.Register(layerReader, parentLayer, platform)
}
if err != nil {
d.err = fmt.Errorf("failed to register layer: %v", err)

Просмотреть файл

@ -26,6 +26,7 @@ type mockLayer struct {
diffID layer.DiffID
chainID layer.ChainID
parent layer.Layer
platform layer.Platform
}
func (ml *mockLayer) TarStream() (io.ReadCloser, error) {
@ -56,6 +57,10 @@ func (ml *mockLayer) DiffSize() (size int64, err error) {
return 0, nil
}
func (ml *mockLayer) Platform() layer.Platform {
return ml.platform
}
func (ml *mockLayer) Metadata() (map[string]string, error) {
return make(map[string]string), nil
}
@ -86,7 +91,7 @@ func (ls *mockLayerStore) Map() map[layer.ChainID]layer.Layer {
return layers
}
func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID) (layer.Layer, error) {
func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID, platform layer.Platform) (layer.Layer, error) {
return ls.RegisterWithDescriptor(reader, parentID, distribution.Descriptor{})
}
@ -267,7 +272,9 @@ func TestSuccessfulDownload(t *testing.T) {
}
layerStore := &mockLayerStore{make(map[layer.ChainID]*mockLayer)}
ldm := NewLayerDownloadManager(layerStore, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
lsMap := make(map[string]layer.Store)
lsMap[runtime.GOOS] = layerStore
ldm := NewLayerDownloadManager(lsMap, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
progressChan := make(chan progress.Progress)
progressDone := make(chan struct{})
@ -286,13 +293,13 @@ func TestSuccessfulDownload(t *testing.T) {
firstDescriptor := descriptors[0].(*mockDownloadDescriptor)
// Pre-register the first layer to simulate an already-existing layer
l, err := layerStore.Register(firstDescriptor.mockTarStream(), "")
l, err := layerStore.Register(firstDescriptor.mockTarStream(), "", layer.Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
firstDescriptor.diffID = l.DiffID()
rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), descriptors, progress.ChanOutput(progressChan))
rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), layer.Platform(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
if err != nil {
t.Fatalf("download error: %v", err)
}
@ -328,7 +335,10 @@ func TestSuccessfulDownload(t *testing.T) {
}
func TestCancelledDownload(t *testing.T) {
ldm := NewLayerDownloadManager(&mockLayerStore{make(map[layer.ChainID]*mockLayer)}, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
layerStore := &mockLayerStore{make(map[layer.ChainID]*mockLayer)}
lsMap := make(map[string]layer.Store)
lsMap[runtime.GOOS] = layerStore
ldm := NewLayerDownloadManager(lsMap, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
progressChan := make(chan progress.Progress)
progressDone := make(chan struct{})
@ -347,7 +357,7 @@ func TestCancelledDownload(t *testing.T) {
}()
descriptors := downloadDescriptors(nil)
_, _, err := ldm.Download(ctx, *image.NewRootFS(), descriptors, progress.ChanOutput(progressChan))
_, _, err := ldm.Download(ctx, *image.NewRootFS(), layer.Platform(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
if err != context.Canceled {
t.Fatal("expected download to be cancelled")
}

Просмотреть файл

@ -96,6 +96,15 @@ func (img *Image) RunConfig() *container.Config {
return img.Config
}
// Platform returns the image's operating system. If not populated, defaults to the host runtime OS.
func (img *Image) Platform() string {
os := img.OS
if os == "" {
os = runtime.GOOS
}
return os
}
// MarshalJSON serializes the image to JSON. It sorts the top-level keys so
// that JSON that's been manipulated by a push/pull cycle with a legacy
// registry won't end up with a different key order.
@ -126,7 +135,7 @@ type ChildConfig struct {
}
// NewChildImage creates a new Image as a child of this image.
func NewChildImage(img *Image, child ChildConfig) *Image {
func NewChildImage(img *Image, child ChildConfig, platform string) *Image {
isEmptyLayer := layer.IsEmpty(child.DiffID)
rootFS := img.RootFS
if rootFS == nil {
@ -146,7 +155,7 @@ func NewChildImage(img *Image, child ChildConfig) *Image {
DockerVersion: dockerversion.Version,
Config: child.Config,
Architecture: runtime.GOARCH,
OS: runtime.GOOS,
OS: platform,
Container: child.ContainerID,
ContainerConfig: *child.ContainerConfig,
Author: child.Author,

Просмотреть файл

@ -3,11 +3,14 @@ package image
import (
"encoding/json"
"fmt"
"runtime"
"strings"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digestset"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/system"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
@ -42,15 +45,17 @@ type store struct {
images map[ID]*imageMeta
fs StoreBackend
digestSet *digestset.Set
platform string
}
// NewImageStore returns new store object for given layer store
func NewImageStore(fs StoreBackend, ls LayerGetReleaser) (Store, error) {
func NewImageStore(fs StoreBackend, platform string, ls LayerGetReleaser) (Store, error) {
is := &store{
ls: ls,
images: make(map[ID]*imageMeta),
fs: fs,
digestSet: digestset.NewSet(),
platform: platform,
}
// load all current images and retain layers
@ -111,6 +116,13 @@ func (is *store) Create(config []byte) (ID, error) {
return "", err
}
// Integrity check - ensure we are creating something for the correct platform
if runtime.GOOS == "windows" && system.LCOWSupported() {
if strings.ToLower(img.Platform()) != strings.ToLower(is.platform) {
return "", fmt.Errorf("cannot create entry for platform %q in image store for platform %q", img.Platform(), is.platform)
}
}
// Must reject any config that references diffIDs from the history
// which aren't among the rootfs layers.
rootFSLayers := make(map[layer.DiffID]struct{})

Просмотреть файл

@ -1,6 +1,7 @@
package image
import (
"runtime"
"testing"
"github.com/docker/docker/layer"
@ -25,7 +26,7 @@ func TestRestore(t *testing.T) {
err = fs.SetMetadata(id2, "parent", []byte(id1))
assert.NoError(t, err)
is, err := NewImageStore(fs, &mockLayerGetReleaser{})
is, err := NewImageStore(fs, runtime.GOOS, &mockLayerGetReleaser{})
assert.NoError(t, err)
assert.Len(t, is.Map(), 2)
@ -142,7 +143,7 @@ func TestParentReset(t *testing.T) {
func defaultImageStore(t *testing.T) (Store, func()) {
fsBackend, cleanup := defaultFSStoreBackend(t)
store, err := NewImageStore(fsBackend, &mockLayerGetReleaser{})
store, err := NewImageStore(fsBackend, runtime.GOOS, &mockLayerGetReleaser{})
assert.NoError(t, err)
return store, cleanup

Просмотреть файл

@ -2,12 +2,14 @@ package tarexport
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution"
@ -85,6 +87,17 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
return fmt.Errorf("invalid manifest, layers length mismatch: expected %d, got %d", expected, actual)
}
// On Windows, validate the platform, defaulting to windows if not present.
platform := layer.Platform(img.OS)
if runtime.GOOS == "windows" {
if platform == "" {
platform = "windows"
}
if (platform != "windows") && (platform != "linux") {
return fmt.Errorf("configuration for this image has an unsupported platform: %s", platform)
}
}
for i, diffID := range img.RootFS.DiffIDs {
layerPath, err := safePath(tmpDir, m.Layers[i])
if err != nil {
@ -94,7 +107,7 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
r.Append(diffID)
newLayer, err := l.ls.Get(r.ChainID())
if err != nil {
newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), m.LayerSources[diffID], progressOutput)
newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), platform, m.LayerSources[diffID], progressOutput)
if err != nil {
return err
}
@ -161,7 +174,7 @@ func (l *tarexporter) setParentID(id, parentID image.ID) error {
return l.is.SetParent(id, parentID)
}
func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, platform layer.Platform, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
// We use system.OpenSequential to use sequential file access on Windows, avoiding
// depleting the standby list. On Linux, this equates to a regular os.Open.
rawTar, err := system.OpenSequential(filename)
@ -191,9 +204,9 @@ func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string,
defer inflatedLayerData.Close()
if ds, ok := l.ls.(layer.DescribableStore); ok {
return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), foreignSrc)
return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), platform, foreignSrc)
}
return l.ls.Register(inflatedLayerData, rootFS.ChainID())
return l.ls.Register(inflatedLayerData, rootFS.ChainID(), platform)
}
func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID digest.Digest, outStream io.Writer) error {
@ -208,6 +221,10 @@ func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID digest.Diges
}
func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer, progressOutput progress.Output) error {
if runtime.GOOS == "windows" {
return errors.New("Windows does not support legacy loading of images")
}
legacyLoadedMap := make(map[string]image.ID)
dirs, err := ioutil.ReadDir(tmpDir)
@ -312,7 +329,7 @@ func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[str
if err != nil {
return err
}
newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, distribution.Descriptor{}, progressOutput)
newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, "", distribution.Descriptor{}, progressOutput)
if err != nil {
return err
}

Просмотреть файл

@ -55,6 +55,10 @@ func (el *emptyLayer) Metadata() (map[string]string, error) {
return make(map[string]string), nil
}
func (el *emptyLayer) Platform() Platform {
return ""
}
// IsEmpty returns true if the layer is an EmptyLayer
func IsEmpty(diffID DiffID) bool {
return diffID == DigestSHA256EmptyTar

13
layer/filestore_unix.go Normal file
Просмотреть файл

@ -0,0 +1,13 @@
// +build !windows
package layer
// SetPlatform writes the "platform" file to the layer filestore
func (fm *fileMetadataTransaction) SetPlatform(platform Platform) error {
return nil
}
// GetPlatform reads the "platform" file from the layer filestore
func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
return "", nil
}

Просмотреть файл

@ -0,0 +1,35 @@
package layer
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
// SetPlatform writes the "platform" file to the layer filestore
func (fm *fileMetadataTransaction) SetPlatform(platform Platform) error {
if platform == "" {
return nil
}
return fm.ws.WriteFile("platform", []byte(platform), 0644)
}
// GetPlatform reads the "platform" file from the layer filestore
func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "platform"))
if err != nil {
// For backwards compatibility, the platform file may not exist. Default to "windows" if missing.
if os.IsNotExist(err) {
return "windows", nil
}
return "", err
}
content := strings.TrimSpace(string(contentBytes))
if content != "windows" && content != "linux" {
return "", fmt.Errorf("invalid platform value: %s", content)
}
return Platform(content), nil
}

Просмотреть файл

@ -64,6 +64,14 @@ func (id ChainID) String() string {
return string(id)
}
// Platform is the platform of a layer
type Platform string
// String returns a string rendition of layers target platform
func (id Platform) String() string {
return string(id)
}
// DiffID is the hash of an individual layer tar.
type DiffID digest.Digest
@ -99,6 +107,9 @@ type Layer interface {
// Parent returns the next layer in the layer chain.
Parent() Layer
// Platform returns the platform of the layer
Platform() Platform
// Size returns the size of the entire layer chain. The size
// is calculated from the total size of all files in the layers.
Size() (int64, error)
@ -179,7 +190,7 @@ type CreateRWLayerOpts struct {
// Store represents a backend for managing both
// read-only and read-write layers.
type Store interface {
Register(io.Reader, ChainID) (Layer, error)
Register(io.Reader, ChainID, Platform) (Layer, error)
Get(ChainID) (Layer, error)
Map() map[ChainID]Layer
Release(Layer) ([]Metadata, error)
@ -197,7 +208,7 @@ type Store interface {
// DescribableStore represents a layer store capable of storing
// descriptors for layers.
type DescribableStore interface {
RegisterWithDescriptor(io.Reader, ChainID, distribution.Descriptor) (Layer, error)
RegisterWithDescriptor(io.Reader, ChainID, Platform, distribution.Descriptor) (Layer, error)
}
// MetadataTransaction represents functions for setting layer metadata
@ -208,6 +219,7 @@ type MetadataTransaction interface {
SetDiffID(DiffID) error
SetCacheID(string) error
SetDescriptor(distribution.Descriptor) error
SetPlatform(Platform) error
TarSplitWriter(compressInput bool) (io.WriteCloser, error)
Commit(ChainID) error
@ -228,6 +240,7 @@ type MetadataStore interface {
GetDiffID(ChainID) (DiffID, error)
GetCacheID(ChainID) (string, error)
GetDescriptor(ChainID) (distribution.Descriptor, error)
GetPlatform(ChainID) (Platform, error)
TarSplitReader(ChainID) (io.ReadCloser, error)
SetMountID(string, string) error

Просмотреть файл

@ -5,6 +5,8 @@ import (
"fmt"
"io"
"io/ioutil"
"runtime"
"strings"
"sync"
"github.com/Sirupsen/logrus"
@ -13,6 +15,7 @@ import (
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/system"
"github.com/opencontainers/go-digest"
"github.com/vbatts/tar-split/tar/asm"
"github.com/vbatts/tar-split/tar/storage"
@ -36,6 +39,8 @@ type layerStore struct {
mountL sync.Mutex
useTarSplit bool
platform string
}
// StoreOptions are the options used to create a new Store instance
@ -47,6 +52,7 @@ type StoreOptions struct {
IDMappings *idtools.IDMappings
PluginGetter plugingetter.PluginGetter
ExperimentalEnabled bool
Platform string
}
// NewStoreFromOptions creates a new Store instance
@ -68,13 +74,13 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) {
return nil, err
}
return NewStoreFromGraphDriver(fms, driver)
return NewStoreFromGraphDriver(fms, driver, options.Platform)
}
// NewStoreFromGraphDriver creates a new Store instance using the provided
// metadata store and graph driver. The metadata store will be used to restore
// the Store.
func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (Store, error) {
func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver, platform string) (Store, error) {
caps := graphdriver.Capabilities{}
if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok {
caps = capDriver.Capabilities()
@ -86,6 +92,7 @@ func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (St
layerMap: map[ChainID]*roLayer{},
mounts: map[string]*mountedLayer{},
useTarSplit: !caps.ReproducesExactDiffs,
platform: platform,
}
ids, mounts, err := store.List()
@ -144,6 +151,11 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
}
platform, err := ls.store.GetPlatform(layer)
if err != nil {
return nil, fmt.Errorf("failed to get platform for %s: %s", layer, err)
}
cl = &roLayer{
chainID: layer,
diffID: diff,
@ -152,6 +164,7 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
layerStore: ls,
references: map[Layer]struct{}{},
descriptor: descriptor,
platform: platform,
}
if parent != "" {
@ -247,17 +260,25 @@ func (ls *layerStore) applyTar(tx MetadataTransaction, ts io.Reader, parent stri
return nil
}
func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) {
return ls.registerWithDescriptor(ts, parent, distribution.Descriptor{})
func (ls *layerStore) Register(ts io.Reader, parent ChainID, platform Platform) (Layer, error) {
return ls.registerWithDescriptor(ts, parent, platform, distribution.Descriptor{})
}
func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, platform Platform, descriptor distribution.Descriptor) (Layer, error) {
// err is used to hold the error which will always trigger
// cleanup of creates sources but may not be an error returned
// to the caller (already exists).
var err error
var pid string
var p *roLayer
// Integrity check - ensure we are creating something for the correct platform
if runtime.GOOS == "windows" && system.LCOWSupported() {
if strings.ToLower(ls.platform) != strings.ToLower(string(platform)) {
return nil, fmt.Errorf("cannot create entry for platform %q in layer store for platform %q", platform, ls.platform)
}
}
if string(parent) != "" {
p = ls.get(parent)
if p == nil {
@ -286,6 +307,7 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descr
layerStore: ls,
references: map[Layer]struct{}{},
descriptor: descriptor,
platform: platform,
}
if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {
@ -388,7 +410,6 @@ func (ls *layerStore) deleteLayer(layer *roLayer, metadata *Metadata) error {
if err != nil {
return err
}
err = ls.store.Remove(layer.chainID)
if err != nil {
return err
@ -518,7 +539,6 @@ func (ls *layerStore) CreateRWLayer(name string, parent ChainID, opts *CreateRWL
if err = ls.driver.CreateReadWrite(m.mountID, pid, createOpts); err != nil {
return nil, err
}
if err = ls.saveMount(m); err != nil {
return nil, err
}

Просмотреть файл

@ -6,6 +6,6 @@ import (
"github.com/docker/distribution"
)
func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
return ls.registerWithDescriptor(ts, parent, descriptor)
func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, platform Platform, descriptor distribution.Descriptor) (Layer, error) {
return ls.registerWithDescriptor(ts, parent, platform, descriptor)
}

Просмотреть файл

@ -71,7 +71,7 @@ func newTestStore(t *testing.T) (Store, string, func()) {
if err != nil {
t.Fatal(err)
}
ls, err := NewStoreFromGraphDriver(fms, graph)
ls, err := NewStoreFromGraphDriver(fms, graph, runtime.GOOS)
if err != nil {
t.Fatal(err)
}
@ -106,7 +106,7 @@ func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) {
}
defer ts.Close()
layer, err := ls.Register(ts, parent)
layer, err := ls.Register(ts, parent, Platform(runtime.GOOS))
if err != nil {
return nil, err
}
@ -404,7 +404,7 @@ func TestStoreRestore(t *testing.T) {
t.Fatal(err)
}
ls2, err := NewStoreFromGraphDriver(ls.(*layerStore).store, ls.(*layerStore).driver)
ls2, err := NewStoreFromGraphDriver(ls.(*layerStore).store, ls.(*layerStore).driver, runtime.GOOS)
if err != nil {
t.Fatal(err)
}
@ -499,7 +499,7 @@ func TestTarStreamStability(t *testing.T) {
t.Fatal(err)
}
layer1, err := ls.Register(bytes.NewReader(tar1), "")
layer1, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
@ -518,7 +518,7 @@ func TestTarStreamStability(t *testing.T) {
t.Fatal(err)
}
layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID())
layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID(), Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
@ -686,12 +686,12 @@ func TestRegisterExistingLayer(t *testing.T) {
t.Fatal(err)
}
layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
@ -726,12 +726,12 @@ func TestTarStreamVerification(t *testing.T) {
t.Fatal(err)
}
layer1, err := ls.Register(bytes.NewReader(tar1), "")
layer1, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
layer2, err := ls.Register(bytes.NewReader(tar2), "")
layer2, err := ls.Register(bytes.NewReader(tar2), "", Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}

Просмотреть файл

@ -94,7 +94,7 @@ func TestLayerMigration(t *testing.T) {
if err != nil {
t.Fatal(err)
}
ls, err := NewStoreFromGraphDriver(fms, graph)
ls, err := NewStoreFromGraphDriver(fms, graph, runtime.GOOS)
if err != nil {
t.Fatal(err)
}
@ -110,14 +110,14 @@ func TestLayerMigration(t *testing.T) {
t.Fatal(err)
}
layer1b, err := ls.Register(bytes.NewReader(tar1), "")
layer1b, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
assertReferences(t, layer1a, layer1b)
// Attempt register, should be same
layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID())
layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
@ -222,7 +222,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
if err != nil {
t.Fatal(err)
}
ls, err := NewStoreFromGraphDriver(fms, graph)
ls, err := NewStoreFromGraphDriver(fms, graph, runtime.GOOS)
if err != nil {
t.Fatal(err)
}
@ -238,7 +238,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
t.Fatal(err)
}
layer1b, err := ls.Register(bytes.NewReader(tar1), "")
layer1b, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}
@ -246,7 +246,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
assertReferences(t, layer1a, layer1b)
// Attempt register, should be same
layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID())
layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), Platform(runtime.GOOS))
if err != nil {
t.Fatal(err)
}

Просмотреть файл

@ -16,6 +16,7 @@ type roLayer struct {
size int64
layerStore *layerStore
descriptor distribution.Descriptor
platform Platform
referenceCount int
references map[Layer]struct{}
@ -142,6 +143,9 @@ func storeLayer(tx MetadataTransaction, layer *roLayer) error {
return err
}
}
if err := tx.SetPlatform(layer.platform); err != nil {
return err
}
return nil
}

7
layer/ro_layer_unix.go Normal file
Просмотреть файл

@ -0,0 +1,7 @@
// +build !windows
package layer
func (rl *roLayer) Platform() Platform {
return ""
}

Просмотреть файл

@ -7,3 +7,10 @@ var _ distribution.Describable = &roLayer{}
func (rl *roLayer) Descriptor() distribution.Descriptor {
return rl.descriptor
}
func (rl *roLayer) Platform() Platform {
if rl.platform == "" {
return "windows"
}
return rl.platform
}

Просмотреть файл

@ -1,6 +1,7 @@
package libcontainerd
import (
"encoding/json"
"errors"
"fmt"
"io"
@ -96,8 +97,17 @@ const defaultOwner = "docker"
func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
clnt.lock(containerID)
defer clnt.unlock(containerID)
logrus.Debugln("libcontainerd: client.Create() with spec", spec)
if b, err := json.Marshal(spec); err == nil {
logrus.Debugln("libcontainerd: client.Create() with spec", string(b))
}
osName := spec.Platform.OS
if osName == "windows" {
return clnt.createWindows(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
}
return clnt.createLinux(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
}
func (clnt *client) createWindows(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
configuration := &hcsshim.ContainerConfig{
SystemType: "Container",
Name: containerID,
@ -265,17 +275,100 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
// Call start, and if it fails, delete the container from our
// internal structure, start will keep HCS in sync by deleting the
// container there.
logrus.Debugf("libcontainerd: Create() id=%s, Calling start()", containerID)
logrus.Debugf("libcontainerd: createWindows() id=%s, Calling start()", containerID)
if err := container.start(attachStdio); err != nil {
clnt.deleteContainer(containerID)
return err
}
logrus.Debugf("libcontainerd: Create() id=%s completed successfully", containerID)
logrus.Debugf("libcontainerd: createWindows() id=%s completed successfully", containerID)
return nil
}
func (clnt *client) createLinux(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
logrus.Debugf("libcontainerd: createLinux(): containerId %s ", containerID)
// TODO @jhowardmsft LCOW Support: This needs to be configurable, not hard-coded.
// However, good-enough for the LCOW bring-up.
configuration := &hcsshim.ContainerConfig{
HvPartition: true,
Name: containerID,
SystemType: "container",
ContainerType: "linux",
TerminateOnLastHandleClosed: true,
HvRuntime: &hcsshim.HvRuntime{
ImagePath: `c:\program files\lcow`,
},
}
var layerOpt *LayerOption
for _, option := range options {
if l, ok := option.(*LayerOption); ok {
layerOpt = l
}
}
// We must have a layer option with at least one path
if layerOpt == nil || layerOpt.LayerPaths == nil {
return fmt.Errorf("no layer option or paths were supplied to the runtime")
}
// LayerFolderPath (writeable layer) + Layers (Guid + path)
configuration.LayerFolderPath = layerOpt.LayerFolderPath
for _, layerPath := range layerOpt.LayerPaths {
_, filename := filepath.Split(layerPath)
g, err := hcsshim.NameToGuid(filename)
if err != nil {
return err
}
configuration.Layers = append(configuration.Layers, hcsshim.Layer{
ID: g.ToString(),
Path: filepath.Join(layerPath, "layer.vhd"),
})
}
hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
if err != nil {
return err
}
// Construct a container object for calling start on it.
container := &container{
containerCommon: containerCommon{
process: process{
processCommon: processCommon{
containerID: containerID,
client: clnt,
friendlyName: InitFriendlyName,
},
},
processes: make(map[string]*process),
},
ociSpec: spec,
hcsContainer: hcsContainer,
}
container.options = options
for _, option := range options {
if err := option.Apply(container); err != nil {
logrus.Errorf("libcontainerd: createLinux() %v", err)
}
}
// Call start, and if it fails, delete the container from our
// internal structure, start will keep HCS in sync by deleting the
// container there.
logrus.Debugf("libcontainerd: createLinux() id=%s, Calling start()", containerID)
if err := container.start(attachStdio); err != nil {
clnt.deleteContainer(containerID)
return err
}
logrus.Debugf("libcontainerd: createLinux() id=%s completed successfully", containerID)
return nil
}
// AddProcess is the handler for adding a process to an already running
// container. It's called through docker exec. It returns the system pid of the
// exec'd process.
@ -292,13 +385,15 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly
// create stdin, even if it's not used - it will be closed shortly. Stderr
// is only created if it we're not -t.
createProcessParms := hcsshim.ProcessConfig{
EmulateConsole: procToAdd.Terminal,
CreateStdInPipe: true,
CreateStdOutPipe: true,
CreateStdErrPipe: !procToAdd.Terminal,
}
createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
if procToAdd.Terminal {
createProcessParms.EmulateConsole = true
createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
}
// Take working directory from the process to add if it is defined,
// otherwise take from the first process.

Просмотреть файл

@ -1,6 +1,7 @@
package libcontainerd
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
@ -10,6 +11,7 @@ import (
"github.com/Microsoft/hcsshim"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/system"
"github.com/opencontainers/runtime-spec/specs-go"
)
@ -83,6 +85,16 @@ func (ctr *container) start(attachStdio StdioCallback) error {
createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
createProcessParms.User = ctr.ociSpec.Process.User.Username
// LCOW requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM.
if system.LCOWSupported() && ctr.ociSpec.Platform.OS == "linux" {
ociBuf, err := json.Marshal(ctr.ociSpec)
if err != nil {
return err
}
ociRaw := json.RawMessage(ociBuf)
createProcessParms.OCISpecification = &ociRaw
}
// Start the command running in the container.
newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
if err != nil {
@ -228,11 +240,14 @@ func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) err
if !isFirstProcessToStart {
si.State = StateExitProcess
} else {
updatePending, err := ctr.hcsContainer.HasPendingUpdates()
if err != nil {
logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)
} else {
si.UpdatePending = updatePending
// Pending updates is only applicable for WCOW
if ctr.ociSpec.Platform.OS == "windows" {
updatePending, err := ctr.hcsContainer.HasPendingUpdates()
if err != nil {
logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)
} else {
si.UpdatePending = updatePending
}
}
logrus.Debugf("libcontainerd: shutting down container %s", ctr.containerID)

Просмотреть файл

@ -80,7 +80,7 @@ func New(stateDir string, options ...RemoteOption) (_ Remote, err error) {
}
}
if err := system.MkdirAll(stateDir, 0700); err != nil {
if err := system.MkdirAll(stateDir, 0700, ""); err != nil {
return nil, err
}

Просмотреть файл

@ -94,7 +94,7 @@ func TestMigrateContainers(t *testing.T) {
t.Fatal(err)
}
is, err := image.NewImageStore(ifs, ls)
is, err := image.NewImageStore(ifs, runtime.GOOS, ls)
if err != nil {
t.Fatal(err)
}
@ -172,12 +172,12 @@ func TestMigrateImages(t *testing.T) {
t.Fatal(err)
}
is, err := image.NewImageStore(ifs, ls)
is, err := image.NewImageStore(ifs, runtime.GOOS, ls)
if err != nil {
t.Fatal(err)
}
ms, err := metadata.NewFSMetadataStore(filepath.Join(tmpdir, "distribution"))
ms, err := metadata.NewFSMetadataStore(filepath.Join(tmpdir, "distribution"), runtime.GOOS)
if err != nil {
t.Fatal(err)
}
@ -433,6 +433,10 @@ func (l *mockLayer) DiffSize() (int64, error) {
return 0, nil
}
func (l *mockLayer) Platform() layer.Platform {
return ""
}
func (l *mockLayer) Metadata() (map[string]string, error) {
return nil, nil
}

Просмотреть файл

@ -30,14 +30,55 @@ func defaultCapabilities() []string {
}
}
// DefaultSpec returns default oci spec used by docker.
// DefaultSpec returns the default spec used by docker for the current Platform
func DefaultSpec() specs.Spec {
s := specs.Spec{
return DefaultOSSpec(runtime.GOOS)
}
// DefaultOSSpec returns the spec for a given OS
func DefaultOSSpec(osName string) specs.Spec {
if osName == "windows" {
return DefaultWindowsSpec()
} else if osName == "solaris" {
return DefaultSolarisSpec()
} else {
return DefaultLinuxSpec()
}
}
// DefaultWindowsSpec create a default spec for running Windows containers
func DefaultWindowsSpec() specs.Spec {
return specs.Spec{
Version: specs.Version,
Platform: specs.Platform{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
},
Windows: &specs.Windows{},
}
}
// DefaultSolarisSpec create a default spec for running Solaris containers
func DefaultSolarisSpec() specs.Spec {
s := specs.Spec{
Version: "0.6.0",
Platform: specs.Platform{
OS: "SunOS",
Arch: runtime.GOARCH,
},
}
s.Solaris = &specs.Solaris{}
return s
}
// DefaultLinuxSpec create a default spec for running Linux containers
func DefaultLinuxSpec() specs.Spec {
s := specs.Spec{
Version: specs.Version,
Platform: specs.Platform{
OS: "linux",
Arch: runtime.GOARCH,
},
}
s.Mounts = []specs.Mount{
{
@ -91,7 +132,6 @@ func DefaultSpec() specs.Spec {
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
},
ReadonlyPaths: []string{
"/proc/asound",
@ -172,5 +212,10 @@ func DefaultSpec() specs.Spec {
},
}
// For LCOW support, don't mask /sys/firmware
if runtime.GOOS != "windows" {
s.Linux.MaskedPaths = append(s.Linux.MaskedPaths, "/sys/firmware")
}
return s
}

Просмотреть файл

@ -1,20 +0,0 @@
package oci
import (
"runtime"
"github.com/opencontainers/runtime-spec/specs-go"
)
// DefaultSpec returns default oci spec used by docker.
func DefaultSpec() specs.Spec {
s := specs.Spec{
Version: "0.6.0",
Platform: specs.Platform{
OS: "SunOS",
Arch: runtime.GOARCH,
},
}
s.Solaris = &specs.Solaris{}
return s
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше