Merge remote-tracking branch 'upstream/master'

Conflicts:
	docs/sources/examples/python_web_app.rst
This commit is contained in:
Eric Hanchrow 2013-05-22 12:52:14 -07:00
commit b4198de6bf
12 changed files with 481 additions and 544 deletions

32
api.go
View File

@ -352,13 +352,6 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars ma
return nil return nil
} }
func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := srv.ImageCreateFromFile(r.Body, w); err != nil {
return err
}
return nil
}
func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
config := &Config{} config := &Config{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil { if err := json.NewDecoder(r.Body).Decode(config); err != nil {
@ -569,6 +562,29 @@ func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars m
return nil return nil
} }
func postImagesGetCache(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
apiConfig := &ApiImageConfig{}
if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
return err
}
image, err := srv.ImageGetCached(apiConfig.Id, apiConfig.Config)
if err != nil {
return err
}
if image == nil {
w.WriteHeader(http.StatusNotFound)
return nil
}
apiId := &ApiId{Id: image.Id}
b, err := json.Marshal(apiId)
if err != nil {
return err
}
writeJson(w, b)
return nil
}
func ListenAndServe(addr string, srv *Server, logging bool) error { func ListenAndServe(addr string, srv *Server, logging bool) error {
r := mux.NewRouter() r := mux.NewRouter()
log.Printf("Listening for HTTP on %s\n", addr) log.Printf("Listening for HTTP on %s\n", addr)
@ -591,11 +607,11 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
"POST": { "POST": {
"/auth": postAuth, "/auth": postAuth,
"/commit": postCommit, "/commit": postCommit,
"/build": postBuild,
"/images/create": postImagesCreate, "/images/create": postImagesCreate,
"/images/{name:.*}/insert": postImagesInsert, "/images/{name:.*}/insert": postImagesInsert,
"/images/{name:.*}/push": postImagesPush, "/images/{name:.*}/push": postImagesPush,
"/images/{name:.*}/tag": postImagesTag, "/images/{name:.*}/tag": postImagesTag,
"/images/getCache": postImagesGetCache,
"/containers/create": postContainersCreate, "/containers/create": postContainersCreate,
"/containers/{name:.*}/kill": postContainersKill, "/containers/{name:.*}/kill": postContainersKill,
"/containers/{name:.*}/restart": postContainersRestart, "/containers/{name:.*}/restart": postContainersRestart,

View File

@ -64,3 +64,8 @@ type ApiWait struct {
type ApiAuth struct { type ApiAuth struct {
Status string Status string
} }
type ApiImageConfig struct {
Id string
*Config
}

View File

@ -14,7 +14,6 @@ import (
"net/http/httptest" "net/http/httptest"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"time" "time"
) )
@ -579,40 +578,6 @@ func TestPostCommit(t *testing.T) {
} }
} }
func TestPostBuild(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
imgs, err := runtime.graph.All()
if err != nil {
t.Fatal(err)
}
beginCount := len(imgs)
req, err := http.NewRequest("POST", "/build", strings.NewReader(Dockerfile))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postBuild(srv, r, req, nil); err != nil {
t.Fatal(err)
}
imgs, err = runtime.graph.All()
if err != nil {
t.Fatal(err)
}
if len(imgs) != beginCount+3 {
t.Fatalf("Expected %d images, %d found", beginCount+3, len(imgs))
}
}
func TestPostImagesCreate(t *testing.T) { func TestPostImagesCreate(t *testing.T) {
// FIXME: Use the staging in order to perform tests // FIXME: Use the staging in order to perform tests

View File

@ -1,14 +1,9 @@
package docker package docker
import ( import (
"bufio"
"encoding/json"
"fmt" "fmt"
"github.com/dotcloud/docker/utils"
"io"
"os" "os"
"path" "path"
"strings"
"time" "time"
) )
@ -16,6 +11,9 @@ type Builder struct {
runtime *Runtime runtime *Runtime
repositories *TagStore repositories *TagStore
graph *Graph graph *Graph
config *Config
image *Image
} }
func NewBuilder(runtime *Runtime) *Builder { func NewBuilder(runtime *Runtime) *Builder {
@ -26,45 +24,6 @@ func NewBuilder(runtime *Runtime) *Builder {
} }
} }
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.CpuShares == 0 {
userConf.CpuShares = imageConf.CpuShares
}
if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
userConf.PortSpecs = imageConf.PortSpecs
}
if !userConf.Tty {
userConf.Tty = imageConf.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) { func (builder *Builder) Create(config *Config) (*Container, error) {
// Lookup image // Lookup image
img, err := builder.repositories.LookupImage(config.Image) img, err := builder.repositories.LookupImage(config.Image)
@ -73,7 +32,7 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
} }
if img.Config != nil { if img.Config != nil {
builder.mergeConfig(config, img.Config) MergeConfig(config, img.Config)
} }
if config.Cmd == nil || len(config.Cmd) == 0 { if config.Cmd == nil || len(config.Cmd) == 0 {
@ -157,312 +116,3 @@ func (builder *Builder) Commit(container *Container, repository, tag, comment, a
} }
return img, nil 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)
utils.Debugf("Removing container %s", c)
}
for i := range images {
builder.runtime.graph.Delete(i)
utils.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
// }
// panic("TODO: reimplement this")
// // 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}, 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
}
utils.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 := utils.Download(sourceUrl, stdout)
if err != nil {
return nil, err
}
defer file.Body.Close()
config, _, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, 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")
}

