Merge pull request #5918 from crosbymichael/volumes-commit

Do not commit host bind mounts into image
This commit is contained in:
Michael Crosby 2014-05-20 14:28:49 -07:00
commit 70d35b9d39
8 changed files with 184 additions and 129 deletions

View File

@ -607,15 +607,18 @@ func (daemon *Daemon) Commit(container *Container, repository, tag, comment, aut
containerID, containerImage string containerID, containerImage string
containerConfig *runconfig.Config containerConfig *runconfig.Config
) )
if container != nil { if container != nil {
containerID = container.ID containerID = container.ID
containerImage = container.Image containerImage = container.Image
containerConfig = container.Config containerConfig = container.Config
} }
img, err := daemon.graph.Create(rwTar, containerID, containerImage, comment, author, containerConfig, config) img, err := daemon.graph.Create(rwTar, containerID, containerImage, comment, author, containerConfig, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Register the image if needed // Register the image if needed
if repository != "" { if repository != "" {
if err := daemon.repositories.Set(repository, tag, img.ID, true); err != nil { if err := daemon.repositories.Set(repository, tag, img.ID, true); err != nil {

View File

@ -162,18 +162,59 @@ func createVolumes(container *Container) error {
return err return err
} }
volumesDriver := container.daemon.volumes.Driver()
// Create the requested volumes if they don't exist // Create the requested volumes if they don't exist
for volPath := range container.Config.Volumes { for volPath := range container.Config.Volumes {
if err := initializeVolume(container, volPath, binds); err != nil {
return err
}
}
for volPath := range binds {
if err := initializeVolume(container, volPath, binds); err != nil {
return err
}
}
return nil
}
func createIfNotExists(path string, isDir bool) error {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
if isDir {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
} else {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_CREATE, 0755)
if err != nil {
return err
}
defer f.Close()
}
}
}
return nil
}
func initializeVolume(container *Container, volPath string, binds map[string]BindMap) error {
volumesDriver := container.daemon.volumes.Driver()
volPath = filepath.Clean(volPath) volPath = filepath.Clean(volPath)
volIsDir := true
// Skip existing volumes // Skip existing volumes
if _, exists := container.Volumes[volPath]; exists { if _, exists := container.Volumes[volPath]; exists {
continue return nil
} }
var srcPath string
var isBindMount bool var (
srcRW := false srcPath string
isBindMount bool
volIsDir = true
srcRW = false
)
// If an external bind is defined for this volume, use that as a source // If an external bind is defined for this volume, use that as a source
if bindMap, exists := binds[volPath]; exists { if bindMap, exists := binds[volPath]; exists {
isBindMount = true isBindMount = true
@ -191,7 +232,6 @@ func createVolumes(container *Container) error {
} }
// Otherwise create an directory in $ROOT/volumes/ and use that // Otherwise create an directory in $ROOT/volumes/ and use that
} else { } else {
// Do not pass a container as the parameter for the volume creation. // Do not pass a container as the parameter for the volume creation.
// The graph driver using the container's information ( Image ) to // The graph driver using the container's information ( Image ) to
// create the parent. // create the parent.
@ -238,15 +278,25 @@ func createVolumes(container *Container) error {
// Do not copy or change permissions if we are mounting from the host // Do not copy or change permissions if we are mounting from the host
if srcRW && !isBindMount { if srcRW && !isBindMount {
if err := copyExistingContents(rootVolPath, srcPath); err != nil {
return err
}
}
return nil
}
func copyExistingContents(rootVolPath, srcPath string) error {
volList, err := ioutil.ReadDir(rootVolPath) volList, err := ioutil.ReadDir(rootVolPath)
if err != nil { if err != nil {
return err return err
} }
if len(volList) > 0 { if len(volList) > 0 {
srcList, err := ioutil.ReadDir(srcPath) srcList, err := ioutil.ReadDir(srcPath)
if err != nil { if err != nil {
return err return err
} }
if len(srcList) == 0 { if len(srcList) == 0 {
// If the source volume is empty copy files from the root into the volume // If the source volume is empty copy files from the root into the volume
if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil { if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil {
@ -255,11 +305,14 @@ func createVolumes(container *Container) error {
} }
} }
var stat syscall.Stat_t var (
stat syscall.Stat_t
srcStat syscall.Stat_t
)
if err := syscall.Stat(rootVolPath, &stat); err != nil { if err := syscall.Stat(rootVolPath, &stat); err != nil {
return err return err
} }
var srcStat syscall.Stat_t
if err := syscall.Stat(srcPath, &srcStat); err != nil { if err := syscall.Stat(srcPath, &srcStat); err != nil {
return err return err
} }
@ -270,29 +323,5 @@ func createVolumes(container *Container) error {
return err return err
} }
} }
}
}
return nil
}
func createIfNotExists(path string, isDir bool) error {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
if isDir {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
} else {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_CREATE, 0755)
if err != nil {
return err
}
defer f.Close()
}
}
}
return nil return nil
} }

