mirror of
https://github.com/moby/moby.git
synced 2026-01-12 19:21:41 +00:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ffd63070f | ||
|
|
701132259d | ||
|
|
18b8eeb484 | ||
|
|
a7c0e9a355 | ||
|
|
19df5a7965 | ||
|
|
8ef72cbc94 | ||
|
|
6cbe27b7a5 | ||
|
|
a82b60b30d | ||
|
|
c08d245539 | ||
|
|
b6a5e604ab | ||
|
|
6e486b638b | ||
|
|
74cd7e822d | ||
|
|
21b9dcd518 | ||
|
|
897cc573f0 | ||
|
|
b0459adc27 | ||
|
|
3edd14b8c2 | ||
|
|
4099a31304 | ||
|
|
6fb495bf6f | ||
|
|
faf8daa7c6 | ||
|
|
8d9aaee60b | ||
|
|
35d704c8a0 | ||
|
|
1df5f4094b | ||
|
|
528da23d6a | ||
|
|
ff5e238de9 | ||
|
|
c63dce393e | ||
|
|
d6a63132ef | ||
|
|
e7271cdaae | ||
|
|
6ca3b151b1 | ||
|
|
0d9475346f | ||
|
|
71199f595d | ||
|
|
58b95878f1 | ||
|
|
e431dc26f1 | ||
|
|
09b1cd58c0 | ||
|
|
d42639e5c5 | ||
|
|
d0c2e31fb9 | ||
|
|
509a01bbe4 | ||
|
|
e7fb7f13d5 | ||
|
|
d172da58ce | ||
|
|
ad86dde10c | ||
|
|
c20e46587d | ||
|
|
eeb03164cf | ||
|
|
bb61678b57 | ||
|
|
a75a1b3859 | ||
|
|
08812096f5 | ||
|
|
5c30faf6f7 | ||
|
|
f7aaa06606 | ||
|
|
7ff65d40d5 | ||
|
|
904c2a0fc3 | ||
|
|
a3ce90b78b | ||
|
|
03b83b3210 | ||
|
|
40ccf1d300 | ||
|
|
038ca5ee39 | ||
|
|
957c500ac9 | ||
|
|
62a595da5c | ||
|
|
d97661aa71 | ||
|
|
c6119da339 | ||
|
|
5051c20833 | ||
|
|
cdc2657ee9 | ||
|
|
76a1a7cf5b | ||
|
|
20c2a4f80f | ||
|
|
ebe157ebb5 | ||
|
|
cb431f223f | ||
|
|
ab34115b42 | ||
|
|
4b3354af3f | ||
|
|
9042535f5a | ||
|
|
8f81e175af | ||
|
|
636c7835d3 | ||
|
|
6d1dd8b41a | ||
|
|
ae97477284 | ||
|
|
86ad98e72a | ||
|
|
03d82922aa | ||
|
|
30d327d37e | ||
|
|
724e2d6b0a | ||
|
|
51d6228261 | ||
|
|
4db680fda4 | ||
|
|
9c7293508d | ||
|
|
9d8743a7ae | ||
|
|
50144aeb42 | ||
|
|
ee298d1420 | ||
|
|
03855b0027 | ||
|
|
2726e3649a | ||
|
|
6ebb249131 | ||
|
|
c45beabcd5 | ||
|
|
a22c78523f | ||
|
|
5a02c9ba0a |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.2 (2012-05-03)
|
||||
+ Support for data volumes ('docker run -v=PATH')
|
||||
+ Share data volumes between containers ('docker run -volumes-from')
|
||||
+ Improved documentation
|
||||
* Upgrade to Go 1.0.3
|
||||
* Various upgrades to the dev environment for contributors
|
||||
|
||||
## 0.2.1 (2012-05-01)
|
||||
+ 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
|
||||
* Improve install process on Vagrant
|
||||
+ New Dockerfile operation: "maintainer"
|
||||
+ New Dockerfile operation: "expose"
|
||||
+ New Dockerfile operation: "cmd"
|
||||
+ Contrib script to build a Debian base layer
|
||||
+ 'docker -d -r': restart crashed containers at daemon startup
|
||||
* Runtime: improve test coverage
|
||||
|
||||
## 0.2.0 (2012-04-23)
|
||||
- Runtime: ghost containers can be killed and waited for
|
||||
* Documentation: update install intructions
|
||||
|
||||
10
Makefile
10
Makefile
@@ -38,14 +38,15 @@ $(DOCKER_BIN): $(DOCKER_DIR)
|
||||
|
||||
$(DOCKER_DIR):
|
||||
@mkdir -p $(dir $@)
|
||||
@rm -f $@
|
||||
@ln -sf $(CURDIR)/ $@
|
||||
@if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@
|
||||
@(cd $(DOCKER_MAIN); go get $(GO_OPTIONS))
|
||||
|
||||
whichrelease:
|
||||
echo $(RELEASE_VERSION)
|
||||
|
||||
release: $(BINRELEASE)
|
||||
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
|
||||
|
||||
srcrelease: $(SRCRELEASE)
|
||||
deps: $(DOCKER_DIR)
|
||||
|
||||
@@ -75,4 +76,7 @@ fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
||||
hack:
|
||||
cd $(CURDIR)/buildbot && vagrant up
|
||||
cd $(CURDIR)/hack && vagrant up
|
||||
|
||||
ssh-dev:
|
||||
cd $(CURDIR)/hack && vagrant ssh
|
||||
|
||||
67
Vagrantfile
vendored
67
Vagrantfile
vendored
@@ -1,55 +1,27 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
def v10(config)
|
||||
config.vm.box = 'precise64'
|
||||
config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
|
||||
BOX_NAME = "ubuntu"
|
||||
BOX_URI = "http://files.vagrantup.com/precise64.box"
|
||||
PPA_KEY = "E61D797F63561DC6"
|
||||
|
||||
# Install ubuntu packaging dependencies and create ubuntu packages
|
||||
config.vm.provision :shell, :inline => "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list"
|
||||
config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y --force-yes lxc-docker'
|
||||
end
|
||||
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
v10(config)
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("1") do |config|
|
||||
v10(config)
|
||||
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
|
||||
# Add docker PPA key to the local repository and install docker
|
||||
pkg_cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys #{PPA_KEY}; "
|
||||
pkg_cmd << "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >/etc/apt/sources.list.d/lxc-docker.list; "
|
||||
pkg_cmd << "apt-get update -qq; apt-get install -q -y lxc-docker"
|
||||
if ARGV.include?("--provider=aws".downcase)
|
||||
# Add AUFS dependency to amazon's VM
|
||||
pkg_cmd << "; apt-get install linux-image-extra-3.2.0-40-virtual"
|
||||
end
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
|
||||
# Providers were added on Vagrant >= 1.1.0
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
config.vm.provider :aws do |aws|
|
||||
config.vm.box = "dummy"
|
||||
config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
|
||||
aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
|
||||
aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
|
||||
aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
|
||||
aws.ssh_private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
aws.region = "us-east-1"
|
||||
aws.ami = "ami-d0f89fb9"
|
||||
aws.ssh_username = "ubuntu"
|
||||
aws.instance_type = "t1.micro"
|
||||
end
|
||||
|
||||
config.vm.provider :rackspace do |rs|
|
||||
config.vm.box = "dummy"
|
||||
config.vm.box_url = "https://github.com/mitchellh/vagrant-rackspace/raw/master/dummy.box"
|
||||
config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"]
|
||||
rs.username = ENV["RS_USERNAME"]
|
||||
rs.api_key = ENV["RS_API_KEY"]
|
||||
rs.public_key_path = ENV["RS_PUBLIC_KEY"]
|
||||
rs.flavor = /512MB/
|
||||
rs.image = /Ubuntu/
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
config.vm.box = 'precise64'
|
||||
config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
|
||||
end
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= "1.2.0" and Vagrant.configure("2") do |config|
|
||||
config.vm.provider :aws do |aws, override|
|
||||
config.vm.box = "dummy"
|
||||
config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
|
||||
@@ -75,8 +47,7 @@ Vagrant::VERSION >= "1.2.0" and Vagrant.configure("2") do |config|
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
config.vm.box = 'precise64'
|
||||
config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
72
commands.go
72
commands.go
@@ -10,6 +10,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.2.0"
|
||||
const VERSION = "0.2.2"
|
||||
|
||||
var (
|
||||
GIT_COMMIT string
|
||||
@@ -400,7 +401,8 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str
|
||||
}
|
||||
|
||||
func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "rm", "CONTAINER [CONTAINER...]", "Remove a container")
|
||||
cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container")
|
||||
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -408,15 +410,40 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
volumes := make(map[string]struct{})
|
||||
for _, name := range cmd.Args() {
|
||||
container := srv.runtime.Get(name)
|
||||
if container == nil {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
// Store all the deleted containers volumes
|
||||
for _, volumeId := range container.Volumes {
|
||||
volumes[volumeId] = struct{}{}
|
||||
}
|
||||
if err := srv.runtime.Destroy(container); err != nil {
|
||||
fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error())
|
||||
}
|
||||
}
|
||||
if *v {
|
||||
// Retrieve all volumes from all remaining containers
|
||||
usedVolumes := make(map[string]*Container)
|
||||
for _, container := range srv.runtime.List() {
|
||||
for _, containerVolumeId := range container.Volumes {
|
||||
usedVolumes[containerVolumeId] = container
|
||||
}
|
||||
}
|
||||
|
||||
for volumeId := range volumes {
|
||||
// If the requested volu
|
||||
if c, exists := usedVolumes[volumeId]; exists {
|
||||
fmt.Fprintf(stdout, "The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id)
|
||||
continue
|
||||
}
|
||||
if err := srv.runtime.volumes.Delete(volumeId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -477,7 +504,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args .
|
||||
}
|
||||
archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)")
|
||||
}
|
||||
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "")
|
||||
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -726,6 +753,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
"Create a new image from a container's changes")
|
||||
flComment := cmd.String("m", "", "Commit message")
|
||||
flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
|
||||
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -734,7 +762,16 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor)
|
||||
|
||||
var config *Config
|
||||
if *flConfig != "" {
|
||||
config = &Config{}
|
||||
if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -903,6 +940,25 @@ func (opts AttachOpts) Get(val string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PathOpts stores a unique set of absolute paths
|
||||
type PathOpts map[string]struct{}
|
||||
|
||||
func NewPathOpts() PathOpts {
|
||||
return make(PathOpts)
|
||||
}
|
||||
|
||||
func (opts PathOpts) String() string {
|
||||
return fmt.Sprintf("%v", map[string]struct{}(opts))
|
||||
}
|
||||
|
||||
func (opts PathOpts) Set(val string) error {
|
||||
if !filepath.IsAbs(val) {
|
||||
return fmt.Errorf("%s is not an absolute path", val)
|
||||
}
|
||||
opts[filepath.Clean(val)] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
|
||||
force := cmd.Bool("f", false, "Force")
|
||||
@@ -925,10 +981,6 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
|
||||
fmt.Fprintln(stdout, "Error: Image not specified")
|
||||
return fmt.Errorf("Image not specified")
|
||||
}
|
||||
if len(config.Cmd) == 0 {
|
||||
fmt.Fprintln(stdout, "Error: Command not specified")
|
||||
return fmt.Errorf("Command not specified")
|
||||
}
|
||||
|
||||
if config.Tty {
|
||||
stdout.SetOptionRawTerminal()
|
||||
@@ -993,11 +1045,11 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewServer() (*Server, error) {
|
||||
func NewServer(autoRestart bool) (*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()
|
||||
runtime, err := NewRuntime(autoRestart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -394,4 +394,5 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
// Try to avoid the timeoout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
57
container.go
57
container.go
@@ -48,6 +48,7 @@ type Container struct {
|
||||
runtime *Runtime
|
||||
|
||||
waitLock chan struct{}
|
||||
Volumes map[string]string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -66,6 +67,8 @@ type Config struct {
|
||||
Cmd []string
|
||||
Dns []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
}
|
||||
|
||||
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
|
||||
var flDns ListOpts
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
|
||||
flVolumes := NewPathOpts()
|
||||
cmd.Var(flVolumes, "v", "Attach a data volume")
|
||||
|
||||
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -136,6 +144,8 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: *flVolumesFrom,
|
||||
}
|
||||
|
||||
if *flMemory > 0 && !capabilities.SwapLimit {
|
||||
@@ -394,10 +404,40 @@ func (container *Container) Start() error {
|
||||
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
container.Config.MemorySwap = -1
|
||||
}
|
||||
container.Volumes = make(map[string]string)
|
||||
|
||||
// Create the requested volumes volumes
|
||||
for volPath := range container.Config.Volumes {
|
||||
if c, err := container.runtime.volumes.Create(nil, container, "", "", nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
container.Volumes[volPath] = c.Id
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config.VolumesFrom != "" {
|
||||
c := container.runtime.Get(container.Config.VolumesFrom)
|
||||
if c == nil {
|
||||
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.Id)
|
||||
}
|
||||
for volPath, id := range c.Volumes {
|
||||
if _, exists := container.Volumes[volPath]; exists {
|
||||
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.Id)
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
container.Volumes[volPath] = id
|
||||
}
|
||||
}
|
||||
|
||||
if err := container.generateLXCConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := []string{
|
||||
"-n", container.Id,
|
||||
"-f", container.lxcConfigPath(),
|
||||
@@ -456,6 +496,7 @@ func (container *Container) Start() error {
|
||||
|
||||
// Init the lock
|
||||
container.waitLock = make(chan struct{})
|
||||
|
||||
container.ToDisk()
|
||||
go container.monitor()
|
||||
return nil
|
||||
@@ -787,6 +828,22 @@ func (container *Container) RootfsPath() string {
|
||||
return path.Join(container.root, "rootfs")
|
||||
}
|
||||
|
||||
func (container *Container) GetVolumes() (map[string]string, error) {
|
||||
ret := make(map[string]string)
|
||||
for volPath, id := range container.Volumes {
|
||||
volume, err := container.runtime.volumes.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root, err := volume.root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[volPath] = path.Join(root, "layer")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (container *Container) rwPath() string {
|
||||
return path.Join(container.root, "rw")
|
||||
}
|
||||
|
||||
@@ -22,9 +22,8 @@ func TestIdFormat(t *testing.T) {
|
||||
defer nuke(runtime)
|
||||
container1, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
|
||||
Memory: 33554432,
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -50,7 +49,6 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c",
|
||||
"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
|
||||
Memory: 33554432,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -116,8 +114,8 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
timeout := make(chan bool)
|
||||
go func() {
|
||||
|
||||
setTimeout(t, "Timeout reading from the process", 3*time.Second, func() {
|
||||
l1, err = bufio.NewReader(stdout1).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -139,15 +137,8 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
if strings.Trim(l3, " \r\n") != "hello" {
|
||||
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3)
|
||||
}
|
||||
timeout <- false
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
timeout <- true
|
||||
}()
|
||||
if <-timeout {
|
||||
t.Fatalf("Timeout reading from the process")
|
||||
}
|
||||
})
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
func TestDiff(t *testing.T) {
|
||||
@@ -193,7 +184,7 @@ func TestDiff(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "")
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -226,7 +217,7 @@ func TestDiff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitRun(t *testing.T) {
|
||||
func TestCommitAutoRun(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -234,9 +225,8 @@ func TestCommitRun(t *testing.T) {
|
||||
defer nuke(runtime)
|
||||
container1, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
||||
Memory: 33554432,
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -258,7 +248,7 @@ func TestCommitRun(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "")
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", &Config{Cmd: []string{"cat", "/world"}})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -267,9 +257,86 @@ func TestCommitRun(t *testing.T) {
|
||||
|
||||
container2, err := runtime.Create(
|
||||
&Config{
|
||||
Image: img.Id,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"cat", "/world"},
|
||||
Image: img.Id,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container2)
|
||||
stdout, err := container2.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stderr, err := container2.StderrPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
output2, err := ioutil.ReadAll(stderr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := stdout.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := stderr.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "hello\n" {
|
||||
t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", output, output2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitRun(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container1, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
rwTar, err := container1.ExportRw()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
|
||||
|
||||
container2, err := runtime.Create(
|
||||
&Config{
|
||||
Image: img.Id,
|
||||
Cmd: []string{"cat", "/world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -354,9 +421,8 @@ func TestRun(t *testing.T) {
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"ls", "-al"},
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"ls", "-al"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DOCKER_PATH = "/home/creack/dotcloud/docker/docker/docker"
|
||||
var DOCKER_PATH string = path.Join(os.Getenv("DOCKERPATH"), "docker")
|
||||
|
||||
// WARNING: this crashTest will 1) crash your host, 2) remove all containers
|
||||
func runDaemon() (*exec.Cmd, error) {
|
||||
os.Remove("/var/run/docker.pid")
|
||||
exec.Command("rm", "-rf", "/var/lib/docker/containers").Run()
|
||||
cmd := exec.Command(DOCKER_PATH, "-d")
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -38,19 +43,43 @@ func crashTest() error {
|
||||
return err
|
||||
}
|
||||
|
||||
var endpoint string
|
||||
if ep := os.Getenv("TEST_ENDPOINT"); ep == "" {
|
||||
endpoint = "192.168.56.1:7979"
|
||||
} else {
|
||||
endpoint = ep
|
||||
}
|
||||
|
||||
c := make(chan bool)
|
||||
var conn io.Writer
|
||||
|
||||
go func() {
|
||||
conn, _ = net.Dial("tcp", endpoint)
|
||||
c <- false
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
c <- true
|
||||
}()
|
||||
<-c
|
||||
|
||||
restartCount := 0
|
||||
totalTestCount := 1
|
||||
for {
|
||||
daemon, err := runDaemon()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
restartCount++
|
||||
// time.Sleep(5000 * time.Millisecond)
|
||||
var stop bool
|
||||
go func() error {
|
||||
stop = false
|
||||
for i := 0; i < 100 && !stop; i++ {
|
||||
for i := 0; i < 100 && !stop; {
|
||||
func() error {
|
||||
cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world")
|
||||
log.Printf("%d", i)
|
||||
cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
|
||||
i++
|
||||
totalTestCount++
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -62,9 +91,10 @@ func crashTest() error {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
io.Copy(os.Stdout, outPipe)
|
||||
}()
|
||||
if conn != nil {
|
||||
go io.Copy(conn, outPipe)
|
||||
}
|
||||
|
||||
// Expecting error, do not check
|
||||
inPipe.Write([]byte("hello world!!!!!\n"))
|
||||
go inPipe.Write([]byte("hello world!!!!!\n"))
|
||||
|
||||
@@ -49,26 +49,39 @@ def docker(args, stdin=None):
|
||||
def image_exists(img):
|
||||
return docker(["inspect", img]).read().strip() != ""
|
||||
|
||||
def run_and_commit(img_in, cmd, stdin=None):
|
||||
def image_config(img):
|
||||
return json.loads(docker(["inspect", img]).read()).get("config", {})
|
||||
|
||||
def run_and_commit(img_in, cmd, stdin=None, author=None, run=None):
|
||||
run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip()
|
||||
print "---> Waiting for " + run_id
|
||||
result=int(docker(["wait", run_id]).read().rstrip())
|
||||
if result != 0:
|
||||
print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result)
|
||||
sys.exit(1)
|
||||
return docker(["commit", run_id]).read().rstrip()
|
||||
return docker(["commit"] + (["-author", author] if author else []) + (["-run", json.dumps(run)] if run is not None else []) + [run_id]).read().rstrip()
|
||||
|
||||
def insert(base, src, dst):
|
||||
def insert(base, src, dst, author=None):
|
||||
print "COPY {} to {} in {}".format(src, dst, base)
|
||||
if dst == "":
|
||||
raise Exception("Missing destination path")
|
||||
stdin = file(src)
|
||||
stdin.seek(0)
|
||||
return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin)
|
||||
|
||||
return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin, author=author)
|
||||
|
||||
def add(base, src, dst, author=None):
|
||||
print "PUSH to {} in {}".format(dst, base)
|
||||
if src == ".":
|
||||
tar = subprocess.Popen(["tar", "-c", "."], stdout=subprocess.PIPE).stdout
|
||||
else:
|
||||
tar = subprocess.Popen(["curl", src], stdout=subprocess.PIPE).stdout
|
||||
if dst == "":
|
||||
raise Exception("Missing argument to push")
|
||||
return run_and_commit(base, "mkdir -p '{0}' && tar -C '{0}' -x".format(dst), stdin=tar, author=author)
|
||||
|
||||
def main():
|
||||
base=""
|
||||
maintainer=""
|
||||
steps = []
|
||||
try:
|
||||
for line in sys.stdin.readlines():
|
||||
@@ -77,22 +90,47 @@ def main():
|
||||
if line == "" or line[0] == "#":
|
||||
continue
|
||||
op, param = line.split(" ", 1)
|
||||
print op.upper() + " " + param
|
||||
if op == "from":
|
||||
print "FROM " + param
|
||||
base = param
|
||||
steps.append(base)
|
||||
elif op == "maintainer":
|
||||
maintainer = param
|
||||
elif op == "run":
|
||||
print "RUN " + param
|
||||
result = run_and_commit(base, param)
|
||||
result = run_and_commit(base, param, author=maintainer)
|
||||
steps.append(result)
|
||||
base = result
|
||||
print "===> " + base
|
||||
elif op == "copy":
|
||||
src, dst = param.split(" ", 1)
|
||||
result = insert(base, src, dst)
|
||||
result = insert(base, src, dst, author=maintainer)
|
||||
steps.append(result)
|
||||
base = result
|
||||
print "===> " + base
|
||||
elif op == "add":
|
||||
src, dst = param.split(" ", 1)
|
||||
result = add(base, src, dst, author=maintainer)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
elif op == "expose":
|
||||
config = image_config(base)
|
||||
if config.get("PortSpecs") is None:
|
||||
config["PortSpecs"] = []
|
||||
portspec = param.strip()
|
||||
config["PortSpecs"].append(portspec)
|
||||
result = run_and_commit(base, "# (nop) expose port {}".format(portspec), author=maintainer, run=config)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
elif op == "cmd":
|
||||
config = image_config(base)
|
||||
cmd = list(json.loads(param))
|
||||
config["Cmd"] = cmd
|
||||
result = run_and_commit(base, "# (nop) set default command to '{}'".format(" ".join(cmd)), author=maintainer, run=config)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
else:
|
||||
print "Skipping uknown op " + op
|
||||
except:
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Start build from a know base image
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
from base:ubuntu-12.10
|
||||
# Update ubuntu sources
|
||||
run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
# Install system packages
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
# Insert files from the host (./myscript must be present in the current directory)
|
||||
copy myscript /usr/local/bin/myscript
|
||||
copy myscript /usr/local/bin/myscript
|
||||
push /src
|
||||
|
||||
3
contrib/docker-build/myscript
Normal file
3
contrib/docker-build/myscript
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo hello, world!
|
||||
61
contrib/mkimage-debian.sh
Executable file
61
contrib/mkimage-debian.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
stableSuite='squeeze'
|
||||
testingSuite='wheezy'
|
||||
unstableSuite='sid'
|
||||
|
||||
# if suite is equal to this, it gets the "latest" tag
|
||||
latestSuite="$testingSuite"
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
|
||||
repo="$1"
|
||||
suite="${2:-$latestSuite}"
|
||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||
|
||||
if [ ! "$repo" ]; then
|
||||
echo >&2 "usage: $0 repo [suite [mirror]]"
|
||||
echo >&2 " ie: $0 tianon/debian squeeze"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
set -x
|
||||
|
||||
# bootstrap
|
||||
mkdir -p "$target"
|
||||
sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$target" "$mirror"
|
||||
|
||||
cd "$target"
|
||||
|
||||
# create the image
|
||||
img=$(sudo tar -c . | docker import -)
|
||||
|
||||
# tag suite
|
||||
docker tag $img $repo $suite
|
||||
|
||||
if [ "$suite" = "$latestSuite" ]; then
|
||||
# tag latest
|
||||
docker tag $img $repo latest
|
||||
fi
|
||||
|
||||
# test the image
|
||||
docker run -i -t $repo:$suite echo success
|
||||
|
||||
# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag
|
||||
if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then
|
||||
# tag the specific version
|
||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||
docker tag $img $repo $ver
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
@@ -28,6 +28,7 @@ func main() {
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
flag.Parse()
|
||||
@@ -45,7 +46,7 @@ func main() {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
if err := daemon(*pidfile); err != nil {
|
||||
if err := daemon(*pidfile, *flAutoRestart); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
@@ -82,7 +83,7 @@ func removePidFile(pidfile string) {
|
||||
}
|
||||
}
|
||||
|
||||
func daemon(pidfile string) error {
|
||||
func daemon(pidfile string, autoRestart bool) error {
|
||||
if err := createPidFile(pidfile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -97,7 +98,7 @@ func daemon(pidfile string) error {
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
service, err := docker.NewServer()
|
||||
service, err := docker.NewServer(autoRestart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,3 +9,19 @@
|
||||
Create a new image from a container's changes
|
||||
|
||||
-m="": Commit message
|
||||
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
|
||||
-run="": Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
|
||||
Full -run example::
|
||||
|
||||
{"Hostname": "",
|
||||
"User": "",
|
||||
"Memory": 0,
|
||||
"MemorySwap": 0,
|
||||
"PortSpecs": ["22", "80", "443"],
|
||||
"Tty": true,
|
||||
"OpenStdin": true,
|
||||
"StdinOnce": true,
|
||||
"Env": ["FOO=BAR", "FOO2=BAR2"],
|
||||
"Cmd": ["cat", "-e", "/etc/resolv.conf"],
|
||||
"Dns": ["8.8.8.8", "8.8.4.4"]}
|
||||
|
||||
@@ -17,3 +17,5 @@
|
||||
-p=[]: Map a network port to the container
|
||||
-t=false: Allocate a pseudo-tty
|
||||
-u="": Username or UID
|
||||
-d=[]: Set custom dns servers for the container
|
||||
-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.
|
||||
|
||||
@@ -12,7 +12,7 @@ Images
|
||||
------
|
||||
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
|
||||
|
||||
Images are stored on your local file system under /var/lib/docker/images
|
||||
Images are stored on your local file system under /var/lib/docker/graph
|
||||
|
||||
|
||||
.. _containers:
|
||||
|
||||
53
docs/sources/examples/couchdb_data_volumes.rst
Normal file
53
docs/sources/examples/couchdb_data_volumes.rst
Normal file
@@ -0,0 +1,53 @@
|
||||
:title: Sharing data between 2 couchdb databases
|
||||
:description: Sharing data between 2 couchdb databases
|
||||
:keywords: docker, example, package installation, networking, couchdb, data volumes
|
||||
|
||||
.. _running_redis_service:
|
||||
|
||||
Create a redis service
|
||||
======================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Here's an example of using data volumes to share the same data between 2 couchdb containers.
|
||||
This could be used for hot upgrades, testing different versions of couchdb on the same data, etc.
|
||||
|
||||
Create first database
|
||||
---------------------
|
||||
|
||||
Note that we're marking /var/lib/couchdb as a data volume.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH1=$(docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
|
||||
Add data to the first database
|
||||
------------------------------
|
||||
|
||||
We're assuming your docker host is reachable at `localhost`. If not, replace `localhost` with the public IP of your docker host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(docker port $COUCH1 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser, and use the couch interface to add data"
|
||||
|
||||
Create second database
|
||||
----------------------
|
||||
|
||||
This time, we're requesting shared access to $COUCH1's volumes.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH2=$(docker run -d -volumes-from $COUCH1) shykes/couchdb:2013-05-03)
|
||||
|
||||
Browse data on the second database
|
||||
----------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database!"
|
||||
|
||||
Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data.
|
||||
@@ -18,3 +18,4 @@ Contents:
|
||||
python_web_app
|
||||
running_redis_service
|
||||
running_ssh_service
|
||||
couchdb_data_volumes
|
||||
|
||||
@@ -15,6 +15,7 @@ This documentation has the following resources:
|
||||
examples/index
|
||||
contributing/index
|
||||
commandline/index
|
||||
registry/index
|
||||
faq
|
||||
|
||||
|
||||
|
||||
464
docs/sources/registry/api.rst
Normal file
464
docs/sources/registry/api.rst
Normal file
@@ -0,0 +1,464 @@
|
||||
===================
|
||||
Docker Registry API
|
||||
===================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. The 3 roles
|
||||
===============
|
||||
|
||||
1.1 Index
|
||||
---------
|
||||
|
||||
The Index is responsible for centralizing information about:
|
||||
- User accounts
|
||||
- Checksums of the images
|
||||
- Public namespaces
|
||||
|
||||
The Index has different components:
|
||||
- Web UI
|
||||
- Meta-data store (comments, stars, list public repositories)
|
||||
- Authentication service
|
||||
- Tokenization
|
||||
|
||||
The index is authoritative for those information.
|
||||
|
||||
We expect that there will be only one instance of the index, run and managed by dotCloud.
|
||||
|
||||
1.2 Registry
|
||||
------------
|
||||
- It stores the images and the graph for a set of repositories
|
||||
- It does not have user accounts data
|
||||
- It has no notion of user accounts or authorization
|
||||
- It delegates authentication and authorization to the Index Auth service using tokens
|
||||
- It supports different storage backends (S3, cloud files, local FS)
|
||||
- It doesn’t have a local database
|
||||
- It will be open-sourced at some point
|
||||
|
||||
We expect that there will be multiple registries out there. To help to grasp the context, here are some examples of registries:
|
||||
|
||||
- **sponsor registry**: such a registry is provided by a third-party hosting infrastructure as a convenience for their customers and the docker community as a whole. Its costs are supported by the third party, but the management and operation of the registry are supported by dotCloud. It features read/write access, and delegates authentication and authorization to the Index.
|
||||
- **mirror registry**: such a registry is provided by a third-party hosting infrastructure but is targeted at their customers only. Some mechanism (unspecified to date) ensures that public images are pulled from a sponsor registry to the mirror registry, to make sure that the customers of the third-party provider can “docker pull” those images locally.
|
||||
- **vendor registry**: such a registry is provided by a software vendor, who wants to distribute docker images. It would be operated and managed by the vendor. Only users authorized by the vendor would be able to get write access. Some images would be public (accessible for anyone), others private (accessible only for authorized users). Authentication and authorization would be delegated to the Index. The goal of vendor registries is to let someone do “docker pull basho/riak1.3” and automatically push from the vendor registry (instead of a sponsor registry); i.e. get all the convenience of a sponsor registry, while retaining control on the asset distribution.
|
||||
- **private registry**: such a registry is located behind a firewall, or protected by an additional security layer (HTTP authorization, SSL client-side certificates, IP address authorization...). The registry is operated by a private entity, outside of dotCloud’s control. It can optionally delegate additional authorization to the Index, but it is not mandatory.
|
||||
|
||||
.. note::
|
||||
|
||||
Mirror registries and private registries which do not use the Index don’t even need to run the registry code. They can be implemented by any kind of transport implementing HTTP GET and PUT. Read-only registries can be powered by a simple static HTTP server.
|
||||
|
||||
.. note::
|
||||
|
||||
The latter implies that while HTTP is the protocol of choice for a registry, multiple schemes are possible (and in some cases, trivial):
|
||||
- HTTP with GET (and PUT for read-write registries);
|
||||
- local mount point;
|
||||
- remote docker addressed through SSH.
|
||||
|
||||
The latter would only require two new commands in docker, e.g. “registryget” and “registryput”, wrapping access to the local filesystem (and optionally doing consistency checks). Authentication and authorization are then delegated to SSH (e.g. with public keys).
|
||||
|
||||
1.3 Docker
|
||||
----------
|
||||
|
||||
On top of being a runtime for LXC, Docker is the Registry client. It supports:
|
||||
- Push / Pull on the registry
|
||||
- Client authentication on the Index
|
||||
|
||||
2. Workflow
|
||||
===========
|
||||
|
||||
2.1 Pull
|
||||
--------
|
||||
|
||||
.. image:: /static_files/docker_pull_chart.png
|
||||
|
||||
1. Contact the Index to know where I should download “samalba/busybox”
|
||||
2. Index replies:
|
||||
a. “samalba/busybox” is on Registry A
|
||||
b. here are the checksums for “samalba/busybox” (for all layers)
|
||||
c. token
|
||||
3. Contact Registry A to receive the layers for “samalba/busybox” (all of them to the base image). Registry A is authoritative for “samalba/busybox” but keeps a copy of all inherited layers and serve them all from the same location.
|
||||
4. registry contacts index to verify if token/user is allowed to download images
|
||||
5. Index returns true/false lettings registry know if it should proceed or error out
|
||||
6. Get the payload for all layers
|
||||
|
||||
It’s possible to run docker pull https://<registry>/repositories/samalba/busybox. In this case, docker bypasses the Index. However the security is not guaranteed (in case Registry A is corrupted) because there won’t be any checksum checks.
|
||||
|
||||
Currently registry redirects to s3 urls for downloads, going forward all downloads need to be streamed through the registry. The Registry will then abstract the calls to S3 by a top-level class which implements sub-classes for S3 and local storage.
|
||||
|
||||
Token is only returned when it is a private repo, public repos do not require tokens to be returned. The Registry will still contact the Index to make sure the pull is authorized (“is it ok to download this repos without a Token?”).
|
||||
|
||||
API (pulling repository foo/bar):
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. (Docker -> Index) GET /v1/repositories/foo/bar/images
|
||||
**Headers**:
|
||||
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
X-Docker-Token: true
|
||||
**Action**:
|
||||
(looking up the foo/bar in db and gets images and checksums for that repo (all if no tag is specified, if tag, only checksums for those tags) see part 4.4.1)
|
||||
|
||||
2. (Index -> Docker) HTTP 200 OK
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
|
||||
**Body**:
|
||||
Jsonified checksums (see part 4.4.1)
|
||||
|
||||
3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
|
||||
4. (Registry -> Index) GET /v1/repositories/foo/bar/images
|
||||
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
|
||||
|
||||
**Body**:
|
||||
<ids and checksums in payload>
|
||||
|
||||
**Action**:
|
||||
( Lookup token see if they have access to pull.)
|
||||
|
||||
If good:
|
||||
HTTP 200 OK
|
||||
Index will invalidate the token
|
||||
If bad:
|
||||
HTTP 401 Unauthorized
|
||||
|
||||
5. (Docker -> Registry) GET /v1/images/928374982374/ancestry
|
||||
**Action**:
|
||||
(for each image id returned in the registry, fetch /json + /layer)
|
||||
|
||||
.. note::
|
||||
|
||||
If someone makes a second request, then we will always give a new token, never reuse tokens.
|
||||
|
||||
2.2 Push
|
||||
--------
|
||||
|
||||
.. image:: /static_files/docker_push_chart.png
|
||||
|
||||
1. Contact the index to allocate the repository name “samalba/busybox” (authentication required with user credentials)
|
||||
2. If authentication works and namespace available, “samalba/busybox” is allocated and a temporary token is returned (namespace is marked as initialized in index)
|
||||
3. Push the image on the registry (along with the token)
|
||||
4. Registry A contacts the Index to verify the token (token must corresponds to the repository name)
|
||||
5. Index validates the token. Registry A starts reading the stream pushed by docker and store the repository (with its images)
|
||||
6. docker contacts the index to give checksums for upload images
|
||||
|
||||
.. note::
|
||||
|
||||
**It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed.
|
||||
|
||||
.. note::
|
||||
|
||||
**Index can be replaced!** For a private Registry deployed, a custom Index can be used to serve and validate token according to different policies.
|
||||
|
||||
Docker computes the checksums and submit them to the Index at the end of the push. When a repository name does not have checksums on the Index, it means that the push is in progress (since checksums are submitted at the end).
|
||||
|
||||
API (pushing repos foo/bar):
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. (Docker -> Index) PUT /v1/repositories/foo/bar/
|
||||
**Headers**:
|
||||
Authorization: Basic sdkjfskdjfhsdkjfh==
|
||||
X-Docker-Token: true
|
||||
|
||||
**Action**::
|
||||
- in index, we allocated a new repository, and set to initialized
|
||||
|
||||
**Body**::
|
||||
(The body contains the list of images that are going to be pushed, with empty checksums. The checksums will be set at the end of the push)::
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
|
||||
|
||||
2. (Index -> Docker) 200 Created
|
||||
**Headers**:
|
||||
- WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
|
||||
|
||||
3. (Docker -> Registry) PUT /v1/images/98765432_parent/json
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
|
||||
4. (Registry->Index) GET /v1/repositories/foo/bar/images
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
**Action**::
|
||||
- Index:
|
||||
will invalidate the token.
|
||||
- Registry:
|
||||
grants a session (if token is approved) and fetches the images id
|
||||
|
||||
5. (Docker -> Registry) PUT /v1/images/98765432_parent/json
|
||||
**Headers**::
|
||||
- Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
- Cookie: (Cookie provided by the Registry)
|
||||
|
||||
6. (Docker -> Registry) PUT /v1/images/98765432/json
|
||||
**Headers**:
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer
|
||||
**Headers**:
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
8. (Docker -> Registry) PUT /v1/images/98765432/layer
|
||||
**Headers**:
|
||||
X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh
|
||||
|
||||
9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest
|
||||
**Headers**:
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
**Body**:
|
||||
“98765432”
|
||||
|
||||
10. (Docker -> Index) PUT /v1/repositories/foo/bar/images
|
||||
|
||||
**Headers**:
|
||||
Authorization: Basic 123oislifjsldfj==
|
||||
X-Docker-Endpoints: registry1.docker.io (no validation on this right now)
|
||||
|
||||
**Body**:
|
||||
(The image, id’s, tags and checksums)
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
|
||||
|
||||
**Return** HTTP 204
|
||||
|
||||
.. note::
|
||||
|
||||
If push fails and they need to start again, what happens in the index, there will already be a record for the namespace/name, but it will be initialized. Should we allow it, or mark as name already used? One edge case could be if someone pushes the same thing at the same time with two different shells.
|
||||
|
||||
If it's a retry on the Registry, Docker has a cookie (provided by the registry after token validation). So the Index won’t have to provide a new token.
|
||||
|
||||
3. How to use the Registry in standalone mode
|
||||
=============================================
|
||||
|
||||
The Index has two main purposes (along with its fancy social features):
|
||||
|
||||
- Resolve short names (to avoid passing absolute URLs all the time)
|
||||
- username/projectname -> https://registry.docker.io/users/<username>/repositories/<projectname>/
|
||||
- team/projectname -> https://registry.docker.io/team/<team>/repositories/<projectname>/
|
||||
- Authenticate a user as a repos owner (for a central referenced repository)
|
||||
|
||||
3.1 Without an Index
|
||||
--------------------
|
||||
Using the Registry without the Index can be useful to store the images on a private network without having to rely on an external entity controlled by dotCloud.
|
||||
|
||||
In this case, the registry will be launched in a special mode (--standalone? --no-index?). In this mode, the only thing which changes is that Registry will never contact the Index to verify a token. It will be the Registry owner responsibility to authenticate the user who pushes (or even pulls) an image using any mechanism (HTTP auth, IP based, etc...).
|
||||
|
||||
In this scenario, the Registry is responsible for the security in case of data corruption since the checksums are not delivered by a trusted entity.
|
||||
|
||||
As hinted previously, a standalone registry can also be implemented by any HTTP server handling GET/PUT requests (or even only GET requests if no write access is necessary).
|
||||
|
||||
3.2 With an Index
|
||||
-----------------
|
||||
|
||||
The Index data needed by the Registry are simple:
|
||||
- Serve the checksums
|
||||
- Provide and authorize a Token
|
||||
|
||||
In the scenario of a Registry running on a private network with the need of centralizing and authorizing, it’s easy to use a custom Index.
|
||||
|
||||
The only challenge will be to tell Docker to contact (and trust) this custom Index. Docker will be configurable at some point to use a specific Index, it’ll be the private entity responsibility (basically the organization who uses Docker in a private environment) to maintain the Index and the Docker’s configuration among its consumers.
|
||||
|
||||
4. The API
|
||||
==========
|
||||
|
||||
The first version of the api is available here: https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md
|
||||
|
||||
4.1 Images
|
||||
----------
|
||||
|
||||
The format returned in the images is not defined here (for layer and json), basically because Registry stores exactly the same kind of information as Docker uses to manage them.
|
||||
|
||||
The format of ancestry is a line-separated list of image ids, in age order. I.e. the image’s parent is on the last line, the parent of the parent on the next-to-last line, etc.; if the image has no parent, the file is empty.
|
||||
|
||||
GET /v1/images/<image_id>/layer
|
||||
PUT /v1/images/<image_id>/layer
|
||||
GET /v1/images/<image_id>/json
|
||||
PUT /v1/images/<image_id>/json
|
||||
GET /v1/images/<image_id>/ancestry
|
||||
PUT /v1/images/<image_id>/ancestry
|
||||
|
||||
4.2 Users
|
||||
---------
|
||||
|
||||
4.2.1 Create a user (Index)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
POST /v1/users
|
||||
|
||||
**Body**:
|
||||
{"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
|
||||
|
||||
**Validation**:
|
||||
- **username** : min 4 character, max 30 characters, all lowercase no special characters.
|
||||
- **password**: min 5 characters
|
||||
|
||||
**Valid**: return HTTP 200
|
||||
|
||||
Errors: HTTP 400 (we should create error codes for possible errors)
|
||||
- invalid json
|
||||
- missing field
|
||||
- wrong format (username, password, email, etc)
|
||||
- forbidden name
|
||||
- name already exists
|
||||
|
||||
.. note::
|
||||
|
||||
A user account will be valid only if the email has been validated (a validation link is sent to the email address).
|
||||
|
||||
4.2.2 Update a user (Index)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
PUT /v1/users/<username>
|
||||
|
||||
**Body**:
|
||||
{"password": "toto"}
|
||||
|
||||
.. note::
|
||||
|
||||
We can also update email address, if they do, they will need to reverify their new email address.
|
||||
|
||||
4.2.3 Login (Index)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Does nothing else but asking for a user authentication. Can be used to validate credentials. HTTP Basic Auth for now, maybe change in future.
|
||||
|
||||
GET /v1/users
|
||||
|
||||
**Return**:
|
||||
- Valid: HTTP 200
|
||||
- Invalid login: HTTP 401
|
||||
- Account inactive: HTTP 403 Account is not Active
|
||||
|
||||
4.3 Tags (Registry)
|
||||
-------------------
|
||||
|
||||
The Registry does not know anything about users. Even though repositories are under usernames, it’s just a namespace for the registry. Allowing us to implement organizations or different namespaces per user later, without modifying the Registry’s API.
|
||||
|
||||
4.3.1 Get all tags
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
GET /v1/repositories/<namespace>/<repository_name>/tags
|
||||
|
||||
**Return**: HTTP 200
|
||||
{
|
||||
"latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
|
||||
“0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
|
||||
}
|
||||
|
||||
4.3.2 Read the content of a tag (resolve the image id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
GET /v1/repositories/<namespace>/<repo_name>/tags/<tag>
|
||||
|
||||
**Return**:
|
||||
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
|
||||
|
||||
4.3.3 Delete a tag (registry)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
DELETE /v1/repositories/<namespace>/<repo_name>/tags/<tag>
|
||||
|
||||
4.4 Images (Index)
|
||||
------------------
|
||||
|
||||
For the Index to “resolve” the repository name to a Registry location, it uses the X-Docker-Endpoints header. In other terms, this requests always add a “X-Docker-Endpoints” to indicate the location of the registry which hosts this repository.
|
||||
|
||||
4.4.1 Get the images
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
GET /v1/repositories/<namespace>/<repo_name>/images
|
||||
|
||||
**Return**: HTTP 200
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
|
||||
|
||||
|
||||
4.4.2 Add/update the images
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You always add images, you never remove them.
|
||||
|
||||
PUT /v1/repositories/<namespace>/<repo_name>/images
|
||||
|
||||
**Body**:
|
||||
[ {“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} ]
|
||||
|
||||
**Return** 204
|
||||
|
||||
5. Chaining Registries
|
||||
======================
|
||||
|
||||
It’s possible to chain Registries server for several reasons:
|
||||
- Load balancing
|
||||
- Delegate the next request to another server
|
||||
|
||||
When a Registry is a reference for a repository, it should host the entire images chain in order to avoid breaking the chain during the download.
|
||||
|
||||
The Index and Registry use this mechanism to redirect on one or the other.
|
||||
|
||||
Example with an image download:
|
||||
On every request, a special header can be returned:
|
||||
|
||||
X-Docker-Endpoints: server1,server2
|
||||
|
||||
On the next request, the client will always pick a server from this list.
|
||||
|
||||
6. Authentication & Authorization
|
||||
=================================
|
||||
|
||||
6.1 On the Index
|
||||
-----------------
|
||||
|
||||
The Index supports both “Basic” and “Token” challenges. Usually when there is a “401 Unauthorized”, the Index replies this::
|
||||
|
||||
401 Unauthorized
|
||||
WWW-Authenticate: Basic realm="auth required",Token
|
||||
|
||||
You have 3 options:
|
||||
|
||||
1. Provide user credentials and ask for a token
|
||||
|
||||
**Header**:
|
||||
- Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
- X-Docker-Token: true
|
||||
|
||||
In this case, along with the 200 response, you’ll get a new token (if user auth is ok):
|
||||
|
||||
**Response**:
|
||||
- 200 OK
|
||||
- X-Docker-Token: Token signature=123abc,repository=”foo/bar”,access=read
|
||||
|
||||
2. Provide user credentials only
|
||||
|
||||
**Header**:
|
||||
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
|
||||
3. Provide Token
|
||||
|
||||
**Header**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
|
||||
|
||||
6.2 On the Registry
|
||||
-------------------
|
||||
|
||||
The Registry only supports the Token challenge::
|
||||
|
||||
401 Unauthorized
|
||||
WWW-Authenticate: Token
|
||||
|
||||
The only way is to provide a token on “401 Unauthorized” responses::
|
||||
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
|
||||
|
||||
Usually, the Registry provides a Cookie when a Token verification succeeded. Every time the Registry passes a Cookie, you have to pass it back the same cookie.::
|
||||
|
||||
200 OK
|
||||
Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly
|
||||
|
||||
Next request::
|
||||
|
||||
GET /(...)
|
||||
Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="
|
||||
15
docs/sources/registry/index.rst
Normal file
15
docs/sources/registry/index.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
:title: docker Registry documentation
|
||||
:description: Documentation for docker Registry and Registry API
|
||||
:keywords: docker, registry, api, index
|
||||
|
||||
|
||||
|
||||
Registry
|
||||
========
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
BIN
docs/sources/static_files/docker_pull_chart.png
Normal file
BIN
docs/sources/static_files/docker_pull_chart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/sources/static_files/docker_push_chart.png
Normal file
BIN
docs/sources/static_files/docker_push_chart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
8
graph.go
8
graph.go
@@ -84,18 +84,24 @@ func (graph *Graph) Get(name string) (*Image, error) {
|
||||
}
|
||||
|
||||
// Create creates a new image and registers it in the graph.
|
||||
func (graph *Graph) Create(layerData Archive, container *Container, comment, author string) (*Image, error) {
|
||||
func (graph *Graph) Create(layerData Archive, container *Container, comment, author string, config *Config) (*Image, error) {
|
||||
img := &Image{
|
||||
Id: GenerateId(),
|
||||
Comment: comment,
|
||||
Created: time.Now(),
|
||||
DockerVersion: VERSION,
|
||||
Author: author,
|
||||
Config: config,
|
||||
}
|
||||
if container != nil {
|
||||
img.Parent = container.Image
|
||||
img.Container = container.Id
|
||||
img.ContainerConfig = *container.Config
|
||||
if config == nil {
|
||||
if parentImage, err := graph.Get(container.Image); err == nil && parentImage != nil {
|
||||
img.Config = parentImage.Config
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := graph.Register(layerData, img); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestGraphCreate(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "")
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func TestMount(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "")
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -166,7 +166,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, err := graph.Create(archive, nil, "Test image", "")
|
||||
img, err := graph.Create(archive, nil, "Test image", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -181,7 +181,7 @@ func TestDelete(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
img, err := graph.Create(archive, nil, "Bla bla", "")
|
||||
img, err := graph.Create(archive, nil, "Bla bla", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -192,11 +192,11 @@ func TestDelete(t *testing.T) {
|
||||
assertNImages(graph, t, 0)
|
||||
|
||||
// Test 2 create (same name) / 1 delete
|
||||
img1, err := graph.Create(archive, nil, "Testing", "")
|
||||
img1, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = graph.Create(archive, nil, "Testing", ""); err != nil {
|
||||
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 2)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This directory contains material helpful for hacking on docker.
|
||||
27
hack/README.rst
Normal file
27
hack/README.rst
Normal file
@@ -0,0 +1,27 @@
|
||||
This directory contains material helpful for hacking on docker.
|
||||
|
||||
make hack
|
||||
=========
|
||||
|
||||
Set up an Ubuntu 13.04 virtual machine for developers including kernel 3.8
|
||||
and buildbot. The environment is setup in a way that can be used through
|
||||
the usual go workflow and/or the root Makefile. You can either edit on
|
||||
your host, or inside the VM (using make ssh-dev) and run and test docker
|
||||
inside the VM.
|
||||
|
||||
dependencies: vagrant, virtualbox packages and python package requests
|
||||
|
||||
|
||||
Buildbot
|
||||
~~~~~~~~
|
||||
|
||||
Buildbot is a continuous integration system designed to automate the
|
||||
build/test cycle. By automatically rebuilding and testing the tree each time
|
||||
something has changed, build problems are pinpointed quickly, before other
|
||||
developers are inconvenienced by the failure.
|
||||
|
||||
When running 'make hack' at the docker root directory, it spawns a virtual
|
||||
machine in the background running a buildbot instance and adds a git
|
||||
post-commit hook that automatically run docker tests for you.
|
||||
|
||||
You can check your buildbot instance at http://192.168.33.21:8010/waterfall
|
||||
35
hack/Vagrantfile
vendored
Normal file
35
hack/Vagrantfile
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
BOX_NAME = "ubuntu-dev"
|
||||
BOX_URI = "http://cloud-images.ubuntu.com/raring/current/raring-server-cloudimg-vagrant-amd64-disk1.box"
|
||||
VM_IP = "192.168.33.21"
|
||||
USER = "vagrant"
|
||||
GOPATH = "/data/docker"
|
||||
DOCKER_PATH = "#{GOPATH}/src/github.com/dotcloud/docker"
|
||||
CFG_PATH = "#{DOCKER_PATH}/hack/environment"
|
||||
BUILDBOT_PATH = "/data/buildbot"
|
||||
|
||||
Vagrant::Config.run do |config|
|
||||
# Setup virtual machine box
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
config.vm.share_folder "v-data", DOCKER_PATH, "#{File.dirname(__FILE__)}/.."
|
||||
config.vm.network :hostonly, VM_IP
|
||||
# Stop if deployment has been done
|
||||
config.vm.provision :shell, :inline => "[ ! -f /usr/bin/git ]"
|
||||
# Touch for makefile
|
||||
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; " \
|
||||
"chown -R #{USER}.#{USER} #{GOPATH}; " \
|
||||
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
# Deploy buildbot CI
|
||||
pkg_cmd = "apt-get install -q -y python-dev python-pip supervisor; " \
|
||||
"pip install -r #{CFG_PATH}/requirements.txt; " \
|
||||
"chown #{USER}.#{USER} /data; cd /data; " \
|
||||
"#{CFG_PATH}/setup.sh #{USER} #{GOPATH} #{DOCKER_PATH} #{CFG_PATH} #{BUILDBOT_PATH}"
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
@@ -1,17 +1,23 @@
|
||||
# This will build a container capable of producing an official binary build of docker and
|
||||
# uploading it to S3
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
from ubuntu:12.10
|
||||
run apt-get update
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
# Packages required to checkout and build docker
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.0.3.linux-amd64.tar.gz
|
||||
run tar -C /usr/local -xzf /go.tar.gz
|
||||
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bashrc
|
||||
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bash_profile
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
|
||||
# Packages required to build an ubuntu package
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
|
||||
copy fake_initctl /usr/local/bin/initctl
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q devscripts
|
||||
copy dockerbuilder /usr/local/bin/dockerbuilder
|
||||
copy s3cfg /.s3cfg
|
||||
# run $img dockerbuilder $REVISION_OR_TAG $S3_ID $S3_KEY
|
||||
run apt-get install -y -q devscripts
|
||||
add . /src
|
||||
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
|
||||
run cp /src/s3cfg /.s3cfg
|
||||
cmd ["dockerbuilder"]
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
set -x
|
||||
set -e
|
||||
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
|
||||
PACKAGE=github.com/dotcloud/docker
|
||||
|
||||
if [ $# -gt 1 ]; then
|
||||
@@ -13,12 +15,10 @@ export REVISION=$1
|
||||
|
||||
if [ -z "$AWS_ID" ]; then
|
||||
echo "Warning: environment variable AWS_ID is not set. Won't upload to S3."
|
||||
NO_S3=1
|
||||
fi
|
||||
|
||||
if [ -z "$AWS_KEY" ]; then
|
||||
echo "Warning: environment variable AWS_KEY is not set. Won't upload to S3."
|
||||
NO_S3=1
|
||||
fi
|
||||
|
||||
if [ -z "$GPG_KEY" ]; then
|
||||
@@ -26,28 +26,15 @@ if [ -z "$GPG_KEY" ]; then
|
||||
NO_UBUNTU=1
|
||||
fi
|
||||
|
||||
if [ -z "$REVISION" ]; then
|
||||
rm -fr docker-master
|
||||
git clone https://github.com/dotcloud/docker docker-master
|
||||
cd docker-master
|
||||
else
|
||||
rm -fr docker-$REVISION
|
||||
git init docker-$REVISION
|
||||
cd docker-$REVISION
|
||||
git fetch -t https://github.com/dotcloud/docker $REVISION
|
||||
git reset --hard FETCH_HEAD
|
||||
fi
|
||||
|
||||
rm -fr docker-release
|
||||
git clone https://github.com/dotcloud/docker docker-release
|
||||
cd docker-release
|
||||
if [ -z "$REVISION" ]; then
|
||||
make release
|
||||
else
|
||||
make release RELEASE_VERSION=$REVISION
|
||||
fi
|
||||
|
||||
if [ -z "$NO_S3" ]; then
|
||||
s3cmd -P put docker-$REVISION.tgz s3://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-$REVISION.tgz
|
||||
fi
|
||||
|
||||
if [ -z "$NO_UBUNTU" ]; then
|
||||
(cd packaging/ubuntu && make ubuntu)
|
||||
fi
|
||||
|
||||
1
hack/environment/README.rst
Normal file
1
hack/environment/README.rst
Normal file
@@ -0,0 +1 @@
|
||||
Files used to setup the developer virtual machine
|
||||
19
hack/environment/bash_profile
Normal file
19
hack/environment/bash_profile
Normal file
@@ -0,0 +1,19 @@
|
||||
# ~/.bash_profile : executed by the command interpreter for login shells.
|
||||
|
||||
# if running bash
|
||||
if [ -n "$BASH_VERSION" ]; then
|
||||
# include .bashrc if it exists
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
. "$HOME/.bashrc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# set PATH so it includes user's private bin if it exists
|
||||
[ -d "$HOME/bin" ] && PATH="$HOME/bin:$PATH"
|
||||
|
||||
docker=/data/docker/src/github.com/dotcloud/docker
|
||||
[ -d $docker ] && cd $docker
|
||||
|
||||
export GOPATH=/data/docker
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
|
||||
@@ -13,8 +13,8 @@ TEST_USER = 'buildbot' # Credential to authenticate build triggers
|
||||
TEST_PWD = 'docker' # Credential to authenticate build triggers
|
||||
BUILDER_NAME = 'docker'
|
||||
BUILDPASSWORD = 'pass-docker' # Credential to authenticate buildworkers
|
||||
DOCKER_PATH = '/data/docker'
|
||||
|
||||
GOPATH = '/data/docker'
|
||||
DOCKER_PATH = '{0}/src/github.com/dotcloud/docker'.format(GOPATH)
|
||||
|
||||
c = BuildmasterConfig = {}
|
||||
|
||||
@@ -28,10 +28,7 @@ c['slavePortnum'] = PORT_MASTER
|
||||
c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])]
|
||||
|
||||
# Docker test command
|
||||
test_cmd = """(
|
||||
cd {0}/..; rm -rf docker-tmp; git clone docker docker-tmp;
|
||||
cd docker-tmp; make test; exit_status=$?;
|
||||
cd ..; rm -rf docker-tmp; exit $exit_status)""".format(DOCKER_PATH)
|
||||
test_cmd = "GOPATH={0} make -C {1} test".format(GOPATH,DOCKER_PATH)
|
||||
|
||||
# Builder
|
||||
factory = BuildFactory()
|
||||
6
hack/environment/requirements.txt
Normal file
6
hack/environment/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
sqlalchemy<=0.7.9
|
||||
sqlalchemy-migrate>=0.7.2
|
||||
buildbot==0.8.7p1
|
||||
buildbot_slave==0.8.7p1
|
||||
nose==1.2.1
|
||||
requests==1.1.0
|
||||
45
hack/environment/setup.sh
Executable file
45
hack/environment/setup.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Setup of buildbot configuration. Package installation is being done by
|
||||
# Vagrantfile
|
||||
# Dependencies: buildbot, buildbot-slave, supervisor
|
||||
|
||||
USER=$1
|
||||
GOPATH=$2
|
||||
DOCKER_PATH=$3
|
||||
CFG_PATH=$4
|
||||
BUILDBOT_PATH=$5
|
||||
SLAVE_NAME="buildworker"
|
||||
SLAVE_SOCKET="localhost:9989"
|
||||
BUILDBOT_PWD="pass-docker"
|
||||
IP=$(sed -nE 's/VM_IP = "(.+)"/\1/p' ${DOCKER_PATH}/hack/Vagrantfile)
|
||||
export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin"
|
||||
|
||||
function run { su $USER -c "$1"; }
|
||||
|
||||
# Exit if buildbot has already been installed
|
||||
[ -d "$BUILDBOT_PATH" ] && exit 0
|
||||
|
||||
# Setup buildbot
|
||||
run "mkdir -p $BUILDBOT_PATH"
|
||||
cd $BUILDBOT_PATH
|
||||
run "buildbot create-master master"
|
||||
run "cp $CFG_PATH/master.cfg master"
|
||||
run "sed -i 's/localhost/$IP/' master/master.cfg"
|
||||
run "sed -i -E 's#(GOPATH = ).+#\1\"$GOPATH\"#' master/master.cfg"
|
||||
run "sed -i -E 's#(DOCKER_PATH = ).+#\1\"$DOCKER_PATH\"#' master/master.cfg"
|
||||
run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD"
|
||||
|
||||
# Allow buildbot subprocesses (docker tests) to properly run in containers,
|
||||
# in particular with docker -u
|
||||
run "sed -i 's/^umask = None/umask = 000/' slave/buildbot.tac"
|
||||
|
||||
# Setup supervisor
|
||||
cp $CFG_PATH/buildbot.conf /etc/supervisor/conf.d/buildbot.conf
|
||||
sed -i -E "s/^chmod=0700.+/chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf
|
||||
kill -HUP $(pgrep -f "/usr/bin/python /usr/bin/supervisord")
|
||||
|
||||
# Add git hook
|
||||
cp $CFG_PATH/post-commit $DOCKER_PATH/.git/hooks
|
||||
sed -i "s/localhost/$IP/" $DOCKER_PATH/.git/hooks/post-commit
|
||||
|
||||
1
image.go
1
image.go
@@ -24,6 +24,7 @@ type Image struct {
|
||||
ContainerConfig Config `json:"container_config,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Config *Config `json:"config,omitempty"`
|
||||
graph *Graph
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,11 @@ lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0
|
||||
|
||||
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
|
||||
lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
|
||||
|
||||
{{if .Volumes}}
|
||||
{{range $virtualPath, $realPath := .GetVolumes}}
|
||||
lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
# drop linux capabilities (apply mainly to the user root in the container)
|
||||
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,27 @@
|
||||
lxc-docker (0.2.2-1) precise; urgency=low
|
||||
- Support for data volumes ('docker run -v=PATH')
|
||||
- Share data volumes between containers ('docker run -volumes-from')
|
||||
- Improved documentation
|
||||
- Upgrade to Go 1.0.3
|
||||
- Various upgrades to the dev environment for contributors
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Fri, 3 May 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.2.1-1) precise; urgency=low
|
||||
|
||||
- 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
|
||||
- Improve install process on Vagrant
|
||||
- New Dockerfile operation: "maintainer"
|
||||
- New Dockerfile operation: "expose"
|
||||
- New Dockerfile operation: "cmd"
|
||||
- Contrib script to build a Debian base layer
|
||||
- 'docker -d -r': restart crashed containers at daemon startup
|
||||
- Runtime: improve test coverage
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Wed, 1 May 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.2.0-1) precise; urgency=low
|
||||
|
||||
- Runtime: ghost containers can be killed and waited for
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
node default {
|
||||
exec {
|
||||
"apt_update" :
|
||||
command => "/usr/bin/apt-get update"
|
||||
}
|
||||
|
||||
Package {
|
||||
require => Exec['apt_update']
|
||||
}
|
||||
|
||||
group { "puppet":
|
||||
ensure => "present"
|
||||
}
|
||||
|
||||
include "docker"
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
class virtualbox {
|
||||
Package { ensure => "installed" }
|
||||
|
||||
# remove some files from the base vagrant image because they're old
|
||||
file { "/home/vagrant/docker-master":
|
||||
ensure => absent,
|
||||
recurse => true,
|
||||
force => true,
|
||||
purge => true,
|
||||
}
|
||||
file { "/usr/local/bin/dockerd":
|
||||
ensure => absent,
|
||||
}
|
||||
file { "/usr/local/bin/docker":
|
||||
ensure => absent,
|
||||
}
|
||||
|
||||
# Set up VirtualBox guest utils
|
||||
package { "virtualbox-guest-utils": }
|
||||
exec { "vbox-add" :
|
||||
command => "/etc/init.d/vboxadd setup",
|
||||
require => [
|
||||
Package["virtualbox-guest-utils"],
|
||||
Package["linux-headers-3.5.0-25-generic"], ],
|
||||
}
|
||||
}
|
||||
|
||||
class docker {
|
||||
# update this with latest go binary dist
|
||||
$go_url = "http://go.googlecode.com/files/go1.0.3.linux-amd64.tar.gz"
|
||||
|
||||
Package { ensure => "installed" }
|
||||
|
||||
package { ["lxc", "debootstrap", "wget", "bsdtar", "git",
|
||||
"linux-image-3.5.0-25-generic",
|
||||
"linux-image-extra-3.5.0-25-generic",
|
||||
"linux-headers-3.5.0-25-generic"]: }
|
||||
|
||||
$ec2_version = file("/etc/ec2_version", "/dev/null")
|
||||
$rax_version = inline_template("<%= %x{/usr/bin/xenstore-read vm-data/provider_data/provider} %>")
|
||||
|
||||
if ($ec2_version) {
|
||||
$vagrant_user = "ubuntu"
|
||||
$vagrant_home = "/home/ubuntu"
|
||||
} elsif ($rax_version) {
|
||||
$vagrant_user = "root"
|
||||
$vagrant_home = "/root"
|
||||
} else {
|
||||
# virtualbox is the vagrant default, so it should be safe to assume
|
||||
$vagrant_user = "vagrant"
|
||||
$vagrant_home = "/home/vagrant"
|
||||
include virtualbox
|
||||
}
|
||||
|
||||
exec { "fetch-go":
|
||||
require => Package["wget"],
|
||||
command => "/usr/bin/wget -O - $go_url | /bin/tar xz -C /usr/local",
|
||||
creates => "/usr/local/go/bin/go",
|
||||
}
|
||||
|
||||
file { "/etc/init/dockerd.conf":
|
||||
mode => 600,
|
||||
owner => "root",
|
||||
group => "root",
|
||||
content => template("docker/dockerd.conf"),
|
||||
}
|
||||
|
||||
file { "/opt/go":
|
||||
owner => $vagrant_user,
|
||||
group => $vagrant_user,
|
||||
recurse => true,
|
||||
}
|
||||
|
||||
file { "${vagrant_home}/.profile":
|
||||
mode => 644,
|
||||
owner => $vagrant_user,
|
||||
group => $vagrant_user,
|
||||
content => template("docker/profile"),
|
||||
}
|
||||
|
||||
exec { "build-docker" :
|
||||
cwd => "/opt/go/src/github.com/dotcloud/docker",
|
||||
user => $vagrant_user,
|
||||
environment => "GOPATH=/opt/go",
|
||||
command => "/usr/local/go/bin/go get -v ./... && /usr/local/go/bin/go install ./docker",
|
||||
creates => "/opt/go/bin/docker",
|
||||
logoutput => "on_failure",
|
||||
require => [ Exec["fetch-go"], File["/opt/go"] ],
|
||||
}
|
||||
|
||||
service { "dockerd" :
|
||||
ensure => "running",
|
||||
start => "/sbin/initctl start dockerd",
|
||||
stop => "/sbin/initctl stop dockerd",
|
||||
require => [ Exec["build-docker"], File["/etc/init/dockerd.conf"] ],
|
||||
name => "dockerd",
|
||||
provider => "base"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
description "Run dockerd"
|
||||
|
||||
stop on runlevel [!2345]
|
||||
start on runlevel [3]
|
||||
|
||||
# if you want it to automatically restart if it crashes, leave the next line in
|
||||
respawn
|
||||
|
||||
script
|
||||
test -f /etc/default/locale && . /etc/default/locale || true
|
||||
LANG=$LANG LC_ALL=$LANG /opt/go/bin/docker -d >> /var/log/dockerd 2>&1
|
||||
end script
|
||||
@@ -1,30 +0,0 @@
|
||||
# ~/.profile: executed by the command interpreter for login shells.
|
||||
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
|
||||
# exists.
|
||||
# see /usr/share/doc/bash/examples/startup-files for examples.
|
||||
# the files are located in the bash-doc package.
|
||||
|
||||
# the default umask is set in /etc/profile; for setting the umask
|
||||
# for ssh logins, install and configure the libpam-umask package.
|
||||
#umask 022
|
||||
|
||||
# if running bash
|
||||
if [ -n "$BASH_VERSION" ]; then
|
||||
# include .bashrc if it exists
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
. "$HOME/.bashrc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# set PATH so it includes user's private bin if it exists
|
||||
if [ -d "$HOME/bin" ] ; then
|
||||
PATH="$HOME/bin:$PATH"
|
||||
fi
|
||||
|
||||
export GOPATH=/opt/go
|
||||
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
|
||||
|
||||
docker=/opt/go/src/github.com/dotcloud/docker
|
||||
if [ -d $docker ]; then
|
||||
cd $docker
|
||||
fi
|
||||
155
runtime.go
155
runtime.go
@@ -31,6 +31,8 @@ type Runtime struct {
|
||||
idIndex *TruncIndex
|
||||
capabilities *Capabilities
|
||||
kernelVersion *KernelVersionInfo
|
||||
autoRestart bool
|
||||
volumes *Graph
|
||||
}
|
||||
|
||||
var sysInitPath string
|
||||
@@ -77,12 +79,58 @@ func (runtime *Runtime) containerRoot(id string) string {
|
||||
return path.Join(runtime.repository, id)
|
||||
}
|
||||
|
||||
func (runtime *Runtime) mergeConfig(userConf, imageConf *Config) {
|
||||
if userConf.Hostname != "" {
|
||||
userConf.Hostname = imageConf.Hostname
|
||||
}
|
||||
if userConf.User != "" {
|
||||
userConf.User = imageConf.User
|
||||
}
|
||||
if userConf.Memory == 0 {
|
||||
userConf.Memory = imageConf.Memory
|
||||
}
|
||||
if userConf.MemorySwap == 0 {
|
||||
userConf.MemorySwap = imageConf.MemorySwap
|
||||
}
|
||||
if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
|
||||
userConf.PortSpecs = imageConf.PortSpecs
|
||||
}
|
||||
if !userConf.Tty {
|
||||
userConf.Tty = userConf.Tty
|
||||
}
|
||||
if !userConf.OpenStdin {
|
||||
userConf.OpenStdin = imageConf.OpenStdin
|
||||
}
|
||||
if !userConf.StdinOnce {
|
||||
userConf.StdinOnce = imageConf.StdinOnce
|
||||
}
|
||||
if userConf.Env == nil || len(userConf.Env) == 0 {
|
||||
userConf.Env = imageConf.Env
|
||||
}
|
||||
if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
|
||||
userConf.Cmd = imageConf.Cmd
|
||||
}
|
||||
if userConf.Dns == nil || len(userConf.Dns) == 0 {
|
||||
userConf.Dns = imageConf.Dns
|
||||
}
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Create(config *Config) (*Container, error) {
|
||||
|
||||
// Lookup image
|
||||
img, err := runtime.repositories.LookupImage(config.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if img.Config != nil {
|
||||
runtime.mergeConfig(config, img.Config)
|
||||
}
|
||||
|
||||
if config.Cmd == nil {
|
||||
return nil, fmt.Errorf("No command specified")
|
||||
}
|
||||
|
||||
// Generate id
|
||||
id := GenerateId()
|
||||
// Generate default hostname
|
||||
@@ -103,6 +151,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
|
||||
// FIXME: do we need to store this in the container?
|
||||
SysInitPath: sysInitPath,
|
||||
}
|
||||
|
||||
container.root = runtime.containerRoot(container.Id)
|
||||
// Step 1: create the container directory.
|
||||
// This doubles as a barrier to avoid race conditions.
|
||||
@@ -167,23 +216,6 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||
// init the wait lock
|
||||
container.waitLock = make(chan struct{})
|
||||
|
||||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// if so, then we need to restart monitor and init a new lock
|
||||
// If the container is supposed to be running, make sure of it
|
||||
if container.State.Running {
|
||||
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if !strings.Contains(string(output), "RUNNING") {
|
||||
Debugf("Container %s was supposed to be running be is not.", container.Id)
|
||||
container.State.setStopped(-127)
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Even if not running, we init the lock (prevents races in start/stop/kill)
|
||||
container.State.initLock()
|
||||
|
||||
@@ -202,11 +234,43 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||
runtime.containers.PushBack(container)
|
||||
runtime.idIndex.Add(container.Id)
|
||||
|
||||
// When we actually restart, Start() do the monitoring.
|
||||
// However, when we simply 'reattach', we have to restart a monitor
|
||||
nomonitor := false
|
||||
|
||||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// if so, then we need to restart monitor and init a new lock
|
||||
// If the container is supposed to be running, make sure of it
|
||||
if container.State.Running {
|
||||
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if !strings.Contains(string(output), "RUNNING") {
|
||||
Debugf("Container %s was supposed to be running be is not.", container.Id)
|
||||
if runtime.autoRestart {
|
||||
Debugf("Restarting")
|
||||
container.State.Ghost = false
|
||||
container.State.setStopped(0)
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
nomonitor = true
|
||||
} else {
|
||||
Debugf("Marking as stopped")
|
||||
container.State.setStopped(-127)
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the container is not running or just has been flagged not running
|
||||
// then close the wait lock chan (will be reset upon start)
|
||||
if !container.State.Running {
|
||||
close(container.waitLock)
|
||||
} else {
|
||||
} else if !nomonitor {
|
||||
container.allocateNetwork()
|
||||
go container.monitor()
|
||||
}
|
||||
@@ -249,7 +313,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
|
||||
|
||||
// Commit creates a new filesystem image from the current state of a container.
|
||||
// The image can optionally be tagged into a repository
|
||||
func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Image, error) {
|
||||
func (runtime *Runtime) Commit(id, repository, tag, comment, author string, config *Config) (*Image, error) {
|
||||
container := runtime.Get(id)
|
||||
if container == nil {
|
||||
return nil, fmt.Errorf("No such container: %s", id)
|
||||
@@ -261,7 +325,7 @@ func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Im
|
||||
return nil, err
|
||||
}
|
||||
// Create a new image from the container's base layers + a new layer from container changes
|
||||
img, err := runtime.graph.Create(rwTar, container, comment, author)
|
||||
img, err := runtime.graph.Create(rwTar, container, comment, author, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -291,9 +355,30 @@ func (runtime *Runtime) restore() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) UpdateCapabilities(quiet bool) {
|
||||
if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil {
|
||||
if !quiet {
|
||||
log.Printf("WARNING: %s\n", err)
|
||||
}
|
||||
} else {
|
||||
_, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes"))
|
||||
_, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes"))
|
||||
runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil
|
||||
if !runtime.capabilities.MemoryLimit && !quiet {
|
||||
log.Printf("WARNING: Your kernel does not support cgroup memory limit.")
|
||||
}
|
||||
|
||||
_, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes"))
|
||||
runtime.capabilities.SwapLimit = err == nil
|
||||
if !runtime.capabilities.SwapLimit && !quiet {
|
||||
log.Printf("WARNING: Your kernel does not support cgroup swap limit.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: harmonize with NewGraph()
|
||||
func NewRuntime() (*Runtime, error) {
|
||||
runtime, err := NewRuntimeFromDirectory("/var/lib/docker")
|
||||
func NewRuntime(autoRestart bool) (*Runtime, error) {
|
||||
runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -306,27 +391,11 @@ func NewRuntime() (*Runtime, error) {
|
||||
log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
|
||||
}
|
||||
}
|
||||
|
||||
if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil {
|
||||
log.Printf("WARNING: %s\n", err)
|
||||
} else {
|
||||
_, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes"))
|
||||
_, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes"))
|
||||
runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil
|
||||
if !runtime.capabilities.MemoryLimit {
|
||||
log.Printf("WARNING: Your kernel does not support cgroup memory limit.")
|
||||
}
|
||||
|
||||
_, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes"))
|
||||
runtime.capabilities.SwapLimit = err == nil
|
||||
if !runtime.capabilities.SwapLimit {
|
||||
log.Printf("WARNING: Your kernel does not support cgroup swap limit.")
|
||||
}
|
||||
}
|
||||
runtime.UpdateCapabilities(false)
|
||||
return runtime, nil
|
||||
}
|
||||
|
||||
func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
||||
func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
|
||||
runtimeRepo := path.Join(root, "containers")
|
||||
|
||||
if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
|
||||
@@ -337,6 +406,10 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
volumes, err := NewGraph(path.Join(root, "volumes"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repositories, err := NewTagStore(path.Join(root, "repositories"), g)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
|
||||
@@ -363,6 +436,8 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
||||
authConfig: authConfig,
|
||||
idIndex: NewTruncIndex(),
|
||||
capabilities: &Capabilities{},
|
||||
autoRestart: autoRestart,
|
||||
volumes: volumes,
|
||||
}
|
||||
|
||||
if err := runtime.restore(); err != nil {
|
||||
|
||||
@@ -60,8 +60,10 @@ func init() {
|
||||
panic("docker tests needs to be run as root")
|
||||
}
|
||||
|
||||
NetworkBridgeIface = "testdockbr0"
|
||||
|
||||
// Make it our Store root
|
||||
runtime, err := NewRuntimeFromDirectory(unitTestStoreBase)
|
||||
runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -87,11 +89,11 @@ func newTestRuntime() (*Runtime, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime, err := NewRuntimeFromDirectory(root)
|
||||
runtime, err := NewRuntimeFromDirectory(root, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime.UpdateCapabilities(true)
|
||||
return runtime, nil
|
||||
}
|
||||
|
||||
@@ -273,7 +275,16 @@ func TestAllocatePortLocalhost(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer container.Kill()
|
||||
time.Sleep(600 * time.Millisecond) // Wait for the container to run
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||
for {
|
||||
if container.State.Running {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
|
||||
conn, err := net.Dial("tcp",
|
||||
fmt.Sprintf(
|
||||
"localhost:%s", container.NetworkSettings.PortMapping["5555"],
|
||||
@@ -293,6 +304,7 @@ func TestAllocatePortLocalhost(t *testing.T) {
|
||||
string(output),
|
||||
)
|
||||
}
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
func TestRestore(t *testing.T) {
|
||||
@@ -308,7 +320,7 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
runtime1, err := NewRuntimeFromDirectory(root)
|
||||
runtime1, err := NewRuntimeFromDirectory(root, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -367,7 +379,7 @@ func TestRestore(t *testing.T) {
|
||||
|
||||
// Here are are simulating a docker restart - that is, reloading all containers
|
||||
// from scratch
|
||||
runtime2, err := NewRuntimeFromDirectory(root)
|
||||
runtime2, err := NewRuntimeFromDirectory(root, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
17
utils.go
17
utils.go
@@ -12,7 +12,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -437,17 +436,23 @@ func CompareKernelVersion(a, b *KernelVersionInfo) int {
|
||||
}
|
||||
|
||||
func FindCgroupMountpoint(cgroupType string) (string, error) {
|
||||
output, err := exec.Command("mount").CombinedOutput()
|
||||
output, err := ioutil.ReadFile("/proc/mounts")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
reg := regexp.MustCompile(`^.* on (.*) type cgroup \(.*` + cgroupType + `[,\)]`)
|
||||
// /proc/mounts has 6 fields per line, one mount per line, e.g.
|
||||
// cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
|
||||
for _, line := range strings.Split(string(output), "\n") {
|
||||
r := reg.FindStringSubmatch(line)
|
||||
if len(r) == 2 {
|
||||
return r[1], nil
|
||||
parts := strings.Split(line, " ")
|
||||
if parts[2] == "cgroup" {
|
||||
for _, opt := range strings.Split(parts[3], ",") {
|
||||
if opt == cgroupType {
|
||||
return parts[1], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user