ソースを参照

Merge branch 'master' into builder_server-3

Conflicts:
	utils/utils.go
Guillaume J. Charmes 12 年 前
コミット
78f86ea502

+ 12 - 0
FIXME

@@ -16,3 +16,15 @@ to put them - so we put them here :)
 * Unify build commands and regular commands
 * Move source code into src/ subdir for clarity
 * Clean up the Makefile, it's a mess
+- docker buidl: show short IDs
+- docker build: on non-existent local path for ADD, don't show full absolute path on the host
+- mount into /dockerinit rather than /sbin/init
+- docker tag foo REPO:TAG
+- use size header for progress bar in pull
+- Clean up context upload in build!!!
+- Parallel pull
+- Ensure /proc/sys/net/ipv4/ip_forward is 1
+- Force DNS to public!
+- Always generate a resolv.conf per container, to avoid changing resolv.conf under thne container's feet
+- Save metadata with import/export
+- Upgrade dockerd without stopping containers

+ 16 - 0
Vagrantfile

@@ -5,11 +5,13 @@ BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
 BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
 AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
 AWS_AMI    = ENV['AWS_AMI']    || "ami-d0f89fb9"
+FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
 
 Vagrant::Config.run do |config|
   # Setup virtual machine box. This VM configuration code is always executed.
   config.vm.box = BOX_NAME
   config.vm.box_url = BOX_URI
+  config.vm.forward_port 4243, 4243
 
   # Provision docker and new kernel if deployment was not done
   if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
@@ -70,3 +72,17 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
     config.vm.box_url = BOX_URI
   end
 end
+
+if !FORWARD_DOCKER_PORTS.nil?
+    Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
+        (49000..49900).each do |port|
+            config.vm.forward_port port, port
+        end
+    end
+
+    Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
+        (49000..49900).each do |port|
+            config.vm.network :forwarded_port, :host => port, :guest => port
+        end
+    end
+end

+ 10 - 3
api.go

@@ -438,17 +438,23 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
 
 func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	config := &Config{}
+	out := &APIRun{}
+
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
 		return err
 	}
+
+	if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
+		out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
+		config.Dns = defaultDns
+	}
+
 	id, err := srv.ContainerCreate(config)
 	if err != nil {
 		return err
 	}
+	out.ID = id
 
-	out := &APIRun{
-		ID: id,
-	}
 	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
 		log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
@@ -457,6 +463,7 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
 		log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
 	}
+
 	b, err := json.Marshal(out)
 	if err != nil {
 		return err

+ 14 - 10
api_params.go

@@ -7,10 +7,12 @@ type APIHistory struct {
 }
 
 type APIImages struct {
-	Repository string `json:",omitempty"`
-	Tag        string `json:",omitempty"`
-	ID         string `json:"Id"`
-	Created    int64
+	Repository  string `json:",omitempty"`
+	Tag         string `json:",omitempty"`
+	ID          string `json:"Id"`
+	Created     int64
+	Size        int64
+	VirtualSize int64
 }
 
 type APIInfo struct {
@@ -29,12 +31,14 @@ type APIRmi struct {
 }
 
 type APIContainers struct {
-	ID      string `json:"Id"`
-	Image   string
-	Command string
-	Created int64
-	Status  string
-	Ports   string
+	ID         string `json:"Id"`
+	Image      string
+	Command    string
+	Created    int64
+	Status     string
+	Ports      string
+	SizeRw     int64
+	SizeRootFs int64
 }
 
 type APISearch struct {

+ 16 - 2
builder.go

@@ -2,11 +2,14 @@ package docker
 
 import (
 	"fmt"
+	"github.com/dotcloud/docker/utils"
 	"os"
 	"path"
 	"time"
 )
 
+var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
+
 type Builder struct {
 	runtime      *Runtime
 	repositories *TagStore
@@ -66,15 +69,26 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
 		return nil, err
 	}
 
+	if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() {
+		//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
+		builder.runtime.Dns = defaultDns
+	}
+
 	// If custom dns exists, then create a resolv.conf for the container
-	if len(config.Dns) > 0 {
+	if len(config.Dns) > 0 || len(builder.runtime.Dns) > 0 {
+		var dns []string
+		if len(config.Dns) > 0 {
+			dns = config.Dns
+		} else {
+			dns = builder.runtime.Dns
+		}
 		container.ResolvConfPath = path.Join(container.root, "resolv.conf")
 		f, err := os.Create(container.ResolvConfPath)
 		if err != nil {
 			return nil, err
 		}
 		defer f.Close()
-		for _, dns := range config.Dns {
+		for _, dns := range dns {
 			if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
 				return nil, err
 			}

+ 25 - 5
commands.go

@@ -20,6 +20,7 @@ import (
 	"path"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"strconv"
 	"strings"
 	"syscall"
@@ -736,6 +737,15 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 	if err != nil {
 		return err
 	}
+	nameParts := strings.SplitN(name, "/", 2)
+	validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
+	if !validNamespace.MatchString(nameParts[0]) {
+		return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", nameParts[0])
+	}
+	validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`)
+	if !validRepo.MatchString(nameParts[1]) {
+		return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", nameParts[1])
+	}
 
 	v := url.Values{}
 	v.Set("registry", *registry)
@@ -820,7 +830,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 
 		w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
 		if !*quiet {
-			fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED")
+			fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tSIZE")
 		}
 
 		for _, out := range outs {
@@ -838,7 +848,12 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 				} else {
 					fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
 				}
-				fmt.Fprintf(w, "%s ago\n", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
+				fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
+				if out.VirtualSize > 0 {
+					fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
+				} else {
+					fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
+				}
 			} else {
 				if *noTrunc {
 					fmt.Fprintln(w, out.ID)
@@ -897,15 +912,20 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 	}
 	w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
 	if !*quiet {
-		fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
+		fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tSIZE")
 	}
 
 	for _, out := range outs {
 		if !*quiet {
 			if *noTrunc {
-				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
+				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
+			} else {
+				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
+			}
+			if out.SizeRootFs > 0 {
+				fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
 			} else {
-				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
+				fmt.Fprintf(w, "%s\n", utils.HumanSize(out.SizeRw))
 			}
 		} else {
 			if *noTrunc {

+ 24 - 0
container.go

@@ -13,6 +13,7 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
 	"sort"
 	"strconv"
 	"strings"
@@ -919,3 +920,26 @@ func validateID(id string) error {
 	}
 	return nil
 }
+
+// GetSize, return real size, virtual size
+func (container *Container) GetSize() (int64, int64) {
+	var sizeRw, sizeRootfs int64
+
+	filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error {
+		if fileInfo != nil {
+			sizeRw += fileInfo.Size()
+		}
+		return nil
+	})
+
+	_, err := os.Stat(container.RootfsPath())
+	if err == nil {
+		filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
+			if fileInfo != nil {
+				sizeRootfs += fileInfo.Size()
+			}
+			return nil
+		})
+	}
+	return sizeRw, sizeRootfs
+}

+ 8 - 4
docker/docker.go

@@ -34,6 +34,7 @@ func main() {
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
 	flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
 	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
+	flDns := flag.String("dns", "", "Set custom dns servers")
 	flag.Parse()
 	if *bridgeName != "" {
 		docker.NetworkBridgeIface = *bridgeName
@@ -66,7 +67,7 @@ func main() {
 			flag.Usage()
 			return
 		}
-		if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors); err != nil {
+		if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil {
 			log.Fatal(err)
 			os.Exit(-1)
 		}
@@ -105,7 +106,7 @@ func removePidFile(pidfile string) {
 	}
 }
 
-func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error {
+func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error {
 	if addr != "127.0.0.1" {
 		log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
 	}
@@ -122,8 +123,11 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error
 		removePidFile(pidfile)
 		os.Exit(0)
 	}()
-
-	server, err := docker.NewServer(autoRestart, enableCors)
+	var dns []string
+	if flDns != "" {
+		dns = []string{flDns}
+	}
+	server, err := docker.NewServer(autoRestart, enableCors, dns)
 	if err != nil {
 		return err
 	}

+ 24 - 7
docs/sources/api/docker_remote_api_v1.2.rst

@@ -47,28 +47,40 @@ List containers
 			"Image": "base:latest",
 			"Command": "echo 1",
 			"Created": 1367854155,
-			"Status": "Exit 0"
+			"Status": "Exit 0",
+			"Ports":"",
+			"SizeRw":12288,
+			"SizeRootFs":0
 		},
 		{
 			"Id": "9cd87474be90",
 			"Image": "base:latest",
 			"Command": "echo 222222",
 			"Created": 1367854155,
-			"Status": "Exit 0"
+			"Status": "Exit 0",
+			"Ports":"",
+			"SizeRw":12288,
+			"SizeRootFs":0
 		},
 		{
 			"Id": "3176a2479c92",
 			"Image": "base:latest",
 			"Command": "echo 3333333333333333",
 			"Created": 1367854154,
-			"Status": "Exit 0"
+			"Status": "Exit 0",
+			"Ports":"",
+			"SizeRw":12288,
+			"SizeRootFs":0
 		},
 		{
 			"Id": "4cb07b47f9fb",
 			"Image": "base:latest",
 			"Command": "echo 444444444444444444444444444444444",
 			"Created": 1367854152,
-			"Status": "Exit 0"
+			"Status": "Exit 0",
+			"Ports":"",
+			"SizeRw":12288,
+			"SizeRootFs":0
 		}
 	   ]
  
@@ -488,13 +500,17 @@ List Images
 			"Repository":"base",
 			"Tag":"ubuntu-12.10",
 			"Id":"b750fe79269d",
-			"Created":1364102658
+			"Created":1364102658,
+			"Size":24653,
+			"VirtualSize":180116135
 		},
 		{
 			"Repository":"base",
 			"Tag":"ubuntu-quantal",
 			"Id":"b750fe79269d",
-			"Created":1364102658
+			"Created":1364102658,
+			"Size":24653,
+			"VirtualSize":180116135
 		}
 	   ]
 
@@ -643,7 +659,8 @@ Inspect an image
 				"Image":"base",
 				"Volumes":null,
 				"VolumesFrom":""
-			}
+			},
+		"Size": 6824592
 	   }
 
 	:statuscode 200: no error

+ 28 - 1
docs/sources/commandline/command/import.rst

@@ -8,6 +8,33 @@
 
 ::
 
-    Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
+    Usage: docker import URL|- [REPOSITORY [TAG]]
 
     Create a new filesystem image from the contents of a tarball
+
+At this time, the URL must start with ``http`` and point to a single file archive (.tar, .tar.gz, .bzip) 
+containing a root filesystem. If you would like to import from a local directory or archive, 
+you can use the ``-`` parameter to take the data from standard in.
+
+Examples
+--------
+
+Import from a remote location
+.............................
+
+``$ docker import http://example.com/exampleimage.tgz exampleimagerepo``
+
+Import from a local file
+........................
+
+Import to docker via pipe and standard in
+
+``$ cat exampleimage.tgz | docker import - exampleimagelocal``
+  
+Import from a local directory
+.............................
+
+``$ sudo tar -c . | docker import - exampleimagedir``
+
+Note the ``sudo`` in this example -- you must preserve the ownership of the files (especially root ownership)
+during the archiving with tar. If you are not root (or sudo) when you tar, then the ownerships might not get preserved.

+ 1 - 1
docs/sources/contributing/devenvironment.rst

@@ -33,7 +33,7 @@ Installation
     sudo apt-get install python-software-properties
     sudo add-apt-repository ppa:gophers/go
     sudo apt-get update
-    sudo apt-get -y install lxc wget bsdtar curl golang-stable git
+    sudo apt-get -y install lxc wget bsdtar curl golang-stable git aufs-tools
 
     export GOPATH=~/go/
     export PATH=$GOPATH/bin:$PATH

+ 9 - 0
graph.go

@@ -90,6 +90,15 @@ func (graph *Graph) Get(name string) (*Image, error) {
 		return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
 	}
 	img.graph = graph
+	if img.Size == 0 {
+		root, err := img.root()
+		if err != nil {
+			return nil, err
+		}
+		if err := StoreSize(img, root); err != nil {
+			return nil, err
+		}
+	}
 	graph.lockSumMap.Lock()
 	defer graph.lockSumMap.Unlock()
 	if _, exists := graph.checksumLock[img.ID]; !exists {

+ 1 - 1
hack/Vagrantfile

@@ -22,7 +22,7 @@ Vagrant::Config.run do |config|
   pkg_cmd = "touch #{DOCKER_PATH}; "
   # Install docker dependencies
   pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
-    "apt-get install -q -y lxc bsdtar git golang make linux-image-extra-3.8.0-19-generic; " \
+    "apt-get install -q -y lxc bsdtar git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
     "chown -R #{USER}.#{USER} #{GOPATH}; " \
     "install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
   config.vm.provision :shell, :inline => pkg_cmd

+ 23 - 0
image.go

@@ -13,6 +13,7 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
 	"strings"
 	"time"
 )
@@ -29,6 +30,7 @@ type Image struct {
 	Config          *Config   `json:"config,omitempty"`
 	Architecture    string    `json:"architecture,omitempty"`
 	graph           *Graph
+	Size            int64
 }
 
 func LoadImage(root string) (*Image, error) {
@@ -94,6 +96,18 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
 	if err := Untar(layerData, layer); err != nil {
 		return err
 	}
+
+	return StoreSize(img, root)
+}
+
+func StoreSize(img *Image, root string) error {
+	layer := layerPath(root)
+
+	filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
+		img.Size += fileInfo.Size()
+		return nil
+	})
+
 	// Store the json ball
 	jsonData, err := json.Marshal(img)
 	if err != nil {
@@ -363,6 +377,15 @@ func (img *Image) Checksum() (string, error) {
 	return hash, nil
 }
 
+func (img *Image) getParentsSize(size int64) int64 {
+	parentImage, err := img.GetParent()
+	if err != nil || parentImage == nil {
+		return size
+	}
+	size += parentImage.Size
+	return parentImage.getParentsSize(size)
+}
+
 // Build an Image object from raw json data
 func NewImgJSON(src []byte) (*Image, error) {
 	ret := &Image{}

+ 5 - 0
mount.go

@@ -2,13 +2,18 @@ package docker
 
 import (
 	"fmt"
+	"github.com/dotcloud/docker/utils"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"syscall"
 	"time"
 )
 
 func Unmount(target string) error {
+	if err := exec.Command("auplink", target, "flush").Run(); err != nil {
+		utils.Debugf("[warning]: couldn't run auplink before unmount: %s", err)
+	}
 	if err := syscall.Unmount(target, 0); err != nil {
 		return err
 	}

+ 1 - 1
packaging/debian/control

@@ -10,7 +10,7 @@ Homepage: http://github.com/dotcloud/docker
 
 Package: lxc-docker
 Architecture: linux-any
-Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar
+Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar, aufs-tools
 Description: Linux container runtime
  Docker complements LXC with a high-level API which operates at the process
  level. It runs unix processes with strong guarantees of isolation and

+ 1 - 1
packaging/ubuntu/control

@@ -8,7 +8,7 @@ Homepage: http://github.com/dotcloud/docker
 
 Package: lxc-docker
 Architecture: linux-any
-Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar
+Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar,aufs-tools
 Conflicts: docker
 Description: lxc-docker is a Linux container runtime
  Docker complements LXC with a high-level API which operates at the process

+ 7 - 2
registry/registry.go

@@ -328,7 +328,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
 	return nil
 }
 
-func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool) (*RepositoryData, error) {
+func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
 	imgListJSON, err := json.Marshal(imgList)
 	if err != nil {
 		return nil, err
@@ -347,6 +347,9 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
 	req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
 	req.ContentLength = int64(len(imgListJSON))
 	req.Header.Set("X-Docker-Token", "true")
+	if validate {
+		req.Header["X-Docker-Endpoints"] = regs
+	}
 
 	res, err := r.client.Do(req)
 	if err != nil {
@@ -364,7 +367,9 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
 		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
 		req.ContentLength = int64(len(imgListJSON))
 		req.Header.Set("X-Docker-Token", "true")
-
+		if validate {
+			req.Header["X-Docker-Endpoints"] = regs
+		}
 		res, err = r.client.Do(req)
 		if err != nil {
 			return nil, err

+ 3 - 1
runtime.go

@@ -32,6 +32,7 @@ type Runtime struct {
 	autoRestart    bool
 	volumes        *Graph
 	srv            *Server
+	Dns            []string
 }
 
 var sysInitPath string
@@ -245,11 +246,12 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
 }
 
 // FIXME: harmonize with NewGraph()
-func NewRuntime(autoRestart bool) (*Runtime, error) {
+func NewRuntime(autoRestart bool, dns []string) (*Runtime, error) {
 	runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
 	if err != nil {
 		return nil, err
 	}
+	runtime.Dns = dns
 
 	if k, err := utils.GetKernelVersion(); err != nil {
 		log.Printf("WARNING: %s\n", err)

+ 14 - 4
server.go

@@ -174,6 +174,8 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
 			out.Tag = tag
 			out.ID = image.ID
 			out.Created = image.Created.Unix()
+			out.Size = image.Size
+			out.VirtualSize = image.getParentsSize(0) + image.Size
 			outs = append(outs, out)
 		}
 	}
@@ -183,6 +185,8 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
 			var out APIImages
 			out.ID = image.ID
 			out.Created = image.Created.Unix()
+			out.Size = image.Size
+			out.VirtualSize = image.getParentsSize(0) + image.Size
 			outs = append(outs, out)
 		}
 	}
@@ -268,6 +272,8 @@ func (srv *Server) Containers(all bool, n int, since, before string) []APIContai
 		c.Created = container.Created.Unix()
 		c.Status = container.State.String()
 		c.Ports = container.NetworkSettings.PortMappingHuman()
+		c.SizeRw, c.SizeRootFs = container.GetSize()
+
 		retContainers = append(retContainers, c)
 	}
 	return retContainers
@@ -497,7 +503,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
 		srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
 	}
 
-	repoData, err := r.PushImageJSONIndex(srvName, imgList, false)
+	repoData, err := r.PushImageJSONIndex(srvName, imgList, false, nil)
 	if err != nil {
 		return err
 	}
@@ -521,7 +527,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
 		}
 	}
 
-	if _, err := r.PushImageJSONIndex(srvName, imgList, true); err != nil {
+	if _, err := r.PushImageJSONIndex(srvName, imgList, true, repoData.Endpoints); err != nil {
 		return err
 	}
 	return nil
@@ -652,6 +658,10 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
 
 func (srv *Server) ContainerCreate(config *Config) (string, error) {
 
+	if config.Memory != 0 && config.Memory < 524288 {
+		return "", fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
+	}
+
 	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
 		config.Memory = 0
 	}
@@ -972,11 +982,11 @@ func (srv *Server) ImageInspect(name string) (*Image, error) {
 	return nil, fmt.Errorf("No such image: %s", name)
 }
 
-func NewServer(autoRestart, enableCors bool) (*Server, error) {
+func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
 	if runtime.GOARCH != "amd64" {
 		log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
 	}
-	runtime, err := NewRuntime(autoRestart)
+	runtime, err := NewRuntime(autoRestart, dns)
 	if err != nil {
 		return nil, err
 	}

+ 22 - 0
server_test.go

@@ -147,3 +147,25 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 	}
 
 }
+
+func TestRunWithTooLowMemoryLimit(t *testing.T) {
+	runtime, err := newTestRuntime()
+	srv := &Server{runtime: runtime}
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+	// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
+	_, err = srv.ContainerCreate(
+		&Config{
+			Image:     GetTestImage(runtime).ID,
+			Memory:    524287,
+			CpuShares: 1000,
+			Cmd:       []string{"/bin/cat"},
+		},
+	)
+	if err == nil {
+		t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
+	}
+
+}

+ 1 - 1
testing/Vagrantfile

@@ -30,7 +30,7 @@ Vagrant::Config.run do |config|
     # Install docker dependencies
     pkg_cmd << "apt-get install -q -y python-software-properties; " \
       "add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
-      "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable make; "
+      "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable aufs-tools make; "
     # Activate new kernel
     pkg_cmd << "shutdown -r +1; "
     config.vm.provision :shell, :inline => pkg_cmd

+ 31 - 0
utils/utils.go

@@ -135,6 +135,20 @@ func HumanDuration(d time.Duration) string {
 	return fmt.Sprintf("%d years", d.Hours()/24/365)
 }
 
+// HumanSize returns a human-readable approximation of a size
+// using SI standard (eg. "44kB", "17MB")
+func HumanSize(size int64) string {
+	i := 0
+	var sizef float64
+	sizef = float64(size)
+	units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
+	for sizef >= 1000.0 {
+		sizef = sizef / 1000.0
+		i++
+	}
+	return fmt.Sprintf("%.4g %s", sizef, units[i])
+}
+
 func Trunc(s string, maxlen int) string {
 	if len(s) <= maxlen {
 		return s
@@ -628,3 +642,20 @@ func IsURL(str string) bool {
 func IsGIT(str string) bool {
 	return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/")
 }
+
+func CheckLocalDns() bool {
+	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
+	if err != nil {
+		Debugf("Error openning resolv.conf: %s", err)
+		return false
+	}
+	for _, ip := range []string{
+		"127.0.0.1",
+		"127.0.1.1",
+	} {
+		if strings.Contains(string(resolv), ip) {
+			return true
+		}
+	}
+	return false
+}

+ 13 - 0
utils/utils_test.go

@@ -261,3 +261,16 @@ func TestCompareKernelVersion(t *testing.T) {
 		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
 		-1)
 }
+
+func TestHumanSize(t *testing.T) {
+
+	size1000 := HumanSize(1000)
+	if size1000 != "1 kB" {
+		t.Errorf("1000 -> expected 1 kB, got %s", size1000)
+	}
+
+	size1024 := HumanSize(1024)
+	if size1024 != "1.024 kB" {
+		t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
+	}
+}