311
builder_client.go Normal file
View File

@ -0,0 +1,311 @@
package docker
import (
"bufio"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"net/url"
"os"
"reflect"
"strings"
)
type BuilderClient interface {
Build(io.Reader) (string, error)
CmdFrom(string) error
CmdRun(string) error
}
type builderClient struct {
cli *DockerCli
image string
maintainer string
config *Config
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
needCommit bool
}
func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
for c := range containers {
if _, _, err := b.cli.call("DELETE", "/containers/"+c, nil); err != nil {
utils.Debugf("%s", err)
}
utils.Debugf("Removing container %s", c)
}
for i := range images {
if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
utils.Debugf("%s", err)
}
utils.Debugf("Removing image %s", i)
}
}
func (b *builderClient) CmdFrom(name string) error {
obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
if statusCode == 404 {
remote := name
var tag string
if strings.Contains(remote, ":") {
remoteParts := strings.Split(remote, ":")
tag = remoteParts[1]
remote = remoteParts[0]
}
var out io.Writer
if os.Getenv("DEBUG") != "" {
out = os.Stdout
} else {
out = &utils.NopWriter{}
}
if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
return err
}
obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
if err != nil {
return err
}
}
if err != nil {
return err
}
img := &ApiId{}
if err := json.Unmarshal(obj, img); err != nil {
return err
}
b.image = img.Id
utils.Debugf("Using image %s", b.image)
return nil
}
func (b *builderClient) CmdMaintainer(name string) error {
b.needCommit = true
b.maintainer = name
return nil
}
func (b *builderClient) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
if err != nil {
return err
}
cmd, env := b.config.Cmd, b.config.Env
b.config.Cmd = nil
MergeConfig(b.config, config)
body, statusCode, err := b.cli.call("POST", "/images/getCache", &ApiImageConfig{Id: b.image, Config: b.config})
if err != nil {
if statusCode != 404 {
return err
}
}
if statusCode != 404 {
apiId := &ApiId{}
if err := json.Unmarshal(body, apiId); err != nil {
return err
}
utils.Debugf("Use cached version")
b.image = apiId.Id
return nil
}
cid, err := b.run()
if err != nil {
return err
}
b.config.Cmd, b.config.Env = cmd, env
return b.commit(cid)
}
func (b *builderClient) CmdEnv(args string) error {
b.needCommit = true
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
}
key := strings.Trim(tmp[0], " ")
value := strings.Trim(tmp[1], " ")
for i, elem := range b.config.Env {
if strings.HasPrefix(elem, key+"=") {
b.config.Env[i] = key + "=" + value
return nil
}
}
b.config.Env = append(b.config.Env, key+"="+value)
return nil
}
func (b *builderClient) CmdCmd(args string) error {
b.needCommit = true
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
b.config.Cmd = []string{"/bin/sh", "-c", args}
} else {
b.config.Cmd = cmd
}
return nil
}
func (b *builderClient) CmdExpose(args string) error {
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return nil
}
func (b *builderClient) CmdInsert(args string) error {
// FIXME: Reimplement this once the remove_hijack branch gets merged.
// We need to retrieve the resulting Id
return fmt.Errorf("INSERT not implemented")
}
func (b *builderClient) run() (string, error) {
if b.image == "" {
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
body, _, err := b.cli.call("POST", "/containers/create", b.config)
if err != nil {
return "", err
}
apiRun := &ApiRun{}
if err := json.Unmarshal(body, apiRun); err != nil {
return "", err
}
for _, warning := range apiRun.Warnings {
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
}
//start the container
_, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/start", nil)
if err != nil {
return "", err
}
b.tmpContainers[apiRun.Id] = struct{}{}
// Wait for it to finish
body, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/wait", nil)
if err != nil {
return "", err
}
apiWait := &ApiWait{}
if err := json.Unmarshal(body, apiWait); err != nil {
return "", err
}
if apiWait.StatusCode != 0 {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
}
return apiRun.Id, nil
}
func (b *builderClient) commit(id string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"true"}
if cid, err := b.run(); err != nil {
return err
} else {
id = cid
}
b.config.Cmd = cmd
}
// Commit the container
v := url.Values{}
v.Set("container", id)
v.Set("author", b.maintainer)
body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
if err != nil {
return err
}
apiId := &ApiId{}
if err := json.Unmarshal(body, apiId); err != nil {
return err
}
b.tmpImages[apiId.Id] = struct{}{}
b.image = apiId.Id
b.needCommit = false
return nil
}
func (b *builderClient) Build(dockerfile io.Reader) (string, error) {
defer b.clearTmp(b.tmpContainers, b.tmpImages)
file := bufio.NewReader(dockerfile)
for {
line, err := file.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return "", 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 "", fmt.Errorf("Invalid Dockerfile format")
}
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
arguments := strings.Trim(tmp[1], " ")
fmt.Printf("%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists {
fmt.Printf("Skipping unknown instruction %s\n", strings.ToUpper(instruction))
}
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
if ret != nil {
return "", ret.(error)
}
fmt.Printf("===> %v\n", b.image)
}
if b.needCommit {
if err := b.commit(""); err != nil {
return "", err
}
}
if b.image != "" {
// The build is successful, keep the temporary containers and images
for i := range b.tmpImages {
delete(b.tmpImages, i)
}
for i := range b.tmpContainers {
delete(b.tmpContainers, i)
}
fmt.Printf("Build finished. image id: %s\n", b.image)
return b.image, nil
}
return "", fmt.Errorf("An error occured during the build\n")
}
func NewBuilderClient(addr string, port int) BuilderClient {
return &builderClient{
cli: NewDockerCli(addr, port),
config: &Config{},
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}),
}
}

