mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
bump to master
This commit is contained in:
commit
62c78696cd
45 changed files with 884 additions and 353 deletions
1
AUTHORS
1
AUTHORS
|
@ -15,6 +15,7 @@ Brian McCallister <brianm@skife.org>
|
||||||
Bruno Bigras <bigras.bruno@gmail.com>
|
Bruno Bigras <bigras.bruno@gmail.com>
|
||||||
Caleb Spare <cespare@gmail.com>
|
Caleb Spare <cespare@gmail.com>
|
||||||
Charles Hooper <charles.hooper@dotcloud.com>
|
Charles Hooper <charles.hooper@dotcloud.com>
|
||||||
|
Daniel Gasienica <daniel@gasienica.ch>
|
||||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||||
Daniel Robinson <gottagetmac@gmail.com>
|
Daniel Robinson <gottagetmac@gmail.com>
|
||||||
Daniel Von Fange <daniel@leancoder.com>
|
Daniel Von Fange <daniel@leancoder.com>
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.3.4 (2013-05-30)
|
||||||
|
+ Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
|
||||||
|
+ Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
|
||||||
|
+ Runtime: interactive TTYs correctly handle window resize
|
||||||
|
* Runtime: fix how configuration is merged between layers
|
||||||
|
+ Remote API: split stdout and stderr on 'docker run'
|
||||||
|
+ Remote API: optionally listen on a different IP and port (use at your own risk)
|
||||||
|
* Documentation: improved install instructions.
|
||||||
|
|
||||||
## 0.3.3 (2013-05-23)
|
## 0.3.3 (2013-05-23)
|
||||||
- Registry: Fix push regression
|
- Registry: Fix push regression
|
||||||
- Various bugfixes
|
- Various bugfixes
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
Solomon Hykes <solomon@dotcloud.com>
|
Solomon Hykes <solomon@dotcloud.com>
|
||||||
Guillaume Charmes <guillaume@dotcloud.com>
|
Guillaume Charmes <guillaume@dotcloud.com>
|
||||||
|
Victor Vieux <victor@dotcloud.com>
|
||||||
api.go: Victor Vieux <victor@dotcloud.com>
|
api.go: Victor Vieux <victor@dotcloud.com>
|
||||||
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
|
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||||
|
|
55
api.go
55
api.go
|
@ -45,6 +45,8 @@ func httpError(w http.ResponseWriter, err error) {
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
|
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
} else if strings.HasPrefix(err.Error(), "Impossible") {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotAcceptable)
|
||||||
} else {
|
} else {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
@ -279,18 +281,27 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||||
tag := r.Form.Get("tag")
|
tag := r.Form.Get("tag")
|
||||||
repo := r.Form.Get("repo")
|
repo := r.Form.Get("repo")
|
||||||
|
|
||||||
|
if version > 1.0 {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
sf := utils.NewStreamFormatter(version > 1.0)
|
||||||
if image != "" { //pull
|
if image != "" { //pull
|
||||||
registry := r.Form.Get("registry")
|
registry := r.Form.Get("registry")
|
||||||
if version > 1.0 {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
}
|
|
||||||
authConfig := &auth.AuthConfig{}
|
authConfig := &auth.AuthConfig{}
|
||||||
json.NewDecoder(r.Body).Decode(authConfig)
|
json.NewDecoder(r.Body).Decode(authConfig)
|
||||||
if err := srv.ImagePull(image, tag, registry, w, version > 1.0, authConfig); err != nil {
|
if err := srv.ImagePull(image, tag, registry, w, sf, authConfig); err != nil {
|
||||||
|
if sf.Used() {
|
||||||
|
w.Write(sf.FormatError(err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else { //import
|
} else { //import
|
||||||
if err := srv.ImageImport(src, repo, tag, r.Body, w); err != nil {
|
if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
|
||||||
|
if sf.Used() {
|
||||||
|
w.Write(sf.FormatError(err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,10 +337,16 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
|
if version > 1.0 {
|
||||||
imgId, err := srv.ImageInsert(name, url, path, w)
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
sf := utils.NewStreamFormatter(version > 1.0)
|
||||||
|
imgId, err := srv.ImageInsert(name, url, path, w, sf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if sf.Used() {
|
||||||
|
w.Write(sf.FormatError(err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
b, err := json.Marshal(&ApiId{Id: imgId})
|
b, err := json.Marshal(&ApiId{Id: imgId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -353,8 +370,15 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
|
||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
|
if version > 1.0 {
|
||||||
if err := srv.ImagePush(name, registry, w, authConfig); err != nil {
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
sf := utils.NewStreamFormatter(version > 1.0)
|
||||||
|
if err := srv.ImagePush(name, registry, w, sf, authConfig); err != nil {
|
||||||
|
if sf.Used() {
|
||||||
|
w.Write(sf.FormatError(err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -623,6 +647,13 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||||
if err := r.ParseMultipartForm(4096); err != nil {
|
if err := r.ParseMultipartForm(4096); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
remote := r.FormValue("t")
|
||||||
|
tag := ""
|
||||||
|
if strings.Contains(remote, ":") {
|
||||||
|
remoteParts := strings.Split(remote, ":")
|
||||||
|
tag = remoteParts[1]
|
||||||
|
remote = remoteParts[0]
|
||||||
|
}
|
||||||
|
|
||||||
dockerfile, _, err := r.FormFile("Dockerfile")
|
dockerfile, _, err := r.FormFile("Dockerfile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -637,8 +668,10 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
|
|
||||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w))
|
b := NewBuildFile(srv, utils.NewWriteFlusher(w))
|
||||||
if _, err := b.Build(dockerfile, context); err != nil {
|
if id, err := b.Build(dockerfile, context); err != nil {
|
||||||
fmt.Fprintf(w, "Error build: %s\n", err)
|
fmt.Fprintf(w, "Error build: %s\n", err)
|
||||||
|
} else if remote != "" {
|
||||||
|
srv.runtime.repositories.Set(remote, tag, id, false)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,9 @@ func Tar(path string, compression Compression) (io.Reader, error) {
|
||||||
func Untar(archive io.Reader, path string) error {
|
func Untar(archive io.Reader, path string) error {
|
||||||
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
|
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
|
||||||
cmd.Stdin = archive
|
cmd.Stdin = archive
|
||||||
|
// Hardcode locale environment for predictable outcome regardless of host configuration.
|
||||||
|
// (see https://github.com/dotcloud/docker/issues/355)
|
||||||
|
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %s", err, output)
|
return fmt.Errorf("%s: %s", err, output)
|
||||||
|
|
139
buildfile.go
139
buildfile.go
|
@ -32,8 +32,6 @@ type buildFile struct {
|
||||||
tmpContainers map[string]struct{}
|
tmpContainers map[string]struct{}
|
||||||
tmpImages map[string]struct{}
|
tmpImages map[string]struct{}
|
||||||
|
|
||||||
needCommit bool
|
|
||||||
|
|
||||||
out io.Writer
|
out io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||||
remote = name
|
remote = name
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.srv.ImagePull(remote, tag, "", b.out, false, nil); err != nil {
|
if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,9 +79,8 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdMaintainer(name string) error {
|
func (b *buildFile) CmdMaintainer(name string) error {
|
||||||
b.needCommit = true
|
|
||||||
b.maintainer = name
|
b.maintainer = name
|
||||||
return nil
|
return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdRun(args string) error {
|
func (b *buildFile) CmdRun(args string) error {
|
||||||
|
@ -95,28 +92,34 @@ func (b *buildFile) CmdRun(args string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, env := b.config.Cmd, b.config.Env
|
cmd := b.config.Cmd
|
||||||
b.config.Cmd = nil
|
b.config.Cmd = nil
|
||||||
MergeConfig(b.config, config)
|
MergeConfig(b.config, config)
|
||||||
|
|
||||||
if cache, err := b.srv.ImageGetCached(b.image, config); err != nil {
|
utils.Debugf("Command to be executed: %v", b.config.Cmd)
|
||||||
|
|
||||||
|
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if cache != nil {
|
} else if cache != nil {
|
||||||
utils.Debugf("Use cached version")
|
utils.Debugf("[BUILDER] Use cached version")
|
||||||
b.image = cache.Id
|
b.image = cache.Id
|
||||||
return nil
|
return nil
|
||||||
|
} else {
|
||||||
|
utils.Debugf("[BUILDER] Cache miss")
|
||||||
}
|
}
|
||||||
|
|
||||||
cid, err := b.run()
|
cid, err := b.run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.config.Cmd, b.config.Env = cmd, env
|
if err := b.commit(cid, cmd, "run"); err != nil {
|
||||||
return b.commit(cid)
|
return err
|
||||||
|
}
|
||||||
|
b.config.Cmd = cmd
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdEnv(args string) error {
|
func (b *buildFile) CmdEnv(args string) error {
|
||||||
b.needCommit = true
|
|
||||||
tmp := strings.SplitN(args, " ", 2)
|
tmp := strings.SplitN(args, " ", 2)
|
||||||
if len(tmp) != 2 {
|
if len(tmp) != 2 {
|
||||||
return fmt.Errorf("Invalid ENV format")
|
return fmt.Errorf("Invalid ENV format")
|
||||||
|
@ -131,60 +134,34 @@ func (b *buildFile) CmdEnv(args string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.config.Env = append(b.config.Env, key+"="+value)
|
b.config.Env = append(b.config.Env, key+"="+value)
|
||||||
return nil
|
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdCmd(args string) error {
|
func (b *buildFile) CmdCmd(args string) error {
|
||||||
b.needCommit = true
|
|
||||||
var cmd []string
|
var cmd []string
|
||||||
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
||||||
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
|
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
|
||||||
b.config.Cmd = []string{"/bin/sh", "-c", args}
|
cmd = []string{"/bin/sh", "-c", args}
|
||||||
} else {
|
|
||||||
b.config.Cmd = cmd
|
|
||||||
}
|
}
|
||||||
|
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.config.Cmd = cmd
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdExpose(args string) error {
|
func (b *buildFile) CmdExpose(args string) error {
|
||||||
ports := strings.Split(args, " ")
|
ports := strings.Split(args, " ")
|
||||||
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
||||||
return nil
|
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdInsert(args string) error {
|
func (b *buildFile) CmdInsert(args string) error {
|
||||||
if b.image == "" {
|
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
|
||||||
return fmt.Errorf("Please provide a source image with `from` prior to insert")
|
}
|
||||||
}
|
|
||||||
tmp := strings.SplitN(args, " ", 2)
|
|
||||||
if len(tmp) != 2 {
|
|
||||||
return fmt.Errorf("Invalid INSERT format")
|
|
||||||
}
|
|
||||||
sourceUrl := strings.Trim(tmp[0], " ")
|
|
||||||
destPath := strings.Trim(tmp[1], " ")
|
|
||||||
|
|
||||||
file, err := utils.Download(sourceUrl, b.out)
|
func (b *buildFile) CmdCopy(args string) error {
|
||||||
if err != nil {
|
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Body.Close()
|
|
||||||
|
|
||||||
b.config.Cmd = []string{"echo", "INSERT", sourceUrl, "in", destPath}
|
|
||||||
cid, err := b.run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
container := b.runtime.Get(cid)
|
|
||||||
if container == nil {
|
|
||||||
return fmt.Errorf("An error occured while creating the container")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := container.Inject(file.Body, destPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.commit(cid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdAdd(args string) error {
|
func (b *buildFile) CmdAdd(args string) error {
|
||||||
|
@ -193,12 +170,13 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||||
}
|
}
|
||||||
tmp := strings.SplitN(args, " ", 2)
|
tmp := strings.SplitN(args, " ", 2)
|
||||||
if len(tmp) != 2 {
|
if len(tmp) != 2 {
|
||||||
return fmt.Errorf("Invalid INSERT format")
|
return fmt.Errorf("Invalid ADD format")
|
||||||
}
|
}
|
||||||
orig := strings.Trim(tmp[0], " ")
|
orig := strings.Trim(tmp[0], " ")
|
||||||
dest := strings.Trim(tmp[1], " ")
|
dest := strings.Trim(tmp[1], " ")
|
||||||
|
|
||||||
b.config.Cmd = []string{"echo", "PUSH", orig, "in", dest}
|
cmd := b.config.Cmd
|
||||||
|
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
|
||||||
cid, err := b.run()
|
cid, err := b.run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -208,19 +186,23 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||||
if container == nil {
|
if container == nil {
|
||||||
return fmt.Errorf("Error while creating the container (CmdAdd)")
|
return fmt.Errorf("Error while creating the container (CmdAdd)")
|
||||||
}
|
}
|
||||||
|
if err := container.EnsureMounted(); err != nil {
|
||||||
if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer container.Unmount()
|
||||||
|
|
||||||
origPath := path.Join(b.context, orig)
|
origPath := path.Join(b.context, orig)
|
||||||
destPath := path.Join(container.rwPath(), dest)
|
destPath := path.Join(container.RootfsPath(), dest)
|
||||||
|
|
||||||
fi, err := os.Stat(origPath)
|
fi, err := os.Stat(origPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
|
if err := os.MkdirAll(destPath, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(path.Join(b.context, orig))
|
files, err := ioutil.ReadDir(path.Join(b.context, orig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -231,12 +213,18 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := utils.CopyDirectory(origPath, destPath); err != nil {
|
if err := utils.CopyDirectory(origPath, destPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
|
||||||
return b.commit(cid)
|
return err
|
||||||
|
}
|
||||||
|
b.config.Cmd = cmd
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) run() (string, error) {
|
func (b *buildFile) run() (string, error) {
|
||||||
|
@ -265,20 +253,30 @@ func (b *buildFile) run() (string, error) {
|
||||||
return c.Id, nil
|
return c.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) commit(id string) error {
|
// Commit the container <id> with the autorun command <autoCmd>
|
||||||
|
func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||||
if b.image == "" {
|
if b.image == "" {
|
||||||
return fmt.Errorf("Please provide a source image with `from` prior to commit")
|
return fmt.Errorf("Please provide a source image with `from` prior to commit")
|
||||||
}
|
}
|
||||||
b.config.Image = b.image
|
b.config.Image = b.image
|
||||||
if id == "" {
|
if id == "" {
|
||||||
cmd := b.config.Cmd
|
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
||||||
b.config.Cmd = []string{"true"}
|
|
||||||
|
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||||
|
return err
|
||||||
|
} else if cache != nil {
|
||||||
|
utils.Debugf("[BUILDER] Use cached version")
|
||||||
|
b.image = cache.Id
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
utils.Debugf("[BUILDER] Cache miss")
|
||||||
|
}
|
||||||
|
|
||||||
if cid, err := b.run(); err != nil {
|
if cid, err := b.run(); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
id = cid
|
id = cid
|
||||||
}
|
}
|
||||||
b.config.Cmd = cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
container := b.runtime.Get(id)
|
container := b.runtime.Get(id)
|
||||||
|
@ -286,20 +284,20 @@ func (b *buildFile) commit(id string) error {
|
||||||
return fmt.Errorf("An error occured while creating the container")
|
return fmt.Errorf("An error occured while creating the container")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: Actually copy the struct
|
||||||
|
autoConfig := *b.config
|
||||||
|
autoConfig.Cmd = autoCmd
|
||||||
// Commit the container
|
// Commit the container
|
||||||
image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil)
|
image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.tmpImages[image.Id] = struct{}{}
|
b.tmpImages[image.Id] = struct{}{}
|
||||||
b.image = image.Id
|
b.image = image.Id
|
||||||
b.needCommit = false
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
||||||
defer b.clearTmp(b.tmpContainers, b.tmpImages)
|
|
||||||
|
|
||||||
if context != nil {
|
if context != nil {
|
||||||
name, err := ioutil.TempDir("/tmp", "docker-build")
|
name, err := ioutil.TempDir("/tmp", "docker-build")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -337,6 +335,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
||||||
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
||||||
if !exists {
|
if !exists {
|
||||||
fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
||||||
if ret != nil {
|
if ret != nil {
|
||||||
|
@ -345,22 +344,10 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
||||||
|
|
||||||
fmt.Fprintf(b.out, "===> %v\n", b.image)
|
fmt.Fprintf(b.out, "===> %v\n", b.image)
|
||||||
}
|
}
|
||||||
if b.needCommit {
|
|
||||||
if err := b.commit(""); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if b.image != "" {
|
if b.image != "" {
|
||||||
// The build is successful, keep the temporary containers and images
|
fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image)
|
||||||
for i := range b.tmpImages {
|
|
||||||
delete(b.tmpImages, i)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(b.out, "Build success.\n Image id:\n%s\n", b.image)
|
|
||||||
return b.image, nil
|
return b.image, nil
|
||||||
}
|
}
|
||||||
for i := range b.tmpContainers {
|
|
||||||
delete(b.tmpContainers, i)
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("An error occured during the build\n")
|
return "", fmt.Errorf("An error occured during the build\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
232
commands.go
232
commands.go
|
@ -17,6 +17,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -27,7 +28,7 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "0.3.3"
|
const VERSION = "0.3.4"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
GIT_COMMIT string
|
GIT_COMMIT string
|
||||||
|
@ -73,37 +74,37 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
|
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
|
||||||
for cmd, description := range map[string]string{
|
for _, command := range [][2]string{
|
||||||
"attach": "Attach to a running container",
|
{"attach", "Attach to a running container"},
|
||||||
"build": "Build a container from a Dockerfile",
|
{"build", "Build a container from a Dockerfile"},
|
||||||
"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, description)
|
help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
|
||||||
}
|
}
|
||||||
fmt.Println(help)
|
fmt.Println(help)
|
||||||
return nil
|
return nil
|
||||||
|
@ -130,16 +131,20 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
cmd := Subcmd("build", "[OPTIONS] [CONTEXT]", "Build an image from a Dockerfile")
|
cmd := Subcmd("build", "[OPTIONS] PATH | -", "Build a new container image from the source code at PATH")
|
||||||
fileName := cmd.String("f", "Dockerfile", "Use `file` as Dockerfile. Can be '-' for stdin")
|
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if cmd.NArg() != 1 {
|
||||||
|
cmd.Usage()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file io.ReadCloser
|
|
||||||
multipartBody io.Reader
|
multipartBody io.Reader
|
||||||
err error
|
file io.ReadCloser
|
||||||
|
contextPath string
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init the needed component for the Multipart
|
// Init the needed component for the Multipart
|
||||||
|
@ -148,27 +153,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
w := multipart.NewWriter(buff)
|
w := multipart.NewWriter(buff)
|
||||||
boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
|
boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
|
||||||
|
|
||||||
// Create a FormFile multipart for the Dockerfile
|
|
||||||
if *fileName == "-" {
|
|
||||||
file = os.Stdin
|
|
||||||
} else {
|
|
||||||
file, err = os.Open(*fileName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
}
|
|
||||||
if wField, err := w.CreateFormFile("Dockerfile", *fileName); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
io.Copy(wField, file)
|
|
||||||
}
|
|
||||||
multipartBody = io.MultiReader(multipartBody, boundary)
|
|
||||||
|
|
||||||
compression := Bzip2
|
compression := Bzip2
|
||||||
|
|
||||||
// Create a FormFile multipart for the context if needed
|
if cmd.Arg(0) == "-" {
|
||||||
if cmd.Arg(0) != "" {
|
file = os.Stdin
|
||||||
|
} else {
|
||||||
|
// Send Dockerfile from arg/Dockerfile (deprecate later)
|
||||||
|
if f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
file = f
|
||||||
|
}
|
||||||
|
// Send context from arg
|
||||||
|
// Create a FormFile multipart for the context if needed
|
||||||
// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
|
// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
|
||||||
context, err := Tar(cmd.Arg(0), compression)
|
context, err := Tar(cmd.Arg(0), compression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -183,19 +180,28 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
// FIXME: Find a way to have a progressbar for the upload too
|
// FIXME: Find a way to have a progressbar for the upload too
|
||||||
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Caching Context %v/%v (%v)\r", false))
|
sf := utils.NewStreamFormatter(false)
|
||||||
|
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
|
||||||
}
|
}
|
||||||
|
|
||||||
multipartBody = io.MultiReader(multipartBody, boundary)
|
multipartBody = io.MultiReader(multipartBody, boundary)
|
||||||
}
|
}
|
||||||
|
// Create a FormFile multipart for the Dockerfile
|
||||||
|
if wField, err := w.CreateFormFile("Dockerfile", "Dockerfile"); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
io.Copy(wField, file)
|
||||||
|
}
|
||||||
|
multipartBody = io.MultiReader(multipartBody, boundary)
|
||||||
|
|
||||||
|
v := &url.Values{}
|
||||||
|
v.Set("t", *tag)
|
||||||
// Send the multipart request with correct content-type
|
// Send the multipart request with correct content-type
|
||||||
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), multipartBody)
|
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", w.FormDataContentType())
|
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||||
if cmd.Arg(0) != "" {
|
if contextPath != "" {
|
||||||
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
|
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
|
||||||
fmt.Println("Uploading Context...")
|
fmt.Println("Uploading Context...")
|
||||||
}
|
}
|
||||||
|
@ -973,12 +979,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
v := url.Values{}
|
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
|
||||||
v.Set("logs", "1")
|
return err
|
||||||
v.Set("stdout", "1")
|
}
|
||||||
v.Set("stderr", "1")
|
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
|
||||||
|
|
||||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1005,15 +1009,35 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splitStderr := container.Config.Tty
|
||||||
|
|
||||||
|
connections := 1
|
||||||
|
if splitStderr {
|
||||||
|
connections += 1
|
||||||
|
}
|
||||||
|
chErrors := make(chan error, connections)
|
||||||
|
cli.monitorTtySize(cmd.Arg(0))
|
||||||
|
if splitStderr {
|
||||||
|
go func() {
|
||||||
|
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
|
||||||
|
}()
|
||||||
|
}
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("stream", "1")
|
v.Set("stream", "1")
|
||||||
v.Set("stdout", "1")
|
|
||||||
v.Set("stderr", "1")
|
|
||||||
v.Set("stdin", "1")
|
v.Set("stdin", "1")
|
||||||
|
v.Set("stdout", "1")
|
||||||
cli.monitorTtySize(cmd.Arg(0))
|
if !splitStderr {
|
||||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
|
v.Set("stderr", "1")
|
||||||
return err
|
}
|
||||||
|
go func() {
|
||||||
|
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
|
||||||
|
}()
|
||||||
|
for connections > 0 {
|
||||||
|
err := <-chErrors
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
connections -= 1
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1177,19 +1201,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
|
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
|
||||||
}
|
}
|
||||||
|
|
||||||
v := url.Values{}
|
splitStderr := !config.Tty
|
||||||
v.Set("logs", "1")
|
|
||||||
v.Set("stream", "1")
|
|
||||||
|
|
||||||
if config.AttachStdin {
|
connections := 0
|
||||||
v.Set("stdin", "1")
|
if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
|
||||||
|
connections += 1
|
||||||
}
|
}
|
||||||
if config.AttachStdout {
|
if splitStderr && config.AttachStderr {
|
||||||
v.Set("stdout", "1")
|
connections += 1
|
||||||
}
|
|
||||||
if config.AttachStderr {
|
|
||||||
v.Set("stderr", "1")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//start the container
|
//start the container
|
||||||
|
@ -1198,10 +1217,38 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
if connections > 0 {
|
||||||
|
chErrors := make(chan error, connections)
|
||||||
cli.monitorTtySize(out.Id)
|
cli.monitorTtySize(out.Id)
|
||||||
if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
|
|
||||||
return err
|
if splitStderr && config.AttachStderr {
|
||||||
|
go func() {
|
||||||
|
chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("logs", "1")
|
||||||
|
v.Set("stream", "1")
|
||||||
|
|
||||||
|
if config.AttachStdin {
|
||||||
|
v.Set("stdin", "1")
|
||||||
|
}
|
||||||
|
if config.AttachStdout {
|
||||||
|
v.Set("stdout", "1")
|
||||||
|
}
|
||||||
|
if !splitStderr && config.AttachStderr {
|
||||||
|
v.Set("stderr", "1")
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
|
||||||
|
}()
|
||||||
|
for connections > 0 {
|
||||||
|
err := <-chErrors
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
connections -= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !config.AttachStdout && !config.AttachStderr {
|
if !config.AttachStdout && !config.AttachStderr {
|
||||||
|
@ -1290,13 +1337,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Header.Get("Content-Type") == "application/json" {
|
if resp.Header.Get("Content-Type") == "application/json" {
|
||||||
type Message struct {
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
Progress string `json:"progress,omitempty"`
|
|
||||||
}
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
dec := json.NewDecoder(resp.Body)
|
||||||
for {
|
for {
|
||||||
var m Message
|
var m utils.JsonMessage
|
||||||
if err := dec.Decode(&m); err == io.EOF {
|
if err := dec.Decode(&m); err == io.EOF {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -1304,6 +1347,8 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
||||||
}
|
}
|
||||||
if m.Progress != "" {
|
if m.Progress != "" {
|
||||||
fmt.Fprintf(out, "Downloading %s\r", m.Progress)
|
fmt.Fprintf(out, "Downloading %s\r", m.Progress)
|
||||||
|
} else if m.Error != "" {
|
||||||
|
return fmt.Errorf(m.Error)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(out, "%s\n", m.Status)
|
fmt.Fprintf(out, "%s\n", m.Status)
|
||||||
}
|
}
|
||||||
|
@ -1316,7 +1361,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
|
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil)
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1334,20 +1379,19 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
|
||||||
defer rwc.Close()
|
defer rwc.Close()
|
||||||
|
|
||||||
receiveStdout := utils.Go(func() error {
|
receiveStdout := utils.Go(func() error {
|
||||||
_, err := io.Copy(os.Stdout, br)
|
_, err := io.Copy(out, br)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
|
if in != nil && setRawTerminal && term.IsTerminal(int(in.Fd())) && os.Getenv("NORAW") == "" {
|
||||||
if oldState, err := term.SetRawTerminal(); err != nil {
|
if oldState, err := term.SetRawTerminal(); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
defer term.RestoreTerminal(oldState)
|
defer term.RestoreTerminal(oldState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendStdin := utils.Go(func() error {
|
sendStdin := utils.Go(func() error {
|
||||||
_, err := io.Copy(rwc, os.Stdin)
|
_, err := io.Copy(rwc, in)
|
||||||
if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
|
if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err)
|
fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,15 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# these should match the names found at http://www.debian.org/releases/
|
# these should match the names found at http://www.debian.org/releases/
|
||||||
stableSuite='squeeze'
|
stableSuite='wheezy'
|
||||||
testingSuite='wheezy'
|
testingSuite='jessie'
|
||||||
unstableSuite='sid'
|
unstableSuite='sid'
|
||||||
|
|
||||||
# if suite is equal to this, it gets the "latest" tag
|
|
||||||
latestSuite="$testingSuite"
|
|
||||||
|
|
||||||
variant='minbase'
|
variant='minbase'
|
||||||
include='iproute,iputils-ping'
|
include='iproute,iputils-ping'
|
||||||
|
|
||||||
repo="$1"
|
repo="$1"
|
||||||
suite="${2:-$latestSuite}"
|
suite="${2:-$stableSuite}"
|
||||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||||
|
|
||||||
if [ ! "$repo" ]; then
|
if [ ! "$repo" ]; then
|
||||||
|
@ -41,17 +38,14 @@ img=$(sudo tar -c . | docker import -)
|
||||||
# tag suite
|
# tag suite
|
||||||
docker tag $img $repo $suite
|
docker tag $img $repo $suite
|
||||||
|
|
||||||
if [ "$suite" = "$latestSuite" ]; then
|
|
||||||
# tag latest
|
|
||||||
docker tag $img $repo latest
|
|
||||||
fi
|
|
||||||
|
|
||||||
# test the image
|
# test the image
|
||||||
docker run -i -t $repo:$suite echo success
|
docker run -i -t $repo:$suite echo success
|
||||||
|
|
||||||
# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag
|
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
|
||||||
if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then
|
# tag latest
|
||||||
# tag the specific version
|
docker tag $img $repo latest
|
||||||
|
|
||||||
|
# tag the specific debian release version
|
||||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||||
docker tag $img $repo $ver
|
docker tag $img $repo $ver
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -6,6 +6,7 @@ SPHINXOPTS =
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
PAPER =
|
PAPER =
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
PYTHON = python
|
||||||
|
|
||||||
# Internal variables.
|
# Internal variables.
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
@ -38,6 +39,7 @@ help:
|
||||||
# @echo " linkcheck to check all external links for integrity"
|
# @echo " linkcheck to check all external links for integrity"
|
||||||
# @echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
# @echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
@echo " docs to build the docs and copy the static files to the outputdir"
|
@echo " docs to build the docs and copy the static files to the outputdir"
|
||||||
|
@echo " server to serve the docs in your browser under \`http://localhost:8000\`"
|
||||||
@echo " publish to publish the app to dotcloud"
|
@echo " publish to publish the app to dotcloud"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@ -49,6 +51,8 @@ docs:
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
|
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
server:
|
||||||
|
@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
|
||||||
|
|
||||||
site:
|
site:
|
||||||
cp -r website $(BUILDDIR)/
|
cp -r website $(BUILDDIR)/
|
||||||
|
|
|
@ -14,20 +14,22 @@ Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* Work in your own fork of the code, we accept pull requests.
|
* Work in your own fork of the code, we accept pull requests.
|
||||||
* Install sphinx: ``pip install sphinx``
|
* Install sphinx: `pip install sphinx`
|
||||||
* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain``
|
* Mac OS X: `[sudo] pip-2.7 install sphinx`)
|
||||||
|
* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain`
|
||||||
|
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
|
||||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
* change the .rst files with your favorite editor to your liking
|
* Change the `.rst` files with your favorite editor to your liking.
|
||||||
* run *make docs* to clean up old files and generate new ones
|
* Run `make docs` to clean up old files and generate new ones.
|
||||||
* your static website can now be found in the _build dir
|
* Your static website can now be found in the `_build` directory.
|
||||||
* to preview what you have generated, cd into _build/html and then run 'python -m SimpleHTTPServer 8000'
|
* To preview what you have generated run `make server` and open <http://localhost:8000/> in your favorite browser.
|
||||||
|
|
||||||
Working using github's file editor
|
Working using GitHub's file editor
|
||||||
----------------------------------
|
----------------------------------
|
||||||
Alternatively, for small changes and typo's you might want to use github's built in file editor. It allows
|
Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows
|
||||||
you to preview your changes right online. Just be carefull not to create many commits.
|
you to preview your changes right online. Just be carefull not to create many commits.
|
||||||
|
|
||||||
Images
|
Images
|
||||||
|
@ -72,4 +74,4 @@ Guides on using sphinx
|
||||||
|
|
||||||
* Code examples
|
* Code examples
|
||||||
|
|
||||||
Start without $, so it's easy to copy and paste.
|
Start without $, so it's easy to copy and paste.
|
||||||
|
|
|
@ -15,10 +15,17 @@ Docker Remote API
|
||||||
- Default port in the docker deamon is 4243
|
- Default port in the docker deamon is 4243
|
||||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||||
|
|
||||||
2. Endpoints
|
2. Version
|
||||||
|
==========
|
||||||
|
|
||||||
|
The current verson of the API is 1.1
|
||||||
|
Calling /images/<name>/insert is the same as calling /v1.1/images/<name>/insert
|
||||||
|
You can still call an old version of the api using /v1.0/images/<name>/insert
|
||||||
|
|
||||||
|
3. Endpoints
|
||||||
============
|
============
|
||||||
|
|
||||||
2.1 Containers
|
3.1 Containers
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
List containers
|
List containers
|
||||||
|
@ -132,6 +139,7 @@ Create a container
|
||||||
:jsonparam config: the container's configuration
|
:jsonparam config: the container's configuration
|
||||||
:statuscode 201: no error
|
:statuscode 201: no error
|
||||||
:statuscode 404: no such container
|
:statuscode 404: no such container
|
||||||
|
:statuscode 406: impossible to attach (container not running)
|
||||||
:statuscode 500: server error
|
:statuscode 500: server error
|
||||||
|
|
||||||
|
|
||||||
|
@ -459,7 +467,7 @@ Remove a container
|
||||||
:statuscode 500: server error
|
:statuscode 500: server error
|
||||||
|
|
||||||
|
|
||||||
2.2 Images
|
3.2 Images
|
||||||
----------
|
----------
|
||||||
|
|
||||||
List Images
|
List Images
|
||||||
|
@ -548,7 +556,19 @@ Create an image
|
||||||
|
|
||||||
POST /images/create?fromImage=base HTTP/1.1
|
POST /images/create?fromImage=base HTTP/1.1
|
||||||
|
|
||||||
**Example response**:
|
**Example response v1.1**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{"status":"Pulling..."}
|
||||||
|
{"progress":"1/? (n/a)"}
|
||||||
|
{"error":"Invalid..."}
|
||||||
|
...
|
||||||
|
|
||||||
|
**Example response v1.0**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
@ -579,7 +599,19 @@ Insert a file in a image
|
||||||
|
|
||||||
POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
|
POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
|
||||||
|
|
||||||
**Example response**:
|
**Example response v1.1**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{"status":"Inserting..."}
|
||||||
|
{"progress":"1/? (n/a)"}
|
||||||
|
{"error":"Invalid..."}
|
||||||
|
...
|
||||||
|
|
||||||
|
**Example response v1.0**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
@ -694,7 +726,19 @@ Push an image on the registry
|
||||||
|
|
||||||
POST /images/test/push HTTP/1.1
|
POST /images/test/push HTTP/1.1
|
||||||
|
|
||||||
**Example response**:
|
**Example response v1.1**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{"status":"Pushing..."}
|
||||||
|
{"progress":"1/? (n/a)"}
|
||||||
|
{"error":"Invalid..."}
|
||||||
|
...
|
||||||
|
|
||||||
|
**Example response v1.0**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
@ -800,7 +844,7 @@ Search images
|
||||||
:statuscode 500: server error
|
:statuscode 500: server error
|
||||||
|
|
||||||
|
|
||||||
2.3 Misc
|
3.3 Misc
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Build an image from Dockerfile via stdin
|
Build an image from Dockerfile via stdin
|
||||||
|
@ -826,6 +870,7 @@ Build an image from Dockerfile via stdin
|
||||||
|
|
||||||
{{ STREAM }}
|
{{ STREAM }}
|
||||||
|
|
||||||
|
:query t: tag to be applied to the resulting image in case of success
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 500: server error
|
:statuscode 500: server error
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
:description: docker documentation
|
:description: docker documentation
|
||||||
:keywords: docker, ipa, documentation
|
:keywords: docker, ipa, documentation
|
||||||
|
|
||||||
API's
|
APIs
|
||||||
=============
|
====
|
||||||
|
|
||||||
This following :
|
This following :
|
||||||
|
|
||||||
|
|
|
@ -246,7 +246,6 @@ The Index has two main purposes (along with its fancy social features):
|
||||||
|
|
||||||
- Resolve short names (to avoid passing absolute URLs all the time)
|
- Resolve short names (to avoid passing absolute URLs all the time)
|
||||||
- username/projectname -> \https://registry.docker.io/users/<username>/repositories/<projectname>/
|
- username/projectname -> \https://registry.docker.io/users/<username>/repositories/<projectname>/
|
||||||
- team/projectname -> \https://registry.docker.io/team/<team>/repositories/<projectname>/
|
|
||||||
- Authenticate a user as a repos owner (for a central referenced repository)
|
- Authenticate a user as a repos owner (for a central referenced repository)
|
||||||
|
|
||||||
3.1 Without an Index
|
3.1 Without an Index
|
||||||
|
|
|
@ -2,12 +2,27 @@
|
||||||
:description: Build a new image from the Dockerfile passed via stdin
|
:description: Build a new image from the Dockerfile passed via stdin
|
||||||
:keywords: build, docker, container, documentation
|
:keywords: build, docker, container, documentation
|
||||||
|
|
||||||
========================================================
|
================================================
|
||||||
``build`` -- Build a container from Dockerfile via stdin
|
``build`` -- Build a container from a Dockerfile
|
||||||
========================================================
|
================================================
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
Usage: docker build -
|
Usage: docker build [OPTIONS] PATH | -
|
||||||
Example: cat Dockerfile | docker build -
|
Build a new container image from the source code at PATH
|
||||||
Build a new image from the Dockerfile passed via stdin
|
-t="": Tag to be applied to the resulting image in case of success.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker build .
|
||||||
|
|
||||||
|
This will take the local Dockerfile
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker build -
|
||||||
|
|
||||||
|
This will read a Dockerfile form Stdin without context
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
:title: Building Blocks
|
|
||||||
:description: An introduction to docker and standard containers?
|
|
||||||
:keywords: containers, lxc, concepts, explanation
|
|
||||||
|
|
||||||
|
|
||||||
Building blocks
|
|
||||||
===============
|
|
||||||
|
|
||||||
.. _images:
|
|
||||||
|
|
||||||
Images
|
|
||||||
------
|
|
||||||
An original container image. These are stored on disk and are comparable with what you normally expect from a stopped virtual machine image. Images are stored (and retrieved from) repository
|
|
||||||
|
|
||||||
Images are stored on your local file system under /var/lib/docker/graph
|
|
||||||
|
|
||||||
|
|
||||||
.. _containers:
|
|
||||||
|
|
||||||
Containers
|
|
||||||
----------
|
|
||||||
A container is a local version of an image. It can be running or stopped, The equivalent would be a virtual machine instance.
|
|
||||||
|
|
||||||
Containers are stored on your local file system under /var/lib/docker/containers
|
|
||||||
|
|
|
@ -13,5 +13,4 @@ Contents:
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
../index
|
../index
|
||||||
buildingblocks
|
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
|
|
||||||
Docker - The Linux container runtime
|
Docker -- The Linux container runtime
|
||||||
------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
Docker 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.
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
:title: Setting up a dev environment
|
:title: Setting Up a Dev Environment
|
||||||
:description: Guides on how to contribute to docker
|
:description: Guides on how to contribute to docker
|
||||||
:keywords: Docker, documentation, developers, contributing, dev environment
|
:keywords: Docker, documentation, developers, contributing, dev environment
|
||||||
|
|
||||||
Setting up a dev environment
|
Setting Up a Dev Environment
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Instructions that have been verified to work on Ubuntu 12.10,
|
Instructions that have been verified to work on Ubuntu 12.10,
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
.. _running_couchdb_service:
|
.. _running_couchdb_service:
|
||||||
|
|
||||||
Create a CouchDB service
|
CouchDB Service
|
||||||
========================
|
===============
|
||||||
|
|
||||||
.. include:: example_header.inc
|
.. include:: example_header.inc
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
:title: Docker Examples
|
:title: Docker Examples
|
||||||
:description: Examples on how to use Docker
|
:description: Examples on how to use Docker
|
||||||
:keywords: docker, hello world, examples
|
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ Contents:
|
||||||
hello_world
|
hello_world
|
||||||
hello_world_daemon
|
hello_world_daemon
|
||||||
python_web_app
|
python_web_app
|
||||||
|
nodejs_web_app
|
||||||
running_redis_service
|
running_redis_service
|
||||||
running_ssh_service
|
running_ssh_service
|
||||||
couchdb_data_volumes
|
couchdb_data_volumes
|
||||||
|
|
236
docs/sources/examples/nodejs_web_app.rst
Normal file
236
docs/sources/examples/nodejs_web_app.rst
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
:title: Running a Node.js app on CentOS
|
||||||
|
:description: Installing and running a Node.js app on CentOS
|
||||||
|
:keywords: docker, example, package installation, node, centos
|
||||||
|
|
||||||
|
.. _nodejs_web_app:
|
||||||
|
|
||||||
|
Node.js Web App
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. include:: example_header.inc
|
||||||
|
|
||||||
|
The goal of this example is to show you how you can build your own docker images
|
||||||
|
from a parent image using a ``Dockerfile`` . We will do that by making a simple
|
||||||
|
Node.js hello world web application running on CentOS. You can get the full
|
||||||
|
source code at https://github.com/gasi/docker-node-hello.
|
||||||
|
|
||||||
|
Create Node.js app
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
First, create a ``package.json`` file that describes your app and its
|
||||||
|
dependencies:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "docker-centos-hello",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Node.js Hello World app on CentOS using docker",
|
||||||
|
"author": "Daniel Gasienica <daniel@gasienica.ch>",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "3.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Then, create an ``index.js`` file that defines a web app using the
|
||||||
|
`Express.js <http://expressjs.com/>`_ framework:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
var express = require('express');
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
var PORT = 8080;
|
||||||
|
|
||||||
|
// App
|
||||||
|
var app = express();
|
||||||
|
app.get('/', function (req, res) {
|
||||||
|
res.send('Hello World\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT)
|
||||||
|
console.log('Running on http://localhost:' + PORT);
|
||||||
|
|
||||||
|
|
||||||
|
In the next steps, we’ll look at how you can run this app inside a CentOS
|
||||||
|
container using docker. First, you’ll need to build a docker image of your app.
|
||||||
|
|
||||||
|
Creating a ``Dockerfile``
|
||||||
|
+++++++++++++++++++++++++
|
||||||
|
|
||||||
|
Create an empty file called ``Dockerfile``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
touch Dockerfile
|
||||||
|
|
||||||
|
Open the ``Dockerfile`` in your favorite text editor and add the following line
|
||||||
|
that defines the version of docker the image requires to build
|
||||||
|
(this example uses docker 0.3.4):
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# DOCKER-VERSION 0.3.4
|
||||||
|
|
||||||
|
Next, define the parent image you want to use to build your own image on top of.
|
||||||
|
Here, we’ll use `CentOS <https://index.docker.io/_/centos/>`_ (tag: ``6.4``)
|
||||||
|
available on the `docker index`_:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
FROM centos:6.4
|
||||||
|
|
||||||
|
Since we’re building a Node.js app, you’ll have to install Node.js as well as
|
||||||
|
npm on your CentOS image. Node.js is required to run your app and npm to install
|
||||||
|
your app’s dependencies defined in ``package.json``.
|
||||||
|
To install the right package for CentOS, we’ll use the instructions from the
|
||||||
|
`Node.js wiki`_:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# Enable EPEL for Node.js
|
||||||
|
RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
|
||||||
|
# Install Node.js and npm
|
||||||
|
RUN yum install -y npm-1.2.17-5.el6
|
||||||
|
|
||||||
|
To bundle your app’s source code inside the docker image, use the ``ADD``
|
||||||
|
command:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# Bundle app source
|
||||||
|
ADD . /src
|
||||||
|
|
||||||
|
Install your app dependencies using npm:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# Install app dependencies
|
||||||
|
RUN cd /src; npm install
|
||||||
|
|
||||||
|
Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` command to have it
|
||||||
|
mapped by the docker daemon:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
Last but not least, define the command to run your app using ``CMD`` which
|
||||||
|
defines your runtime, i.e. ``node``, and the path to our app, i.e.
|
||||||
|
``src/index.js`` (see the step where we added the source to the container):
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
CMD ["node", "/src/index.js"]
|
||||||
|
|
||||||
|
Your ``Dockerfile`` should now look like this:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
|
||||||
|
# DOCKER-VERSION 0.3.4
|
||||||
|
FROM centos:6.4
|
||||||
|
|
||||||
|
# Enable EPEL for Node.js
|
||||||
|
RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
|
||||||
|
# Install Node.js and npm
|
||||||
|
RUN yum install -y npm-1.2.17-5.el6
|
||||||
|
|
||||||
|
# Bundle app source
|
||||||
|
ADD . /src
|
||||||
|
# Install app dependencies
|
||||||
|
RUN cd /src; npm install
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["node", "/src/index.js"]
|
||||||
|
|
||||||
|
|
||||||
|
Building your image
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
Go to the directory that has your ``Dockerfile`` and run the following command
|
||||||
|
to build a docker image. The ``-t`` flag let’s you tag your image so it’s easier
|
||||||
|
to find later using the ``docker images`` command:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker build -t <your username>/centos-node-hello .
|
||||||
|
|
||||||
|
Your image will now be listed by docker:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker images
|
||||||
|
|
||||||
|
> # Example
|
||||||
|
> REPOSITORY TAG ID CREATED
|
||||||
|
> centos 6.4 539c0211cd76 8 weeks ago
|
||||||
|
> gasi/centos-node-hello latest d64d3505b0d2 2 hours ago
|
||||||
|
|
||||||
|
|
||||||
|
Run the image
|
||||||
|
+++++++++++++
|
||||||
|
|
||||||
|
Running your image with ``-d`` runs the container in detached mode, leaving the
|
||||||
|
container running in the background. Run the image you previously built:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker run -d <your username>/centos-node-hello
|
||||||
|
|
||||||
|
Print the output of your app:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# Get container ID
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# Print app output
|
||||||
|
docker logs <container id>
|
||||||
|
|
||||||
|
> # Example
|
||||||
|
> Running on http://localhost:8080
|
||||||
|
|
||||||
|
|
||||||
|
Test
|
||||||
|
++++
|
||||||
|
|
||||||
|
To test your app, get the the port of your app that docker mapped:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
> # Example
|
||||||
|
> ID IMAGE COMMAND ... PORTS
|
||||||
|
> ecce33b30ebf gasi/centos-node-hello:latest node /src/index.js 49160->8080
|
||||||
|
|
||||||
|
In the example above, docker mapped the ``8080`` port of the container to
|
||||||
|
``49160``.
|
||||||
|
|
||||||
|
Now you can call your app using ``curl`` (install if needed via:
|
||||||
|
``sudo apt-get install curl``):
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
curl -i localhost:49160
|
||||||
|
|
||||||
|
> HTTP/1.1 200 OK
|
||||||
|
> X-Powered-By: Express
|
||||||
|
> Content-Type: text/html; charset=utf-8
|
||||||
|
> Content-Length: 12
|
||||||
|
> Date: Sun, 02 Jun 2013 03:53:22 GMT
|
||||||
|
> Connection: keep-alive
|
||||||
|
>
|
||||||
|
> Hello World
|
||||||
|
|
||||||
|
We hope this tutorial helped you get up and running with Node.js and CentOS on
|
||||||
|
docker. You can get the full source code at
|
||||||
|
https://github.com/gasi/docker-node-hello.
|
||||||
|
|
||||||
|
Continue to :ref:`running_redis_service`.
|
||||||
|
|
||||||
|
|
||||||
|
.. _Node.js wiki: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#rhelcentosscientific-linux-6
|
||||||
|
.. _docker index: https://index.docker.io/
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
.. _python_web_app:
|
.. _python_web_app:
|
||||||
|
|
||||||
Building a python web app
|
Python Web App
|
||||||
=========================
|
==============
|
||||||
|
|
||||||
.. include:: example_header.inc
|
.. include:: example_header.inc
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
.. _running_examples:
|
.. _running_examples:
|
||||||
|
|
||||||
Running The Examples
|
Running the Examples
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
|
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
.. _running_redis_service:
|
.. _running_redis_service:
|
||||||
|
|
||||||
Create a redis service
|
Redis Service
|
||||||
======================
|
=============
|
||||||
|
|
||||||
.. include:: example_header.inc
|
.. include:: example_header.inc
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ Snapshot the installation
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker ps -a # grab the container id (this will be the last one in the list)
|
docker ps -a # grab the container id (this will be the first one in the list)
|
||||||
docker commit <container_id> <your username>/redis
|
docker commit <container_id> <your username>/redis
|
||||||
|
|
||||||
Run the service
|
Run the service
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
.. _running_ssh_service:
|
.. _running_ssh_service:
|
||||||
|
|
||||||
Create an ssh daemon service
|
SSH Daemon Service
|
||||||
============================
|
==================
|
||||||
|
|
||||||
.. include:: example_header.inc
|
.. include:: example_header.inc
|
||||||
|
|
||||||
|
@ -20,8 +20,7 @@ minutes and not entirely smooth, but gives you a good idea.
|
||||||
<div style="margin-top:10px;">
|
<div style="margin-top:10px;">
|
||||||
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
|
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
You can also get this sshd container by using
|
You can also get this sshd container by using
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -30,3 +29,49 @@ You can also get this sshd container by using
|
||||||
|
|
||||||
The password is 'screencast'
|
The password is 'screencast'
|
||||||
|
|
||||||
|
**Video's Transcription:**
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# Hello! We are going to try and install openssh on a container and run it as a servic
|
||||||
|
# let's pull base to get a base ubuntu image.
|
||||||
|
$ docker pull base
|
||||||
|
# I had it so it was quick
|
||||||
|
# now let's connect using -i for interactive and with -t for terminal
|
||||||
|
# we execute /bin/bash to get a prompt.
|
||||||
|
$ docker run -i -t base /bin/bash
|
||||||
|
# now let's commit it
|
||||||
|
# which container was it?
|
||||||
|
$ docker ps -a |more
|
||||||
|
$ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd
|
||||||
|
# I gave the name dhrp/sshd for the container
|
||||||
|
# now we can run it again
|
||||||
|
$ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode
|
||||||
|
# is it running?
|
||||||
|
$ docker ps
|
||||||
|
# yes!
|
||||||
|
# let's stop it
|
||||||
|
$ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562
|
||||||
|
$ docker ps
|
||||||
|
# and reconnect, but now open a port to it
|
||||||
|
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
|
||||||
|
$ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22
|
||||||
|
# it has now given us a port to connect to
|
||||||
|
# we have to connect using a public ip of our host
|
||||||
|
$ hostname
|
||||||
|
$ ifconfig
|
||||||
|
$ ssh root@192.168.33.10 -p 49153
|
||||||
|
# Ah! forgot to set root passwd
|
||||||
|
$ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd
|
||||||
|
$ docker ps -a
|
||||||
|
$ docker run -i -t dhrp/sshd /bin/bash
|
||||||
|
$ passwd
|
||||||
|
$ exit
|
||||||
|
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
|
||||||
|
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
|
||||||
|
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
|
||||||
|
$ ifconfig
|
||||||
|
$ ssh root@192.168.33.10 -p 49154
|
||||||
|
# Thanks for watching, Thatcher thatcher@dotcloud.com
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
|
|
||||||
Docker - The Linux container runtime
|
Docker -- The Linux container runtime
|
||||||
------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
Docker 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.
|
||||||
|
|
||||||
|
|
|
@ -67,3 +67,21 @@ To start on system boot:
|
||||||
::
|
::
|
||||||
|
|
||||||
sudo systemctl enable docker
|
sudo systemctl enable docker
|
||||||
|
|
||||||
|
Network Configuration
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
IPv4 packet forwarding is disabled by default on Arch, so internet access from inside
|
||||||
|
the container may not work.
|
||||||
|
|
||||||
|
To enable the forwarding, run as root on the host system:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sysctl net.ipv4.ip_forward=1
|
||||||
|
|
||||||
|
And, to make it persistent across reboots, enable it on the host's **/etc/sysctl.conf**:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
net.ipv4.ip_forward=1
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
:keywords: Examples, Usage, basic commands, docker, documentation, examples
|
:keywords: Examples, Usage, basic commands, docker, documentation, examples
|
||||||
|
|
||||||
|
|
||||||
The basics
|
The Basics
|
||||||
=============
|
==========
|
||||||
|
|
||||||
Starting Docker
|
Starting Docker
|
||||||
---------------
|
---------------
|
||||||
|
|
|
@ -125,8 +125,14 @@ curl was installed within the image.
|
||||||
.. note::
|
.. note::
|
||||||
The path must include the file name.
|
The path must include the file name.
|
||||||
|
|
||||||
.. note::
|
2.8 ADD
|
||||||
This instruction has temporarily disabled
|
-------
|
||||||
|
|
||||||
|
``ADD <src> <dest>``
|
||||||
|
|
||||||
|
The `ADD` instruction will insert the files from the `<src>` path of the context into `<dest>` path
|
||||||
|
of the container.
|
||||||
|
The context must be set in order to use this instruction. (see examples)
|
||||||
|
|
||||||
3. Dockerfile Examples
|
3. Dockerfile Examples
|
||||||
======================
|
======================
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
.. _working_with_the_repository:
|
.. _working_with_the_repository:
|
||||||
|
|
||||||
Working with the repository
|
Working with the Repository
|
||||||
============================
|
===========================
|
||||||
|
|
||||||
|
|
||||||
Top-level repositories and user repositories
|
Top-level repositories and user repositories
|
||||||
|
@ -14,9 +14,9 @@ Top-level repositories and user repositories
|
||||||
Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind
|
Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind
|
||||||
Docker, and user repositories.
|
Docker, and user repositories.
|
||||||
|
|
||||||
* Top-level repositories can easily be recognized by not having a / (slash) in their name. These repositories can
|
* Top-level repositories can easily be recognized by not having a ``/`` (slash) in their name. These repositories can
|
||||||
generally be trusted.
|
generally be trusted.
|
||||||
* User repositories always come in the form of <username>/<repo_name>. This is what your published images will look like.
|
* User repositories always come in the form of ``<username>/<repo_name>``. This is what your published images will look like.
|
||||||
* User images are not checked, it is therefore up to you whether or not you trust the creator of this image.
|
* User images are not checked, it is therefore up to you whether or not you trust the creator of this image.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -270,7 +270,7 @@
|
||||||
<li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
|
<li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
|
||||||
<li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
|
<li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
|
||||||
<li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
|
<li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
|
||||||
<li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.</li>
|
<li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.</li>
|
||||||
<li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
|
<li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
|
||||||
<li>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.</li>
|
<li>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.</li>
|
||||||
<li>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.</li>
|
<li>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.</li>
|
||||||
|
|
4
graph.go
4
graph.go
|
@ -107,6 +107,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
|
||||||
DockerVersion: VERSION,
|
DockerVersion: VERSION,
|
||||||
Author: author,
|
Author: author,
|
||||||
Config: config,
|
Config: config,
|
||||||
|
Architecture: "x86_64",
|
||||||
}
|
}
|
||||||
if container != nil {
|
if container != nil {
|
||||||
img.Parent = container.Image
|
img.Parent = container.Image
|
||||||
|
@ -165,7 +166,8 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)", false), tmp.Root)
|
sf := utils.NewStreamFormatter(false)
|
||||||
|
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
|
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
|
||||||
|
|
|
@ -1,23 +1,31 @@
|
||||||
# This will build a container capable of producing an official binary build of docker and
|
# This will build a container capable of producing an official binary build of docker and
|
||||||
# uploading it to S3
|
# uploading it to S3
|
||||||
|
from ubuntu:12.04
|
||||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||||
from ubuntu:12.10
|
# Workaround the upstart issue
|
||||||
|
run dpkg-divert --local --rename --add /sbin/initctl
|
||||||
|
run ln -s /bin/true /sbin/initctl
|
||||||
|
# Enable universe and gophers PPA
|
||||||
|
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties
|
||||||
|
run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
|
||||||
|
run add-apt-repository -y ppa:gophers/go/ubuntu
|
||||||
run apt-get update
|
run apt-get update
|
||||||
|
# Packages required to checkout, build and upload docker
|
||||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
|
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
|
||||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||||
# Packages required to checkout and build docker
|
|
||||||
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.linux-amd64.tar.gz
|
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.linux-amd64.tar.gz
|
||||||
run tar -C /usr/local -xzf /go.tar.gz
|
run tar -C /usr/local -xzf /go.tar.gz
|
||||||
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bashrc
|
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
|
||||||
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bash_profile
|
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
|
||||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
|
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
|
||||||
# Packages required to build an ubuntu package
|
# Packages required to build an ubuntu package
|
||||||
|
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable
|
||||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
|
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
|
||||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
|
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
|
||||||
copy fake_initctl /usr/local/bin/initctl
|
|
||||||
run apt-get install -y -q devscripts
|
run apt-get install -y -q devscripts
|
||||||
add . /src
|
# Copy dockerbuilder files into the container
|
||||||
|
add . /src
|
||||||
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
|
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
|
||||||
run cp /src/s3cfg /.s3cfg
|
run cp /src/s3cfg /.s3cfg
|
||||||
cmd ["dockerbuilder"]
|
cmd ["dockerbuilder"]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
set -x
|
set -x
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
export PATH=$PATH:/usr/local/go/bin
|
export PATH=/usr/local/go/bin:$PATH
|
||||||
|
|
||||||
PACKAGE=github.com/dotcloud/docker
|
PACKAGE=github.com/dotcloud/docker
|
||||||
|
|
||||||
|
@ -36,5 +36,6 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$NO_UBUNTU" ]; then
|
if [ -z "$NO_UBUNTU" ]; then
|
||||||
|
export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
|
||||||
(cd packaging/ubuntu && make ubuntu)
|
(cd packaging/ubuntu && make ubuntu)
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
echo Whatever you say, man
|
|
2
hack/infrastructure/MAINTAINERS
Normal file
2
hack/infrastructure/MAINTAINERS
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Ken Cochrane <ken@dotcloud.com>
|
||||||
|
Jerome Petazzoni <jerome@dotcloud.com>
|
5
hack/infrastructure/README.md
Normal file
5
hack/infrastructure/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Docker project infrastructure
|
||||||
|
|
||||||
|
This directory holds all information about the technical infrastructure of the docker project; servers, dns, email, and all the corresponding tools and configuration.
|
||||||
|
|
||||||
|
Obviously credentials should not be stored in this repo, but how to obtain and use them should be documented here.
|
1
image.go
1
image.go
|
@ -27,6 +27,7 @@ type Image struct {
|
||||||
DockerVersion string `json:"docker_version,omitempty"`
|
DockerVersion string `json:"docker_version,omitempty"`
|
||||||
Author string `json:"author,omitempty"`
|
Author string `json:"author,omitempty"`
|
||||||
Config *Config `json:"config,omitempty"`
|
Config *Config `json:"config,omitempty"`
|
||||||
|
Architecture string `json:"architecture,omitempty"`
|
||||||
graph *Graph
|
graph *Graph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
lxc-docker (0.3.4-1) UNRELEASED; urgency=low
|
||||||
|
- Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
|
||||||
|
- Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
|
||||||
|
- Runtime: interactive TTYs correctly handle window resize
|
||||||
|
- Runtime: fix how configuration is merged between layers
|
||||||
|
- Remote API: split stdout and stderr on 'docker run'
|
||||||
|
- Remote API: optionally listen on a different IP and port (use at your own risk)
|
||||||
|
- Documentation: improved install instructions.
|
||||||
|
|
||||||
|
-- dotCloud <ops@dotcloud.com> Thu, 30 May 2013 00:00:00 -0700
|
||||||
|
|
||||||
lxc-docker (0.3.2-1) UNRELEASED; urgency=low
|
lxc-docker (0.3.2-1) UNRELEASED; urgency=low
|
||||||
- Runtime: Store the actual archive on commit
|
- Runtime: Store the actual archive on commit
|
||||||
- Registry: Improve the checksum process
|
- Registry: Improve the checksum process
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
lxc-docker (0.3.4-1) precise; urgency=low
|
||||||
|
- Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
|
||||||
|
- Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
|
||||||
|
- Runtime: interactive TTYs correctly handle window resize
|
||||||
|
- Runtime: fix how configuration is merged between layers
|
||||||
|
- Remote API: split stdout and stderr on 'docker run'
|
||||||
|
- Remote API: optionally listen on a different IP and port (use at your own risk)
|
||||||
|
- Documentation: improved install instructions.
|
||||||
|
|
||||||
|
-- dotCloud <ops@dotcloud.com> Thu, 30 May 2013 00:00:00 -0700
|
||||||
|
|
||||||
|
|
||||||
lxc-docker (0.3.3-1) precise; urgency=low
|
lxc-docker (0.3.3-1) precise; urgency=low
|
||||||
- Registry: Fix push regression
|
- Registry: Fix push regression
|
||||||
- Various bugfixes
|
- Various bugfixes
|
||||||
|
|
|
@ -5,6 +5,5 @@ stop on starting rc RUNLEVEL=[016]
|
||||||
respawn
|
respawn
|
||||||
|
|
||||||
script
|
script
|
||||||
# FIXME: docker should not depend on the system having en_US.UTF-8
|
/usr/bin/docker -d
|
||||||
LC_ALL='en_US.UTF-8' /usr/bin/docker -d
|
|
||||||
end script
|
end script
|
||||||
|
|
|
@ -5,9 +5,12 @@ import (
|
||||||
"github.com/dotcloud/docker/utils"
|
"github.com/dotcloud/docker/utils"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -65,7 +68,7 @@ func init() {
|
||||||
runtime: runtime,
|
runtime: runtime,
|
||||||
}
|
}
|
||||||
// Retrieve the Image
|
// Retrieve the Image
|
||||||
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, false, nil); err != nil {
|
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,24 +280,50 @@ func TestGet(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
|
||||||
|
strPort := strconv.Itoa(port)
|
||||||
|
container, err := NewBuilder(runtime).Create(&Config{
|
||||||
|
Image: GetTestImage(runtime).Id,
|
||||||
|
Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort},
|
||||||
|
PortSpecs: []string{strPort},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := container.Start(); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "address already in use") {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return container, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Run a container with a TCP port allocated, and test that it can receive connections on localhost
|
// Run a container with a TCP port allocated, and test that it can receive connections on localhost
|
||||||
func TestAllocatePortLocalhost(t *testing.T) {
|
func TestAllocatePortLocalhost(t *testing.T) {
|
||||||
runtime, err := newTestRuntime()
|
runtime, err := newTestRuntime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
container, err := NewBuilder(runtime).Create(&Config{
|
port := 5554
|
||||||
Image: GetTestImage(runtime).Id,
|
|
||||||
Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p 5555"},
|
var container *Container
|
||||||
PortSpecs: []string{"5555"},
|
for {
|
||||||
},
|
port += 1
|
||||||
)
|
log.Println("Trying port", port)
|
||||||
if err != nil {
|
t.Log("Trying port", port)
|
||||||
t.Fatal(err)
|
container, err = findAvailalblePort(runtime, port)
|
||||||
}
|
if container != nil {
|
||||||
if err := container.Start(); err != nil {
|
break
|
||||||
t.Fatal(err)
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("Port", port, "already in use")
|
||||||
|
t.Log("Port", port, "already in use")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer container.Kill()
|
defer container.Kill()
|
||||||
|
|
||||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||||
|
@ -308,7 +337,7 @@ func TestAllocatePortLocalhost(t *testing.T) {
|
||||||
|
|
||||||
conn, err := net.Dial("tcp",
|
conn, err := net.Dial("tcp",
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"localhost:%s", container.NetworkSettings.PortMapping["5555"],
|
"localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
76
server.go
76
server.go
|
@ -68,7 +68,7 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
|
||||||
return outs, nil
|
return outs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, error) {
|
func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
|
||||||
out = utils.NewWriteFlusher(out)
|
out = utils.NewWriteFlusher(out)
|
||||||
img, err := srv.runtime.repositories.LookupImage(name)
|
img, err := srv.runtime.repositories.LookupImage(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -92,7 +92,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil {
|
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), path); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
// FIXME: Handle custom repo, tag comment, author
|
// FIXME: Handle custom repo, tag comment, author
|
||||||
|
@ -100,7 +100,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "%s\n", img.Id)
|
out.Write(sf.FormatStatus(img.Id))
|
||||||
return img.ShortId(), nil
|
return img.ShortId(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, json bool) error {
|
func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, sf *utils.StreamFormatter) error {
|
||||||
history, err := r.GetRemoteHistory(imgId, endpoint, token)
|
history, err := r.GetRemoteHistory(imgId, endpoint, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -302,7 +302,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
|
||||||
// FIXME: Launch the getRemoteImage() in goroutines
|
// FIXME: Launch the getRemoteImage() in goroutines
|
||||||
for _, id := range history {
|
for _, id := range history {
|
||||||
if !srv.runtime.graph.Exists(id) {
|
if !srv.runtime.graph.Exists(id) {
|
||||||
fmt.Fprintf(out, utils.FormatStatus("Pulling %s metadata", json), id)
|
out.Write(sf.FormatStatus("Pulling %s metadata", id))
|
||||||
imgJson, err := r.GetRemoteImageJson(id, endpoint, token)
|
imgJson, err := r.GetRemoteImageJson(id, endpoint, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FIXME: Keep goging in case of error?
|
// FIXME: Keep goging in case of error?
|
||||||
|
@ -314,12 +314,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the layer
|
// Get the layer
|
||||||
fmt.Fprintf(out, utils.FormatStatus("Pulling %s fs layer", json), id)
|
out.Write(sf.FormatStatus("Pulling %s fs layer", id))
|
||||||
layer, contentLength, err := r.GetRemoteImageLayer(img.Id, endpoint, token)
|
layer, contentLength, err := r.GetRemoteImageLayer(img.Id, endpoint, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, utils.FormatProgress("%v/%v (%v)", json), json), false, img); err != nil {
|
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,8 +327,8 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, json bool) error {
|
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, sf *utils.StreamFormatter) error {
|
||||||
fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
|
out.Write(sf.FormatStatus("Pulling repository %s from %s", remote, auth.IndexServerAddress()))
|
||||||
repoData, err := r.GetRepositoryData(remote)
|
repoData, err := r.GetRepositoryData(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -365,11 +365,11 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
|
||||||
utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
|
utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, utils.FormatStatus("Pulling image %s (%s) from %s", json), img.Id, img.Tag, remote)
|
out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.Id, img.Tag, remote))
|
||||||
success := false
|
success := false
|
||||||
for _, ep := range repoData.Endpoints {
|
for _, ep := range repoData.Endpoints {
|
||||||
if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
|
if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
|
||||||
fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
|
out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
success = true
|
success = true
|
||||||
|
@ -394,17 +394,17 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, json bool, authConfig *auth.AuthConfig) error {
|
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
|
||||||
r := registry.NewRegistry(srv.runtime.root, authConfig)
|
r := registry.NewRegistry(srv.runtime.root, authConfig)
|
||||||
out = utils.NewWriteFlusher(out)
|
out = utils.NewWriteFlusher(out)
|
||||||
if endpoint != "" {
|
if endpoint != "" {
|
||||||
if err := srv.pullImage(r, out, name, endpoint, nil, json); err != nil {
|
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := srv.pullRepository(r, out, name, tag, json); err != nil {
|
if err := srv.pullRepository(r, out, name, tag, sf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,14 +477,14 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
|
||||||
return imgList, nil
|
return imgList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string) error {
|
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, sf *utils.StreamFormatter) error {
|
||||||
out = utils.NewWriteFlusher(out)
|
out = utils.NewWriteFlusher(out)
|
||||||
fmt.Fprintf(out, "Processing checksums\n")
|
out.Write(sf.FormatStatus("Processing checksums"))
|
||||||
imgList, err := srv.getImageList(localRepo)
|
imgList, err := srv.getImageList(localRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "Sending images list\n")
|
out.Write(sf.FormatStatus("Sending image list"))
|
||||||
|
|
||||||
repoData, err := r.PushImageJsonIndex(name, imgList, false)
|
repoData, err := r.PushImageJsonIndex(name, imgList, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -492,18 +492,18 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ep := range repoData.Endpoints {
|
for _, ep := range repoData.Endpoints {
|
||||||
fmt.Fprintf(out, "Pushing repository %s to %s (%d tags)\r\n", name, ep, len(localRepo))
|
out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
|
||||||
// For each image within the repo, push them
|
// For each image within the repo, push them
|
||||||
for _, elem := range imgList {
|
for _, elem := range imgList {
|
||||||
if _, exists := repoData.ImgList[elem.Id]; exists {
|
if _, exists := repoData.ImgList[elem.Id]; exists {
|
||||||
fmt.Fprintf(out, "Image %s already on registry, skipping\n", name)
|
out.Write(sf.FormatStatus("Image %s already on registry, skipping", name))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens); err != nil {
|
if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens, sf); err != nil {
|
||||||
// FIXME: Continue on error?
|
// FIXME: Continue on error?
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "Pushing tags for rev [%s] on {%s}\n", elem.Id, ep+"/users/"+name+"/"+elem.Tag)
|
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.Id, ep+"/users/"+name+"/"+elem.Tag))
|
||||||
if err := r.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
|
if err := r.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -516,13 +516,13 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string) error {
|
func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string, sf *utils.StreamFormatter) error {
|
||||||
out = utils.NewWriteFlusher(out)
|
out = utils.NewWriteFlusher(out)
|
||||||
jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
|
jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err)
|
return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "Pushing %s\r\n", imgId)
|
out.Write(sf.FormatStatus("Pushing %s", imgId))
|
||||||
|
|
||||||
// Make sure we have the image's checksum
|
// Make sure we have the image's checksum
|
||||||
checksum, err := srv.getChecksum(imgId)
|
checksum, err := srv.getChecksum(imgId)
|
||||||
|
@ -537,7 +537,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
|
||||||
// Send the json
|
// Send the json
|
||||||
if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
|
if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
|
||||||
if err == registry.ErrAlreadyExists {
|
if err == registry.ErrAlreadyExists {
|
||||||
fmt.Fprintf(out, "Image %s already uploaded ; skipping\n", imgData.Id)
|
out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.Id))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -570,22 +570,22 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the layer
|
// Send the layer
|
||||||
if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, "", false), ep, token); err != nil {
|
if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "%v/%v (%v)"), sf), ep, token); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) ImagePush(name, endpoint string, out io.Writer, authConfig *auth.AuthConfig) error {
|
func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
|
||||||
out = utils.NewWriteFlusher(out)
|
out = utils.NewWriteFlusher(out)
|
||||||
img, err := srv.runtime.graph.Get(name)
|
img, err := srv.runtime.graph.Get(name)
|
||||||
r := registry.NewRegistry(srv.runtime.root, authConfig)
|
r := registry.NewRegistry(srv.runtime.root, authConfig)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
|
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
|
||||||
// If it fails, try to get the repository
|
// If it fails, try to get the repository
|
||||||
if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
|
if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
|
||||||
if err := srv.pushRepository(r, out, name, localRepo); err != nil {
|
if err := srv.pushRepository(r, out, name, localRepo, sf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -593,14 +593,14 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, authConfig *a
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "The push refers to an image: [%s]\n", name)
|
out.Write(sf.FormatStatus("The push refers to an image: [%s]", name))
|
||||||
if err := srv.pushImage(r, out, name, img.Id, endpoint, nil); err != nil {
|
if err := srv.pushImage(r, out, name, img.Id, endpoint, nil, sf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error {
|
func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer, sf *utils.StreamFormatter) error {
|
||||||
var archive io.Reader
|
var archive io.Reader
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
|
|
||||||
|
@ -609,21 +609,21 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
|
||||||
} else {
|
} else {
|
||||||
u, err := url.Parse(src)
|
u, err := url.Parse(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(out, "Error: %s\n", err)
|
return err
|
||||||
}
|
}
|
||||||
if u.Scheme == "" {
|
if u.Scheme == "" {
|
||||||
u.Scheme = "http"
|
u.Scheme = "http"
|
||||||
u.Host = src
|
u.Host = src
|
||||||
u.Path = ""
|
u.Path = ""
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "Downloading from %s\n", u)
|
out.Write(sf.FormatStatus("Downloading from %s", u))
|
||||||
// Download with curl (pretty progress bar)
|
// Download with curl (pretty progress bar)
|
||||||
// If curl is not available, fallback to http.Get()
|
// If curl is not available, fallback to http.Get()
|
||||||
resp, err = utils.Download(u.String(), out)
|
resp, err = utils.Download(u.String(), out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)\r", false)
|
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%v/%v (%v)"), sf)
|
||||||
}
|
}
|
||||||
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
|
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -635,7 +635,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "%s\n", img.ShortId())
|
out.Write(sf.FormatStatus(img.ShortId()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,7 +790,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
|
||||||
if container == nil {
|
if container == nil {
|
||||||
return fmt.Errorf("No such container: %s", name)
|
return fmt.Errorf("No such container: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
//logs
|
//logs
|
||||||
if logs {
|
if logs {
|
||||||
if stdout {
|
if stdout {
|
||||||
|
@ -816,6 +815,9 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
|
||||||
if container.State.Ghost {
|
if container.State.Ghost {
|
||||||
return fmt.Errorf("Impossible to attach to a ghost container")
|
return fmt.Errorf("Impossible to attach to a ghost container")
|
||||||
}
|
}
|
||||||
|
if !container.State.Running {
|
||||||
|
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cStdin io.ReadCloser
|
cStdin io.ReadCloser
|
||||||
|
|
2
term/MAINTAINERS
Normal file
2
term/MAINTAINERS
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Guillaume Charmes <guillaume@dotcloud.com>
|
||||||
|
Solomon Hykes <solomon@dotcloud.com>
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"index/suffixarray"
|
"index/suffixarray"
|
||||||
|
@ -69,7 +70,7 @@ type progressReader struct {
|
||||||
readProgress int // How much has been read so far (bytes)
|
readProgress int // How much has been read so far (bytes)
|
||||||
lastUpdate int // How many bytes read at least update
|
lastUpdate int // How many bytes read at least update
|
||||||
template string // Template to print. Default "%v/%v (%v)"
|
template string // Template to print. Default "%v/%v (%v)"
|
||||||
json bool
|
sf *StreamFormatter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *progressReader) Read(p []byte) (n int, err error) {
|
func (r *progressReader) Read(p []byte) (n int, err error) {
|
||||||
|
@ -93,7 +94,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
// Send newline when complete
|
// Send newline when complete
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(r.output, FormatStatus("", r.json))
|
r.output.Write(r.sf.FormatStatus(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
return read, err
|
return read, err
|
||||||
|
@ -101,11 +102,12 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
|
||||||
func (r *progressReader) Close() error {
|
func (r *progressReader) Close() error {
|
||||||
return io.ReadCloser(r.reader).Close()
|
return io.ReadCloser(r.reader).Close()
|
||||||
}
|
}
|
||||||
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
|
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader {
|
||||||
if template == "" {
|
tpl := string(template)
|
||||||
template = "%v/%v (%v)\r"
|
if tpl == "" {
|
||||||
|
tpl = string(sf.FormatProgress("", "%v/%v (%v)"))
|
||||||
}
|
}
|
||||||
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json}
|
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HumanDuration returns a human-readable approximation of a duration
|
// HumanDuration returns a human-readable approximation of a duration
|
||||||
|
@ -564,16 +566,57 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
||||||
return &WriteFlusher{w: w, flusher: flusher}
|
return &WriteFlusher{w: w, flusher: flusher}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatStatus(str string, json bool) string {
|
type JsonMessage struct {
|
||||||
if json {
|
Status string `json:"status,omitempty"`
|
||||||
return "{\"status\" : \"" + str + "\"}"
|
Progress string `json:"progress,omitempty"`
|
||||||
}
|
Error string `json:"error,omitempty"`
|
||||||
return str + "\r\n"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatProgress(str string, json bool) string {
|
type StreamFormatter struct {
|
||||||
if json {
|
json bool
|
||||||
return "{\"progress\" : \"" + str + "\"}"
|
used bool
|
||||||
}
|
}
|
||||||
return "Downloading " + str + "\r"
|
|
||||||
|
func NewStreamFormatter(json bool) *StreamFormatter {
|
||||||
|
return &StreamFormatter{json, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte {
|
||||||
|
sf.used = true
|
||||||
|
str := fmt.Sprintf(format, a...)
|
||||||
|
if sf.json {
|
||||||
|
b, err := json.Marshal(&JsonMessage{Status:str});
|
||||||
|
if err != nil {
|
||||||
|
return sf.FormatError(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return []byte(str + "\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *StreamFormatter) FormatError(err error) []byte {
|
||||||
|
sf.used = true
|
||||||
|
if sf.json {
|
||||||
|
if b, err := json.Marshal(&JsonMessage{Error:err.Error()}); err == nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return []byte("{\"error\":\"format error\"}")
|
||||||
|
}
|
||||||
|
return []byte("Error: " + err.Error() + "\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *StreamFormatter) FormatProgress(action, str string) []byte {
|
||||||
|
sf.used = true
|
||||||
|
if sf.json {
|
||||||
|
b, err := json.Marshal(&JsonMessage{Progress:str})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return []byte(action + " " + str + "\r")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *StreamFormatter) Used() bool {
|
||||||
|
return sf.used
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue