Compare commits

...

100 Commits

Author SHA1 Message Date
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
38 changed files with 1584 additions and 406 deletions

View File

@@ -1,5 +1,30 @@
# Changelog
## 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

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)

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
builder.go Normal file
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
builder_test.go Normal file
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")
}
}

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.1"
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 {

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,

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
}

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"},
},

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

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

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

@@ -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 apache 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"]

View File

@@ -0,0 +1,14 @@
:title: docker documentation
:description: Documentation for docker builder
:keywords: docker, builder, dockerfile
Builder
=======
Contents:
.. toctree::
:maxdepth: 2
basics

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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -7,7 +7,8 @@
<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">
@@ -23,6 +24,29 @@
<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>
@@ -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>

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

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.

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

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``

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>

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;
}

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

View File

@@ -9,14 +9,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 +31,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 +89,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
}
@@ -103,7 +115,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
if err := graph.Register(layerData, img); err != nil {
return nil, err
}
img.Checksum()
go img.Checksum()
return img, nil
}
@@ -131,6 +143,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 +266,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

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,8 +53,7 @@ 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 {
@@ -261,19 +261,22 @@ 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)
checksums := make(map[string]string)
if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil {
if err := json.Unmarshal(checksumDict, checksums); err != nil {
if err := json.Unmarshal(checksumDict, &checksums); err != nil {
return "", err
}
if checksum, ok := (*checksums)[img.Id]; ok {
if checksum, ok := checksums[img.Id]; ok {
return checksum, nil
}
}
@@ -299,20 +302,26 @@ 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
// Reload the json file to make sure not to overwrite faster sums
img.graph.lockSumFile.Lock()
defer img.graph.lockSumFile.Unlock()
if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil {
if err := json.Unmarshal(checksumDict, &checksums); err != nil {
return "", err
}
}
(*checksums)[img.Id] = hash
checksumJson, err := json.Marshal(checksums)
if err != nil {
return hash, err
}
if err := ioutil.WriteFile(checksumDictPth, checksumJson, 0600); err != nil {
return hash, err
}

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

View File

@@ -1,21 +1,48 @@
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

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

View File

@@ -9,6 +9,7 @@ import (
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
)
@@ -69,7 +70,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 +194,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
@@ -302,6 +306,50 @@ 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 := []struct {
Id string `json: "id"`
Checksum string `json: "checksum"`
}{}
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)
@@ -421,9 +469,15 @@ 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
}
@@ -606,8 +660,7 @@ func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Re
}
func (graph *Graph) Checksums(output io.Writer, repo Repository) ([]map[string]string, error) {
var result []map[string]string
checksums := map[string]string{}
checksums := make(map[string]string)
for _, id := range repo {
img, err := graph.Get(id)
if err != nil {
@@ -628,7 +681,7 @@ func (graph *Graph) Checksums(output io.Writer, repo Repository) ([]map[string]s
}
}
i := 0
result = make([]map[string]string, len(checksums))
result := make([]map[string]string, len(checksums))
for id, sum := range checksums {
result[i] = map[string]string{
"id": id,
@@ -638,3 +691,33 @@ func (graph *Graph) Checksums(output io.Writer, repo Repository) ([]map[string]s
}
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
}

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 {

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,

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
}