зеркало из https://github.com/microsoft/docker.git
Merge pull request #33241 from Microsoft/jjh/multi-layerstore
LCOW: Support most operations excluding remote filesystem
This commit is contained in:
Коммит
930e689668
|
@ -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,11 +275,13 @@ 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" {
|
||||
if b.platform == "windows" || (b.platform != "windows" && !system.LCOWSupported()) {
|
||||
return nil, errors.New("Windows does not support FROM scratch")
|
||||
}
|
||||
}
|
||||
return scratchImage, nil
|
||||
}
|
||||
imageMount, err := b.imageSources.Get(name, localOnly)
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -82,19 +84,25 @@ 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>.).*$`)
|
||||
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")
|
||||
|
||||
// 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,13 +97,24 @@ 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(),
|
||||
|
@ -109,7 +122,6 @@ func NewBaseContainer(id, root string) *Container {
|
|||
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{},
|
||||
},
|
||||
}
|
||||
|
||||
def, err := signal.ParseSignal(signal.DefaultStopSignal)
|
||||
|
@ -27,9 +25,7 @@ func TestContainerStopSignal(t *testing.T) {
|
|||
}
|
||||
|
||||
c = &Container{
|
||||
CommonContainer: CommonContainer{
|
||||
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{},
|
||||
},
|
||||
}
|
||||
|
||||
s := c.StopTimeout()
|
||||
|
@ -51,9 +45,7 @@ func TestContainerStopTimeout(t *testing.T) {
|
|||
|
||||
stopTimeout := 15
|
||||
c = &Container{
|
||||
CommonContainer: CommonContainer{
|
||||
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,7 +38,6 @@ func TestHealthStates(t *testing.T) {
|
|||
}
|
||||
|
||||
c := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "id",
|
||||
Name: "name",
|
||||
Config: &containertypes.Config{
|
||||
|
@ -47,7 +46,6 @@ func TestHealthStates(t *testing.T) {
|
|||
"com.docker.swarm.task.id": "id",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
daemon := &daemon.Daemon{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
131
daemon/daemon.go
131
daemon/daemon.go
|
@ -69,16 +69,23 @@ 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
|
||||
|
@ -94,8 +101,7 @@ type Daemon struct {
|
|||
apparmorEnabled bool
|
||||
shutdown bool
|
||||
idMappings *idtools.IDMappings
|
||||
layerStore layer.Store
|
||||
imageStore image.Store
|
||||
stores map[string]daemonStore // By container target platform
|
||||
PluginStore *plugin.Store // todo: remove
|
||||
pluginManager *plugin.Manager
|
||||
nameIndex *registrar.Registrar
|
||||
|
@ -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,15 +594,30 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
d.PluginStore = pluginStore
|
||||
|
@ -625,41 +644,57 @@ 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{
|
||||
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: driverName,
|
||||
GraphDriver: ds.graphDriver,
|
||||
GraphDriverOptions: config.GraphOptions,
|
||||
IDMappings: idMappings,
|
||||
PluginGetter: d.PluginStore,
|
||||
ExperimentalEnabled: config.Experimental,
|
||||
Platform: platform,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
graphDriver := d.layerStore.DriverName()
|
||||
imageRoot := filepath.Join(config.Root, "image", graphDriver)
|
||||
ds.graphDriver = ls.DriverName() // As layerstore may set the driver
|
||||
ds.layerStore = ls
|
||||
d.stores[platform] = ds
|
||||
graphDrivers = append(graphDrivers, ls.DriverName())
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
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)
|
||||
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
|
||||
volStore, err := d.configureVolumes(rootIDs)
|
||||
|
@ -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"))
|
||||
for platform, ds := range d.stores {
|
||||
dms, err := dmetadata.NewFSMetadataStore(filepath.Join(ds.imageRoot, "distribution"), platform)
|
||||
if err != nil {
|
||||
return nil, 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, graphDriver, d.layerStore, d.imageStore, referenceStore, distributionMetadataStore); err != nil {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
// 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",
|
||||
},
|
||||
}
|
||||
|
||||
c2 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de",
|
||||
Name: "drunk_hawking",
|
||||
},
|
||||
}
|
||||
|
||||
c3 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "3cdbd1aa394fd68559fd1441d6eff2abfafdcba06e72d2febdba229008b0bf57",
|
||||
Name: "3cdbd1aa",
|
||||
},
|
||||
}
|
||||
|
||||
c4 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "75fb0b800922abdbef2d27e60abcdfaf7fb0698b2a96d22d3354da361a6ff4a5",
|
||||
Name: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
|
||||
},
|
||||
}
|
||||
|
||||
c5 := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
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{},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,8 +95,10 @@ 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 platform := range daemon.stores {
|
||||
layerRefs := daemon.getLayerRefs(platform)
|
||||
allLayers := daemon.stores[platform].layerStore.Map()
|
||||
var allLayersSize int64
|
||||
for _, l := range allLayers {
|
||||
select {
|
||||
|
@ -107,10 +110,11 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
|
|||
if _, ok := layerRefs[l.ChainID()]; ok {
|
||||
allLayersSize += size
|
||||
} else {
|
||||
logrus.Warnf("found leaked image layer %v", l.ChainID())
|
||||
logrus.Warnf("found leaked image layer %v platform %s", l.ChainID(), platform)
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("failed to get diff size for layer %v", l.ChainID())
|
||||
logrus.Warnf("failed to get diff size for layer %v %s", l.ChainID(), platform)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ func TestLogContainerEventCopyLabels(t *testing.T) {
|
|||
defer e.Evict(l)
|
||||
|
||||
container := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "container_id",
|
||||
Name: "container_name",
|
||||
Config: &containertypes.Config{
|
||||
|
@ -26,7 +25,6 @@ func TestLogContainerEventCopyLabels(t *testing.T) {
|
|||
"os": "alpine",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
daemon := &Daemon{
|
||||
EventsService: e,
|
||||
|
@ -49,7 +47,6 @@ func TestLogContainerEventWithAttributes(t *testing.T) {
|
|||
defer e.Evict(l)
|
||||
|
||||
container := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "container_id",
|
||||
Name: "container_name",
|
||||
Config: &containertypes.Config{
|
||||
|
@ -58,7 +55,6 @@ func TestLogContainerEventWithAttributes(t *testing.T) {
|
|||
"os": "alpine",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
daemon := &Daemon{
|
||||
EventsService: e,
|
||||
|
|
|
@ -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,7 +19,6 @@ 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{
|
||||
|
@ -29,7 +28,6 @@ func TestNoneHealthcheck(t *testing.T) {
|
|||
},
|
||||
},
|
||||
State: &container.State{},
|
||||
},
|
||||
}
|
||||
daemon := &Daemon{}
|
||||
|
||||
|
@ -58,13 +56,11 @@ func TestHealthStates(t *testing.T) {
|
|||
}
|
||||
|
||||
c := &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
ID: "container_id",
|
||||
Name: "container_name",
|
||||
Config: &containertypes.Config{
|
||||
Image: "image_name",
|
||||
},
|
||||
},
|
||||
}
|
||||
daemon := &Daemon{
|
||||
EventsService: e,
|
||||
|
|
|
@ -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()) {
|
||||
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, nil
|
||||
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,7 +18,6 @@ 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
|
||||
|
@ -69,10 +68,9 @@ func TestBackportMountSpec(t *testing.T) {
|
|||
"/apple": {},
|
||||
"/elderberry": {},
|
||||
}},
|
||||
}}
|
||||
}
|
||||
|
||||
d.containers.Add("1", &container.Container{
|
||||
CommonContainer: container.CommonContainer{
|
||||
State: &container.State{},
|
||||
ID: "1",
|
||||
MountPoints: map[string]*volume.MountPoint{
|
||||
|
@ -83,7 +81,6 @@ func TestBackportMountSpec(t *testing.T) {
|
|||
"data:/kumquat:ro",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
type expected struct {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -560,6 +580,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|||
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
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,12 +240,15 @@ func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) err
|
|||
if !isFirstProcessToStart {
|
||||
si.State = StateExitProcess
|
||||
} else {
|
||||
// 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)
|
||||
if err := ctr.shutdown(); err != nil {
|
||||
|
|
|
@ -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
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче