Compare commits

...

113 Commits

Author SHA1 Message Date
Guillaume J. Charmes e289308dff Bumped version to 0.3.2 2013-05-09 13:51:32 -07:00
Guillaume J. Charmes 8fb8a08ff2 Merge pull request #568 from dotcloud/improve_checksum-2
* Runtime: Store the actual archive on commit
* Registry: Improve the checksum process
- Registry: Fix error 400 on push
2013-05-09 13:25:15 -07:00
Guillaume J. Charmes 30f009150f Fix error code message upon error on push 2013-05-09 13:24:00 -07:00
Guillaume J. Charmes 0410397704 Update basics.rst 2013-05-09 13:05:18 -06:00
Guillaume J. Charmes 3cbf5670c5 Send the images in correct order 2013-05-09 11:12:37 -07:00
Guillaume J. Charmes 55cf05835b Remove the pushImageRec and use iteration instead 2013-05-08 23:29:02 -07:00
Guillaume J. Charmes c7a7983fcb Improve the checksum process 2013-05-08 22:45:40 -07:00
Guillaume J. Charmes 44b33b44aa Factorize the checksums functions 2013-05-08 22:37:33 -07:00
Guillaume J. Charmes 463658dc8f Refactor PushRepository to fix error 400 and to increase processing speed 2013-05-08 22:06:23 -07:00
Guillaume J. Charmes 80f4b0df75 * Registry: Use the size to have a good progress bar while pushing
* Registry: Use the actual archive if it exists in order to speed up the push
+ Registry: Remove the archive if it exists after the push
2013-05-08 19:36:12 -07:00
Guillaume J. Charmes aaaf3f0726 Store the actual archive when commit 2013-05-08 19:08:11 -07:00
Thatcher eed64e6777 Merge pull request #566 from dhrp/move-website-to-dir
Moved the static website html pages to their own folder under docs.
2013-05-08 17:13:45 -07:00
Thatcher Peskens 0f4469c2b1 Moved the static website html pages to their own folder under docs.
This should make previewing documentation easier.
Also updated the Makefile to now copy the theme dir into the _build/website/ dir. Make connect and Make push work.
2013-05-08 17:05:46 -07:00
Guillaume J. Charmes 06767fb99d Bumped version to 0.3.1 2013-05-08 16:52:47 -07:00
Guillaume J. Charmes 5098c4fc00 Display the go version inf CmdInfo in non-debug mode 2013-05-08 16:40:48 -07:00
Guillaume J. Charmes c255976909 Merge pull request #557 from dotcloud/improve_checksum
* Registry: Improve checksum
2013-05-08 16:30:42 -07:00
Guillaume J. Charmes 0e23b4e10e Store the checksums when pulling a repository 2013-05-08 16:27:35 -07:00
Guillaume J. Charmes d6c24092eb + Runtime: Add go version to debug infos 2013-05-08 15:35:35 -07:00
Guillaume J. Charmes 6cafed45af Better error output upon push failure 2013-05-08 14:19:38 -07:00
Guillaume J. Charmes 3484781a6f Merge pull request #562 from tianon/go1.1
- Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
2013-05-08 14:15:03 -07:00
Tianon Gravi c4ad6b077d Swap "go get" for "go get -d", especially to compile on go1.1rc; fixes #561 2013-05-08 14:51:50 -06:00
Ken Cochrane 45b5d3027e Merge pull request #559 from dhrp/docs-table-th-fix
- Documentation: CSS fix for docker documentation to make REST API docs look better.
2013-05-08 12:18:33 -07:00
Thatcher Peskens 070b1cd541 Added a line to css to make sure th woul align left. 2013-05-08 12:13:31 -07:00
Guillaume J. Charmes 8ff1765674 Make the checksum async within commit 2013-05-08 12:01:12 -07:00
Guillaume J. Charmes c4ebf870c8 Use make instead of new 2013-05-08 10:35:41 -07:00
Guillaume J. Charmes 7b1ec9ff30 Merge pull request #539 from justone/fix-byparent
- Images: fix ByParent function
2013-05-07 11:34:59 -07:00
Guillaume J. Charmes 244e6022ec Merge pull request #550 from unclejack/handle-empty-kernel-flavor-without-dash
* Runtime: kernel version - don't show the dash if flavor is empty
2013-05-07 11:24:25 -07:00
Guillaume J. Charmes f8dd04d567 Merge pull request #552 from dotcloud/548-no_command_panic-fix
- Builder: Check the command existance prior create and add Unit tests for the case
2013-05-07 11:19:34 -07:00
Guillaume J. Charmes 42b1ea4889 Check the command existance prior create and add Unit tests for the case 2013-05-07 11:18:13 -07:00
unclejack d2eb2455a1 kernel version - don't show the dash if flavor is empty 2013-05-07 20:57:21 +03:00
Guillaume J. Charmes a2b5196061 Merge pull request #543 from dotcloud/pull-official-tag-fix
- Registry: Fix pull for official images with specific tag
2013-05-07 10:56:37 -07:00
Ken Cochrane f46ab22b7a Merge pull request #544 from DanielVF/master
- Documentation: Fixed CouchDB example page header mistake
2013-05-07 10:51:35 -07:00
Ken Cochrane 074310063d Merge pull request #535 from dhrp/website_update
* Documentation: updated www.docker.io website.
2013-05-07 10:45:29 -07:00
Guillaume J. Charmes 01575e1f67 Merge pull request #542 from dotcloud/docker-search
+ Registry: Add docker search top level command in order to search a repository
2013-05-07 10:43:19 -07:00
shin- 82513815f1 Added actual doc file 2013-05-07 10:31:55 -07:00
shin- 4df26b9ee7 Added doc page for new search command 2013-05-07 10:29:49 -07:00
Ken Cochrane fc2df7e634 Merge pull request #546 from shamrin/patch-3
- Documentation: fixed README formatting
2013-05-07 10:01:16 -07:00
Alexey Shamrin c718eb282b README: fix Markdown formatting 2013-05-07 17:57:26 +04:00
Daniel Von Fange 0a13ce9bef CouchDB example page was titled redis 2013-05-07 08:33:29 -04:00
Joffrey F 0a197f9b4f Fixes bug when pulling an official image (no user namespace) with a specified tag 2013-05-07 04:54:58 -07:00
shin- 3d25e09c3b missing comma 2013-05-07 03:54:31 -07:00
shin- d56c5406ac Implemented command 2013-05-07 03:49:08 -07:00
Nate Jones 23c5c13014 fix ByParent 2013-05-06 21:31:59 -07:00
Guillaume J. Charmes 6ac33eb649 Merge pull request #523 from steakknife/522-docker-build
* Builder: use any whitespaces instead of tabs
2013-05-06 19:06:11 -07:00
Guillaume J. Charmes a02ad8c896 Merge pull request #537 from dotcloud/builder-env
+ Builder: Implement ENV within docker builder
2013-05-06 18:57:15 -07:00
Guillaume J. Charmes 4c7c177e4e Add the ENV instruciton to the docker builder documentation 2013-05-06 18:54:27 -07:00
Guillaume J. Charmes e45aef0c82 Implement ENV within docker builder 2013-05-06 18:39:56 -07:00
Guillaume J. Charmes 8472a27e80 Merge pull request #497 from justone/dot-graph-images
+ images: output graph of images to dot (graphviz)
2013-05-06 17:48:07 -07:00
Thatcher Peskens 0d929d13d3 Updated index to reflect new (short) 'engine' message (merged with solomon's new text), also moved around the layout a bit and fixed some small text errors. 2013-05-06 17:41:51 -07:00
Guillaume J. Charmes a0d80ed3e6 Merge pull request #534 from dotcloud/510-update-go
* Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
2013-05-06 17:27:02 -07:00
Guillaume J. Charmes e8853ec3a4 Merge pull request #532 from dotcloud/login_cookie_fix
- Registry: Fix issue when login in with a different user and trying to push
2013-05-06 17:23:57 -07:00
Guillaume J. Charmes 8ea9811089 Merge pull request #512 from dotcloud/builder-doc
+ Documentation: Add the documentation for docker builder
2013-05-06 17:22:35 -07:00
Guillaume J. Charmes f3f2cba386 Merge pull request #504 from dotcloud/builder-autorun
+ Builder: Implement the autorun capability within docker builder
2013-05-06 17:21:31 -07:00
Guillaume J. Charmes d581f0808c Merge pull request #511 from dotcloud/builder-cache
+ Builder: Add caching to docker builder
2013-05-06 17:20:22 -07:00
Guillaume J. Charmes ce4e87196f Merge pull request #472 from dotcloud/builder
+ Builder: Add support for docker builder with native API as top level command
2013-05-06 17:18:56 -07:00
Guillaume J. Charmes 7757be1f45 Rebase fix 2013-05-06 17:12:56 -07:00
Guillaume J. Charmes 49b05eb24a Update docker builder doc 2013-05-06 17:10:42 -07:00
Guillaume J. Charmes dae2828957 Moving runtime.Create to builder.Create 2013-05-06 17:09:58 -07:00
Guillaume J. Charmes 3439cd9cea Rebase fix 2013-05-06 17:07:56 -07:00
Guillaume J. Charmes 979db00d9a Fix typo in builder 2013-05-06 17:01:59 -07:00
Guillaume J. Charmes db4417b601 Implement the CMD instruction in order to allow autorun 2013-05-06 17:01:59 -07:00
Guillaume J. Charmes a64ebe5feb Allow to stack multiple EXPOSE instructions 2013-05-06 17:01:59 -07:00
Guillaume J. Charmes 602786cd60 Moving runtime.Create to builder.Create 2013-05-06 17:00:51 -07:00
Guillaume J. Charmes 35c59f4e05 Rebase fix 2013-05-06 16:58:09 -07:00
Guillaume J. Charmes 756df27e45 Add compatibility with contrib builder 2013-05-06 16:44:37 -07:00
Guillaume J. Charmes a46fc3a59e Implement caching for docker builder 2013-05-06 16:44:37 -07:00
Guillaume J. Charmes 9959e2cd63 Rebase master (autorun) 2013-05-06 16:44:37 -07:00
Guillaume J. Charmes f911ccc27b Moving runtime.Create to builder.Create 2013-05-06 16:44:37 -07:00
Guillaume J. Charmes 96069de4e0 Add build command 2013-05-06 16:44:37 -07:00
Guillaume J. Charmes d92166cc79 Fix merge issue 2013-05-06 16:43:50 -07:00
Guillaume J. Charmes ebb59c1125 Remove the open from CmdBuild 2013-05-06 16:42:54 -07:00
Guillaume J. Charmes e2880950c5 Add build command 2013-05-06 16:42:53 -07:00
Guillaume J. Charmes 62a1850c16 Make the autopull compatible with new registry 2013-05-06 16:40:45 -07:00
Guillaume J. Charmes 2bc4ad9402 Rebase fix 2013-05-06 16:01:01 -07:00
Guillaume J. Charmes ae1e655fb1 Implement EXPOSE to builder 2013-05-06 16:01:01 -07:00
Guillaume J. Charmes 92e98c66af Implement MAINTAINER to builder 2013-05-06 16:01:01 -07:00
Guillaume J. Charmes 6d6a03dfba More consistent docker build test 2013-05-06 16:01:01 -07:00
Guillaume J. Charmes 924b61328c Make the FROM instruction pull the image if not existing 2013-05-06 16:01:01 -07:00
Guillaume J. Charmes 4ebec08add Trim the splited builder lines 2013-05-06 16:01:01 -07:00
Guillaume J. Charmes 15ea5a479a Update the TestBuild with new format 2013-05-06 16:01:01 -07:00
Guillaume J. Charmes 6c168a8986 Rebase master (autorun) 2013-05-06 16:01:00 -07:00
Guillaume J. Charmes 4386edff0b Better varibale names 2013-05-06 16:01:00 -07:00
Guillaume J. Charmes 6bfb652f5b Change dockerbulder format, no more tabs and COPY becomes INSERT to avoid conflict with contrib script 2013-05-06 16:01:00 -07:00
Guillaume J. Charmes bbb634a980 Add doc for the builder 2013-05-06 16:01:00 -07:00
Guillaume J. Charmes 034c7a7a5e Remove the open from CmdBuild 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes 4390a3182f Fix image pipe with Builder COPY 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes e337949cb0 Add builder_test.go 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes dade95844f Make Builder.Build return the builded image 2013-05-06 16:00:30 -07:00
Nate Jones 74b9e851f6 use new image as base of next command 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes ff95f2b0ec Update the unit tests to reflect the new API 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes f7c5e92a2e Move runtime.Commit to builder.Commit 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes 6f2125386a Moving runtime.Create to builder.Create 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes 0aebb25410 Implement the COPY operator within the builder 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes 9db4972a70 Make sure the destination directory exists when using docker insert 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes 9751483112 Add insert command in order to insert external files within an image 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes 7bccdc0d33 Add a Builder.Commit method 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes 97215ca384 make builder.Run public it now runs only given arguments without sh -c 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes b8f66c0d14 Clear the containers/images upon failure 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes 27319da0d2 Add build command 2013-05-06 16:00:30 -07:00
Guillaume J. Charmes f20b5e1323 Fix issue when login in with a different user and trying to push 2013-05-06 15:58:04 -07:00
Daniel Mizyrycki 15b85d9d76 packaging ubuntu; issue #510: Use goland-stable PPA package to build docker 2013-05-06 15:56:50 -07:00
Solomon Hykes b38fc9fcdc Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-05-06 14:13:27 -07:00
Solomon Hykes 8646f7f11c + Website: new high-level overview 2013-05-06 14:11:38 -07:00
Guillaume J. Charmes 87cc8b6058 Update documentation, use docker-latest instead of docker-master 2013-05-06 13:26:23 -07:00
Guillaume J. Charmes 0fabd390a9 Update ubuntulinux.rst 2013-05-06 13:11:08 -07:00
Solomon Hykes 2ac7298e4e Bring back lego picture 2013-05-06 12:24:44 -07:00
Solomon Hykes 840bde4393 + Documentation: new introduction and high-level overview 2013-05-06 12:17:51 -07:00
Guillaume J. Charmes bbad653b1a Update ubuntulinux.rst 2013-05-06 12:00:39 -07:00
Barry Allard 424cc678eb closes #522 2013-05-04 21:20:41 -07:00
Solomon Hykes 1561232261 First draft of new README. Feedback and contributions welcome! 2013-05-04 19:47:57 -07:00
Nate Jones 359ecf88de add doc for images -viz 2013-05-03 21:12:43 -07:00
Nate Jones 3dba4022ad add tests for 'images' subcommand 2013-05-03 21:12:43 -07:00
Nate Jones f4de9d919d add image graph output (dot/graphviz) 2013-05-03 21:12:43 -07:00
47 changed files with 1824 additions and 666 deletions
+32
View File
@@ -1,5 +1,37 @@
# Changelog
## 0.3.2 (2013-05-09)
* Runtime: Store the actual archive on commit
* Registry: Improve the checksum process
* Registry: Use the size to have a good progress bar while pushing
* Registry: Use the actual archive if it exists in order to speed up the push
- Registry: Fix error 400 on push
## 0.3.1 (2013-05-08)
+ Builder: Implement the autorun capability within docker builder
+ Builder: Add caching to docker builder
+ Builder: Add support for docker builder with native API as top level command
+ Runtime: Add go version to debug infos
+ Builder: Implement ENV within docker builder
+ Registry: Add docker search top level command in order to search a repository
+ Images: output graph of images to dot (graphviz)
+ Documentation: new introduction and high-level overview
+ Documentation: Add the documentation for docker builder
+ Website: new high-level overview
- Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
- Images: fix ByParent function
- Builder: Check the command existance prior create and add Unit tests for the case
- Registry: Fix pull for official images with specific tag
- Registry: Fix issue when login in with a different user and trying to push
- Documentation: CSS fix for docker documentation to make REST API docs look better.
- Documentation: Fixed CouchDB example page header mistake
- Documentation: fixed README formatting
* Registry: Improve checksum - async calculation
* Runtime: kernel version - don't show the dash if flavor is empty
* Documentation: updated www.docker.io website.
* Builder: use any whitespaces instead of tabs
* Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
## 0.3.0 (2013-05-06)
+ Registry: Implement the new registry
+ Documentation: new example: sharing data between 2 couchdb databases
+1 -1
View File
@@ -39,7 +39,7 @@ $(DOCKER_BIN): $(DOCKER_DIR)
$(DOCKER_DIR):
@mkdir -p $(dir $@)
@if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@
@(cd $(DOCKER_MAIN); go get $(GO_OPTIONS))
@(cd $(DOCKER_MAIN); go get -d $(GO_OPTIONS))
whichrelease:
echo $(RELEASE_VERSION)
+74 -17
View File
@@ -1,37 +1,94 @@
Docker: the Linux container runtime
===================================
Docker: the Linux container engine
==================================
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers.
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
Docker containers are both *hardware-agnostic* and *platform-agnostic*. This means that they can run anywhere, from your
laptop to the largest EC2 compute instance and everything in between - and they don't require that you use a particular
language, framework or packaging system. That makes them great building blocks for deploying and scaling web apps, databases
and backend services without depending on a particular stack or provider.
Docker is an open-source implementation of the deployment engine which powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service.
It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
of applications and databases.
![Docker L](docs/sources/static_files/lego_docker.jpg "Docker")
* *Heterogeneous payloads*: any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
## Better than VMs
* *Any server*: docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
A common method for distributing applications and sandbox their execution is to use virtual machines, or VMs. Typical VM formats
are VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory these formats should allow every developer to
automatically package their application into a "machine" for easy distribution and deployment. In practice, that almost never
happens, for a few reasons:
* *Isolation*: docker isolates processes from each other and from the underlying host, using lightweight containers.
* *Size*: VMs are very large which makes them impractical to store and transfer.
* *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and
large-scale deployment of cpu and memory-intensive applications on large numbers of machines.
* *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead.
* *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most:
building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery.
* *Repeatability*: because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization,
containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary
for containerization, including Linux with [openvz](http://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net),
Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html).
Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves
all 4 problems. Docker containers are small (and their transfer can be optimized with layers), they have basically zero memory and cpu overhead,
the are completely portable and are designed from the ground up with an application-centric design.
The best part: because docker operates at the OS level, it can still be run inside a VM!
## Plays well with others
Docker does not require that you buy into a particular programming language, framework, packaging system or configuration language.
Is your application a unix process? Does it use files, tcp connections, environment variables, standard unix streams and command-line
arguments as inputs and outputs? Then docker can run it.
Can your application's build be expressed a sequence of such commands? Then docker can build it.
Notable features
-----------------
## Escape dependency hell
* Filesystem isolation: each process container runs in a completely separate root filesystem.
A common problem for developers is the difficulty of managing all their application's dependencies in a simple and automated way.
* Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.
This is usually difficult for several reasons:
* Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.
* *Cross-platform dependencies*. Modern applications often depend on a combination of system libraries and binaries, language-specific packages, framework-specific modules,
internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work
well with each other, requiring awkward custom integrations.
* Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.
* Conflicting dependencies. Different applications may depend on different versions of the same dependency. Packaging tools handle these situations with various degrees of ease -
but they all handle them in different and incompatible ways, which again forces the developer to do extra work.
* Custom dependencies. A developer may need to prepare a custom version of his application's dependency. Some packaging systems can handle custom versions of a dependency,
others can't - and all of them handle it differently.
* Logging: the standard streams (stdout/stderr/stdin) of each process container are collected and logged for real-time or batch retrieval.
* Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.
Docker solves dependency hell by giving the developer a simple way to express *all* his application's dependencies in one place,
and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
*replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers.
Docker defines a build as running a sequence unix commands, one after the other, in the same container. Build commands modify the contents of the container
(usually by installing new files on the filesystem), the next command modifies it some more, etc. Since each build command inherits the result of the previous
commands, the *order* in which the commands are executed expresses *dependencies*.
Here's a typical docker build process:
```bash
from ubuntu:12.10
run apt-get update
run apt-get install python
run apt-get install python-pip
run pip install django
run apt-get install curl
run curl http://github.com/shykes/helloflask/helloflask/master.tar.gz | tar -zxv
run cd master && pip install -r requirements.txt
```
Note that Docker doesn't care *how* dependencies are built - as long as they can be built by running a unix command in a container.
* Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.
Install instructions
==================
+463
View File
@@ -0,0 +1,463 @@
package docker
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"path"
"strings"
"time"
)
type Builder struct {
runtime *Runtime
repositories *TagStore
graph *Graph
}
func NewBuilder(runtime *Runtime) *Builder {
return &Builder{
runtime: runtime,
graph: runtime.graph,
repositories: runtime.repositories,
}
}
func (builder *Builder) 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 (builder *Builder) Create(config *Config) (*Container, error) {
// Lookup image
img, err := builder.repositories.LookupImage(config.Image)
if err != nil {
return nil, err
}
if img.Config != nil {
builder.mergeConfig(config, img.Config)
}
if config.Cmd == nil || len(config.Cmd) == 0 {
return nil, fmt.Errorf("No command specified")
}
// Generate id
id := GenerateId()
// Generate default hostname
// FIXME: the lxc template no longer needs to set a default hostname
if config.Hostname == "" {
config.Hostname = id[:12]
}
container := &Container{
// FIXME: we should generate the ID here instead of receiving it as an argument
Id: id,
Created: time.Now(),
Path: config.Cmd[0],
Args: config.Cmd[1:], //FIXME: de-duplicate from config
Config: config,
Image: img.Id, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
// FIXME: do we need to store this in the container?
SysInitPath: sysInitPath,
}
container.root = builder.runtime.containerRoot(container.Id)
// Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions.
if err := os.Mkdir(container.root, 0700); err != nil {
return nil, err
}
// If custom dns exists, then create a resolv.conf for the container
if len(config.Dns) > 0 {
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
f, err := os.Create(container.ResolvConfPath)
if err != nil {
return nil, err
}
defer f.Close()
for _, dns := range config.Dns {
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
return nil, err
}
}
} else {
container.ResolvConfPath = "/etc/resolv.conf"
}
// Step 2: save the container json
if err := container.ToDisk(); err != nil {
return nil, err
}
// Step 3: register the container
if err := builder.runtime.Register(container); err != nil {
return nil, err
}
return container, nil
}
// Commit creates a new filesystem image from the current state of a container.
// The image can optionally be tagged into a repository
func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) {
// FIXME: freeze the container before copying it to avoid data corruption?
// FIXME: this shouldn't be in commands.
rwTar, err := container.ExportRw()
if err != nil {
return nil, err
}
// Create a new image from the container's base layers + a new layer from container changes
img, err := builder.graph.Create(rwTar, container, comment, author, config)
if err != nil {
return nil, err
}
// Register the image if needed
if repository != "" {
if err := builder.repositories.Set(repository, tag, img.Id, true); err != nil {
return img, err
}
}
return img, nil
}
func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
for c := range containers {
tmp := builder.runtime.Get(c)
builder.runtime.Destroy(tmp)
Debugf("Removing container %s", c)
}
for i := range images {
builder.runtime.graph.Delete(i)
Debugf("Removing image %s", i)
}
}
func (builder *Builder) getCachedImage(image *Image, config *Config) (*Image, error) {
// Retrieve all images
images, err := builder.graph.All()
if err != nil {
return nil, err
}
// Store the tree in a map of map (map[parentId][childId])
imageMap := make(map[string]map[string]struct{})
for _, img := range images {
if _, exists := imageMap[img.Parent]; !exists {
imageMap[img.Parent] = make(map[string]struct{})
}
imageMap[img.Parent][img.Id] = struct{}{}
}
// Loop on the children of the given image and check the config
for elem := range imageMap[image.Id] {
img, err := builder.graph.Get(elem)
if err != nil {
return nil, err
}
if CompareConfig(&img.ContainerConfig, config) {
return img, nil
}
}
return nil, nil
}
func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, error) {
var (
image, base *Image
config *Config
maintainer string
env map[string]string = make(map[string]string)
tmpContainers map[string]struct{} = make(map[string]struct{})
tmpImages map[string]struct{} = make(map[string]struct{})
)
defer builder.clearTmp(tmpContainers, tmpImages)
file := bufio.NewReader(dockerfile)
for {
line, err := file.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
// Skip comments and empty line
if len(line) == 0 || line[0] == '#' {
continue
}
tmp := strings.SplitN(line, " ", 2)
if len(tmp) != 2 {
return nil, fmt.Errorf("Invalid Dockerfile format")
}
instruction := strings.Trim(tmp[0], " ")
arguments := strings.Trim(tmp[1], " ")
switch strings.ToLower(instruction) {
case "from":
fmt.Fprintf(stdout, "FROM %s\n", arguments)
image, err = builder.runtime.repositories.LookupImage(arguments)
if err != nil {
if builder.runtime.graph.IsNotExist(err) {
var tag, remote string
if strings.Contains(arguments, ":") {
remoteParts := strings.Split(arguments, ":")
tag = remoteParts[1]
remote = remoteParts[0]
} else {
remote = arguments
}
if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
return nil, err
}
image, err = builder.runtime.repositories.LookupImage(arguments)
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
config = &Config{}
break
case "maintainer":
fmt.Fprintf(stdout, "MAINTAINER %s\n", arguments)
maintainer = arguments
break
case "run":
fmt.Fprintf(stdout, "RUN %s\n", arguments)
if image == nil {
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, nil, builder.runtime.capabilities)
if err != nil {
return nil, err
}
for key, value := range env {
config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, value))
}
if cache, err := builder.getCachedImage(image, config); err != nil {
return nil, err
} else if cache != nil {
image = cache
fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
break
}
Debugf("Env -----> %v ------ %v\n", config.Env, env)
// Create the container and start it
c, err := builder.Create(config)
if err != nil {
return nil, err
}
if os.Getenv("DEBUG") != "" {
out, _ := c.StdoutPipe()
err2, _ := c.StderrPipe()
go io.Copy(os.Stdout, out)
go io.Copy(os.Stdout, err2)
}
if err := c.Start(); err != nil {
return nil, err
}
tmpContainers[c.Id] = struct{}{}
// Wait for it to finish
if result := c.Wait(); result != 0 {
return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
}
// Commit the container
base, err = builder.Commit(c, "", "", "", maintainer, nil)
if err != nil {
return nil, err
}
tmpImages[base.Id] = struct{}{}
fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
// use the base as the new image
image = base
break
case "env":
tmp := strings.SplitN(arguments, " ", 2)
if len(tmp) != 2 {
return nil, fmt.Errorf("Invalid ENV format")
}
key := strings.Trim(tmp[0], " ")
value := strings.Trim(tmp[1], " ")
fmt.Fprintf(stdout, "ENV %s %s\n", key, value)
env[key] = value
if image != nil {
fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
} else {
fmt.Fprintf(stdout, "===> <nil>\n")
}
break
case "cmd":
fmt.Fprintf(stdout, "CMD %s\n", arguments)
// Create the container and start it
c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
if err != nil {
return nil, err
}
if err := c.Start(); err != nil {
return nil, err
}
tmpContainers[c.Id] = struct{}{}
cmd := []string{}
if err := json.Unmarshal([]byte(arguments), &cmd); err != nil {
return nil, err
}
config.Cmd = cmd
// Commit the container
base, err = builder.Commit(c, "", "", "", maintainer, config)
if err != nil {
return nil, err
}
tmpImages[base.Id] = struct{}{}
fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
image = base
break
case "expose":
ports := strings.Split(arguments, " ")
fmt.Fprintf(stdout, "EXPOSE %v\n", ports)
if image == nil {
return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
}
// Create the container and start it
c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
if err != nil {
return nil, err
}
if err := c.Start(); err != nil {
return nil, err
}
tmpContainers[c.Id] = struct{}{}
config.PortSpecs = append(ports, config.PortSpecs...)
// Commit the container
base, err = builder.Commit(c, "", "", "", maintainer, config)
if err != nil {
return nil, err
}
tmpImages[base.Id] = struct{}{}
fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
image = base
break
case "insert":
if image == nil {
return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
}
tmp = strings.SplitN(arguments, " ", 2)
if len(tmp) != 2 {
return nil, fmt.Errorf("Invalid INSERT format")
}
sourceUrl := strings.Trim(tmp[0], " ")
destPath := strings.Trim(tmp[1], " ")
fmt.Fprintf(stdout, "COPY %s to %s in %s\n", sourceUrl, destPath, base.ShortId())
file, err := Download(sourceUrl, stdout)
if err != nil {
return nil, err
}
defer file.Body.Close()
config, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, nil, builder.runtime.capabilities)
if err != nil {
return nil, err
}
c, err := builder.Create(config)
if err != nil {
return nil, err
}
if err := c.Start(); err != nil {
return nil, err
}
// Wait for echo to finish
if result := c.Wait(); result != 0 {
return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
}
if err := c.Inject(file.Body, destPath); err != nil {
return nil, err
}
base, err = builder.Commit(c, "", "", "", maintainer, nil)
if err != nil {
return nil, err
}
fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
image = base
break
default:
fmt.Fprintf(stdout, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
}
}
if image != nil {
// The build is successful, keep the temporary containers and images
for i := range tmpImages {
delete(tmpImages, i)
}
for i := range tmpContainers {
delete(tmpContainers, i)
}
fmt.Fprintf(stdout, "Build finished. image id: %s\n", image.ShortId())
return image, nil
}
return nil, fmt.Errorf("An error occured during the build\n")
}
+88
View File
@@ -0,0 +1,88 @@
package docker
import (
"strings"
"testing"
)
const Dockerfile = `
# VERSION 0.1
# DOCKER-VERSION 0.2
from ` + unitTestImageName + `
run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd
insert https://raw.github.com/dotcloud/docker/master/CHANGELOG.md /tmp/CHANGELOG.md
`
func TestBuild(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
builder := NewBuilder(runtime)
img, err := builder.Build(strings.NewReader(Dockerfile), &nopWriter{})
if err != nil {
t.Fatal(err)
}
container, err := builder.Create(
&Config{
Image: img.Id,
Cmd: []string{"cat", "/tmp/passwd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err := container.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "root:testpass\n" {
t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
}
container2, err := builder.Create(
&Config{
Image: img.Id,
Cmd: []string{"ls", "-d", "/var/run/sshd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2)
output, err = container2.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "/var/run/sshd\n" {
t.Fatal("/var/run/sshd has not been created")
}
container3, err := builder.Create(
&Config{
Image: img.Id,
Cmd: []string{"cat", "/tmp/CHANGELOG.md"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container3)
output, err = container3.Output()
if err != nil {
t.Fatal(err)
}
if len(output) == 0 {
t.Fatal("/tmp/CHANGELOG.md has not been copied")
}
}
+215 -70
View File
@@ -6,10 +6,12 @@ import (
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/rcli"
"github.com/shin-/cookiejar"
"io"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strconv"
@@ -19,7 +21,7 @@ import (
"unicode"
)
const VERSION = "0.3.0"
const VERSION = "0.3.2"
var (
GIT_COMMIT string
@@ -34,6 +36,7 @@ func (srv *Server) Help() string {
help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
for _, cmd := range [][]string{
{"attach", "Attach to a running container"},
{"build", "Build a container from Dockerfile via stdin"},
{"commit", "Create a new image from a container's changes"},
{"diff", "Inspect changes on a container's filesystem"},
{"export", "Stream the contents of a container as a tar archive"},
@@ -41,6 +44,7 @@ func (srv *Server) Help() string {
{"images", "List images"},
{"import", "Create a new filesystem image from the contents of a tarball"},
{"info", "Display system-wide information"},
{"insert", "Insert a file in an image"},
{"inspect", "Return low-level information on a container"},
{"kill", "Kill a running container"},
{"login", "Register or Login to the docker registry server"},
@@ -53,6 +57,7 @@ func (srv *Server) Help() string {
{"rm", "Remove a container"},
{"rmi", "Remove an image"},
{"run", "Run a command in a new container"},
{"search", "Search for an image in the docker index"},
{"start", "Start a stopped container"},
{"stop", "Stop a running container"},
{"tag", "Tag an image into a repository"},
@@ -64,6 +69,67 @@ func (srv *Server) Help() string {
return help
}
func (srv *Server) CmdInsert(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
stdout.Flush()
cmd := rcli.Subcmd(stdout, "insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 3 {
cmd.Usage()
return nil
}
imageId := cmd.Arg(0)
url := cmd.Arg(1)
path := cmd.Arg(2)
img, err := srv.runtime.repositories.LookupImage(imageId)
if err != nil {
return err
}
file, err := Download(url, stdout)
if err != nil {
return err
}
defer file.Body.Close()
config, err := ParseRun([]string{img.Id, "echo", "insert", url, path}, nil, srv.runtime.capabilities)
if err != nil {
return err
}
b := NewBuilder(srv.runtime)
c, err := b.Create(config)
if err != nil {
return err
}
if err := c.Inject(ProgressReader(file.Body, int(file.ContentLength), stdout, "Downloading %v/%v (%v)"), path); err != nil {
return err
}
// FIXME: Handle custom repo, tag comment, author
img, err = b.Commit(c, "", "", img.Comment, img.Author, nil)
if err != nil {
return err
}
fmt.Fprintf(stdout, "%s\n", img.Id)
return nil
}
func (srv *Server) CmdBuild(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
stdout.Flush()
cmd := rcli.Subcmd(stdout, "build", "-", "Build a container from Dockerfile via stdin")
if err := cmd.Parse(args); err != nil {
return nil
}
img, err := NewBuilder(srv.runtime).Build(stdin, stdout)
if err != nil {
return err
}
fmt.Fprintf(stdout, "%s\n", img.ShortId())
return nil
}
// 'docker login': login / register a user to registry service.
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
// Read a line on raw terminal with support for simple backspace
@@ -154,6 +220,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ..
if err != nil {
fmt.Fprintf(stdout, "Error: %s\r\n", err)
} else {
srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar()
srv.runtime.authConfig = newAuthConfig
}
if status != "" {
@@ -217,10 +284,12 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string
len(srv.runtime.List()),
VERSION,
imgcount)
fmt.Fprintf(stdout, "Go version: %s\n", runtime.Version())
if !rcli.DEBUG_FLAG {
if os.Getenv("DEBUG") == "" {
return nil
}
fmt.Fprintln(stdout, "debug mode enabled")
fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine())
return nil
@@ -613,85 +682,126 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
//limit := cmd.Int("l", 0, "Only show the N most recent versions of each image")
quiet := cmd.Bool("q", false, "only show numeric IDs")
flAll := cmd.Bool("a", false, "show all images")
flViz := cmd.Bool("viz", false, "output graph in graphviz format")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() > 1 {
cmd.Usage()
return nil
}
var nameFilter string
if cmd.NArg() == 1 {
nameFilter = cmd.Arg(0)
}
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED")
}
var allImages map[string]*Image
var err error
if *flAll {
allImages, err = srv.runtime.graph.Map()
} else {
allImages, err = srv.runtime.graph.Heads()
}
if err != nil {
return err
}
for name, repository := range srv.runtime.repositories.Repositories {
if nameFilter != "" && name != nameFilter {
continue
if *flViz {
images, _ := srv.runtime.graph.All()
if images == nil {
return nil
}
for tag, id := range repository {
image, err := srv.runtime.graph.Get(id)
fmt.Fprintf(stdout, "digraph docker {\n")
var parentImage *Image
var err error
for _, image := range images {
parentImage, err = image.GetParent()
if err != nil {
log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
fmt.Errorf("Error while getting parent image: %v", err)
return nil
}
if parentImage != nil {
fmt.Fprintf(stdout, " \"%s\" -> \"%s\"\n", parentImage.ShortId(), image.ShortId())
} else {
fmt.Fprintf(stdout, " base -> \"%s\" [style=invis]\n", image.ShortId())
}
}
reporefs := make(map[string][]string)
for name, repository := range srv.runtime.repositories.Repositories {
for tag, id := range repository {
reporefs[TruncateId(id)] = append(reporefs[TruncateId(id)], fmt.Sprintf("%s:%s", name, tag))
}
}
for id, repos := range reporefs {
fmt.Fprintf(stdout, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", id, id, strings.Join(repos, "\\n"))
}
fmt.Fprintf(stdout, " base [style=invisible]\n")
fmt.Fprintf(stdout, "}\n")
} else {
if cmd.NArg() > 1 {
cmd.Usage()
return nil
}
var nameFilter string
if cmd.NArg() == 1 {
nameFilter = cmd.Arg(0)
}
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED")
}
var allImages map[string]*Image
var err error
if *flAll {
allImages, err = srv.runtime.graph.Map()
} else {
allImages, err = srv.runtime.graph.Heads()
}
if err != nil {
return err
}
for name, repository := range srv.runtime.repositories.Repositories {
if nameFilter != "" && name != nameFilter {
continue
}
delete(allImages, id)
if !*quiet {
for idx, field := range []string{
/* REPOSITORY */ name,
/* TAG */ tag,
/* ID */ TruncateId(id),
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
} {
if idx == 0 {
w.Write([]byte(field))
} else {
w.Write([]byte("\t" + field))
}
for tag, id := range repository {
image, err := srv.runtime.graph.Get(id)
if err != nil {
log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
continue
}
delete(allImages, id)
if !*quiet {
for idx, field := range []string{
/* REPOSITORY */ name,
/* TAG */ tag,
/* ID */ TruncateId(id),
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
} {
if idx == 0 {
w.Write([]byte(field))
} else {
w.Write([]byte("\t" + field))
}
}
w.Write([]byte{'\n'})
} else {
stdout.Write([]byte(image.ShortId() + "\n"))
}
w.Write([]byte{'\n'})
} else {
stdout.Write([]byte(image.ShortId() + "\n"))
}
}
}
// Display images which aren't part of a
if nameFilter == "" {
for id, image := range allImages {
if !*quiet {
for idx, field := range []string{
/* REPOSITORY */ "<none>",
/* TAG */ "<none>",
/* ID */ TruncateId(id),
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
} {
if idx == 0 {
w.Write([]byte(field))
} else {
w.Write([]byte("\t" + field))
// Display images which aren't part of a
if nameFilter == "" {
for id, image := range allImages {
if !*quiet {
for idx, field := range []string{
/* REPOSITORY */ "<none>",
/* TAG */ "<none>",
/* ID */ TruncateId(id),
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
} {
if idx == 0 {
w.Write([]byte(field))
} else {
w.Write([]byte("\t" + field))
}
}
w.Write([]byte{'\n'})
} else {
stdout.Write([]byte(image.ShortId() + "\n"))
}
w.Write([]byte{'\n'})
} else {
stdout.Write([]byte(image.ShortId() + "\n"))
}
}
}
if !*quiet {
w.Flush()
if !*quiet {
w.Flush()
}
}
return nil
}
@@ -776,7 +886,12 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
}
}
img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, config)
container := srv.runtime.Get(containerName)
if container == nil {
return fmt.Errorf("No such container: %s", containerName)
}
img, err := NewBuilder(srv.runtime).Commit(container, repository, tag, *flComment, *flAuthor, config)
if err != nil {
return err
}
@@ -890,6 +1005,34 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args .
return <-container.Attach(stdin, nil, stdout, stdout)
}
func (srv *Server) CmdSearch(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
cmd := rcli.Subcmd(stdout, "search", "NAME", "Search the docker index for images")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 1 {
cmd.Usage()
return nil
}
term := cmd.Arg(0)
results, err := srv.runtime.graph.SearchRepositories(stdout, term)
if err != nil {
return err
}
fmt.Fprintf(stdout, "Found %d results matching your query (\"%s\")\n", results.NumResults, results.Query)
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\n")
for _, repo := range results.Results {
description := repo["description"]
if len(description) > 45 {
description = Trunc(description, 42) + "..."
}
fmt.Fprintf(w, "%s\t%s\n", repo["name"], description)
}
w.Flush()
return nil
}
// Ports type - Used to parse multiple -p flags
type ports []int
@@ -994,8 +1137,10 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
// or tell the client there is no options
stdout.Flush()
b := NewBuilder(srv.runtime)
// Create new container
container, err := srv.runtime.Create(config)
container, err := b.Create(config)
if err != nil {
// If container not found, try to pull it
if srv.runtime.graph.IsNotExist(err) {
@@ -1003,7 +1148,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
if err = srv.CmdPull(stdin, stdout, config.Image); err != nil {
return err
}
if container, err = srv.runtime.Create(config); err != nil {
if container, err = b.Create(config); err != nil {
return err
}
} else {
+72 -1
View File
@@ -73,6 +73,77 @@ func cmdWait(srv *Server, container *Container) error {
return closeWrap(stdout, stdoutPipe)
}
func cmdImages(srv *Server, args ...string) (string, error) {
stdout, stdoutPipe := io.Pipe()
go func() {
if err := srv.CmdImages(nil, stdoutPipe, args...); err != nil {
return
}
// force the pipe closed, so that the code below gets an EOF
stdoutPipe.Close()
}()
output, err := ioutil.ReadAll(stdout)
if err != nil {
return "", err
}
// Cleanup pipes
return string(output), closeWrap(stdout, stdoutPipe)
}
// TestImages checks that 'docker images' displays information correctly
func TestImages(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
output, err := cmdImages(srv)
if !strings.Contains(output, "REPOSITORY") {
t.Fatal("'images' should have a header")
}
if !strings.Contains(output, "docker-ut") {
t.Fatal("'images' should show the docker-ut image")
}
if !strings.Contains(output, "e9aa60c60128") {
t.Fatal("'images' should show the docker-ut image id")
}
output, err = cmdImages(srv, "-q")
if strings.Contains(output, "REPOSITORY") {
t.Fatal("'images -q' should not have a header")
}
if strings.Contains(output, "docker-ut") {
t.Fatal("'images' should not show the docker-ut image name")
}
if !strings.Contains(output, "e9aa60c60128") {
t.Fatal("'images' should show the docker-ut image id")
}
output, err = cmdImages(srv, "-viz")
if !strings.HasPrefix(output, "digraph docker {") {
t.Fatal("'images -v' should start with the dot header")
}
if !strings.HasSuffix(output, "}\n") {
t.Fatal("'images -v' should end with a '}'")
}
if !strings.Contains(output, "base -> \"e9aa60c60128\" [style=invis]") {
t.Fatal("'images -v' should have the docker-ut image id node")
}
// todo: add checks for -a
}
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
func TestRunHostname(t *testing.T) {
runtime, err := newTestRuntime()
@@ -339,7 +410,7 @@ func TestAttachDisconnect(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Memory: 33554432,
+17
View File
@@ -178,6 +178,23 @@ func (settings *NetworkSettings) PortMappingHuman() string {
return strings.Join(mapping, ", ")
}
// Inject the io.Reader at the given path. Note: do not close the reader
func (container *Container) Inject(file io.Reader, pth string) error {
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
return err
}
// FIXME: Handle permissions/already existing dest
dest, err := os.Create(path.Join(container.rwPath(), pth))
if err != nil {
return err
}
if _, err := io.Copy(dest, file); err != nil {
return err
}
return nil
}
func (container *Container) Cmd() *exec.Cmd {
return container.cmd
}
+43 -31
View File
@@ -20,7 +20,7 @@ func TestIdFormat(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container1, err := runtime.Create(
container1, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
@@ -44,7 +44,7 @@ func TestMultipleAttachRestart(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/sh", "-c",
@@ -148,8 +148,10 @@ func TestDiff(t *testing.T) {
}
defer nuke(runtime)
builder := NewBuilder(runtime)
// Create a container and remove a file
container1, err := runtime.Create(
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/rm", "/etc/passwd"},
@@ -190,7 +192,7 @@ func TestDiff(t *testing.T) {
}
// Create a new container from the commited image
container2, err := runtime.Create(
container2, err := builder.Create(
&Config{
Image: img.Id,
Cmd: []string{"cat", "/etc/passwd"},
@@ -223,7 +225,9 @@ func TestCommitAutoRun(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container1, err := runtime.Create(
builder := NewBuilder(runtime)
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
@@ -254,8 +258,7 @@ func TestCommitAutoRun(t *testing.T) {
}
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
container2, err := runtime.Create(
container2, err := builder.Create(
&Config{
Image: img.Id,
},
@@ -301,7 +304,10 @@ func TestCommitRun(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container1, err := runtime.Create(
builder := NewBuilder(runtime)
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
@@ -333,7 +339,7 @@ func TestCommitRun(t *testing.T) {
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
container2, err := runtime.Create(
container2, err := builder.Create(
&Config{
Image: img.Id,
Cmd: []string{"cat", "/world"},
@@ -380,7 +386,7 @@ func TestStart(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Memory: 33554432,
@@ -419,7 +425,7 @@ func TestRun(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"ls", "-al"},
@@ -447,7 +453,7 @@ func TestOutput(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"echo", "-n", "foobar"},
@@ -472,7 +478,7 @@ func TestKillDifferentUser(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"tail", "-f", "/etc/resolv.conf"},
User: "daemon",
@@ -520,7 +526,7 @@ func TestKill(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"cat", "/dev/zero"},
},
@@ -566,7 +572,9 @@ func TestExitCode(t *testing.T) {
}
defer nuke(runtime)
trueContainer, err := runtime.Create(&Config{
builder := NewBuilder(runtime)
trueContainer, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/true", ""},
})
@@ -581,7 +589,7 @@ func TestExitCode(t *testing.T) {
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
}
falseContainer, err := runtime.Create(&Config{
falseContainer, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/false", ""},
})
@@ -603,7 +611,7 @@ func TestRestart(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"echo", "-n", "foobar"},
},
@@ -636,7 +644,7 @@ func TestRestartStdin(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"cat"},
@@ -715,8 +723,10 @@ func TestUser(t *testing.T) {
}
defer nuke(runtime)
builder := NewBuilder(runtime)
// Default user must be root
container, err := runtime.Create(&Config{
container, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"id"},
},
@@ -734,7 +744,7 @@ func TestUser(t *testing.T) {
}
// Set a username
container, err = runtime.Create(&Config{
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"id"},
@@ -754,7 +764,7 @@ func TestUser(t *testing.T) {
}
// Set a UID
container, err = runtime.Create(&Config{
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"id"},
@@ -774,7 +784,7 @@ func TestUser(t *testing.T) {
}
// Set a different user by uid
container, err = runtime.Create(&Config{
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"id"},
@@ -796,7 +806,7 @@ func TestUser(t *testing.T) {
}
// Set a different user by username
container, err = runtime.Create(&Config{
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"id"},
@@ -823,7 +833,9 @@ func TestMultipleContainers(t *testing.T) {
}
defer nuke(runtime)
container1, err := runtime.Create(&Config{
builder := NewBuilder(runtime)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"cat", "/dev/zero"},
},
@@ -833,7 +845,7 @@ func TestMultipleContainers(t *testing.T) {
}
defer runtime.Destroy(container1)
container2, err := runtime.Create(&Config{
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"cat", "/dev/zero"},
},
@@ -879,7 +891,7 @@ func TestStdin(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"cat"},
@@ -926,7 +938,7 @@ func TestTty(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"cat"},
@@ -973,7 +985,7 @@ func TestEnv(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/usr/bin/env"},
},
@@ -1047,7 +1059,7 @@ func TestLXCConfig(t *testing.T) {
memMin := 33554432
memMax := 536870912
mem := memMin + rand.Intn(memMax-memMin)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/true"},
@@ -1074,7 +1086,7 @@ func BenchmarkRunSequencial(b *testing.B) {
}
defer nuke(runtime)
for i := 0; i < b.N; i++ {
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"echo", "-n", "foo"},
},
@@ -1109,7 +1121,7 @@ func BenchmarkRunParallel(b *testing.B) {
complete := make(chan error)
tasks = append(tasks, complete)
go func(i int, complete chan error) {
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"echo", "-n", "foo"},
},
+1 -1
View File
@@ -89,7 +89,7 @@ def main():
# Skip comments and empty lines
if line == "" or line[0] == "#":
continue
op, param = line.split(" ", 1)
op, param = line.split(None, 1)
print op.upper() + " " + param
if op == "from":
base = param
+2 -2
View File
@@ -36,9 +36,9 @@ else
fi
echo "Downloading docker binary and uncompressing into /usr/local/bin..."
curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz |
curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz |
tar -C /usr/local/bin --strip-components=1 -zxf- \
docker-master/docker
docker-latest/docker
if [ -f /etc/init/dockerd.conf ]
then
+12 -11
View File
@@ -46,23 +46,24 @@ clean:
docs:
-rm -rf $(BUILDDIR)/*
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html
cp sources/index.html $(BUILDDIR)/html/
cp -r sources/gettingstarted $(BUILDDIR)/html/
cp sources/dotcloud.yml $(BUILDDIR)/html/
cp sources/CNAME $(BUILDDIR)/html/
cp sources/.nojekyll $(BUILDDIR)/html/
cp sources/nginx.conf $(BUILDDIR)/html/
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
site:
cp -r website $(BUILDDIR)/
cp -r theme/docker/static/ $(BUILDDIR)/website/
@echo
@echo "The Website pages are in $(BUILDDIR)/site."
connect:
@echo pushing changes to staging site
@cd _build/html/ ; \
@dotcloud list ; \
@echo connecting dotcloud to www.docker.io website, make sure to use user 1
@cd _build/website/ ; \
dotcloud list ; \
dotcloud connect dockerwebsite
push:
@cd _build/html/ ; \
@cd _build/website/ ; \
dotcloud push
github-deploy: docs
+1
View File
@@ -15,6 +15,7 @@ Installation
* Work in your own fork of the code, we accept pull requests.
* Install sphinx: ``pip install sphinx``
* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain``
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
Usage
View File
-1
View File
@@ -1 +0,0 @@
docker.io
+130
View File
@@ -0,0 +1,130 @@
==============
Docker Builder
==============
.. contents:: Table of Contents
1. Format
=========
The Docker builder format is quite simple:
``instruction arguments``
The first instruction must be `FROM`
All instruction are to be placed in a file named `Dockerfile`
In order to place comments within a Dockerfile, simply prefix the line with "`#`"
2. Instructions
===============
Docker builder comes with a set of instructions:
1. FROM: Set from what image to build
2. RUN: Execute a command
3. INSERT: Insert a remote file (http) into the image
2.1 FROM
--------
``FROM <image>``
The `FROM` instruction must be the first one in order for Builder to know from where to run commands.
`FROM` can also be used in order to build multiple images within a single Dockerfile
2.2 MAINTAINER
--------------
``MAINTAINER <name>``
The `MAINTAINER` instruction allow you to set the Author field of the generated images.
This instruction is never automatically reset.
2.3 RUN
-------
``RUN <command>``
The `RUN` instruction is the main one, it allows you to execute any commands on the `FROM` image and to save the results.
You can use as many `RUN` as you want within a Dockerfile, the commands will be executed on the result of the previous command.
2.4 CMD
-------
``CMD <command>``
The `CMD` instruction sets the command to be executed when running the image.
It is equivalent to do `docker commit -run '{"Cmd": <command>}'` outside the builder.
.. note::
Do not confuse `RUN` with `CMD`. `RUN` actually run a command and save the result, `CMD` does not execute anything.
2.5 EXPOSE
----------
``EXPOSE <port> [<port>...]``
The `EXPOSE` instruction sets ports to be publicly exposed when running the image.
This is equivalent to do `docker commit -run '{"PortSpecs": ["<port>", "<port2>"]}'` outside the builder.
2.6 ENV
-------
``ENV <key> <value>``
The `ENV` instruction set as environment variable `<key>` with the value `<value>`. This value will be passed to all future ``RUN`` instructions.
.. note::
The environment variables are local to the Dockerfile, they will not be set as autorun.
2.7 INSERT
----------
``INSERT <file url> <path>``
The `INSERT` instruction will download the file at the given url and place it within the image at the given path.
.. note::
The path must include the file name.
3. Dockerfile Examples
======================
::
# Nginx
#
# VERSION 0.0.1
# DOCKER-VERSION 0.2
from ubuntu
maintainer Guillaume J. Charmes "guillaume@dotcloud.com"
# make sure the package repository is up to date
run echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
run apt-get update
run apt-get install -y inotify-tools nginx apache2 openssh-server
insert https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper
::
# Firefox over VNC
#
# VERSION 0.3
# DOCKER-VERSION 0.2
from ubuntu
# make sure the package repository is up to date
run echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
run apt-get update
# Install vnc, xvfb in order to create a 'fake' display and firefox
run apt-get install -y x11vnc xvfb firefox
run mkdir /.vnc
# Setup a password
run x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way to do it, but it does the trick)
run bash -c 'echo "firefox" >> /.bashrc'
expose 5900
cmd ["x11vnc", "-forever", "-usepw", "-create"]
+14
View File
@@ -0,0 +1,14 @@
:title: docker documentation
:description: Documentation for docker builder
:keywords: docker, builder, dockerfile
Builder
=======
Contents:
.. toctree::
:maxdepth: 2
basics
+2
View File
@@ -27,6 +27,7 @@ Available Commands
:maxdepth: 1
command/attach
command/build
command/commit
command/diff
command/export
@@ -46,6 +47,7 @@ Available Commands
command/rm
command/rmi
command/run
command/search
command/start
command/stop
command/tag
@@ -0,0 +1,9 @@
========================================================
``build`` -- Build a container from Dockerfile via stdin
========================================================
::
Usage: docker build -
Example: cat Dockerfile | docker build -
Build a new image from the Dockerfile passed via stdin
@@ -10,3 +10,13 @@
-a=false: show all images
-q=false: only show numeric IDs
-viz=false: output in graphviz format
Displaying images visually
--------------------------
::
docker images -viz | dot -Tpng -o docker.png
.. image:: images/docker_images.gif
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@@ -0,0 +1,10 @@
===================================================================
``search`` -- Search for an image in the docker index
===================================================================
::
Usage: docker search TERM
Searches for the TERM parameter on the Docker index and prints out a list of repositories
that match.
+1 -121
View File
@@ -5,124 +5,4 @@
:note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated
Introduction
============
Docker - The Linux container runtime
------------------------------------
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
What is a Standard Container?
-----------------------------
Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
Standard operations
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
Content-agnostic
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
Infrastructure-agnostic
~~~~~~~~~~~~~~~~~~~~~~~~~~
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
Designed for automation
~~~~~~~~~~~~~~~~~~~~~~~~~~
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
Industrial-grade delivery
~~~~~~~~~~~~~~~~~~~~~~~~~~
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
Standard Container Specification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(TODO)
Image format
~~~~~~~~~~~~
Standard operations
~~~~~~~~~~~~~~~~~~~
- Copy
- Run
- Stop
- Wait
- Commit
- Attach standard streams
- List filesystem changes
- ...
Execution environment
~~~~~~~~~~~~~~~~~~~~~
Root filesystem
^^^^^^^^^^^^^^^
Environment variables
^^^^^^^^^^^^^^^^^^^^^
Process arguments
^^^^^^^^^^^^^^^^^
Networking
^^^^^^^^^^
Process namespacing
^^^^^^^^^^^^^^^^^^^
Resource limits
^^^^^^^^^^^^^^^
Process monitoring
^^^^^^^^^^^^^^^^^^
Logging
^^^^^^^
Signals
^^^^^^^
Pseudo-terminal allocation
^^^^^^^^^^^^^^^^^^^^^^^^^^
Security
^^^^^^^^
This document has been moved to :ref:`introduction`, please update your bookmarks.
+1 -1
View File
@@ -2,7 +2,7 @@
:description: An introduction to docker and standard containers?
:keywords: containers, lxc, concepts, explanation
.. _introduction:
Introduction
============
@@ -2,9 +2,9 @@
:description: Sharing data between 2 couchdb databases
:keywords: docker, example, package installation, networking, couchdb, data volumes
.. _running_redis_service:
.. _running_couchdb_service:
Create a redis service
Create a CouchDB service
======================
.. include:: example_header.inc
+2 -1
View File
@@ -17,7 +17,8 @@ This documentation has the following resources:
commandline/index
registry/index
index/index
builder/index
faq
.. image:: http://www.docker.io/_static/lego_docker.jpg
.. image:: http://www.docker.io/_static/lego_docker.jpg
+4 -4
View File
@@ -26,9 +26,9 @@ Install the docker binary:
::
wget http://get.docker.io/builds/Linux/x86_64/docker-master.tgz
tar -xf docker-master.tgz
sudo cp ./docker-master /usr/local/bin
wget http://get.docker.io/builds/Linux/x86_64/docker-latest.tgz
tar -xf docker-latest.tgz
sudo cp ./docker-latest/docker /usr/local/bin
Note: docker currently only supports 64-bit Linux hosts.
@@ -50,4 +50,4 @@ Run your first container!
Continue with the :ref:`hello_world` example.
Continue with the :ref:`hello_world` example.
+2 -2
View File
@@ -18,7 +18,7 @@ The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in orde
.. code-block:: bash
sudo apt-get install linux-image-extra-`uname -r`
sudo apt-get install linux-image-extra-`uname -r` lxc bsdtar
Installation
@@ -48,7 +48,7 @@ Now install it, you will see another warning that the package cannot be authenti
.. code-block:: bash
sudo apt-get install lxc-docker
curl get.docker.io | sudo sh -x
Verify it worked
+3 -3
View File
@@ -11,7 +11,7 @@ Get the latest docker binary:
::
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz
@@ -19,7 +19,7 @@ Unpack it to your current dir
::
tar -xf docker-master.tgz
tar -xf docker-latest.tgz
Stop your current daemon. How you stop your daemon depends on how you started it.
@@ -38,4 +38,4 @@ Now start the daemon
sudo ./docker -d &
Alternatively you can replace the docker binary in ``/usr/local/bin``
Alternatively you can replace the docker binary in ``/usr/local/bin``
+2 -1
View File
@@ -6,6 +6,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
<title>Docker - {{ meta['title'] if meta and meta['title'] else title }}</title>
@@ -74,7 +75,7 @@
</div>
<div style="margin-left: -12px; float: left;">
<a href="{{ pathto('./', 1) }}"><img style="margin-top: 12px; height: 38px" src="{{ pathto('_static/img/docker-letters-logo.gif', 1) }}"></a>
<a href="http://www.docker.io"><img style="margin-top: 12px; height: 38px" src="{{ pathto('_static/img/docker-letters-logo.gif', 1) }}"></a>
</div>
</div>
+5 -1
View File
@@ -82,7 +82,7 @@ h4 {
.btn-custom {
background-color: #292929 !important;
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
background-image: -moz-linear-gradient(top, #515151, #282828);
background-image: -ms-linear-gradient(top, #515151, #282828);
@@ -330,3 +330,7 @@ section.header {
@media (max-width: 480px) {
}
/* Misc fixes */
table th {
text-align: left;
}
+5
View File
@@ -449,4 +449,9 @@ section.header {
@media (max-width: 480px) {
}
/* Misc fixes */
table th {
text-align: left;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -13,15 +13,15 @@
<meta name="viewport" content="width=device-width">
<!-- twitter bootstrap -->
<link rel="stylesheet" href="../_static/css/bootstrap.min.css">
<link rel="stylesheet" href="../_static/css/bootstrap-responsive.min.css">
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
<link rel="stylesheet" href="../static/css/bootstrap-responsive.min.css">
<!-- main style file -->
<link rel="stylesheet" href="../_static/css/main.css">
<link rel="stylesheet" href="../static/css/main.css">
<!-- vendor scripts -->
<script src="../_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
<script src="../_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
<script src="../static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
<script src="../static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
</head>
@@ -35,8 +35,8 @@
<div style="float: right" class="pull-right">
<ul class="nav">
<li><a href="../">Introduction</a></li>
<li class="active"><a href="./">Getting started</a></li>
<li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
<li class="active"><a href="">Getting started</a></li>
<li class=""><a href="http://docs.docker.io/en/latest/concepts/introduction/">Documentation</a></li>
</ul>
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
@@ -46,7 +46,7 @@
</div>
<div style="margin-left: -12px; float: left;">
<a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../_static/img/docker-letters-logo.gif"></a>
<a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../static/img/docker-letters-logo.gif"></a>
</div>
</div>
</div>
@@ -186,7 +186,7 @@
<!-- bootstrap javascipts -->
<script src="../_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
<script src="../static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
<!-- Google analytics -->
<script type="text/javascript">
@@ -7,21 +7,45 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Docker - the Linux container runtime</title>
<meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
<title>Docker - the Linux container engine</title>
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
<meta name="viewport" content="width=device-width">
<!-- twitter bootstrap -->
<link rel="stylesheet" href="_static/css/bootstrap.min.css">
<link rel="stylesheet" href="_static/css/bootstrap-responsive.min.css">
<link rel="stylesheet" href="static/css/bootstrap.min.css">
<link rel="stylesheet" href="static/css/bootstrap-responsive.min.css">
<!-- main style file -->
<link rel="stylesheet" href="_static/css/main.css">
<link rel="stylesheet" href="static/css/main.css">
<!-- vendor scripts -->
<script src="_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
<script src="_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
<script src="static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
<script src="static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
<style>
.indexlabel {
float: left;
width: 150px;
display: block;
padding: 10px 20px 10px;
font-size: 20px;
font-weight: 200;
background-color: #a30000;
color: white;
height: 22px;
}
.searchbutton {
font-size: 20px;
height: 40px;
}
.debug {
border: 1px red dotted;
}
</style>
</head>
@@ -34,9 +58,9 @@
<div class="pull-right" >
<ul class="nav">
<li class="active"><a href="./">Introduction</a></li>
<li ><a href="gettingstarted/">Getting started</a></li>
<li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
<li class="active"><a href="../sources">Introduction</a></li>
<li ><a href="gettingstarted">Getting started</a></li>
<li class=""><a href="http://docs.docker.io/en/latest/concepts/introduction/">Documentation</a></li>
</ul>
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
@@ -49,49 +73,40 @@
</div>
<div class="container">
<div class="row">
<div class="text-center">
<img src="_static/img/docker-letters-logo.gif">
</div>
</div>
</div>
<div class="container">
<div class="container" style="margin-top: 30px;">
<div class="row">
<div class="span12">
<section class="contentblock header">
<div class="span6" style="margin:10px 0px 0px 30px; float: right; ">
<div class="span5" style="margin-bottom: 15px;">
<div style="text-align: center;" >
<img src="static/img/docker_letters_500px.png">
<h2>The Linux container engine</h2>
</div>
<div style="display: block; text-align: center; margin-top: 20px;">
<h5>
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider.
</h5>
</div>
<div style="display: block; text-align: center; margin-top: 30px;">
<a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
</div>
</div>
<div class="span6" >
<div class="js-video" >
<iframe width="640" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
<iframe width="600" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<div style="text-align: center; padding: 50px 30px 50px 30px;">
<h1>Docker</h1>
<h2>The Linux container runtime</h2>
</div>
<div style="display: block; text-align: center; padding: 10px 30px 50px 30px;">
<p>
Docker complements LXC with a high-level API which operates at the process level.
It runs unix processes with strong guarantees of isolation and repeatability across servers.
</p>
<p>
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
</p>
</div>
<div style="display: block; text-align: center;">
<a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
</div>
<br style="clear: both"/>
</section>
</div>
@@ -101,31 +116,56 @@
<div class="container">
<div class="row">
<div class="span3">
<div class="span6">
<section class="contentblock">
<h4>Heterogeneous payloads</h4>
<p>Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.</p>
</section>
</div>
<div class="span3">
<section class="contentblock">
<h4>Any server</h4>
<p>Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.</p>
</section>
</div>
<div class="span3">
<section class="contentblock">
<h4>Isolation</h4>
<p>docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
<p>Docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
<h4>Repeatability</h4>
<p>Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
</section>
</div>
<div class="span3">
<div class="span6">
<section class="contentblock">
<h4>Repeatability</h4>
<p>Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
<h1>New! Docker Index</h1>
On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them.
<br><br>
<a href="https://index.docker.io" target="_blank">
<div class="indexlabel">
DOCKER index
</div>
</a>
&nbsp;
<input type="button" class="searchbutton" type="submit" value="Search images"
onClick="window.open('https://index.docker.io')" />
</section>
<section class="contentblock">
<div id="wufoo-z7x3p3">
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
</div>
<script type="text/javascript">var z7x3p3;(function(d, t) {
var s = d.createElement(t), options = {
'userName':'dotclouddocker',
'formHash':'z7x3p3',
'autoResize':true,
'height':'577',
'async':true,
'header':'show'};
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
s.onload = s.onreadystatechange = function() {
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
})(document, 'script');</script>
</section>
</div>
</div>
</div>
<style>
@@ -173,18 +213,12 @@
</div>
</div>
<!-- <p>Docker encapsulates heterogeneous payloads in <a href="#container">Standard Containers</a>, and runs them on any server with strong guarantees of isolation and repeatability.</p>
<p>It is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.</p>
-->
<div class="container">
<div class="row">
<div class="span6">
<section class="contentblock">
<!-- <img src="_static/lego_docker.jpg" width="600px" style="float:right; margin-left: 10px"> -->
<h2>Notable features</h2>
<ul>
@@ -208,35 +242,26 @@
<li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
</ul>
<h2>Who started it</h2>
<p>
Docker is an open-source implementation of the deployment engine which powers <a href="http://dotcloud.com">dotCloud</a>, a popular Platform-as-a-Service.</p>
<p>It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
of applications and databases.
</p>
</section>
</div>
<div class="span6">
<section class="contentblock">
<div id="wufoo-z7x3p3">
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
</div>
<script type="text/javascript">var z7x3p3;(function(d, t) {
var s = d.createElement(t), options = {
'userName':'dotclouddocker',
'formHash':'z7x3p3',
'autoResize':true,
'height':'577',
'async':true,
'header':'show'};
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
s.onload = s.onreadystatechange = function() {
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
})(document, 'script');</script>
</section>
<section class="contentblock">
<h3 id="twitter">Twitter</h3>
<a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
</section>
</div>
</div>
@@ -265,7 +290,7 @@
<!-- bootstrap javascipts -->
<script src="_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
<script src="static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
<!-- Google analytics -->
<script type="text/javascript">
+1
View File
@@ -0,0 +1 @@
../theme/docker/static
+51 -14
View File
@@ -1,6 +1,7 @@
package docker
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
@@ -9,14 +10,18 @@ import (
"path"
"path/filepath"
"strings"
"sync"
"time"
)
// A Graph is a store for versioned filesystem images and the relationship between them.
type Graph struct {
Root string
idIndex *TruncIndex
httpClient *http.Client
Root string
idIndex *TruncIndex
httpClient *http.Client
checksumLock map[string]*sync.Mutex
lockSumFile *sync.Mutex
lockSumMap *sync.Mutex
}
// NewGraph instantiates a new graph at the given root path in the filesystem.
@@ -27,12 +32,15 @@ func NewGraph(root string) (*Graph, error) {
return nil, err
}
// Create the root directory if it doesn't exists
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
graph := &Graph{
Root: abspath,
idIndex: NewTruncIndex(),
Root: abspath,
idIndex: NewTruncIndex(),
checksumLock: make(map[string]*sync.Mutex),
lockSumFile: &sync.Mutex{},
lockSumMap: &sync.Mutex{},
}
if err := graph.restore(); err != nil {
return nil, err
@@ -82,6 +90,11 @@ func (graph *Graph) Get(name string) (*Image, error) {
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.Id)
}
img.graph = graph
graph.lockSumMap.Lock()
defer graph.lockSumMap.Unlock()
if _, exists := graph.checksumLock[img.Id]; !exists {
graph.checksumLock[img.Id] = &sync.Mutex{}
}
return img, nil
}
@@ -100,16 +113,16 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
img.Container = container.Id
img.ContainerConfig = *container.Config
}
if err := graph.Register(layerData, img); err != nil {
if err := graph.Register(layerData, true, img); err != nil {
return nil, err
}
img.Checksum()
go img.Checksum()
return img, nil
}
// Register imports a pre-existing image into the graph.
// FIXME: pass img as first argument
func (graph *Graph) Register(layerData Archive, img *Image) error {
func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
if err := ValidateId(img.Id); err != nil {
return err
}
@@ -122,7 +135,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
if err != nil {
return fmt.Errorf("Mktemp failed: %s", err)
}
if err := StoreImage(img, layerData, tmp); err != nil {
if err := StoreImage(img, layerData, tmp, store); err != nil {
return err
}
// Commit
@@ -131,6 +144,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
}
img.graph = graph
graph.idIndex.Add(img.Id)
graph.checksumLock[img.Id] = &sync.Mutex{}
return nil
}
@@ -253,14 +267,14 @@ func (graph *Graph) WalkAll(handler func(*Image)) error {
func (graph *Graph) ByParent() (map[string][]*Image, error) {
byParent := make(map[string][]*Image)
err := graph.WalkAll(func(image *Image) {
image, err := graph.Get(image.Parent)
parent, err := graph.Get(image.Parent)
if err != nil {
return
}
if children, exists := byParent[image.Parent]; exists {
byParent[image.Parent] = []*Image{image}
if children, exists := byParent[parent.Id]; exists {
byParent[parent.Id] = []*Image{image}
} else {
byParent[image.Parent] = append(children, image)
byParent[parent.Id] = append(children, image)
}
})
return byParent, err
@@ -287,3 +301,26 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
func (graph *Graph) imageRoot(id string) string {
return path.Join(graph.Root, id)
}
func (graph *Graph) getStoredChecksums() (map[string]string, error) {
checksums := make(map[string]string)
// FIXME: Store the checksum in memory
if checksumDict, err := ioutil.ReadFile(path.Join(graph.Root, "checksums")); err == nil {
if err := json.Unmarshal(checksumDict, &checksums); err != nil {
return nil, err
}
}
return checksums, nil
}
func (graph *Graph) storeChecksums(checksums map[string]string) error {
checksumJson, err := json.Marshal(checksums)
if err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJson, 0600); err != nil {
return err
}
return nil
}
+68 -27
View File
@@ -35,8 +35,9 @@ func LoadImage(root string) (*Image, error) {
if err != nil {
return nil, err
}
var img Image
if err := json.Unmarshal(jsonData, &img); err != nil {
img := &Image{}
if err := json.Unmarshal(jsonData, img); err != nil {
return nil, err
}
if err := ValidateId(img.Id); err != nil {
@@ -52,11 +53,10 @@ func LoadImage(root string) (*Image, error) {
} else if !stat.IsDir() {
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root))
}
return &img, nil
return img, nil
}
func StoreImage(img *Image, layerData Archive, root string) error {
func StoreImage(img *Image, layerData Archive, root string, store bool) error {
// Check that root doesn't already exist
if _, err := os.Stat(root); err == nil {
return fmt.Errorf("Image %s already exists", img.Id)
@@ -68,6 +68,28 @@ func StoreImage(img *Image, layerData Archive, root string) error {
if err := os.MkdirAll(layer, 0700); err != nil {
return err
}
if store {
layerArchive := layerArchivePath(root)
file, err := os.OpenFile(layerArchive, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
// FIXME: Retrieve the image layer size from here?
if _, err := io.Copy(file, layerData); err != nil {
return err
}
// FIXME: Don't close/open, read/write instead of Copy
file.Close()
file, err = os.Open(layerArchive)
if err != nil {
return err
}
defer file.Close()
layerData = file
}
if err := Untar(layerData, layer); err != nil {
return err
}
@@ -86,6 +108,10 @@ func layerPath(root string) string {
return path.Join(root, "layer")
}
func layerArchivePath(root string) string {
return path.Join(root, "layer.tar.xz")
}
func jsonPath(root string) string {
return path.Join(root, "json")
}
@@ -261,21 +287,20 @@ func (img *Image) layer() (string, error) {
}
func (img *Image) Checksum() (string, error) {
img.graph.checksumLock[img.Id].Lock()
defer img.graph.checksumLock[img.Id].Unlock()
root, err := img.root()
if err != nil {
return "", err
}
checksumDictPth := path.Join(root, "..", "..", "checksums")
checksums := new(map[string]string)
if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil {
if err := json.Unmarshal(checksumDict, checksums); err != nil {
return "", err
}
if checksum, ok := (*checksums)[img.Id]; ok {
return checksum, nil
}
checksums, err := img.graph.getStoredChecksums()
if err != nil {
return "", err
}
if checksum, ok := checksums[img.Id]; ok {
return checksum, nil
}
layer, err := img.layer()
@@ -287,9 +312,20 @@ func (img *Image) Checksum() (string, error) {
return "", err
}
layerData, err := Tar(layer, Xz)
if err != nil {
return "", err
var layerData io.Reader
if file, err := os.Open(layerArchivePath(root)); err != nil {
if os.IsNotExist(err) {
layerData, err = Tar(layer, Xz)
if err != nil {
return "", err
}
} else {
return "", err
}
} else {
defer file.Close()
layerData = file
}
h := sha256.New()
@@ -299,22 +335,27 @@ func (img *Image) Checksum() (string, error) {
if _, err := h.Write([]byte("\n")); err != nil {
return "", err
}
if _, err := io.Copy(h, layerData); err != nil {
return "", err
}
hash := "sha256:" + hex.EncodeToString(h.Sum(nil))
if *checksums == nil {
*checksums = map[string]string{}
}
(*checksums)[img.Id] = hash
checksumJson, err := json.Marshal(checksums)
// Reload the json file to make sure not to overwrite faster sums
img.graph.lockSumFile.Lock()
defer img.graph.lockSumFile.Unlock()
checksums, err = img.graph.getStoredChecksums()
if err != nil {
return "", err
}
checksums[img.Id] = hash
// Dump the checksums to disc
if err := img.graph.storeChecksums(checksums); err != nil {
return hash, err
}
if err := ioutil.WriteFile(checksumDictPth, checksumJson, 0600); err != nil {
return hash, err
}
return hash, nil
}
+7 -4
View File
@@ -1,12 +1,15 @@
BUILDBOT_IP = '192.168.33.32'
GOPHERS_KEY = "308C15A29AD198E9"
Vagrant::Config.run do |config|
config.vm.box = 'precise64'
config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
config.vm.share_folder 'v-data', '/data/docker', "#{File.dirname(__FILE__)}/../.."
config.vm.network :hostonly,BUILDBOT_IP
# Add docker PPA key to the local repository and install docker
pkg_cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys #{GOPHERS_KEY}; " \
"echo 'deb http://ppa.launchpad.net/gophers/go/ubuntu precise main' >/etc/apt/sources.list.d/gophers-go.list; " \
# Install ubuntu packaging dependencies and create ubuntu packages
config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y git debhelper autotools-dev devscripts golang'
config.vm.provision :shell, :inline => "export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/ubuntu; make ubuntu"
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y git debhelper autotools-dev devscripts golang-stable; " \
"export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/ubuntu; make ubuntu"
config.vm.provision :shell, :inline => pkg_cmd
end
+50 -12
View File
@@ -1,21 +1,59 @@
lxc-docker (0.3.2-1) precise; urgency=low
- Runtime: Store the actual archive on commit
- Registry: Improve the checksum process
- Registry: Use the size to have a good progress bar while pushing
- Registry: Use the actual archive if it exists in order to speed up the push
- Registry: Fix error 400 on push
-- dotCloud <ops@dotcloud.com> Fri, 9 May 2013 00:00:00 -0700
lxc-docker (0.3.1-1) precise; urgency=low
- Builder: Implement the autorun capability within docker builder
- Builder: Add caching to docker builder
- Builder: Add support for docker builder with native API as top level command
- Runtime: Add go version to debug infos
- Builder: Implement ENV within docker builder
- Registry: Add docker search top level command in order to search a repository
- Images: output graph of images to dot (graphviz)
- Documentation: new introduction and high-level overview
- Documentation: Add the documentation for docker builder
- Website: new high-level overview
- Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
- Images: fix ByParent function
- Builder: Check the command existance prior create and add Unit tests for the case
- Registry: Fix pull for official images with specific tag
- Registry: Fix issue when login in with a different user and trying to push
- Documentation: CSS fix for docker documentation to make REST API docs look better.
- Documentation: Fixed CouchDB example page header mistake
- Documentation: fixed README formatting
- Registry: Improve checksum - async calculation
- Runtime: kernel version - don't show the dash if flavor is empty
- Documentation: updated www.docker.io website.
- Builder: use any whitespaces instead of tabs
- Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
-- dotCloud <ops@dotcloud.com> Fri, 8 May 2013 00:00:00 -0700
lxc-docker (0.3.0-1) precise; urgency=low
- Registry: Implement the new registry
- Documentation: new example: sharing data between 2 couchdb databases
- Runtime: Fix the command existance check
- Runtime: strings.Split may return an empty string on no match
- Runtime: Fix an index out of range crash if cgroup memory is not
- Documentation: Various improvments
- Vagrant: Use only one deb line in /etc/apt
- Registry: Implement the new registry
- Documentation: new example: sharing data between 2 couchdb databases
- Runtime: Fix the command existance check
- Runtime: strings.Split may return an empty string on no match
- Runtime: Fix an index out of range crash if cgroup memory is not
- Documentation: Various improvments
- Vagrant: Use only one deb line in /etc/apt
-- dotCloud <ops@dotcloud.com> Fri, 5 May 2013 00:00:00 -0700
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
- 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
+1 -1
View File
@@ -2,7 +2,7 @@ Source: lxc-docker
Section: misc
Priority: extra
Maintainer: Daniel Mizyrycki <daniel@dotcloud.com>
Build-Depends: debhelper,autotools-dev,devscripts,golang
Build-Depends: debhelper,autotools-dev,devscripts,golang-stable
Standards-Version: 3.9.3
Homepage: http://github.com/dotcloud/docker
+208 -100
View File
@@ -9,6 +9,8 @@ import (
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"strings"
)
@@ -69,7 +71,7 @@ func (graph *Graph) getRemoteHistory(imgId, registry string, token []string) ([]
func (graph *Graph) getHttpClient() *http.Client {
if graph.httpClient == nil {
graph.httpClient = new(http.Client)
graph.httpClient = &http.Client{}
graph.httpClient.Jar = cookiejar.NewCookieJar()
}
return graph.httpClient
@@ -193,24 +195,27 @@ func (graph *Graph) getRemoteTags(stdout io.Writer, registries []string, reposit
return nil, fmt.Errorf("Repository not found")
}
result := new(map[string]string)
result := make(map[string]string)
rawJson, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if err = json.Unmarshal(rawJson, result); err != nil {
if err = json.Unmarshal(rawJson, &result); err != nil {
return nil, err
}
return *result, nil
return result, nil
}
return nil, fmt.Errorf("Could not reach any registry endpoint")
}
func (graph *Graph) getImageForTag(stdout io.Writer, tag, remote, registry string, token []string) (string, error) {
client := graph.getHttpClient()
if !strings.Contains(remote, "/") {
remote = "library/" + remote
}
registryEndpoint := "https://" + registry + "/v1"
repositoryTarget := registryEndpoint + "/repositories/" + remote + "/tags/" + tag
@@ -255,7 +260,7 @@ func (graph *Graph) PullImage(stdout io.Writer, imgId, registry string, token []
// FIXME: Keep goging in case of error?
return err
}
if err = graph.Register(layer, img); err != nil {
if err = graph.Register(layer, false, img); err != nil {
return err
}
}
@@ -302,6 +307,47 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re
return fmt.Errorf("Index response didn't contain any endpoints")
}
checksumsJson, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
// Reload the json file to make sure not to overwrite faster sums
err = func() error {
localChecksums := make(map[string]string)
remoteChecksums := []ImgListJson{}
checksumDictPth := path.Join(graph.Root, "..", "checksums")
if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil {
return err
}
graph.lockSumFile.Lock()
defer graph.lockSumFile.Unlock()
if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil {
if err := json.Unmarshal(checksumDict, &localChecksums); err != nil {
return err
}
}
for _, elem := range remoteChecksums {
localChecksums[elem.Id] = elem.Checksum
}
checksumsJson, err = json.Marshal(localChecksums)
if err != nil {
return err
}
if err := ioutil.WriteFile(checksumDictPth, checksumsJson, 0600); err != nil {
return err
}
return nil
}()
if err != nil {
return err
}
var tagsList map[string]string
if askedTag == "" {
tagsList, err = graph.getRemoteTags(stdout, endpoints, remote, token)
@@ -347,14 +393,10 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re
return nil
}
func pushImageRec(graph *Graph, stdout io.Writer, img *Image, registry string, token []string) error {
if parent, err := img.GetParent(); err != nil {
return err
} else if parent != nil {
if err := pushImageRec(graph, stdout, parent, registry, token); err != nil {
return err
}
}
// Push a local image to the registry
func (graph *Graph) PushImage(stdout io.Writer, img *Image, registry string, token []string) error {
registry = "https://" + registry + "/v1"
client := graph.getHttpClient()
jsonRaw, err := ioutil.ReadFile(path.Join(graph.Root, img.Id, "json"))
if err != nil {
@@ -377,6 +419,7 @@ func pushImageRec(graph *Graph, stdout io.Writer, img *Image, registry string, t
return fmt.Errorf("Error while retrieving checksum for %s: %v", img.Id, err)
}
req.Header.Set("X-Docker-Checksum", checksum)
Debugf("Setting checksum for %s: %s", img.ShortId(), checksum)
res, err := doWithCookies(client, req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
@@ -402,14 +445,35 @@ func pushImageRec(graph *Graph, stdout io.Writer, img *Image, registry string, t
}
fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id)
layerData, err := graph.TempLayerArchive(img.Id, Xz, stdout)
root, err := img.root()
if err != nil {
return fmt.Errorf("Failed to generate layer archive: %s", err)
return err
}
var layerData *TempArchive
// If the archive exists, use it
file, err := os.Open(layerArchivePath(root))
if err != nil {
if os.IsNotExist(err) {
// If the archive does not exist, create one from the layer
layerData, err = graph.TempLayerArchive(img.Id, Xz, stdout)
if err != nil {
return fmt.Errorf("Failed to generate layer archive: %s", err)
}
} else {
return err
}
} else {
defer file.Close()
st, err := file.Stat()
if err != nil {
return err
}
layerData = &TempArchive{file, st.Size()}
}
req3, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/layer",
ProgressReader(layerData, -1, stdout, ""))
ProgressReader(layerData, int(layerData.Size), stdout, ""))
if err != nil {
return err
}
@@ -421,19 +485,19 @@ func pushImageRec(graph *Graph, stdout io.Writer, img *Image, registry string, t
if err != nil {
return fmt.Errorf("Failed to upload layer: %s", err)
}
res3.Body.Close()
defer res3.Body.Close()
if res3.StatusCode != 200 {
return fmt.Errorf("Received HTTP code %d while uploading layer", res3.StatusCode)
errBody, err := ioutil.ReadAll(res3.Body)
if err != nil {
return fmt.Errorf("HTTP code %d while uploading metadata and error when"+
" trying to parse response body: %v", res.StatusCode, err)
}
return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res3.StatusCode, errBody)
}
return nil
}
// Push a local image to the registry with its history if needed
func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, registry string, token []string) error {
registry = "https://" + registry + "/v1"
return pushImageRec(graph, stdout, imgOrig, registry, token)
}
// push a tag on the registry.
// Remote has the format '<user>/<repo>
func (graph *Graph) pushTag(remote, revision, tag, registry string, token []string) error {
@@ -483,48 +547,89 @@ func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId, registry
return nil
}
// Retrieve the checksum of an image
// Priority:
// - Check on the stored checksums
// - Check if the archive exists, if it does not, ask the registry
// - If the archive does exists, process the checksum from it
// - If the archive does not exists and not found on registry, process checksum from layer
func (graph *Graph) getChecksum(imageId string) (string, error) {
// FIXME: Use in-memory map instead of reading the file each time
if sums, err := graph.getStoredChecksums(); err != nil {
return "", err
} else if checksum, exists := sums[imageId]; exists {
return checksum, nil
}
img, err := graph.Get(imageId)
if err != nil {
return "", err
}
if _, err := os.Stat(layerArchivePath(graph.imageRoot(imageId))); err != nil {
if os.IsNotExist(err) {
// TODO: Ask the registry for the checksum
// As the archive is not there, it is supposed to come from a pull.
} else {
return "", err
}
}
checksum, err := img.Checksum()
if err != nil {
return "", err
}
return checksum, nil
}
type ImgListJson struct {
Id string `json:"id"`
Checksum string `json:"checksum,omitempty"`
tag string
}
// Push a repository to the registry.
// Remote has the format '<user>/<repo>
func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Repository, authConfig *auth.AuthConfig) error {
client := graph.getHttpClient()
// FIXME: Do not reset the cookie each time? (need to reset it in case updating latest of a repo and repushing)
client.Jar = cookiejar.NewCookieJar()
var imgList []*ImgListJson
checksums, err := graph.Checksums(stdout, localRepo)
if err != nil {
return err
}
fmt.Fprintf(stdout, "Processing checksums\n")
imageSet := make(map[string]struct{})
imgList := make([]map[string]string, len(checksums))
checksums2 := make([]map[string]string, len(checksums))
uploadedImages, err := graph.getImagesInRepository(remote, authConfig)
if err != nil {
return fmt.Errorf("Error occured while fetching the list: %s", err)
}
// Filter list to only send images/checksums not already uploaded
i := 0
for _, obj := range checksums {
found := false
for _, uploadedImg := range uploadedImages {
if obj["id"] == uploadedImg["id"] && uploadedImg["checksum"] != "" {
found = true
break
for tag, id := range localRepo {
img, err := graph.Get(id)
if err != nil {
return err
}
img.WalkHistory(func(img *Image) error {
if _, exists := imageSet[img.Id]; exists {
return nil
}
}
if !found {
imgList[i] = map[string]string{"id": obj["id"]}
checksums2[i] = obj
i += 1
}
imageSet[img.Id] = struct{}{}
checksum, err := graph.getChecksum(img.Id)
if err != nil {
return err
}
imgList = append([]*ImgListJson{{
Id: img.Id,
Checksum: checksum,
tag: tag,
}}, imgList...)
return nil
})
}
checksums = checksums2[:i]
imgList = imgList[:i]
imgListJson, err := json.Marshal(imgList)
if err != nil {
return err
}
Debugf("json sent: %s\n", imgListJson)
fmt.Fprintf(stdout, "Sending image list\n")
req, err := http.NewRequest("PUT", INDEX_ENDPOINT+"/repositories/"+remote+"/", bytes.NewReader(imgListJson))
if err != nil {
return err
@@ -532,11 +637,13 @@ func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Re
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.ContentLength = int64(len(imgListJson))
req.Header.Set("X-Docker-Token", "true")
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
for res.StatusCode >= 300 && res.StatusCode < 400 {
Debugf("Redirected to %s\n", res.Header.Get("Location"))
req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJson))
@@ -546,6 +653,7 @@ func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Re
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.ContentLength = int64(len(imgListJson))
req.Header.Set("X-Docker-Token", "true")
res, err = client.Do(req)
if err != nil {
return err
@@ -554,7 +662,11 @@ func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Re
}
if res.StatusCode != 200 && res.StatusCode != 201 {
return fmt.Errorf("Error: Status %d trying to push repository %s", res.StatusCode, remote)
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
return fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody)
}
var token, endpoints []string
@@ -570,71 +682,67 @@ func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Re
return fmt.Errorf("Index response didn't contain any endpoints")
}
// FIXME: Send only needed images
for _, registry := range endpoints {
fmt.Fprintf(stdout, "Pushing repository %s to %s (%d tags)\r\n", remote, registry,
len(localRepo))
fmt.Fprintf(stdout, "Pushing repository %s to %s (%d tags)\r\n", remote, registry, len(localRepo))
// For each image within the repo, push them
for tag, imgId := range localRepo {
if err := graph.pushPrimitive(stdout, remote, tag, imgId, registry, token); err != nil {
for _, elem := range imgList {
if err := graph.pushPrimitive(stdout, remote, elem.tag, elem.Id, registry, token); err != nil {
// FIXME: Continue on error?
return err
}
}
}
checksumsJson, err := json.Marshal(checksums)
if err != nil {
return err
}
req2, err := http.NewRequest("PUT", INDEX_ENDPOINT+"/repositories/"+remote+"/images", bytes.NewReader(checksumsJson))
req2, err := http.NewRequest("PUT", INDEX_ENDPOINT+"/repositories/"+remote+"/images", bytes.NewReader(imgListJson))
if err != nil {
return err
}
req2.SetBasicAuth(authConfig.Username, authConfig.Password)
req2.Header["X-Docker-Endpoints"] = endpoints
req2.ContentLength = int64(len(checksumsJson))
req2.ContentLength = int64(len(imgListJson))
res2, err := client.Do(req2)
if err != nil {
return err
}
res2.Body.Close()
defer res2.Body.Close()
if res2.StatusCode != 204 {
return fmt.Errorf("Error: Status %d trying to push checksums %s", res.StatusCode, remote)
if errBody, err := ioutil.ReadAll(res2.Body); err != nil {
return err
} else {
return fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res2.StatusCode, remote, errBody)
}
}
return nil
}
func (graph *Graph) Checksums(output io.Writer, repo Repository) ([]map[string]string, error) {
var result []map[string]string
checksums := map[string]string{}
for _, id := range repo {
img, err := graph.Get(id)
if err != nil {
return nil, err
}
err = img.WalkHistory(func(image *Image) error {
fmt.Fprintf(output, "Computing checksum for image %s\n", image.Id)
if _, exists := checksums[image.Id]; !exists {
checksums[image.Id], err = image.Checksum()
if err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
}
i := 0
result = make([]map[string]string, len(checksums))
for id, sum := range checksums {
result[i] = map[string]string{
"id": id,
"checksum": sum,
}
i++
}
return result, nil
type SearchResults struct {
Query string `json:"query"`
NumResults int `json:"num_results"`
Results []map[string]string `json:"results"`
}
func (graph *Graph) SearchRepositories(stdout io.Writer, term string) (*SearchResults, error) {
client := graph.getHttpClient()
u := INDEX_ENDPOINT + "/search?q=" + url.QueryEscape(term)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, fmt.Errorf("Unexepected status code %d", res.StatusCode)
}
rawData, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
result := new(SearchResults)
err = json.Unmarshal(rawData, result)
return result, err
}
+4 -136
View File
@@ -12,7 +12,6 @@ import (
"path"
"sort"
"strings"
"time"
)
type Capabilities struct {
@@ -79,114 +78,6 @@ 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 || len(config.Cmd) == 0 {
return nil, fmt.Errorf("No command specified")
}
// Generate id
id := GenerateId()
// Generate default hostname
// FIXME: the lxc template no longer needs to set a default hostname
if config.Hostname == "" {
config.Hostname = id[:12]
}
container := &Container{
// FIXME: we should generate the ID here instead of receiving it as an argument
Id: id,
Created: time.Now(),
Path: config.Cmd[0],
Args: config.Cmd[1:], //FIXME: de-duplicate from config
Config: config,
Image: img.Id, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
// 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.
if err := os.Mkdir(container.root, 0700); err != nil {
return nil, err
}
// If custom dns exists, then create a resolv.conf for the container
if len(config.Dns) > 0 {
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
f, err := os.Create(container.ResolvConfPath)
if err != nil {
return nil, err
}
defer f.Close()
for _, dns := range config.Dns {
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
return nil, err
}
}
} else {
container.ResolvConfPath = "/etc/resolv.conf"
}
// Step 2: save the container json
if err := container.ToDisk(); err != nil {
return nil, err
}
// Step 3: register the container
if err := runtime.Register(container); err != nil {
return nil, err
}
return container, nil
}
func (runtime *Runtime) Load(id string) (*Container, error) {
container := &Container{root: runtime.containerRoot(id)}
if err := container.FromDisk(); err != nil {
@@ -287,6 +178,10 @@ func (runtime *Runtime) LogToDisk(src *writeBroadcaster, dst string) error {
}
func (runtime *Runtime) Destroy(container *Container) error {
if container == nil {
return fmt.Errorf("The given container is <nil>")
}
element := runtime.getContainerElement(container.Id)
if element == nil {
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
@@ -311,33 +206,6 @@ func (runtime *Runtime) Destroy(container *Container) error {
return nil
}
// 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, config *Config) (*Image, error) {
container := runtime.Get(id)
if container == nil {
return nil, fmt.Errorf("No such container: %s", id)
}
// FIXME: freeze the container before copying it to avoid data corruption?
// FIXME: this shouldn't be in commands.
rwTar, err := container.ExportRw()
if err != nil {
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, config)
if err != nil {
return nil, err
}
// Register the image if needed
if repository != "" {
if err := runtime.repositories.Set(repository, tag, img.Id, true); err != nil {
return img, err
}
}
return img, nil
}
func (runtime *Runtime) restore() error {
dir, err := ioutil.ReadDir(runtime.repository)
if err != nil {
+36 -8
View File
@@ -118,7 +118,10 @@ func TestRuntimeCreate(t *testing.T) {
if len(runtime.List()) != 0 {
t.Errorf("Expected 0 containers, %v found", len(runtime.List()))
}
container, err := runtime.Create(&Config{
builder := NewBuilder(runtime)
container, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"ls", "-al"},
},
@@ -157,6 +160,26 @@ func TestRuntimeCreate(t *testing.T) {
if !runtime.Exists(container.Id) {
t.Errorf("Exists() returned false for a newly created container")
}
// Make sure crete with bad parameters returns an error
_, err = builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
},
)
if err == nil {
t.Fatal("Builder.Create should throw an error when Cmd is missing")
}
_, err = builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{},
},
)
if err == nil {
t.Fatal("Builder.Create should throw an error when Cmd is empty")
}
}
func TestDestroy(t *testing.T) {
@@ -165,7 +188,7 @@ func TestDestroy(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"ls", "-al"},
},
@@ -212,7 +235,10 @@ func TestGet(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime)
container1, err := runtime.Create(&Config{
builder := NewBuilder(runtime)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"ls", "-al"},
},
@@ -222,7 +248,7 @@ func TestGet(t *testing.T) {
}
defer runtime.Destroy(container1)
container2, err := runtime.Create(&Config{
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"ls", "-al"},
},
@@ -232,7 +258,7 @@ func TestGet(t *testing.T) {
}
defer runtime.Destroy(container2)
container3, err := runtime.Create(&Config{
container3, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"ls", "-al"},
},
@@ -262,7 +288,7 @@ func TestAllocatePortLocalhost(t *testing.T) {
if err != nil {
t.Fatal(err)
}
container, err := runtime.Create(&Config{
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p 5555"},
PortSpecs: []string{"5555"},
@@ -325,8 +351,10 @@ func TestRestore(t *testing.T) {
t.Fatal(err)
}
builder := NewBuilder(runtime1)
// Create a container with one instance of docker
container1, err := runtime1.Create(&Config{
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime1).Id,
Cmd: []string{"ls", "-al"},
},
@@ -337,7 +365,7 @@ func TestRestore(t *testing.T) {
defer runtime1.Destroy(container1)
// Create a second container meant to be killed
container2, err := runtime1.Create(&Config{
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime1).Id,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
+59 -2
View File
@@ -155,6 +155,13 @@ func SelfPath() string {
return path
}
type nopWriter struct {
}
func (w *nopWriter) Write(buf []byte) (int, error) {
return len(buf), nil
}
type nopWriteCloser struct {
io.Writer
}
@@ -397,7 +404,6 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
return written, err
}
func HashData(src io.Reader) (string, error) {
h := sha256.New()
if _, err := io.Copy(h, src); err != nil {
@@ -419,7 +425,11 @@ func GetKernelVersion() (*KernelVersionInfo, error) {
}
func (k *KernelVersionInfo) String() string {
return fmt.Sprintf("%d.%d.%d-%s", k.Kernel, k.Major, k.Minor, k.Flavor)
flavor := ""
if len(k.Flavor) > 0 {
flavor = fmt.Sprintf("-%s", k.Flavor)
}
return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, flavor)
}
// Compare two KernelVersionInfo struct.
@@ -467,3 +477,50 @@ func FindCgroupMountpoint(cgroupType string) (string, error) {
return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
}
// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
// If OpenStdin is set, then it differs
func CompareConfig(a, b *Config) bool {
if a == nil || b == nil ||
a.OpenStdin || b.OpenStdin {
return false
}
if a.AttachStdout != b.AttachStdout ||
a.AttachStderr != b.AttachStderr ||
a.User != b.User ||
a.Memory != b.Memory ||
a.MemorySwap != b.MemorySwap ||
a.OpenStdin != b.OpenStdin ||
a.Tty != b.Tty {
return false
}
if len(a.Cmd) != len(b.Cmd) ||
len(a.Dns) != len(b.Dns) ||
len(a.Env) != len(b.Env) ||
len(a.PortSpecs) != len(b.PortSpecs) {
return false
}
for i := 0; i < len(a.Cmd); i++ {
if a.Cmd[i] != b.Cmd[i] {
return false
}
}
for i := 0; i < len(a.Dns); i++ {
if a.Dns[i] != b.Dns[i] {
return false
}
}
for i := 0; i < len(a.Env); i++ {
if a.Env[i] != b.Env[i] {
return false
}
}
for i := 0; i < len(a.PortSpecs); i++ {
if a.PortSpecs[i] != b.PortSpecs[i] {
return false
}
}
return true
}