View File

@ -1,89 +0,0 @@
package docker
import (
"github.com/dotcloud/docker/utils"
"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), &utils.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

@ -54,37 +54,37 @@ func ParseCommands(args ...string) error {
func (cli *DockerCli) CmdHelp(args ...string) error { func (cli *DockerCli) CmdHelp(args ...string) error {
help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n" help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
for _, cmd := range [][]string{ for cmd, description := range map[string]string{
{"attach", "Attach to a running container"}, "attach": "Attach to a running container",
{"build", "Build a container from Dockerfile via stdin"}, "build": "Build a container from Dockerfile or via stdin",
{"commit", "Create a new image from a container's changes"}, "commit": "Create a new image from a container's changes",
{"diff", "Inspect changes on a container's filesystem"}, "diff": "Inspect changes on a container's filesystem",
{"export", "Stream the contents of a container as a tar archive"}, "export": "Stream the contents of a container as a tar archive",
{"history", "Show the history of an image"}, "history": "Show the history of an image",
{"images", "List images"}, "images": "List images",
{"import", "Create a new filesystem image from the contents of a tarball"}, "import": "Create a new filesystem image from the contents of a tarball",
{"info", "Display system-wide information"}, "info": "Display system-wide information",
{"insert", "Insert a file in an image"}, "insert": "Insert a file in an image",
{"inspect", "Return low-level information on a container"}, "inspect": "Return low-level information on a container",
{"kill", "Kill a running container"}, "kill": "Kill a running container",
{"login", "Register or Login to the docker registry server"}, "login": "Register or Login to the docker registry server",
{"logs", "Fetch the logs of a container"}, "logs": "Fetch the logs of a container",
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, "port": "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT",
{"ps", "List containers"}, "ps": "List containers",
{"pull", "Pull an image or a repository from the docker registry server"}, "pull": "Pull an image or a repository from the docker registry server",
{"push", "Push an image or a repository to the docker registry server"}, "push": "Push an image or a repository to the docker registry server",
{"restart", "Restart a running container"}, "restart": "Restart a running container",
{"rm", "Remove a container"}, "rm": "Remove a container",
{"rmi", "Remove an image"}, "rmi": "Remove an image",
{"run", "Run a command in a new container"}, "run": "Run a command in a new container",
{"search", "Search for an image in the docker index"}, "search": "Search for an image in the docker index",
{"start", "Start a stopped container"}, "start": "Start a stopped container",
{"stop", "Stop a running container"}, "stop": "Stop a running container",
{"tag", "Tag an image into a repository"}, "tag": "Tag an image into a repository",
{"version", "Show the docker version information"}, "version": "Show the docker version information",
{"wait", "Block until a container stops, then print its exit code"}, "wait": "Block until a container stops, then print its exit code",
} { } {
help += fmt.Sprintf(" %-10.10s%s\n", cmd[0], cmd[1]) help += fmt.Sprintf(" %-10.10s%s\n", cmd, description)
} }
fmt.Println(help) fmt.Println(help)
return nil return nil
@ -112,13 +112,29 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
} }
func (cli *DockerCli) CmdBuild(args ...string) error { func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Subcmd("build", "-", "Build an image from Dockerfile via stdin") cmd := Subcmd("build", "-|Dockerfile", "Build an image from Dockerfile or via stdin")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil return nil
} }
var (
file io.ReadCloser
err error
)
err := cli.stream("POST", "/build", os.Stdin, os.Stdout) if cmd.NArg() == 0 {
if err != nil { file, err = os.Open("Dockerfile")
if err != nil {
return err
}
} else if cmd.Arg(0) == "-" {
file = os.Stdin
} else {
file, err = os.Open(cmd.Arg(0))
if err != nil {
return err
}
}
if _, err := NewBuilderClient("0.0.0.0", 4243).Build(file); err != nil {
return err return err
} }
return nil return nil

View File

@ -118,7 +118,8 @@ Create a container
.. sourcecode:: http .. sourcecode:: http
HTTP/1.1 201 OK HTTP/1.1 201 OK
Content-Type: application/json
{ {
"Id":"e90e34656806" "Id":"e90e34656806"
"Warnings":[] "Warnings":[]

View File

@ -40,7 +40,7 @@ We attach to the new container to see what is going on. Ctrl-C to disconnect
.. code-block:: bash .. code-block:: bash
BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/hykes/helloflask/master) BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name. Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name.
@ -58,7 +58,7 @@ Use the new image we just created and create a new container with network port 5
.. code-block:: bash .. code-block:: bash
docker logs $WEB_WORKER docker logs $WEB_WORKER
* Running on \http://0.0.0.0:5000/ * Running on http://0.0.0.0:5000/
view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output. view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
@ -70,8 +70,8 @@ lookup the public-facing port which is NAT-ed store the private port used by the
.. code-block:: bash .. code-block:: bash
sudo aptitude install curl # install curl if necessary, then ...
curl http://127.0.0.1:$WEB_PORT curl http://`hostname`:$WEB_PORT
Hello world! Hello world!
access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console. access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.

View File

@ -107,8 +107,7 @@ The `ENV` instruction sets the environment variable `<key>` to the value
functionally equivalent to prefixing the command with `<key>=<value>` functionally equivalent to prefixing the command with `<key>=<value>`
.. note:: .. note::
The environment variables are local to the Dockerfile, they will not persist The environment variables will persist when a container is run from the resulting image.
when a container is run from the resulting image.
2.7 INSERT 2.7 INSERT
---------- ----------
@ -122,6 +121,8 @@ curl was installed within the image.
.. note:: .. note::
The path must include the file name. The path must include the file name.
.. note::
This instruction has temporarily disabled
3. Dockerfile Examples 3. Dockerfile Examples
====================== ======================
@ -179,4 +180,4 @@ curl was installed within the image.
# Will output something like ===> 695d7793cbe4 # Will output something like ===> 695d7793cbe4
# You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with # You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
# /oink. # /oink.

View File

@ -142,8 +142,10 @@ func (srv *Server) ImagesViz(out io.Writer) error {
} }
func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) { func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
var allImages map[string]*Image var (
var err error allImages map[string]*Image
err error
)
if all { if all {
allImages, err = srv.runtime.graph.Map() allImages, err = srv.runtime.graph.Map()
} else { } else {
@ -152,7 +154,7 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null' outs := []ApiImages{} //produce [] when empty instead of 'null'
for name, repository := range srv.runtime.repositories.Repositories { for name, repository := range srv.runtime.repositories.Repositories {
if filter != "" && name != filter { if filter != "" && name != filter {
continue continue
@ -361,7 +363,7 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
for _, img := range repoData.ImgList { for _, img := range repoData.ImgList {
if askedTag != "" && img.Tag != askedTag { if askedTag != "" && img.Tag != askedTag {
utils.Debugf("%s does not match %s, skipping", img.Tag, askedTag) utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
continue continue
} }
fmt.Fprintf(out, "Pulling image %s (%s) from %s\n", img.Id, img.Tag, remote) fmt.Fprintf(out, "Pulling image %s (%s) from %s\n", img.Id, img.Tag, remote)
@ -378,11 +380,10 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
return fmt.Errorf("Could not find repository on any of the indexed registries.") return fmt.Errorf("Could not find repository on any of the indexed registries.")
} }
} }
// If we asked for a specific tag, do not register the others
if askedTag != "" {
return nil
}
for tag, id := range tagsList { for tag, id := range tagsList {
if askedTag != "" && tag != askedTag {
continue
}
if err := srv.runtime.repositories.Set(remote, tag, id, true); err != nil { if err := srv.runtime.repositories.Set(remote, tag, id, true); err != nil {
return err return err
} }
@ -656,15 +657,6 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
return container.ShortId(), nil return container.ShortId(), nil
} }
func (srv *Server) ImageCreateFromFile(dockerfile io.Reader, out io.Writer) error {
img, err := NewBuilder(srv.runtime).Build(dockerfile, out)
if err != nil {
return err
}
fmt.Fprintf(out, "%s\n", img.ShortId())
return nil
}
func (srv *Server) ContainerRestart(name string, t int) error { func (srv *Server) ContainerRestart(name string, t int) error {
if container := srv.runtime.Get(name); container != nil { if container := srv.runtime.Get(name); container != nil {
if err := container.Restart(t); err != nil { if err := container.Restart(t); err != nil {
@ -725,6 +717,36 @@ func (srv *Server) ImageDelete(name string) error {
return nil return nil
} }
func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) {
// Retrieve all images
images, err := srv.runtime.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[imgId] {
img, err := srv.runtime.graph.Get(elem)
if err != nil {
return nil, err
}
if CompareConfig(&img.ContainerConfig, config) {
return img, nil
}
}
return nil, nil
}
func (srv *Server) ContainerStart(name string) error { func (srv *Server) ContainerStart(name string) error {
if container := srv.runtime.Get(name); container != nil { if container := srv.runtime.Get(name); container != nil {
if err := container.Start(); err != nil { if err := container.Start(); err != nil {

View File

@ -47,3 +47,42 @@ func CompareConfig(a, b *Config) bool {
return true return true
} }
func 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.CpuShares == 0 {
userConf.CpuShares = imageConf.CpuShares
}
if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
userConf.PortSpecs = imageConf.PortSpecs
}
if !userConf.Tty {
userConf.Tty = imageConf.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
}
}