... | ... |
@@ -10,6 +10,7 @@ import ( |
10 | 10 |
"log" |
11 | 11 |
"net/http" |
12 | 12 |
"net/url" |
13 |
+ "path/filepath" |
|
13 | 14 |
"runtime" |
14 | 15 |
"strconv" |
15 | 16 |
"strings" |
... | ... |
@@ -400,7 +401,8 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str |
400 | 400 |
} |
401 | 401 |
|
402 | 402 |
func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
403 |
- cmd := rcli.Subcmd(stdout, "rm", "CONTAINER [CONTAINER...]", "Remove a container") |
|
403 |
+ cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container") |
|
404 |
+ v := cmd.Bool("v", false, "Remove the volumes associated to the container") |
|
404 | 405 |
if err := cmd.Parse(args); err != nil { |
405 | 406 |
return nil |
406 | 407 |
} |
... | ... |
@@ -408,15 +410,40 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) |
408 | 408 |
cmd.Usage() |
409 | 409 |
return nil |
410 | 410 |
} |
411 |
+ volumes := make(map[string]struct{}) |
|
411 | 412 |
for _, name := range cmd.Args() { |
412 | 413 |
container := srv.runtime.Get(name) |
413 | 414 |
if container == nil { |
414 | 415 |
return fmt.Errorf("No such container: %s", name) |
415 | 416 |
} |
417 |
+ // Store all the deleted containers volumes |
|
418 |
+ for _, volumeId := range container.Volumes { |
|
419 |
+ volumes[volumeId] = struct{}{} |
|
420 |
+ } |
|
416 | 421 |
if err := srv.runtime.Destroy(container); err != nil { |
417 | 422 |
fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error()) |
418 | 423 |
} |
419 | 424 |
} |
425 |
+ if *v { |
|
426 |
+ // Retrieve all volumes from all remaining containers |
|
427 |
+ usedVolumes := make(map[string]*Container) |
|
428 |
+ for _, container := range srv.runtime.List() { |
|
429 |
+ for _, containerVolumeId := range container.Volumes { |
|
430 |
+ usedVolumes[containerVolumeId] = container |
|
431 |
+ } |
|
432 |
+ } |
|
433 |
+ |
|
434 |
+ for volumeId := range volumes { |
|
435 |
+ // If the requested volu |
|
436 |
+ if c, exists := usedVolumes[volumeId]; exists { |
|
437 |
+ fmt.Fprintf(stdout, "The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id) |
|
438 |
+ continue |
|
439 |
+ } |
|
440 |
+ if err := srv.runtime.volumes.Delete(volumeId); err != nil { |
|
441 |
+ return err |
|
442 |
+ } |
|
443 |
+ } |
|
444 |
+ } |
|
420 | 445 |
return nil |
421 | 446 |
} |
422 | 447 |
|
... | ... |
@@ -913,6 +940,25 @@ func (opts AttachOpts) Get(val string) bool { |
913 | 913 |
return false |
914 | 914 |
} |
915 | 915 |
|
916 |
+// PathOpts stores a unique set of absolute paths |
|
917 |
+type PathOpts map[string]struct{} |
|
918 |
+ |
|
919 |
+func NewPathOpts() PathOpts { |
|
920 |
+ return make(PathOpts) |
|
921 |
+} |
|
922 |
+ |
|
923 |
+func (opts PathOpts) String() string { |
|
924 |
+ return fmt.Sprintf("%v", map[string]struct{}(opts)) |
|
925 |
+} |
|
926 |
+ |
|
927 |
+func (opts PathOpts) Set(val string) error { |
|
928 |
+ if !filepath.IsAbs(val) { |
|
929 |
+ return fmt.Errorf("%s is not an absolute path", val) |
|
930 |
+ } |
|
931 |
+ opts[filepath.Clean(val)] = struct{}{} |
|
932 |
+ return nil |
|
933 |
+} |
|
934 |
+ |
|
916 | 935 |
func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
917 | 936 |
cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository") |
918 | 937 |
force := cmd.Bool("f", false, "Force") |
... | ... |
@@ -48,6 +48,7 @@ type Container struct { |
48 | 48 |
runtime *Runtime |
49 | 49 |
|
50 | 50 |
waitLock chan struct{} |
51 |
+ Volumes map[string]string |
|
51 | 52 |
} |
52 | 53 |
|
53 | 54 |
type Config struct { |
... | ... |
@@ -66,6 +67,8 @@ type Config struct { |
66 | 66 |
Cmd []string |
67 | 67 |
Dns []string |
68 | 68 |
Image string // Name of the image as it was passed by the operator (eg. could be symbolic) |
69 |
+ Volumes map[string]struct{} |
|
70 |
+ VolumesFrom string |
|
69 | 71 |
} |
70 | 72 |
|
71 | 73 |
func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) { |
... | ... |
@@ -97,6 +100,11 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con |
97 | 97 |
var flDns ListOpts |
98 | 98 |
cmd.Var(&flDns, "dns", "Set custom dns servers") |
99 | 99 |
|
100 |
+ flVolumes := NewPathOpts() |
|
101 |
+ cmd.Var(flVolumes, "v", "Attach a data volume") |
|
102 |
+ |
|
103 |
+ flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container") |
|
104 |
+ |
|
100 | 105 |
if err := cmd.Parse(args); err != nil { |
101 | 106 |
return nil, err |
102 | 107 |
} |
... | ... |
@@ -136,6 +144,8 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con |
136 | 136 |
Cmd: runCmd, |
137 | 137 |
Dns: flDns, |
138 | 138 |
Image: image, |
139 |
+ Volumes: flVolumes, |
|
140 |
+ VolumesFrom: *flVolumesFrom, |
|
139 | 141 |
} |
140 | 142 |
|
141 | 143 |
if *flMemory > 0 && !capabilities.SwapLimit { |
... | ... |
@@ -394,10 +404,40 @@ func (container *Container) Start() error { |
394 | 394 |
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") |
395 | 395 |
container.Config.MemorySwap = -1 |
396 | 396 |
} |
397 |
+ container.Volumes = make(map[string]string) |
|
398 |
+ |
|
399 |
+ // Create the requested volumes volumes |
|
400 |
+ for volPath := range container.Config.Volumes { |
|
401 |
+ if c, err := container.runtime.volumes.Create(nil, container, "", "", nil); err != nil { |
|
402 |
+ return err |
|
403 |
+ } else { |
|
404 |
+ if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { |
|
405 |
+ return nil |
|
406 |
+ } |
|
407 |
+ container.Volumes[volPath] = c.Id |
|
408 |
+ } |
|
409 |
+ } |
|
410 |
+ |
|
411 |
+ if container.Config.VolumesFrom != "" { |
|
412 |
+ c := container.runtime.Get(container.Config.VolumesFrom) |
|
413 |
+ if c == nil { |
|
414 |
+ return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.Id) |
|
415 |
+ } |
|
416 |
+ for volPath, id := range c.Volumes { |
|
417 |
+ if _, exists := container.Volumes[volPath]; exists { |
|
418 |
+ return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.Id) |
|
419 |
+ } |
|
420 |
+ if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { |
|
421 |
+ return nil |
|
422 |
+ } |
|
423 |
+ container.Volumes[volPath] = id |
|
424 |
+ } |
|
425 |
+ } |
|
397 | 426 |
|
398 | 427 |
if err := container.generateLXCConfig(); err != nil { |
399 | 428 |
return err |
400 | 429 |
} |
430 |
+ |
|
401 | 431 |
params := []string{ |
402 | 432 |
"-n", container.Id, |
403 | 433 |
"-f", container.lxcConfigPath(), |
... | ... |
@@ -456,6 +496,7 @@ func (container *Container) Start() error { |
456 | 456 |
|
457 | 457 |
// Init the lock |
458 | 458 |
container.waitLock = make(chan struct{}) |
459 |
+ |
|
459 | 460 |
container.ToDisk() |
460 | 461 |
go container.monitor() |
461 | 462 |
return nil |
... | ... |
@@ -787,6 +828,22 @@ func (container *Container) RootfsPath() string { |
787 | 787 |
return path.Join(container.root, "rootfs") |
788 | 788 |
} |
789 | 789 |
|
790 |
+func (container *Container) GetVolumes() (map[string]string, error) { |
|
791 |
+ ret := make(map[string]string) |
|
792 |
+ for volPath, id := range container.Volumes { |
|
793 |
+ volume, err := container.runtime.volumes.Get(id) |
|
794 |
+ if err != nil { |
|
795 |
+ return nil, err |
|
796 |
+ } |
|
797 |
+ root, err := volume.root() |
|
798 |
+ if err != nil { |
|
799 |
+ return nil, err |
|
800 |
+ } |
|
801 |
+ ret[volPath] = path.Join(root, "layer") |
|
802 |
+ } |
|
803 |
+ return ret, nil |
|
804 |
+} |
|
805 |
+ |
|
790 | 806 |
func (container *Container) rwPath() string { |
791 | 807 |
return path.Join(container.root, "rw") |
792 | 808 |
} |
... | ... |
@@ -17,3 +17,5 @@ |
17 | 17 |
-p=[]: Map a network port to the container |
18 | 18 |
-t=false: Allocate a pseudo-tty |
19 | 19 |
-u="": Username or UID |
20 |
+ -d=[]: Set custom dns servers for the container |
|
21 |
+ -v=[]: Creates a new volumes and mount it at the specified path. A container ID can be passed instead of a path in order to mount all volumes from the given container. |
... | ... |
@@ -79,7 +79,11 @@ lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0 |
79 | 79 |
|
80 | 80 |
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container |
81 | 81 |
lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0 |
82 |
- |
|
82 |
+{{if .Volumes}} |
|
83 |
+{{range $virtualPath, $realPath := .GetVolumes}} |
|
84 |
+lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0 |
|
85 |
+{{end}} |
|
86 |
+{{end}} |
|
83 | 87 |
|
84 | 88 |
# drop linux capabilities (apply mainly to the user root in the container) |
85 | 89 |
lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config |
... | ... |
@@ -32,6 +32,7 @@ type Runtime struct { |
32 | 32 |
capabilities *Capabilities |
33 | 33 |
kernelVersion *KernelVersionInfo |
34 | 34 |
autoRestart bool |
35 |
+ volumes *Graph |
|
35 | 36 |
} |
36 | 37 |
|
37 | 38 |
var sysInitPath string |
... | ... |
@@ -405,6 +406,10 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { |
405 | 405 |
if err != nil { |
406 | 406 |
return nil, err |
407 | 407 |
} |
408 |
+ volumes, err := NewGraph(path.Join(root, "volumes")) |
|
409 |
+ if err != nil { |
|
410 |
+ return nil, err |
|
411 |
+ } |
|
408 | 412 |
repositories, err := NewTagStore(path.Join(root, "repositories"), g) |
409 | 413 |
if err != nil { |
410 | 414 |
return nil, fmt.Errorf("Couldn't create Tag store: %s", err) |
... | ... |
@@ -432,6 +437,7 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { |
432 | 432 |
idIndex: NewTruncIndex(), |
433 | 433 |
capabilities: &Capabilities{}, |
434 | 434 |
autoRestart: autoRestart, |
435 |
+ volumes: volumes, |
|
435 | 436 |
} |
436 | 437 |
|
437 | 438 |
if err := runtime.restore(); err != nil { |