diff --git a/api/server/server.go b/api/server/server.go index 098a4a8672..162f60d4b6 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1205,12 +1205,8 @@ func (s *Server) getContainersByName(version version.Version, w http.ResponseWri return fmt.Errorf("Missing parameter") } - if version.LessThan("1.20") { - containerJSONRaw, err := s.daemon.ContainerInspectPre120(vars["name"]) - if err != nil { - return err - } - return writeJSON(w, http.StatusOK, containerJSONRaw) + if version.LessThan("1.20") && runtime.GOOS != "windows" { + return getContainersByNameDownlevel(w, s, vars["name"]) } containerJSON, err := s.daemon.ContainerInspect(vars["name"]) diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 70a170bd9f..a16740e015 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -121,3 +121,13 @@ func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) } } } + +// getContainersByNameDownlevel performs processing for pre 1.20 APIs. This +// is only relevant on non-Windows daemons. +func getContainersByNameDownlevel(w http.ResponseWriter, s *Server, namevar string) error { + containerJSONRaw, err := s.daemon.ContainerInspectPre120(namevar) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, containerJSONRaw) +} diff --git a/api/server/server_windows.go b/api/server/server_windows.go index 5105b9b82a..190e98f6b0 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -60,3 +60,9 @@ func allocateDaemonPort(addr string) error { func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) { } + +// getContainersByNameDownlevel performs processing for pre 1.20 APIs. This +// is only relevant on non-Windows daemons. +func getContainersByNameDownlevel(w http.ResponseWriter, s *Server, namevar string) error { + return nil +} diff --git a/api/types/types.go b/api/types/types.go index 1c34694d01..c99e5a9a45 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -250,7 +250,8 @@ type ContainerJSON struct { Config *runconfig.Config } -// backcompatibility struct along with ContainerConfig +// backcompatibility struct along with ContainerConfig. Note this is not +// used by the Windows daemon. type ContainerJSONPre120 struct { *ContainerJSONBase Volumes map[string]string diff --git a/daemon/archive.go b/daemon/archive.go index f6b5698353..272112ced6 100644 --- a/daemon/archive.go +++ b/daemon/archive.go @@ -17,7 +17,7 @@ import ( // path does not refer to a directory. var ErrExtractPointNotDirectory = errors.New("extraction point is not a directory") -// ContainerCopy performs a depracated operation of archiving the resource at +// ContainerCopy performs a deprecated operation of archiving the resource at // the specified path in the conatiner identified by the given name. func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) { container, err := daemon.Get(name) @@ -25,7 +25,7 @@ func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, err return nil, err } - if res[0] == '/' { + if res[0] == '/' || res[0] == '\\' { res = res[1:] } @@ -90,7 +90,7 @@ func (container *Container) StatPath(path string) (stat *types.ContainerPathStat // Consider the given path as an absolute path in the container. absPath := path if !filepath.IsAbs(absPath) { - absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path) + absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(os.PathSeparator), path), path) } resolvedPath, err := container.GetResourcePath(absPath) @@ -157,7 +157,7 @@ func (container *Container) ArchivePath(path string) (content io.ReadCloser, sta // Consider the given path as an absolute path in the container. absPath := path if !filepath.IsAbs(absPath) { - absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path) + absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(os.PathSeparator), path), path) } resolvedPath, err := container.GetResourcePath(absPath) @@ -230,7 +230,7 @@ func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool, // Consider the given path as an absolute path in the container. absPath := path if !filepath.IsAbs(absPath) { - absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path) + absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(os.PathSeparator), path), path) } resolvedPath, err := container.GetResourcePath(absPath) @@ -261,19 +261,14 @@ func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool, if err != nil { return err } - absPath = filepath.Join("/", baseRel) + absPath = filepath.Join(string(os.PathSeparator), baseRel) // Need to check if the path is in a volume. If it is, it cannot be in a // read-only volume. If it is not in a volume, the container cannot be // configured with a read-only rootfs. - var toVolume bool - for _, mnt := range container.MountPoints { - if toVolume = mnt.hasResource(absPath); toVolume { - if mnt.RW { - break - } - return ErrVolumeReadonly - } + toVolume, err := checkIfPathIsInAVolume(container, absPath) + if err != nil { + return err } if !toVolume && container.hostConfig.ReadonlyRootfs { diff --git a/daemon/archive_unix.go b/daemon/archive_unix.go new file mode 100644 index 0000000000..100fc78880 --- /dev/null +++ b/daemon/archive_unix.go @@ -0,0 +1,19 @@ +// +build !windows + +package daemon + +// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it +// cannot be in a read-only volume. If it is not in a volume, the container +// cannot be configured with a read-only rootfs. +func checkIfPathIsInAVolume(container *Container, absPath string) (bool, error) { + var toVolume bool + for _, mnt := range container.MountPoints { + if toVolume = mnt.hasResource(absPath); toVolume { + if mnt.RW { + break + } + return false, ErrVolumeReadonly + } + } + return toVolume, nil +} diff --git a/daemon/archive_windows.go b/daemon/archive_windows.go new file mode 100644 index 0000000000..4e6c48a1ff --- /dev/null +++ b/daemon/archive_windows.go @@ -0,0 +1,10 @@ +package daemon + +// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it +// cannot be in a read-only volume. If it is not in a volume, the container +// cannot be configured with a read-only rootfs. +// +// This is a no-op on Windows which does not support volumes. +func checkIfPathIsInAVolume(container *Container, absPath string) (bool, error) { + return false, nil +} diff --git a/daemon/config.go b/daemon/config.go index 874ea4912a..56addfd408 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -17,11 +17,9 @@ type CommonConfig struct { AutoRestart bool Bridge bridgeConfig // Bridge holds bridge network specific configuration. Context map[string][]string - CorsHeaders string DisableBridge bool Dns []string DnsSearch []string - EnableCors bool ExecDriver string ExecOptions []string ExecRoot string @@ -51,8 +49,6 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) cmd.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", usageFn("Storage driver to use")) cmd.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, defaultExec, usageFn("Exec driver to use")) cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU")) - cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header")) - cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API")) // FIXME: why the inconsistency between "hosts" and "sockets"? cmd.Var(opts.NewListOptsRef(&config.Dns, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use")) cmd.Var(opts.NewListOptsRef(&config.DnsSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use")) diff --git a/daemon/config_linux.go b/daemon/config_linux.go index 783637ea6d..eb1c125d8c 100644 --- a/daemon/config_linux.go +++ b/daemon/config_linux.go @@ -22,6 +22,8 @@ type Config struct { // Fields below here are platform specific. + CorsHeaders string + EnableCors bool EnableSelinuxSupport bool SocketGroup string Ulimits map[string]*ulimit.Ulimit @@ -71,6 +73,8 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin cmd.BoolVar(&config.Bridge.InterContainerCommunication, []string{"#icc", "-icc"}, true, usageFn("Enable inter-container communication")) cmd.Var(opts.NewIpOpt(&config.Bridge.DefaultIP, "0.0.0.0"), []string{"#ip", "-ip"}, usageFn("Default IP when binding container ports")) cmd.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic")) + cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header")) + cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API")) config.attachExperimentalFlags(cmd, usageFn) } diff --git a/daemon/container.go b/daemon/container.go index ddfa6e5427..855900f061 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -64,28 +64,18 @@ type CommonContainer struct { Config *runconfig.Config ImageID string `json:"Image"` NetworkSettings *network.Settings - ResolvConfPath string - HostnamePath string - HostsPath string LogPath string Name string Driver string ExecDriver string MountLabel, ProcessLabel string RestartCount int - UpdateDns bool HasBeenStartedBefore bool - - MountPoints map[string]*mountPoint - Volumes map[string]string // Deprecated since 1.7, kept for backwards compatibility - VolumesRW map[string]bool // Deprecated since 1.7, kept for backwards compatibility - - hostConfig *runconfig.HostConfig - command *execdriver.Command - - monitor *containerMonitor - execCommands *execStore - daemon *Daemon + hostConfig *runconfig.HostConfig + command *execdriver.Command + monitor *containerMonitor + execCommands *execStore + daemon *Daemon // logDriver for closing logDriver logger.Logger logCopier *logger.Copier @@ -1076,94 +1066,6 @@ func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) return written, err } -func (container *Container) networkMounts() []execdriver.Mount { - var mounts []execdriver.Mount - if container.ResolvConfPath != "" { - label.SetFileLabel(container.ResolvConfPath, container.MountLabel) - mounts = append(mounts, execdriver.Mount{ - Source: container.ResolvConfPath, - Destination: "/etc/resolv.conf", - Writable: !container.hostConfig.ReadonlyRootfs, - Private: true, - }) - } - if container.HostnamePath != "" { - label.SetFileLabel(container.HostnamePath, container.MountLabel) - mounts = append(mounts, execdriver.Mount{ - Source: container.HostnamePath, - Destination: "/etc/hostname", - Writable: !container.hostConfig.ReadonlyRootfs, - Private: true, - }) - } - if container.HostsPath != "" { - label.SetFileLabel(container.HostsPath, container.MountLabel) - mounts = append(mounts, execdriver.Mount{ - Source: container.HostsPath, - Destination: "/etc/hosts", - Writable: !container.hostConfig.ReadonlyRootfs, - Private: true, - }) - } - return mounts -} - -func (container *Container) addBindMountPoint(name, source, destination string, rw bool) { - container.MountPoints[destination] = &mountPoint{ - Name: name, - Source: source, - Destination: destination, - RW: rw, - } -} - -func (container *Container) addLocalMountPoint(name, destination string, rw bool) { - container.MountPoints[destination] = &mountPoint{ - Name: name, - Driver: volume.DefaultDriverName, - Destination: destination, - RW: rw, - } -} - -func (container *Container) addMountPointWithVolume(destination string, vol volume.Volume, rw bool) { - container.MountPoints[destination] = &mountPoint{ - Name: vol.Name(), - Driver: vol.DriverName(), - Destination: destination, - RW: rw, - Volume: vol, - } -} - -func (container *Container) isDestinationMounted(destination string) bool { - return container.MountPoints[destination] != nil -} - -func (container *Container) prepareMountPoints() error { - for _, config := range container.MountPoints { - if len(config.Driver) > 0 { - v, err := createVolume(config.Name, config.Driver) - if err != nil { - return err - } - config.Volume = v - } - } - return nil -} - -func (container *Container) removeMountPoints() error { - for _, m := range container.MountPoints { - if m.Volume != nil { - if err := removeVolume(m.Volume); err != nil { - return err - } - } - } - return nil -} - func (container *Container) shouldRestart() bool { return container.hostConfig.RestartPolicy.Name == "always" || (container.hostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0) diff --git a/daemon/container_unix.go b/daemon/container_unix.go index 49ee6b6789..b5163a1110 100644 --- a/daemon/container_unix.go +++ b/daemon/container_unix.go @@ -27,12 +27,14 @@ import ( "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" + "github.com/docker/docker/volume" "github.com/docker/libnetwork" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/options" "github.com/docker/libnetwork/types" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/devices" + "github.com/opencontainers/runc/libcontainer/label" ) const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" @@ -41,9 +43,15 @@ type Container struct { CommonContainer // Fields below here are platform specific. - - AppArmorProfile string activeLinks map[string]*links.Link + AppArmorProfile string + HostnamePath string + HostsPath string + MountPoints map[string]*mountPoint + ResolvConfPath string + UpdateDns bool + Volumes map[string]string // Deprecated since 1.7, kept for backwards compatibility + VolumesRW map[string]bool // Deprecated since 1.7, kept for backwards compatibility } func killProcessDirectly(container *Container) error { @@ -1150,3 +1158,91 @@ func (container *Container) PrepareStorage() error { func (container *Container) CleanupStorage() error { return nil } + +func (container *Container) networkMounts() []execdriver.Mount { + var mounts []execdriver.Mount + if container.ResolvConfPath != "" { + label.SetFileLabel(container.ResolvConfPath, container.MountLabel) + mounts = append(mounts, execdriver.Mount{ + Source: container.ResolvConfPath, + Destination: "/etc/resolv.conf", + Writable: !container.hostConfig.ReadonlyRootfs, + Private: true, + }) + } + if container.HostnamePath != "" { + label.SetFileLabel(container.HostnamePath, container.MountLabel) + mounts = append(mounts, execdriver.Mount{ + Source: container.HostnamePath, + Destination: "/etc/hostname", + Writable: !container.hostConfig.ReadonlyRootfs, + Private: true, + }) + } + if container.HostsPath != "" { + label.SetFileLabel(container.HostsPath, container.MountLabel) + mounts = append(mounts, execdriver.Mount{ + Source: container.HostsPath, + Destination: "/etc/hosts", + Writable: !container.hostConfig.ReadonlyRootfs, + Private: true, + }) + } + return mounts +} + +func (container *Container) addBindMountPoint(name, source, destination string, rw bool) { + container.MountPoints[destination] = &mountPoint{ + Name: name, + Source: source, + Destination: destination, + RW: rw, + } +} + +func (container *Container) addLocalMountPoint(name, destination string, rw bool) { + container.MountPoints[destination] = &mountPoint{ + Name: name, + Driver: volume.DefaultDriverName, + Destination: destination, + RW: rw, + } +} + +func (container *Container) addMountPointWithVolume(destination string, vol volume.Volume, rw bool) { + container.MountPoints[destination] = &mountPoint{ + Name: vol.Name(), + Driver: vol.DriverName(), + Destination: destination, + RW: rw, + Volume: vol, + } +} + +func (container *Container) isDestinationMounted(destination string) bool { + return container.MountPoints[destination] != nil +} + +func (container *Container) prepareMountPoints() error { + for _, config := range container.MountPoints { + if len(config.Driver) > 0 { + v, err := createVolume(config.Name, config.Driver) + if err != nil { + return err + } + config.Volume = v + } + } + return nil +} + +func (container *Container) removeMountPoints() error { + for _, m := range container.MountPoints { + if m.Volume != nil { + if err := removeVolume(m.Volume); err != nil { + return err + } + } + } + return nil +} diff --git a/daemon/container_windows.go b/daemon/container_windows.go index ebba2e72f6..2a64f68238 100644 --- a/daemon/container_windows.go +++ b/daemon/container_windows.go @@ -22,18 +22,6 @@ type Container struct { CommonContainer // Fields below here are platform specific. - - // TODO Windows. Further factoring out of unused fields will be necessary. - - // ---- START OF TEMPORARY DECLARATION ---- - // TODO Windows. Temporarily keeping fields in to assist in compilation - // of the daemon on Windows without affecting many other files in a single - // PR, thus making code review significantly harder. These lines will be - // removed in subsequent PRs. - - AppArmorProfile string - // ---- END OF TEMPORARY DECLARATION ---- - } func killProcessDirectly(container *Container) error { @@ -213,3 +201,13 @@ func (container *Container) CleanupStorage() error { } return nil } + +// TODO Windows. This can be further factored out. Used in daemon.go +func (container *Container) prepareMountPoints() error { + return nil +} + +// TODO Windows. This can be further factored out. Used in delete.go +func (container *Container) removeMountPoints() error { + return nil +} diff --git a/daemon/create.go b/daemon/create.go index 9b576dd9a1..7eb9da8f78 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -2,15 +2,11 @@ package daemon import ( "fmt" - "os" - "path/filepath" - "strings" "github.com/Sirupsen/logrus" "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" "github.com/opencontainers/runc/libcontainer/label" ) @@ -96,47 +92,10 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos } defer container.Unmount() - for spec := range config.Volumes { - var ( - name, destination string - parts = strings.Split(spec, ":") - ) - switch len(parts) { - case 2: - name, destination = parts[0], filepath.Clean(parts[1]) - default: - name = stringid.GenerateRandomID() - destination = filepath.Clean(parts[0]) - } - // Skip volumes for which we already have something mounted on that - // destination because of a --volume-from. - if container.isDestinationMounted(destination) { - continue - } - path, err := container.GetResourcePath(destination) - if err != nil { - return nil, nil, err - } - - stat, err := os.Stat(path) - if err == nil && !stat.IsDir() { - return nil, nil, fmt.Errorf("cannot mount volume over existing file, file exists %s", path) - } - - v, err := createVolume(name, config.VolumeDriver) - if err != nil { - return nil, nil, err - } - if err := label.Relabel(v.Path(), container.MountLabel, "z"); err != nil { - return nil, nil, err - } - - if err := container.copyImagePathContent(v, destination); err != nil { - return nil, nil, err - } - - container.addMountPointWithVolume(destination, v, true) + if err := createContainerPlatformSpecificSettings(container, config); err != nil { + return nil, nil, err } + if err := container.ToDisk(); err != nil { logrus.Errorf("Error saving new container to disk: %v", err) return nil, nil, err diff --git a/daemon/create_unix.go b/daemon/create_unix.go new file mode 100644 index 0000000000..921d44eb73 --- /dev/null +++ b/daemon/create_unix.go @@ -0,0 +1,60 @@ +// +build !windows + +package daemon + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/runconfig" + "github.com/opencontainers/runc/libcontainer/label" +) + +// createContainerPlatformSpecificSettings performs platform specific container create functionality +func createContainerPlatformSpecificSettings(container *Container, config *runconfig.Config) error { + for spec := range config.Volumes { + var ( + name, destination string + parts = strings.Split(spec, ":") + ) + switch len(parts) { + case 2: + name, destination = parts[0], filepath.Clean(parts[1]) + default: + name = stringid.GenerateRandomID() + destination = filepath.Clean(parts[0]) + } + // Skip volumes for which we already have something mounted on that + // destination because of a --volume-from. + if container.isDestinationMounted(destination) { + continue + } + path, err := container.GetResourcePath(destination) + if err != nil { + return err + } + + stat, err := os.Stat(path) + if err == nil && !stat.IsDir() { + return fmt.Errorf("cannot mount volume over existing file, file exists %s", path) + } + + v, err := createVolume(name, config.VolumeDriver) + if err != nil { + return err + } + if err := label.Relabel(v.Path(), container.MountLabel, "z"); err != nil { + return err + } + + if err := container.copyImagePathContent(v, destination); err != nil { + return err + } + + container.addMountPointWithVolume(destination, v, true) + } + return nil +} diff --git a/daemon/create_windows.go b/daemon/create_windows.go new file mode 100644 index 0000000000..401d68d8c5 --- /dev/null +++ b/daemon/create_windows.go @@ -0,0 +1,10 @@ +package daemon + +import ( + "github.com/docker/docker/runconfig" +) + +// createContainerPlatformSpecificSettings performs platform specific container create functionality +func createContainerPlatformSpecificSettings(container *Container, config *runconfig.Config) error { + return nil +} diff --git a/daemon/daemon.go b/daemon/daemon.go index b02d975034..911b47df5b 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -144,19 +144,17 @@ func (daemon *Daemon) containerRoot(id string) string { // Load reads the contents of a container from disk // This is typically done at startup. func (daemon *Daemon) load(id string) (*Container, error) { - container := &Container{ - CommonContainer: daemon.newBaseContainer(id), - } + container := daemon.newBaseContainer(id) if err := container.FromDisk(); err != nil { return nil, err } if container.ID != id { - return container, fmt.Errorf("Container %s is stored at %s", container.ID, id) + return &container, fmt.Errorf("Container %s is stored at %s", container.ID, id) } - return container, nil + return &container, nil } // Register makes a container object usable by the daemon as @@ -478,11 +476,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID base.Driver = daemon.driver.String() base.ExecDriver = daemon.execDriver.Name() - container := &Container{ - CommonContainer: base, - } - - return container, err + return &base, err } func GetFullContainerName(name string) (string, error) { @@ -947,18 +941,6 @@ func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig. return nil } -func (daemon *Daemon) newBaseContainer(id string) CommonContainer { - return CommonContainer{ - ID: id, - State: NewState(), - MountPoints: make(map[string]*mountPoint), - Volumes: make(map[string]string), - VolumesRW: make(map[string]bool), - execCommands: newExecStore(), - root: daemon.containerRoot(id), - } -} - func setDefaultMtu(config *Config) { // do nothing if the config does not have the default 0 value. if config.Mtu != 0 { diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 4adcac24d6..68510f1ac1 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -539,3 +539,17 @@ func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig. return nil } + +func (daemon *Daemon) newBaseContainer(id string) Container { + return Container{ + CommonContainer: CommonContainer{ + ID: id, + State: NewState(), + execCommands: newExecStore(), + root: daemon.containerRoot(id), + }, + MountPoints: make(map[string]*mountPoint), + Volumes: make(map[string]string), + VolumesRW: make(map[string]bool), + } +} diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index 10bb039d54..e89526d834 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -169,3 +169,14 @@ func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig. } return nil } + +func (daemon *Daemon) newBaseContainer(id string) Container { + return Container{ + CommonContainer: CommonContainer{ + ID: id, + State: NewState(), + execCommands: newExecStore(), + root: daemon.containerRoot(id), + }, + } +} diff --git a/daemon/inspect.go b/daemon/inspect.go index d4e96e2e48..df909e1854 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -21,53 +21,11 @@ func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error return nil, err } - mountPoints := make([]types.MountPoint, 0, len(container.MountPoints)) - for _, m := range container.MountPoints { - mountPoints = append(mountPoints, types.MountPoint{ - Name: m.Name, - Source: m.Path(), - Destination: m.Destination, - Driver: m.Driver, - Mode: m.Mode, - RW: m.RW, - }) - } + mountPoints := addMountPoints(container) return &types.ContainerJSON{base, mountPoints, container.Config}, nil } -func (daemon *Daemon) ContainerInspectPre120(name string) (*types.ContainerJSONPre120, error) { - container, err := daemon.Get(name) - if err != nil { - return nil, err - } - - container.Lock() - defer container.Unlock() - - base, err := daemon.getInspectData(container) - if err != nil { - return nil, err - } - - volumes := make(map[string]string) - volumesRW := make(map[string]bool) - for _, m := range container.MountPoints { - volumes[m.Destination] = m.Path() - volumesRW[m.Destination] = m.RW - } - - config := &types.ContainerConfig{ - container.Config, - container.hostConfig.Memory, - container.hostConfig.MemorySwap, - container.hostConfig.CPUShares, - container.hostConfig.CpusetCpus, - } - - return &types.ContainerJSONPre120{base, volumes, volumesRW, config}, nil -} - func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSONBase, error) { // make a copy to play with hostConfig := *container.hostConfig @@ -104,9 +62,6 @@ func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSON State: containerState, Image: container.ImageID, NetworkSettings: container.NetworkSettings, - ResolvConfPath: container.ResolvConfPath, - HostnamePath: container.HostnamePath, - HostsPath: container.HostsPath, LogPath: container.LogPath, Name: container.Name, RestartCount: container.RestartCount, @@ -114,11 +69,13 @@ func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSON ExecDriver: container.ExecDriver, MountLabel: container.MountLabel, ProcessLabel: container.ProcessLabel, - AppArmorProfile: container.AppArmorProfile, ExecIDs: container.GetExecIDs(), HostConfig: &hostConfig, } + // Now set any platform-specific fields + contJSONBase = setPlatformSpecificContainerFields(container, contJSONBase) + contJSONBase.GraphDriver.Name = container.Driver graphDriverData, err := daemon.driver.GetMetadata(container.ID) if err != nil { diff --git a/daemon/inspect_unix.go b/daemon/inspect_unix.go new file mode 100644 index 0000000000..7d326543ad --- /dev/null +++ b/daemon/inspect_unix.go @@ -0,0 +1,62 @@ +// +build !windows + +package daemon + +import "github.com/docker/docker/api/types" + +// This sets platform-specific fields +func setPlatformSpecificContainerFields(container *Container, contJSONBase *types.ContainerJSONBase) *types.ContainerJSONBase { + contJSONBase.AppArmorProfile = container.AppArmorProfile + contJSONBase.ResolvConfPath = container.ResolvConfPath + contJSONBase.HostnamePath = container.HostnamePath + contJSONBase.HostsPath = container.HostsPath + + return contJSONBase +} + +func (daemon *Daemon) ContainerInspectPre120(name string) (*types.ContainerJSONPre120, error) { + container, err := daemon.Get(name) + if err != nil { + return nil, err + } + + container.Lock() + defer container.Unlock() + + base, err := daemon.getInspectData(container) + if err != nil { + return nil, err + } + + volumes := make(map[string]string) + volumesRW := make(map[string]bool) + for _, m := range container.MountPoints { + volumes[m.Destination] = m.Path() + volumesRW[m.Destination] = m.RW + } + + config := &types.ContainerConfig{ + container.Config, + container.hostConfig.Memory, + container.hostConfig.MemorySwap, + container.hostConfig.CPUShares, + container.hostConfig.CpusetCpus, + } + + return &types.ContainerJSONPre120{base, volumes, volumesRW, config}, nil +} + +func addMountPoints(container *Container) []types.MountPoint { + mountPoints := make([]types.MountPoint, 0, len(container.MountPoints)) + for _, m := range container.MountPoints { + mountPoints = append(mountPoints, types.MountPoint{ + Name: m.Name, + Source: m.Path(), + Destination: m.Destination, + Driver: m.Driver, + Mode: m.Mode, + RW: m.RW, + }) + } + return mountPoints +} diff --git a/daemon/inspect_windows.go b/daemon/inspect_windows.go new file mode 100644 index 0000000000..c5baa1382c --- /dev/null +++ b/daemon/inspect_windows.go @@ -0,0 +1,12 @@ +package daemon + +import "github.com/docker/docker/api/types" + +// This sets platform-specific fields +func setPlatformSpecificContainerFields(container *Container, contJSONBase *types.ContainerJSONBase) *types.ContainerJSONBase { + return contJSONBase +} + +func addMountPoints(container *Container) []types.MountPoint { + return nil +} diff --git a/daemon/volumes.go b/daemon/volumes.go index d484648375..5d43c73ec5 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -8,20 +8,17 @@ import ( "path/filepath" "strings" - "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/system" - "github.com/docker/docker/runconfig" "github.com/docker/docker/volume" - "github.com/docker/docker/volume/drivers" - "github.com/docker/docker/volume/local" - "github.com/opencontainers/runc/libcontainer/label" ) // ErrVolumeReadonly is used to signal an error when trying to copy data into // a volume mount that is not writable. var ErrVolumeReadonly = errors.New("mounted volume is marked read-only") +// TODO Windows. Further platform refactoring can still be done in volumes*.go + type mountPoint struct { Name string Destination string @@ -70,73 +67,6 @@ func (m *mountPoint) Path() string { return m.Source } -// BackwardsCompatible decides whether this mount point can be -// used in old versions of Docker or not. -// Only bind mounts and local volumes can be used in old versions of Docker. -func (m *mountPoint) BackwardsCompatible() bool { - return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName -} - -func parseBindMount(spec string, mountLabel string, config *runconfig.Config) (*mountPoint, error) { - bind := &mountPoint{ - RW: true, - } - arr := strings.Split(spec, ":") - - switch len(arr) { - case 2: - bind.Destination = arr[1] - case 3: - bind.Destination = arr[1] - mode := arr[2] - isValid, isRw := volume.ValidateMountMode(mode) - if !isValid { - return nil, fmt.Errorf("invalid mode for volumes-from: %s", mode) - } - bind.RW = isRw - // Mode field is used by SELinux to decide whether to apply label - bind.Mode = mode - default: - return nil, fmt.Errorf("Invalid volume specification: %s", spec) - } - - name, source, err := parseVolumeSource(arr[0]) - if err != nil { - return nil, err - } - - if len(source) == 0 { - bind.Driver = config.VolumeDriver - if len(bind.Driver) == 0 { - bind.Driver = volume.DefaultDriverName - } - } else { - bind.Source = filepath.Clean(source) - } - - bind.Name = name - bind.Destination = filepath.Clean(bind.Destination) - return bind, nil -} - -func parseVolumesFrom(spec string) (string, string, error) { - if len(spec) == 0 { - return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec) - } - - specParts := strings.SplitN(spec, ":", 2) - id := specParts[0] - mode := "rw" - - if len(specParts) == 2 { - mode = specParts[1] - if isValid, _ := volume.ValidateMountMode(mode); !isValid { - return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode) - } - } - return id, mode, nil -} - func copyExistingContents(source, destination string) error { volList, err := ioutil.ReadDir(source) if err != nil { @@ -156,211 +86,3 @@ func copyExistingContents(source, destination string) error { } return copyOwnership(source, destination) } - -// registerMountPoints initializes the container mount points with the configured volumes and bind mounts. -// It follows the next sequence to decide what to mount in each final destination: -// -// 1. Select the previously configured mount points for the containers, if any. -// 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. -// 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. -func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error { - binds := map[string]bool{} - mountPoints := map[string]*mountPoint{} - - // 1. Read already configured mount points. - for name, point := range container.MountPoints { - mountPoints[name] = point - } - - // 2. Read volumes from other containers. - for _, v := range hostConfig.VolumesFrom { - containerID, mode, err := parseVolumesFrom(v) - if err != nil { - return err - } - - c, err := daemon.Get(containerID) - if err != nil { - return err - } - - for _, m := range c.MountPoints { - cp := &mountPoint{ - Name: m.Name, - Source: m.Source, - RW: m.RW && volume.ReadWrite(mode), - Driver: m.Driver, - Destination: m.Destination, - } - - if len(cp.Source) == 0 { - v, err := createVolume(cp.Name, cp.Driver) - if err != nil { - return err - } - cp.Volume = v - } - - mountPoints[cp.Destination] = cp - } - } - - // 3. Read bind mounts - for _, b := range hostConfig.Binds { - // #10618 - bind, err := parseBindMount(b, container.MountLabel, container.Config) - if err != nil { - return err - } - - if binds[bind.Destination] { - return fmt.Errorf("Duplicate bind mount %s", bind.Destination) - } - - if len(bind.Name) > 0 && len(bind.Driver) > 0 { - // create the volume - v, err := createVolume(bind.Name, bind.Driver) - if err != nil { - return err - } - bind.Volume = v - bind.Source = v.Path() - // Since this is just a named volume and not a typical bind, set to shared mode `z` - if bind.Mode == "" { - bind.Mode = "z" - } - } - - if err := label.Relabel(bind.Source, container.MountLabel, bind.Mode); err != nil { - return err - } - binds[bind.Destination] = true - mountPoints[bind.Destination] = bind - } - - // Keep backwards compatible structures - bcVolumes := map[string]string{} - bcVolumesRW := map[string]bool{} - for _, m := range mountPoints { - if m.BackwardsCompatible() { - bcVolumes[m.Destination] = m.Path() - bcVolumesRW[m.Destination] = m.RW - } - } - - container.Lock() - container.MountPoints = mountPoints - container.Volumes = bcVolumes - container.VolumesRW = bcVolumesRW - container.Unlock() - - return nil -} - -// TODO Windows. Factor out as not relevant (as Windows daemon support not in pre-1.7) -// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7. -// It reads the container configuration and creates valid mount points for the old volumes. -func (daemon *Daemon) verifyVolumesInfo(container *Container) error { - // Inspect old structures only when we're upgrading from old versions - // to versions >= 1.7 and the MountPoints has not been populated with volumes data. - if len(container.MountPoints) == 0 && len(container.Volumes) > 0 { - for destination, hostPath := range container.Volumes { - vfsPath := filepath.Join(daemon.root, "vfs", "dir") - rw := container.VolumesRW != nil && container.VolumesRW[destination] - - if strings.HasPrefix(hostPath, vfsPath) { - id := filepath.Base(hostPath) - if err := migrateVolume(id, hostPath); err != nil { - return err - } - container.addLocalMountPoint(id, destination, rw) - } else { // Bind mount - id, source, err := parseVolumeSource(hostPath) - // We should not find an error here coming - // from the old configuration, but who knows. - if err != nil { - return err - } - container.addBindMountPoint(id, source, destination, rw) - } - } - } else if len(container.MountPoints) > 0 { - // Volumes created with a Docker version >= 1.7. We verify integrity in case of data created - // with Docker 1.7 RC versions that put the information in - // DOCKER_ROOT/volumes/VOLUME_ID rather than DOCKER_ROOT/volumes/VOLUME_ID/_container_data. - l, err := getVolumeDriver(volume.DefaultDriverName) - if err != nil { - return err - } - - for _, m := range container.MountPoints { - if m.Driver != volume.DefaultDriverName { - continue - } - dataPath := l.(*local.Root).DataPath(m.Name) - volumePath := filepath.Dir(dataPath) - - d, err := ioutil.ReadDir(volumePath) - if err != nil { - // If the volume directory doesn't exist yet it will be recreated, - // so we only return the error when there is a different issue. - if !os.IsNotExist(err) { - return err - } - // Do not check when the volume directory does not exist. - continue - } - if validVolumeLayout(d) { - continue - } - - if err := os.Mkdir(dataPath, 0755); err != nil { - return err - } - - // Move data inside the data directory - for _, f := range d { - oldp := filepath.Join(volumePath, f.Name()) - newp := filepath.Join(dataPath, f.Name()) - if err := os.Rename(oldp, newp); err != nil { - logrus.Errorf("Unable to move %s to %s\n", oldp, newp) - } - } - } - - return container.ToDisk() - } - - return nil -} - -func createVolume(name, driverName string) (volume.Volume, error) { - vd, err := getVolumeDriver(driverName) - if err != nil { - return nil, err - } - return vd.Create(name) -} - -func removeVolume(v volume.Volume) error { - vd, err := getVolumeDriver(v.DriverName()) - if err != nil { - return nil - } - return vd.Remove(v) -} - -func getVolumeDriver(name string) (volume.Driver, error) { - if name == "" { - name = volume.DefaultDriverName - } - return volumedrivers.Lookup(name) -} - -func parseVolumeSource(spec string) (string, string, error) { - if !filepath.IsAbs(spec) { - return spec, "", nil - } - - return "", spec, nil -} diff --git a/daemon/volumes_linux.go b/daemon/volumes_linux.go index 0155c52279..d0a752a300 100644 --- a/daemon/volumes_linux.go +++ b/daemon/volumes_linux.go @@ -3,15 +3,21 @@ package daemon import ( + "fmt" + "io/ioutil" "os" "path/filepath" "sort" "strings" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/system" + "github.com/docker/docker/runconfig" "github.com/docker/docker/volume" + "github.com/docker/docker/volume/drivers" "github.com/docker/docker/volume/local" + "github.com/opencontainers/runc/libcontainer/label" ) // copyOwnership copies the permissions and uid:gid of the source file @@ -49,6 +55,48 @@ func (container *Container) setupMounts() ([]execdriver.Mount, error) { return append(mounts, container.networkMounts()...), nil } +func parseBindMount(spec string, mountLabel string, config *runconfig.Config) (*mountPoint, error) { + bind := &mountPoint{ + RW: true, + } + arr := strings.Split(spec, ":") + + switch len(arr) { + case 2: + bind.Destination = arr[1] + case 3: + bind.Destination = arr[1] + mode := arr[2] + isValid, isRw := volume.ValidateMountMode(mode) + if !isValid { + return nil, fmt.Errorf("invalid mode for volumes-from: %s", mode) + } + bind.RW = isRw + // Mode field is used by SELinux to decide whether to apply label + bind.Mode = mode + default: + return nil, fmt.Errorf("Invalid volume specification: %s", spec) + } + + name, source, err := parseVolumeSource(arr[0]) + if err != nil { + return nil, err + } + + if len(source) == 0 { + bind.Driver = config.VolumeDriver + if len(bind.Driver) == 0 { + bind.Driver = volume.DefaultDriverName + } + } else { + bind.Source = filepath.Clean(source) + } + + bind.Name = name + bind.Destination = filepath.Clean(bind.Destination) + return bind, nil +} + func sortMounts(m []execdriver.Mount) []execdriver.Mount { sort.Sort(mounts(m)) return m @@ -118,3 +166,235 @@ func validVolumeLayout(files []os.FileInfo) bool { return true } + +// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7. +// It reads the container configuration and creates valid mount points for the old volumes. +func (daemon *Daemon) verifyVolumesInfo(container *Container) error { + // Inspect old structures only when we're upgrading from old versions + // to versions >= 1.7 and the MountPoints has not been populated with volumes data. + if len(container.MountPoints) == 0 && len(container.Volumes) > 0 { + for destination, hostPath := range container.Volumes { + vfsPath := filepath.Join(daemon.root, "vfs", "dir") + rw := container.VolumesRW != nil && container.VolumesRW[destination] + + if strings.HasPrefix(hostPath, vfsPath) { + id := filepath.Base(hostPath) + if err := migrateVolume(id, hostPath); err != nil { + return err + } + container.addLocalMountPoint(id, destination, rw) + } else { // Bind mount + id, source, err := parseVolumeSource(hostPath) + // We should not find an error here coming + // from the old configuration, but who knows. + if err != nil { + return err + } + container.addBindMountPoint(id, source, destination, rw) + } + } + } else if len(container.MountPoints) > 0 { + // Volumes created with a Docker version >= 1.7. We verify integrity in case of data created + // with Docker 1.7 RC versions that put the information in + // DOCKER_ROOT/volumes/VOLUME_ID rather than DOCKER_ROOT/volumes/VOLUME_ID/_container_data. + l, err := getVolumeDriver(volume.DefaultDriverName) + if err != nil { + return err + } + + for _, m := range container.MountPoints { + if m.Driver != volume.DefaultDriverName { + continue + } + dataPath := l.(*local.Root).DataPath(m.Name) + volumePath := filepath.Dir(dataPath) + + d, err := ioutil.ReadDir(volumePath) + if err != nil { + // If the volume directory doesn't exist yet it will be recreated, + // so we only return the error when there is a different issue. + if !os.IsNotExist(err) { + return err + } + // Do not check when the volume directory does not exist. + continue + } + if validVolumeLayout(d) { + continue + } + + if err := os.Mkdir(dataPath, 0755); err != nil { + return err + } + + // Move data inside the data directory + for _, f := range d { + oldp := filepath.Join(volumePath, f.Name()) + newp := filepath.Join(dataPath, f.Name()) + if err := os.Rename(oldp, newp); err != nil { + logrus.Errorf("Unable to move %s to %s\n", oldp, newp) + } + } + } + + return container.ToDisk() + } + + return nil +} + +func parseVolumesFrom(spec string) (string, string, error) { + if len(spec) == 0 { + return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec) + } + + specParts := strings.SplitN(spec, ":", 2) + id := specParts[0] + mode := "rw" + + if len(specParts) == 2 { + mode = specParts[1] + if isValid, _ := volume.ValidateMountMode(mode); !isValid { + return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode) + } + } + return id, mode, nil +} + +// registerMountPoints initializes the container mount points with the configured volumes and bind mounts. +// It follows the next sequence to decide what to mount in each final destination: +// +// 1. Select the previously configured mount points for the containers, if any. +// 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. +// 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. +func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error { + binds := map[string]bool{} + mountPoints := map[string]*mountPoint{} + + // 1. Read already configured mount points. + for name, point := range container.MountPoints { + mountPoints[name] = point + } + + // 2. Read volumes from other containers. + for _, v := range hostConfig.VolumesFrom { + containerID, mode, err := parseVolumesFrom(v) + if err != nil { + return err + } + + c, err := daemon.Get(containerID) + if err != nil { + return err + } + + for _, m := range c.MountPoints { + cp := &mountPoint{ + Name: m.Name, + Source: m.Source, + RW: m.RW && volume.ReadWrite(mode), + Driver: m.Driver, + Destination: m.Destination, + } + + if len(cp.Source) == 0 { + v, err := createVolume(cp.Name, cp.Driver) + if err != nil { + return err + } + cp.Volume = v + } + + mountPoints[cp.Destination] = cp + } + } + + // 3. Read bind mounts + for _, b := range hostConfig.Binds { + // #10618 + bind, err := parseBindMount(b, container.MountLabel, container.Config) + if err != nil { + return err + } + + if binds[bind.Destination] { + return fmt.Errorf("Duplicate bind mount %s", bind.Destination) + } + + if len(bind.Name) > 0 && len(bind.Driver) > 0 { + // create the volume + v, err := createVolume(bind.Name, bind.Driver) + if err != nil { + return err + } + bind.Volume = v + bind.Source = v.Path() + // Since this is just a named volume and not a typical bind, set to shared mode `z` + if bind.Mode == "" { + bind.Mode = "z" + } + } + + if err := label.Relabel(bind.Source, container.MountLabel, bind.Mode); err != nil { + return err + } + binds[bind.Destination] = true + mountPoints[bind.Destination] = bind + } + + // Keep backwards compatible structures + bcVolumes := map[string]string{} + bcVolumesRW := map[string]bool{} + for _, m := range mountPoints { + if m.BackwardsCompatible() { + bcVolumes[m.Destination] = m.Path() + bcVolumesRW[m.Destination] = m.RW + } + } + + container.Lock() + container.MountPoints = mountPoints + container.Volumes = bcVolumes + container.VolumesRW = bcVolumesRW + container.Unlock() + + return nil +} + +func createVolume(name, driverName string) (volume.Volume, error) { + vd, err := getVolumeDriver(driverName) + if err != nil { + return nil, err + } + return vd.Create(name) +} + +func removeVolume(v volume.Volume) error { + vd, err := getVolumeDriver(v.DriverName()) + if err != nil { + return nil + } + return vd.Remove(v) +} + +func getVolumeDriver(name string) (volume.Driver, error) { + if name == "" { + name = volume.DefaultDriverName + } + return volumedrivers.Lookup(name) +} + +func parseVolumeSource(spec string) (string, string, error) { + if !filepath.IsAbs(spec) { + return spec, "", nil + } + + return "", spec, nil +} + +// BackwardsCompatible decides whether this mount point can be +// used in old versions of Docker or not. +// Only bind mounts and local volumes can be used in old versions of Docker. +func (m *mountPoint) BackwardsCompatible() bool { + return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName +} diff --git a/daemon/volumes_windows.go b/daemon/volumes_windows.go index e75b2dc691..40db7eda95 100644 --- a/daemon/volumes_windows.go +++ b/daemon/volumes_windows.go @@ -3,9 +3,8 @@ package daemon import ( - "os" - "github.com/docker/docker/daemon/execdriver" + "github.com/docker/docker/runconfig" ) // Not supported on Windows @@ -17,10 +16,13 @@ func (container *Container) setupMounts() ([]execdriver.Mount, error) { return nil, nil } -func migrateVolume(id, vfs string) error { +// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7. +// As the Windows daemon was not supported before 1.7, this is a no-op +func (daemon *Daemon) verifyVolumesInfo(container *Container) error { return nil } -func validVolumeLayout(files []os.FileInfo) bool { - return true +// TODO Windows: This can be further factored out. Called from daemon\daemon.go +func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error { + return nil } diff --git a/docker/daemon.go b/docker/daemon.go index 731595e653..ac2a1d36d8 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -212,10 +212,8 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { } serverConfig := &apiserver.ServerConfig{ - Logging: true, - EnableCors: cli.EnableCors, - CorsHeaders: cli.CorsHeaders, - Version: dockerversion.VERSION, + Logging: true, + Version: dockerversion.VERSION, } serverConfig = setPlatformServerConfig(serverConfig, cli.Config) diff --git a/docker/daemon_unix.go b/docker/daemon_unix.go index c64ae967fe..ab75bb6210 100644 --- a/docker/daemon_unix.go +++ b/docker/daemon_unix.go @@ -17,6 +17,9 @@ import ( func setPlatformServerConfig(serverConfig *apiserver.ServerConfig, daemonCfg *daemon.Config) *apiserver.ServerConfig { serverConfig.SocketGroup = daemonCfg.SocketGroup + serverConfig.EnableCors = daemonCfg.EnableCors + serverConfig.CorsHeaders = daemonCfg.CorsHeaders + return serverConfig }