View File

@ -2,12 +2,6 @@ package graph
import ( import (
"fmt" "fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/daemon/graphdriver"
"github.com/dotcloud/docker/dockerversion"
"github.com/dotcloud/docker/image"
"github.com/dotcloud/docker/runconfig"
"github.com/dotcloud/docker/utils"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -17,6 +11,13 @@ import (
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/daemon/graphdriver"
"github.com/dotcloud/docker/dockerversion"
"github.com/dotcloud/docker/image"
"github.com/dotcloud/docker/runconfig"
"github.com/dotcloud/docker/utils"
) )
// A Graph is a store for versioned filesystem images and the relationship between them. // A Graph is a store for versioned filesystem images and the relationship between them.
@ -141,11 +142,13 @@ func (graph *Graph) Create(layerData archive.ArchiveReader, containerID, contain
Architecture: runtime.GOARCH, Architecture: runtime.GOARCH,
OS: runtime.GOOS, OS: runtime.GOOS,
} }
if containerID != "" { if containerID != "" {
img.Parent = containerImage img.Parent = containerImage
img.Container = containerID img.Container = containerID
img.ContainerConfig = *containerConfig img.ContainerConfig = *containerConfig
} }
if err := graph.Register(nil, layerData, img); err != nil { if err := graph.Register(nil, layerData, img); err != nil {
return nil, err return nil, err
} }

View File

@ -83,3 +83,28 @@ func TestCommitTTY(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestCommitWithHostBindMount(t *testing.T) {
cmd := exec.Command(dockerBinary, "run", "--name", "bind-commit", "-v", "/dev/null:/winning", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
t.Fatal(err)
}
cmd = exec.Command(dockerBinary, "commit", "bind-commit", "bindtest")
imageId, _, err := runCommandWithOutput(cmd)
if err != nil {
t.Fatal(err)
}
imageId = strings.Trim(imageId, "\r\n")
cmd = exec.Command(dockerBinary, "run", "bindtest", "true")
if _, err := runCommand(cmd); err != nil {
t.Fatal(err)
}
deleteAllContainers()
deleteImages(imageId)
logDone("commit - commit bind mounted file")
}

View File

@ -1,9 +1,10 @@
package runconfig package runconfig
import ( import (
"github.com/dotcloud/docker/nat"
"strings" "strings"
"testing" "testing"
"github.com/dotcloud/docker/nat"
) )
func parse(t *testing.T, args string) (*Config, *HostConfig, error) { func parse(t *testing.T, args string) (*Config, *HostConfig, error) {
@ -93,32 +94,20 @@ func TestParseRunVolumes(t *testing.T) {
t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes) t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
} }
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds) t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
} }
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" { if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" {
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
} else if _, exists := config.Volumes["/containerVar"]; !exists {
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
} }
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" { if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" {
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
} else if _, exists := config.Volumes["/containerVar"]; !exists {
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
} }
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds) t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
} else if _, exists := config.Volumes["/containerVar"]; !exists { } else if _, exists := config.Volumes["/containerVar"]; !exists {
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
} }

View File

@ -1,9 +1,10 @@
package runconfig package runconfig
import ( import (
"strings"
"github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/nat"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"strings"
) )
func Merge(userConf, imageConf *Config) error { func Merge(userConf, imageConf *Config) error {
@ -82,6 +83,7 @@ func Merge(userConf, imageConf *Config) error {
} }
} }
} }
if userConf.Cmd == nil || len(userConf.Cmd) == 0 { if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
userConf.Cmd = imageConf.Cmd userConf.Cmd = imageConf.Cmd
} }

View File

@ -135,8 +135,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
if arr[0] == "/" { if arr[0] == "/" {
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'") return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
} }
dstDir := arr[1] // after creating the bind mount we want to delete it from the flVolumes values because
flVolumes.Set(dstDir) // we do not want bind mounts being committed to image configs
binds = append(binds, bind) binds = append(binds, bind)
flVolumes.Delete(bind) flVolumes.Delete(bind)
} else if bind == "/" { } else if bind == "/" {

View File

@ -1050,8 +1050,12 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status {
if container == nil { if container == nil {
return job.Errorf("No such container: %s", name) return job.Errorf("No such container: %s", name)
} }
var config = container.Config
var newConfig runconfig.Config var (
config = container.Config
newConfig runconfig.Config
)
if err := job.GetenvJson("config", &newConfig); err != nil { if err := job.GetenvJson("config", &newConfig); err != nil {
return job.Error(err) return job.Error(err)
} }