mirror of https://github.com/docker/docs.git
Merge remote-tracking branch 'upstream/master'
Conflicts: docs/sources/examples/python_web_app.rst
This commit is contained in:
commit
b4198de6bf
32
api.go
32
api.go
|
@ -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,
|
||||||
|
|
|
@ -64,3 +64,8 @@ type ApiWait struct {
|
||||||
type ApiAuth struct {
|
type ApiAuth struct {
|
||||||
Status string
|
Status string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ApiImageConfig struct {
|
||||||
|
Id string
|
||||||
|
*Config
|
||||||
|
}
|
||||||
|
|
35
api_test.go
35
api_test.go
|
@ -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
|
||||||
|
|
||||||
|
|
358
builder.go
358
builder.go
|
@ -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")
|
|
||||||
}
|
|
||||||
|
|
|
@ -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{}),
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
82
commands.go
82
commands.go
|
@ -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
|
||||||
|
|
|
@ -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":[]
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
56
server.go
56
server.go
|
@ -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 {
|
||||||
|
|
39
utils.go
39
utils.go
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue