Browse code

bump to 0.2.2

Victor Vieux authored on 2013/05/06 18:52:15
Showing 14 changed files
... ...
@@ -1,6 +1,13 @@
1 1
 # Changelog
2 2
 
3
-## 0.2.1 (2012-05-01)
3
+## 0.2.2 (2013-05-03)
4
+ + Support for data volumes ('docker run -v=PATH')
5
+ + Share data volumes between containers ('docker run -volumes-from')
6
+ + Improved documentation
7
+ * Upgrade to Go 1.0.3
8
+ * Various upgrades to the dev environment for contributors
9
+
10
+## 0.2.1 (2013-05-01)
4 11
  + 'docker commit -run' bundles a layer with default runtime options: command, ports etc. 
5 12
  * Improve install process on Vagrant
6 13
  + New Dockerfile operation: "maintainer"
... ...
@@ -10,7 +17,7 @@
10 10
  + 'docker -d -r': restart crashed containers at daemon startup
11 11
  * Runtime: improve test coverage
12 12
 
13
-## 0.2.0 (2012-04-23)
13
+## 0.2.0 (2013-04-23)
14 14
  - Runtime: ghost containers can be killed and waited for
15 15
  * Documentation: update install intructions
16 16
  - Packaging: fix Vagrantfile
... ...
@@ -1,8 +1,8 @@
1 1
 # -*- mode: ruby -*-
2 2
 # vi: set ft=ruby :
3 3
 
4
-BOX_NAME = "ubuntu"
5
-BOX_URI = "http://files.vagrantup.com/precise64.box"
4
+BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
5
+BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
6 6
 PPA_KEY = "E61D797F63561DC6"
7 7
 
8 8
 Vagrant::Config.run do |config|
... ...
@@ -11,7 +11,7 @@ Vagrant::Config.run do |config|
11 11
   config.vm.box_url = BOX_URI
12 12
   # Add docker PPA key to the local repository and install docker
13 13
   pkg_cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys #{PPA_KEY}; "
14
-  pkg_cmd << "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list; "
14
+  pkg_cmd << "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >/etc/apt/sources.list.d/lxc-docker.list; "
15 15
   pkg_cmd << "apt-get update -qq; apt-get install -q -y lxc-docker"
16 16
   if ARGV.include?("--provider=aws".downcase)
17 17
     # Add AUFS dependency to amazon's VM
... ...
@@ -320,9 +320,16 @@ func ListenAndServe(addr string, srv *Server) error {
320 320
 
321 321
 	r.Path("/containers/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
322 322
 		log.Println(r.Method, r.RequestURI)
323
+		if err := r.ParseForm(); err != nil {
324
+			http.Error(w, err.Error(), http.StatusInternalServerError)
325
+		}
323 326
 		vars := mux.Vars(r)
324 327
 		name := vars["name"]
325
-		if err := srv.ContainerDestroy(name); err != nil {
328
+		var v bool
329
+		if r.Form.Get("v") == "1" {
330
+			v = true
331
+		}
332
+		if err := srv.ContainerDestroy(name, v); err != nil {
326 333
 			httpError(w, err)
327 334
 		} else {
328 335
 			w.WriteHeader(http.StatusOK)
... ...
@@ -13,12 +13,13 @@ import (
13 13
 	"net/http/httputil"
14 14
 	"net/url"
15 15
 	"os"
16
+	"path/filepath"
16 17
 	"strconv"
17 18
 	"text/tabwriter"
18 19
 	"time"
19 20
 )
20 21
 
21
-const VERSION = "0.2.1"
22
+const VERSION = "0.2.2"
22 23
 
23 24
 var (
24 25
 	GIT_COMMIT string
... ...
@@ -434,7 +435,7 @@ func CmdRmi(args ...string) error {
434 434
 		return nil
435 435
 	}
436 436
 
437
-	for _, name := range args {
437
+	for _, name := range cmd.Args() {
438 438
 		_, _, err := call("DELETE", "/images/"+name, nil)
439 439
 		if err != nil {
440 440
 			fmt.Printf("%s", err)
... ...
@@ -476,7 +477,8 @@ func CmdHistory(args ...string) error {
476 476
 }
477 477
 
478 478
 func CmdRm(args ...string) error {
479
-	cmd := Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove a container")
479
+	cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container")
480
+	v := cmd.Bool("v", false, "Remove the volumes associated to the container")
480 481
 	if err := cmd.Parse(args); err != nil {
481 482
 		return nil
482 483
 	}
... ...
@@ -484,9 +486,12 @@ func CmdRm(args ...string) error {
484 484
 		cmd.Usage()
485 485
 		return nil
486 486
 	}
487
-
488
-	for _, name := range args {
489
-		_, _, err := call("DELETE", "/containers/"+name, nil)
487
+	val := url.Values{}
488
+	if *v {
489
+		val.Set("v", "1")
490
+	}
491
+	for _, name := range cmd.Args() {
492
+		_, _, err := call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
490 493
 		if err != nil {
491 494
 			fmt.Printf("%s", err)
492 495
 		} else {
... ...
@@ -930,6 +935,25 @@ func (opts AttachOpts) Get(val string) bool {
930 930
 	return false
931 931
 }
932 932
 
933
+// PathOpts stores a unique set of absolute paths
934
+type PathOpts map[string]struct{}
935
+
936
+func NewPathOpts() PathOpts {
937
+	return make(PathOpts)
938
+}
939
+
940
+func (opts PathOpts) String() string {
941
+	return fmt.Sprintf("%v", map[string]struct{}(opts))
942
+}
943
+
944
+func (opts PathOpts) Set(val string) error {
945
+	if !filepath.IsAbs(val) {
946
+		return fmt.Errorf("%s is not an absolute path", val)
947
+	}
948
+	opts[filepath.Clean(val)] = struct{}{}
949
+	return nil
950
+}
951
+
933 952
 func CmdTag(args ...string) error {
934 953
 	cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
935 954
 	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) (*Config, *flag.FlagSet, error) {
... ...
@@ -92,6 +95,11 @@ func ParseRun(args []string) (*Config, *flag.FlagSet, error) {
92 92
 	var flDns ListOpts
93 93
 	cmd.Var(&flDns, "dns", "Set custom dns servers")
94 94
 
95
+	flVolumes := NewPathOpts()
96
+	cmd.Var(flVolumes, "v", "Attach a data volume")
97
+
98
+	flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
99
+
95 100
 	if err := cmd.Parse(args); err != nil {
96 101
 		return nil, cmd, err
97 102
 	}
... ...
@@ -131,6 +139,8 @@ func ParseRun(args []string) (*Config, *flag.FlagSet, error) {
131 131
 		Cmd:          runCmd,
132 132
 		Dns:          flDns,
133 133
 		Image:        image,
134
+		Volumes:      flVolumes,
135
+		VolumesFrom:  *flVolumesFrom,
134 136
 	}
135 137
 
136 138
 	// When allocating stdin in attached mode, close stdin at client disconnect
... ...
@@ -384,10 +394,40 @@ func (container *Container) Start() error {
384 384
 		log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
385 385
 		container.Config.MemorySwap = -1
386 386
 	}
387
+	container.Volumes = make(map[string]string)
388
+
389
+	// Create the requested volumes volumes
390
+	for volPath := range container.Config.Volumes {
391
+		if c, err := container.runtime.volumes.Create(nil, container, "", "", nil); err != nil {
392
+			return err
393
+		} else {
394
+			if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
395
+				return nil
396
+			}
397
+			container.Volumes[volPath] = c.Id
398
+		}
399
+	}
400
+
401
+	if container.Config.VolumesFrom != "" {
402
+		c := container.runtime.Get(container.Config.VolumesFrom)
403
+		if c == nil {
404
+			return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.Id)
405
+		}
406
+		for volPath, id := range c.Volumes {
407
+			if _, exists := container.Volumes[volPath]; exists {
408
+				return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.Id)
409
+			}
410
+			if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
411
+				return nil
412
+			}
413
+			container.Volumes[volPath] = id
414
+		}
415
+	}
387 416
 
388 417
 	if err := container.generateLXCConfig(); err != nil {
389 418
 		return err
390 419
 	}
420
+
391 421
 	params := []string{
392 422
 		"-n", container.Id,
393 423
 		"-f", container.lxcConfigPath(),
... ...
@@ -446,6 +486,7 @@ func (container *Container) Start() error {
446 446
 
447 447
 	// Init the lock
448 448
 	container.waitLock = make(chan struct{})
449
+
449 450
 	container.ToDisk()
450 451
 	go container.monitor()
451 452
 	return nil
... ...
@@ -777,6 +818,22 @@ func (container *Container) RootfsPath() string {
777 777
 	return path.Join(container.root, "rootfs")
778 778
 }
779 779
 
780
+func (container *Container) GetVolumes() (map[string]string, error) {
781
+	ret := make(map[string]string)
782
+	for volPath, id := range container.Volumes {
783
+		volume, err := container.runtime.volumes.Get(id)
784
+		if err != nil {
785
+			return nil, err
786
+		}
787
+		root, err := volume.root()
788
+		if err != nil {
789
+			return nil, err
790
+		}
791
+		ret[volPath] = path.Join(root, "layer")
792
+	}
793
+	return ret, nil
794
+}
795
+
780 796
 func (container *Container) rwPath() string {
781 797
 	return path.Join(container.root, "rw")
782 798
 }
... ...
@@ -17,3 +17,6 @@
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.
22
+      -volumes-from="": Mount all volumes from the given container.
... ...
@@ -12,7 +12,7 @@ Images
12 12
 ------
13 13
 An original container image. These are stored on disk and are comparable with what you normally expect from a stopped virtual machine image. Images are stored (and retrieved from) repository
14 14
 
15
-Images are stored on your local file system under /var/lib/docker/images
15
+Images are stored on your local file system under /var/lib/docker/graph
16 16
 
17 17
 
18 18
 .. _containers:
19 19
new file mode 100644
... ...
@@ -0,0 +1,53 @@
0
+:title: Sharing data between 2 couchdb databases
1
+:description: Sharing data between 2 couchdb databases
2
+:keywords: docker, example, package installation, networking, couchdb, data volumes
3
+
4
+.. _running_redis_service:
5
+
6
+Create a redis service
7
+======================
8
+
9
+.. include:: example_header.inc
10
+
11
+Here's an example of using data volumes to share the same data between 2 couchdb containers.
12
+This could be used for hot upgrades, testing different versions of couchdb on the same data, etc.
13
+
14
+Create first database
15
+---------------------
16
+
17
+Note that we're marking /var/lib/couchdb as a data volume.
18
+
19
+.. code-block:: bash
20
+
21
+    COUCH1=$(docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
22
+
23
+Add data to the first database
24
+------------------------------
25
+
26
+We're assuming your docker host is reachable at `localhost`. If not, replace `localhost` with the public IP of your docker host.
27
+
28
+.. code-block:: bash
29
+
30
+    HOST=localhost
31
+    URL="http://$HOST:$(docker port $COUCH1 5984)/_utils/"
32
+    echo "Navigate to $URL in your browser, and use the couch interface to add data"
33
+
34
+Create second database
35
+----------------------
36
+
37
+This time, we're requesting shared access to $COUCH1's volumes.
38
+
39
+.. code-block:: bash
40
+
41
+    COUCH2=$(docker run -d -volumes-from $COUCH1) shykes/couchdb:2013-05-03)
42
+
43
+Browse data on the second database
44
+----------------------------------
45
+
46
+.. code-block:: bash
47
+
48
+    HOST=localhost
49
+    URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/"
50
+    echo "Navigate to $URL in your browser. You should see the same data as in the first database!"
51
+
52
+Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data.
... ...
@@ -18,3 +18,4 @@ Contents:
18 18
    python_web_app
19 19
    running_redis_service
20 20
    running_ssh_service
21
+   couchdb_data_volumes
... ...
@@ -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
... ...
@@ -1,3 +1,12 @@
1
+lxc-docker (0.2.2-1) precise; urgency=low
2
+ - Support for data volumes ('docker run -v=PATH')
3
+ - Share data volumes between containers ('docker run -volumes-from')
4
+ - Improved documentation
5
+ - Upgrade to Go 1.0.3
6
+ - Various upgrades to the dev environment for contributors
7
+
8
+ -- dotCloud <ops@dotcloud.com>  Fri, 3 May 2013 00:00:00 -0700
9
+
1 10
 
2 11
 lxc-docker (0.2.1-1) precise; urgency=low
3 12
 
... ...
@@ -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
... ...
@@ -79,10 +80,10 @@ func (runtime *Runtime) containerRoot(id string) string {
79 79
 }
80 80
 
81 81
 func (runtime *Runtime) mergeConfig(userConf, imageConf *Config) {
82
-	if userConf.Hostname != "" {
82
+	if userConf.Hostname == "" {
83 83
 		userConf.Hostname = imageConf.Hostname
84 84
 	}
85
-	if userConf.User != "" {
85
+	if userConf.User == "" {
86 86
 		userConf.User = imageConf.User
87 87
 	}
88 88
 	if userConf.Memory == 0 {
... ...
@@ -126,7 +127,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
126 126
 		runtime.mergeConfig(config, img.Config)
127 127
 	}
128 128
 
129
-	if config.Cmd == nil {
129
+	if config.Cmd == nil || len(config.Cmd) == 0 {
130 130
 		return nil, fmt.Errorf("No command specified")
131 131
 	}
132 132
 
... ...
@@ -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 {
... ...
@@ -294,11 +294,38 @@ func (srv *Server) ContainerRestart(name string, t int) error {
294 294
 	return nil
295 295
 }
296 296
 
297
-func (srv *Server) ContainerDestroy(name string) error {
297
+func (srv *Server) ContainerDestroy(name string, v bool) error {
298
+
298 299
 	if container := srv.runtime.Get(name); container != nil {
300
+		volumes := make(map[string]struct{})
301
+		// Store all the deleted containers volumes
302
+		for _, volumeId := range container.Volumes {
303
+			volumes[volumeId] = struct{}{}
304
+		}
299 305
 		if err := srv.runtime.Destroy(container); err != nil {
300 306
 			return fmt.Errorf("Error destroying container %s: %s", name, err.Error())
301 307
 		}
308
+
309
+		if v {
310
+			// Retrieve all volumes from all remaining containers
311
+			usedVolumes := make(map[string]*Container)
312
+			for _, container := range srv.runtime.List() {
313
+				for _, containerVolumeId := range container.Volumes {
314
+					usedVolumes[containerVolumeId] = container
315
+				}
316
+			}
317
+
318
+			for volumeId := range volumes {
319
+				// If the requested volu
320
+				if c, exists := usedVolumes[volumeId]; exists {
321
+					log.Printf("The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id)
322
+					continue
323
+				}
324
+				if err := srv.runtime.volumes.Delete(volumeId); err != nil {
325
+					return err
326
+				}
327
+			}
328
+		}
302 329
 	} else {
303 330
 		return fmt.Errorf("No such container: %s", name)
304 331
 	}
... ...
@@ -466,7 +466,7 @@ func FindCgroupMountpoint(cgroupType string) (string, error) {
466 466
 	// cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
467 467
 	for _, line := range strings.Split(string(output), "\n") {
468 468
 		parts := strings.Split(line, " ")
469
-		if parts[2] == "cgroup" {
469
+		if len(parts) == 6 && parts[2] == "cgroup" {
470 470
 			for _, opt := range strings.Split(parts[3], ",") {
471 471
 				if opt == cgroupType {
472 472
 					return parts[1], nil