This commit is contained in:
Victor Vieux 2013-06-03 11:06:13 +00:00
Родитель 49e656839f e42eb7fa8c
Коммит 62c78696cd
45 изменённых файлов: 884 добавлений и 353 удалений

Просмотреть файл

@ -15,6 +15,7 @@ Brian McCallister <brianm@skife.org>
Bruno Bigras <bigras.bruno@gmail.com>
Caleb Spare <cespare@gmail.com>
Charles Hooper <charles.hooper@dotcloud.com>
Daniel Gasienica <daniel@gasienica.ch>
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
Daniel Robinson <gottagetmac@gmail.com>
Daniel Von Fange <daniel@leancoder.com>

Просмотреть файл

@ -1,5 +1,14 @@
# 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)
- Registry: Fix push regression
- Various bugfixes

Просмотреть файл

@ -1,4 +1,5 @@
Solomon Hykes <solomon@dotcloud.com>
Guillaume Charmes <guillaume@dotcloud.com>
Victor Vieux <victor@dotcloud.com>
api.go: Victor Vieux <victor@dotcloud.com>
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>

55
api.go
Просмотреть файл

@ -45,6 +45,8 @@ func httpError(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusNotFound)
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
http.Error(w, err.Error(), http.StatusBadRequest)
} else if strings.HasPrefix(err.Error(), "Impossible") {
http.Error(w, err.Error(), http.StatusNotAcceptable)
} else {
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")
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
registry := r.Form.Get("registry")
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
authConfig := &auth.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
}
} 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
}
}
@ -326,10 +337,16 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
imgId, err := srv.ImageInsert(name, url, path, w)
if version > 1.0 {
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 {
return err
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
}
b, err := json.Marshal(&ApiId{Id: imgId})
if err != nil {
@ -353,8 +370,15 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ImagePush(name, registry, w, authConfig); err != nil {
if version > 1.0 {
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 nil
@ -623,6 +647,13 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
if err := r.ParseMultipartForm(4096); err != nil {
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")
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))
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)
} else if remote != "" {
srv.runtime.repositories.Set(remote, tag, id, false)
}
return nil
}

Просмотреть файл

@ -54,6 +54,9 @@ func Tar(path string, compression Compression) (io.Reader, error) {
func Untar(archive io.Reader, path string) error {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
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()
if err != nil {
return fmt.Errorf("%s: %s", err, output)

Просмотреть файл

@ -32,8 +32,6 @@ type buildFile struct {
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
needCommit bool
out io.Writer
}
@ -63,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
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
}
@ -81,9 +79,8 @@ func (b *buildFile) CmdFrom(name string) error {
}
func (b *buildFile) CmdMaintainer(name string) error {
b.needCommit = true
b.maintainer = name
return nil
return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
}
func (b *buildFile) CmdRun(args string) error {
@ -95,28 +92,34 @@ func (b *buildFile) CmdRun(args string) error {
return err
}
cmd, env := b.config.Cmd, b.config.Env
cmd := b.config.Cmd
b.config.Cmd = nil
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
} else if cache != nil {
utils.Debugf("Use cached version")
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.Id
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
}
cid, err := b.run()
if err != nil {
return err
}
b.config.Cmd, b.config.Env = cmd, env
return b.commit(cid)
if err := b.commit(cid, cmd, "run"); err != nil {
return err
}
b.config.Cmd = cmd
return nil
}
func (b *buildFile) CmdEnv(args string) error {
b.needCommit = true
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
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)
return nil
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
}
func (b *buildFile) CmdCmd(args string) error {
b.needCommit = true
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
b.config.Cmd = []string{"/bin/sh", "-c", args}
} else {
b.config.Cmd = cmd
cmd = []string{"/bin/sh", "-c", args}
}
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
return err
}
b.config.Cmd = cmd
return nil
}
func (b *buildFile) CmdExpose(args string) error {
ports := strings.Split(args, " ")
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 {
if b.image == "" {
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], " ")
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
}
file, err := utils.Download(sourceUrl, b.out)
if err != nil {
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) CmdCopy(args string) error {
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
}
func (b *buildFile) CmdAdd(args string) error {
@ -193,12 +170,13 @@ func (b *buildFile) CmdAdd(args string) error {
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid INSERT format")
return fmt.Errorf("Invalid ADD format")
}
orig := strings.Trim(tmp[0], " ")
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()
if err != nil {
return err
@ -208,19 +186,23 @@ func (b *buildFile) CmdAdd(args string) error {
if container == nil {
return fmt.Errorf("Error while creating the container (CmdAdd)")
}
if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil {
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
origPath := path.Join(b.context, orig)
destPath := path.Join(container.rwPath(), dest)
destPath := path.Join(container.RootfsPath(), dest)
fi, err := os.Stat(origPath)
if err != nil {
return err
}
if fi.IsDir() {
if err := os.MkdirAll(destPath, 0700); err != nil {
return err
}
files, err := ioutil.ReadDir(path.Join(b.context, orig))
if err != nil {
return err
@ -231,12 +213,18 @@ func (b *buildFile) CmdAdd(args string) error {
}
}
} else {
if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
return err
}
if err := utils.CopyDirectory(origPath, destPath); err != nil {
return err
}
}
return b.commit(cid)
if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
return err
}
b.config.Cmd = cmd
return nil
}
func (b *buildFile) run() (string, error) {
@ -265,20 +253,30 @@ func (b *buildFile) run() (string, error) {
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 == "" {
return fmt.Errorf("Please provide a source image with `from` prior to commit")
}
b.config.Image = b.image
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"true"}
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
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 {
return err
} else {
id = cid
}
b.config.Cmd = cmd
}
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")
}
// Note: Actually copy the struct
autoConfig := *b.config
autoConfig.Cmd = autoCmd
// Commit the container
image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil)
image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig)
if err != nil {
return err
}
b.tmpImages[image.Id] = struct{}{}
b.image = image.Id
b.needCommit = false
return nil
}
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
defer b.clearTmp(b.tmpContainers, b.tmpImages)
if context != nil {
name, err := ioutil.TempDir("/tmp", "docker-build")
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:]))
if !exists {
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()
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)
}
if b.needCommit {
if err := b.commit(""); err != nil {
return "", err
}
}
if b.image != "" {
// The build is successful, keep the temporary containers and images
for i := range b.tmpImages {
delete(b.tmpImages, i)
}
fmt.Fprintf(b.out, "Build success.\n Image id:\n%s\n", b.image)
fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image)
return b.image, nil
}
for i := range b.tmpContainers {
delete(b.tmpContainers, i)
}
return "", fmt.Errorf("An error occured during the build\n")
}

Просмотреть файл

@ -17,6 +17,7 @@ import (
"net/url"
"os"
"os/signal"
"path"
"path/filepath"
"reflect"
"strconv"
@ -27,7 +28,7 @@ import (
"unicode"
)
const VERSION = "0.3.3"
const VERSION = "0.3.4"
var (
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)
for cmd, description := range map[string]string{
"attach": "Attach to a running container",
"build": "Build a container from a Dockerfile",
"commit": "Create a new image from a container's changes",
"diff": "Inspect changes on a container's filesystem",
"export": "Stream the contents of a container as a tar archive",
"history": "Show the history of an image",
"images": "List images",
"import": "Create a new filesystem image from the contents of a tarball",
"info": "Display system-wide information",
"insert": "Insert a file in an image",
"inspect": "Return low-level information on a container",
"kill": "Kill a running container",
"login": "Register or Login to the docker registry server",
"logs": "Fetch the logs of a container",
"port": "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT",
"ps": "List containers",
"pull": "Pull an image or a repository from the docker registry server",
"push": "Push an image or a repository to the docker registry server",
"restart": "Restart a running container",
"rm": "Remove a container",
"rmi": "Remove an image",
"run": "Run a command in a new container",
"search": "Search for an image in the docker index",
"start": "Start a stopped container",
"stop": "Stop a running container",
"tag": "Tag an image into a repository",
"version": "Show the docker version information",
"wait": "Block until a container stops, then print its exit code",
for _, command := range [][2]string{
{"attach", "Attach to a running container"},
{"build", "Build a container from a Dockerfile"},
{"commit", "Create a new image from a container's changes"},
{"diff", "Inspect changes on a container's filesystem"},
{"export", "Stream the contents of a container as a tar archive"},
{"history", "Show the history of an image"},
{"images", "List images"},
{"import", "Create a new filesystem image from the contents of a tarball"},
{"info", "Display system-wide information"},
{"insert", "Insert a file in an image"},
{"inspect", "Return low-level information on a container"},
{"kill", "Kill a running container"},
{"login", "Register or Login to the docker registry server"},
{"logs", "Fetch the logs of a container"},
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
{"ps", "List containers"},
{"pull", "Pull an image or a repository from the docker registry server"},
{"push", "Push an image or a repository to the docker registry server"},
{"restart", "Restart a running container"},
{"rm", "Remove a container"},
{"rmi", "Remove an image"},
{"run", "Run a command in a new container"},
{"search", "Search for an image in the docker index"},
{"start", "Start a stopped container"},
{"stop", "Stop a running container"},
{"tag", "Tag an image into a repository"},
{"version", "Show the docker version information"},
{"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)
return nil
@ -130,16 +131,20 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
}
func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Subcmd("build", "[OPTIONS] [CONTEXT]", "Build an image from a Dockerfile")
fileName := cmd.String("f", "Dockerfile", "Use `file` as Dockerfile. Can be '-' for stdin")
cmd := Subcmd("build", "[OPTIONS] PATH | -", "Build a new container image from the source code at PATH")
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 1 {
cmd.Usage()
return nil
}
var (
file io.ReadCloser
multipartBody io.Reader
err error
file io.ReadCloser
contextPath string
)
// Init the needed component for the Multipart
@ -148,27 +153,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
w := multipart.NewWriter(buff)
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
// 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?
context, err := Tar(cmd.Arg(0), compression)
if err != nil {
@ -183,19 +180,28 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
return err
} else {
// 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)
}
// 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
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 {
return err
}
req.Header.Set("Content-Type", w.FormDataContentType())
if cmd.Arg(0) != "" {
if contextPath != "" {
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
fmt.Println("Uploading Context...")
}
@ -973,12 +979,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return nil
}
v := url.Values{}
v.Set("logs", "1")
v.Set("stdout", "1")
v.Set("stderr", "1")
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil {
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
return err
}
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
return err
}
return nil
@ -1005,15 +1009,35 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
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.Set("stream", "1")
v.Set("stdout", "1")
v.Set("stderr", "1")
v.Set("stdin", "1")
cli.monitorTtySize(cmd.Arg(0))
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
return err
v.Set("stdout", "1")
if !splitStderr {
v.Set("stderr", "1")
}
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
}
@ -1177,19 +1201,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
}
v := url.Values{}
v.Set("logs", "1")
v.Set("stream", "1")
splitStderr := !config.Tty
if config.AttachStdin {
v.Set("stdin", "1")
connections := 0
if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
connections += 1
}
if config.AttachStdout {
v.Set("stdout", "1")
}
if config.AttachStderr {
v.Set("stderr", "1")
if splitStderr && config.AttachStderr {
connections += 1
}
//start the container
@ -1198,10 +1217,38 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return err
}
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
if connections > 0 {
chErrors := make(chan error, connections)
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 {
@ -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" {
type Message struct {
Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"`
}
dec := json.NewDecoder(resp.Body)
for {
var m Message
var m utils.JsonMessage
if err := dec.Decode(&m); err == io.EOF {
break
} 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 != "" {
fmt.Fprintf(out, "Downloading %s\r", m.Progress)
} else if m.Error != "" {
return fmt.Errorf(m.Error)
} else {
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
}
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)
if err != nil {
return err
@ -1334,20 +1379,19 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
defer rwc.Close()
receiveStdout := utils.Go(func() error {
_, err := io.Copy(os.Stdout, br)
_, err := io.Copy(out, br)
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 {
return err
} else {
defer term.RestoreTerminal(oldState)
}
}
sendStdin := utils.Go(func() error {
_, err := io.Copy(rwc, os.Stdin)
_, err := io.Copy(rwc, in)
if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err)
}

Просмотреть файл

@ -2,18 +2,15 @@
set -e
# these should match the names found at http://www.debian.org/releases/
stableSuite='squeeze'
testingSuite='wheezy'
stableSuite='wheezy'
testingSuite='jessie'
unstableSuite='sid'
# if suite is equal to this, it gets the "latest" tag
latestSuite="$testingSuite"
variant='minbase'
include='iproute,iputils-ping'
repo="$1"
suite="${2:-$latestSuite}"
suite="${2:-$stableSuite}"
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
if [ ! "$repo" ]; then
@ -41,17 +38,14 @@ img=$(sudo tar -c . | docker import -)
# tag suite
docker tag $img $repo $suite
if [ "$suite" = "$latestSuite" ]; then
# tag latest
docker tag $img $repo latest
fi
# test the image
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" != "$unstableSuite" -a "$suite" != 'unstable' ]; then
# tag the specific version
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
# tag latest
docker tag $img $repo latest
# tag the specific debian release version
ver=$(docker run $repo:$suite cat /etc/debian_version)
docker tag $img $repo $ver
fi

Просмотреть файл

@ -6,6 +6,7 @@ SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
PYTHON = python
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
@ -38,6 +39,7 @@ help:
# @echo " linkcheck to check all external links for integrity"
# @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 " server to serve the docs in your browser under \`http://localhost:8000\`"
@echo " publish to publish the app to dotcloud"
clean:
@ -49,6 +51,8 @@ docs:
@echo
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
server:
@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
site:
cp -r website $(BUILDDIR)/

Просмотреть файл

@ -14,20 +14,22 @@ Installation
------------
* Work in your own fork of the code, we accept pull requests.
* Install sphinx: ``pip install sphinx``
* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain``
* Install sphinx: `pip install sphinx`
* 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**
Usage
-----
* change the .rst files with your favorite editor to your liking
* run *make docs* to clean up old files and generate new ones
* your static website can now be found in the _build dir
* to preview what you have generated, cd into _build/html and then run 'python -m SimpleHTTPServer 8000'
* Change the `.rst` files with your favorite editor to your liking.
* Run `make docs` to clean up old files and generate new ones.
* Your static website can now be found in the `_build` directory.
* 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.
Images
@ -72,4 +74,4 @@ Guides on using sphinx
* 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
- 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
@ -132,6 +139,7 @@ Create a container
:jsonparam config: the container's configuration
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 406: impossible to attach (container not running)
:statuscode 500: server error
@ -459,7 +467,7 @@ Remove a container
:statuscode 500: server error
2.2 Images
3.2 Images
----------
List Images
@ -548,7 +556,19 @@ Create an image
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
@ -579,7 +599,19 @@ Insert a file in a image
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
@ -694,7 +726,19 @@ Push an image on the registry
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
@ -800,7 +844,7 @@ Search images
:statuscode 500: server error
2.3 Misc
3.3 Misc
--------
Build an image from Dockerfile via stdin
@ -826,6 +870,7 @@ Build an image from Dockerfile via stdin
{{ STREAM }}
:query t: tag to be applied to the resulting image in case of success
:statuscode 200: no error
:statuscode 500: server error

Просмотреть файл

@ -2,8 +2,8 @@
:description: docker documentation
:keywords: docker, ipa, documentation
API's
=============
APIs
====
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)
- 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)
3.1 Without an Index

Просмотреть файл

@ -2,12 +2,27 @@
:description: Build a new image from the Dockerfile passed via stdin
:keywords: build, docker, container, documentation
========================================================
``build`` -- Build a container from Dockerfile via stdin
========================================================
================================================
``build`` -- Build a container from a Dockerfile
================================================
::
Usage: docker build -
Example: cat Dockerfile | docker build -
Build a new image from the Dockerfile passed via stdin
Usage: docker build [OPTIONS] PATH | -
Build a new container image from the source code at PATH
-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
../index
buildingblocks

Просмотреть файл

@ -5,8 +5,8 @@
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.

Просмотреть файл

@ -1,8 +1,8 @@
:title: Setting up a dev environment
:title: Setting Up a Dev Environment
:description: Guides on how to contribute to docker
: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,

Просмотреть файл

@ -4,8 +4,8 @@
.. _running_couchdb_service:
Create a CouchDB service
========================
CouchDB Service
===============
.. include:: example_header.inc

Просмотреть файл

@ -1,6 +1,6 @@
:title: Docker Examples
: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_daemon
python_web_app
nodejs_web_app
running_redis_service
running_ssh_service
couchdb_data_volumes

Просмотреть файл

@ -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, well look at how you can run this app inside a CentOS
container using docker. First, youll 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, well use `CentOS <https://index.docker.io/_/centos/>`_ (tag: ``6.4``)
available on the `docker index`_:
.. code-block:: bash
FROM centos:6.4
Since were building a Node.js app, youll 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 apps dependencies defined in ``package.json``.
To install the right package for CentOS, well 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 apps 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 youll 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 lets you tag your image so its 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:
Building a python web app
=========================
Python Web App
==============
.. include:: example_header.inc

Просмотреть файл

@ -4,7 +4,7 @@
.. _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:

Просмотреть файл

@ -4,8 +4,8 @@
.. _running_redis_service:
Create a redis service
======================
Redis Service
=============
.. include:: example_header.inc
@ -34,7 +34,7 @@ Snapshot the installation
.. 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
Run the service

Просмотреть файл

@ -4,8 +4,8 @@
.. _running_ssh_service:
Create an ssh daemon service
============================
SSH Daemon Service
==================
.. include:: example_header.inc
@ -20,8 +20,7 @@ minutes and not entirely smooth, but gives you a good idea.
<div style="margin-top:10px;">
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
</div>
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'
**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
============
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.

Просмотреть файл

@ -67,3 +67,21 @@ To start on system boot:
::
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
The basics
=============
The Basics
==========
Starting Docker
---------------

Просмотреть файл

@ -125,8 +125,14 @@ curl was installed within the image.
.. note::
The path must include the file name.
.. note::
This instruction has temporarily disabled
2.8 ADD
-------
``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
======================

Просмотреть файл

@ -4,8 +4,8 @@
.. _working_with_the_repository:
Working with the repository
============================
Working with the Repository
===========================
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
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.
* 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.

Просмотреть файл

@ -270,7 +270,7 @@
<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>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>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>

Просмотреть файл

@ -107,6 +107,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
DockerVersion: VERSION,
Author: author,
Config: config,
Architecture: "x86_64",
}
if container != nil {
img.Parent = container.Image
@ -165,7 +166,8 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
if err != nil {
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.

Просмотреть файл

@ -1,23 +1,31 @@
# This will build a container capable of producing an official binary build of docker and
# uploading it to S3
from ubuntu:12.04
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
# 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 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 tar -C /usr/local -xzf /go.tar.gz
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bashrc
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bash_profile
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
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 build-essential
# 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 autotools-dev
copy fake_initctl /usr/local/bin/initctl
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/s3cfg /.s3cfg
cmd ["dockerbuilder"]

Просмотреть файл

@ -2,7 +2,7 @@
set -x
set -e
export PATH=$PATH:/usr/local/go/bin
export PATH=/usr/local/go/bin:$PATH
PACKAGE=github.com/dotcloud/docker
@ -36,5 +36,6 @@ else
fi
if [ -z "$NO_UBUNTU" ]; then
export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
(cd packaging/ubuntu && make ubuntu)
fi

Просмотреть файл

@ -1,3 +0,0 @@
#!/bin/sh
echo Whatever you say, man

Просмотреть файл

@ -0,0 +1,2 @@
Ken Cochrane <ken@dotcloud.com>
Jerome Petazzoni <jerome@dotcloud.com>

Просмотреть файл

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

Просмотреть файл

@ -27,6 +27,7 @@ type Image struct {
DockerVersion string `json:"docker_version,omitempty"`
Author string `json:"author,omitempty"`
Config *Config `json:"config,omitempty"`
Architecture string `json:"architecture,omitempty"`
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
- Runtime: Store the actual archive on commit
- 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
- Registry: Fix push regression
- Various bugfixes

Просмотреть файл

@ -5,6 +5,5 @@ stop on starting rc RUNLEVEL=[016]
respawn
script
# FIXME: docker should not depend on the system having en_US.UTF-8
LC_ALL='en_US.UTF-8' /usr/bin/docker -d
/usr/bin/docker -d
end script

Просмотреть файл

@ -5,9 +5,12 @@ import (
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/user"
"strconv"
"strings"
"sync"
"testing"
"time"
@ -65,7 +68,7 @@ func init() {
runtime: runtime,
}
// 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)
}
}
@ -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
func TestAllocatePortLocalhost(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p 5555"},
PortSpecs: []string{"5555"},
},
)
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
t.Fatal(err)
port := 5554
var container *Container
for {
port += 1
log.Println("Trying port", port)
t.Log("Trying port", port)
container, err = findAvailalblePort(runtime, port)
if container != nil {
break
}
if err != nil {
t.Fatal(err)
}
log.Println("Port", port, "already in use")
t.Log("Port", port, "already in use")
}
defer container.Kill()
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",
fmt.Sprintf(
"localhost:%s", container.NetworkSettings.PortMapping["5555"],
"localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)],
),
)
if err != nil {

Просмотреть файл

@ -68,7 +68,7 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
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)
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
@ -92,7 +92,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
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
}
// 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 {
return "", err
}
fmt.Fprintf(out, "%s\n", img.Id)
out.Write(sf.FormatStatus(img.Id))
return img.ShortId(), nil
}
@ -292,7 +292,7 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
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)
if err != nil {
return err
@ -302,7 +302,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
// FIXME: Launch the getRemoteImage() in goroutines
for _, id := range history {
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)
if err != nil {
// 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
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)
if err != nil {
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
}
}
@ -327,8 +327,8 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
return nil
}
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, json bool) error {
fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, sf *utils.StreamFormatter) error {
out.Write(sf.FormatStatus("Pulling repository %s from %s", remote, auth.IndexServerAddress()))
repoData, err := r.GetRepositoryData(remote)
if err != nil {
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)
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
for _, ep := range repoData.Endpoints {
if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
continue
}
success = true
@ -394,17 +394,17 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
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)
out = utils.NewWriteFlusher(out)
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 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
}
@ -477,14 +477,14 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
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)
fmt.Fprintf(out, "Processing checksums\n")
out.Write(sf.FormatStatus("Processing checksums"))
imgList, err := srv.getImageList(localRepo)
if err != nil {
return err
}
fmt.Fprintf(out, "Sending images list\n")
out.Write(sf.FormatStatus("Sending image list"))
repoData, err := r.PushImageJsonIndex(name, imgList, false)
if err != nil {
@ -492,18 +492,18 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
}
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 _, elem := range imgList {
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
}
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?
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 {
return err
}
@ -516,13 +516,13 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
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)
jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
if err != nil {
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
checksum, err := srv.getChecksum(imgId)
@ -537,7 +537,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
// Send the json
if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
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 err
@ -570,22 +570,22 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
}
// 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 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)
img, err := srv.runtime.graph.Get(name)
r := registry.NewRegistry(srv.runtime.root, authConfig)
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 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 nil
@ -593,14 +593,14 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, authConfig *a
return err
}
fmt.Fprintf(out, "The push refers to an image: [%s]\n", name)
if err := srv.pushImage(r, out, name, img.Id, endpoint, nil); err != nil {
out.Write(sf.FormatStatus("The push refers to an image: [%s]", name))
if err := srv.pushImage(r, out, name, img.Id, endpoint, nil, sf); err != nil {
return err
}
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 resp *http.Response
@ -609,21 +609,21 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
} else {
u, err := url.Parse(src)
if err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
return err
}
if u.Scheme == "" {
u.Scheme = "http"
u.Host = src
u.Path = ""
}
fmt.Fprintf(out, "Downloading from %s\n", u)
out.Write(sf.FormatStatus("Downloading from %s", u))
// Download with curl (pretty progress bar)
// If curl is not available, fallback to http.Get()
resp, err = utils.Download(u.String(), out)
if err != nil {
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)
if err != nil {
@ -635,7 +635,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
return err
}
}
fmt.Fprintf(out, "%s\n", img.ShortId())
out.Write(sf.FormatStatus(img.ShortId()))
return nil
}
@ -790,7 +790,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if container == nil {
return fmt.Errorf("No such container: %s", name)
}
//logs
if logs {
if stdout {
@ -816,6 +815,9 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if container.State.Ghost {
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 (
cStdin io.ReadCloser

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"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"index/suffixarray"
@ -69,7 +70,7 @@ type progressReader struct {
readProgress int // How much has been read so far (bytes)
lastUpdate int // How many bytes read at least update
template string // Template to print. Default "%v/%v (%v)"
json bool
sf *StreamFormatter
}
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
if err != nil {
fmt.Fprintf(r.output, FormatStatus("", r.json))
r.output.Write(r.sf.FormatStatus(""))
}
return read, err
@ -101,11 +102,12 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
func (r *progressReader) Close() error {
return io.ReadCloser(r.reader).Close()
}
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
if template == "" {
template = "%v/%v (%v)\r"
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader {
tpl := string(template)
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
@ -564,16 +566,57 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
return &WriteFlusher{w: w, flusher: flusher}
}
func FormatStatus(str string, json bool) string {
if json {
return "{\"status\" : \"" + str + "\"}"
}
return str + "\r\n"
type JsonMessage struct {
Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"`
Error string `json:"error,omitempty"`
}
func FormatProgress(str string, json bool) string {
if json {
return "{\"progress\" : \"" + str + "\"}"
}
return "Downloading " + str + "\r"
type StreamFormatter struct {
json bool
used bool
}
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
}