2013-01-19 16:07:19 -08:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2013-01-23 23:14:46 -08:00
|
|
|
"bufio"
|
2013-01-19 16:07:19 -08:00
|
|
|
"errors"
|
|
|
|
"log"
|
|
|
|
"io"
|
2013-01-20 22:22:51 -08:00
|
|
|
"io/ioutil"
|
2013-01-23 23:14:46 -08:00
|
|
|
"net"
|
2013-01-19 16:07:19 -08:00
|
|
|
"net/url"
|
2013-01-23 23:14:46 -08:00
|
|
|
"net/http"
|
2013-01-19 16:07:19 -08:00
|
|
|
"os/exec"
|
|
|
|
"flag"
|
|
|
|
"reflect"
|
|
|
|
"fmt"
|
|
|
|
"github.com/kr/pty"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
"math/rand"
|
|
|
|
"crypto/sha256"
|
|
|
|
"bytes"
|
|
|
|
"text/tabwriter"
|
2013-01-20 14:13:25 -08:00
|
|
|
"sort"
|
2013-01-20 15:55:00 -08:00
|
|
|
"os"
|
2013-01-20 22:21:26 -08:00
|
|
|
"archive/tar"
|
2013-01-23 23:14:46 -08:00
|
|
|
"encoding/json"
|
2013-01-19 16:07:19 -08:00
|
|
|
)
|
|
|
|
|
2013-01-20 00:35:35 -08:00
|
|
|
func (docker *Docker) CmdHelp(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
|
|
if len(args) == 0 {
|
|
|
|
fmt.Fprintf(stdout, "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n")
|
|
|
|
for _, cmd := range [][]interface{}{
|
|
|
|
{"run", "Run a command in a container"},
|
|
|
|
{"list", "Display a list of containers"},
|
2013-01-22 18:29:44 -08:00
|
|
|
{"get", "Download a tarball and create a container from it"},
|
|
|
|
{"put", "Upload a tarball and create a container from it"},
|
|
|
|
{"rm", "Remove containers"},
|
2013-01-20 00:35:35 -08:00
|
|
|
{"wait", "Wait for the state of a container to change"},
|
|
|
|
{"stop", "Stop a running container"},
|
|
|
|
{"logs", "Fetch the logs of a container"},
|
2013-01-20 22:21:26 -08:00
|
|
|
{"diff", "Inspect changes on a container's filesystem"},
|
2013-01-22 18:29:44 -08:00
|
|
|
{"fork", "Duplicate a container"},
|
2013-01-20 00:35:35 -08:00
|
|
|
{"attach", "Attach to the standard inputs and outputs of a running container"},
|
|
|
|
{"info", "Display system-wide information"},
|
2013-01-20 15:55:00 -08:00
|
|
|
{"web", "Generate a web UI"},
|
2013-01-20 00:35:35 -08:00
|
|
|
} {
|
|
|
|
fmt.Fprintf(stdout, " %-10.10s%s\n", cmd...)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if method := docker.getMethod(args[0]); method == nil {
|
|
|
|
return errors.New("No such command: " + args[0])
|
|
|
|
} else {
|
|
|
|
method(stdin, stdout, "--help")
|
|
|
|
}
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
|
|
flags := Subcmd(stdout, "list", "[OPTIONS] [NAME]", "List containers")
|
|
|
|
limit := flags.Int("l", 0, "Only show the N most recent versions of each name")
|
|
|
|
quiet := flags.Bool("q", false, "only show numeric IDs")
|
2013-01-20 00:37:52 -08:00
|
|
|
flags.Parse(args)
|
|
|
|
if flags.NArg() > 1 {
|
|
|
|
flags.Usage()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var nameFilter string
|
|
|
|
if flags.NArg() == 1 {
|
|
|
|
nameFilter = flags.Arg(0)
|
|
|
|
}
|
2013-01-20 14:13:25 -08:00
|
|
|
var names []string
|
2013-01-22 18:29:44 -08:00
|
|
|
for name := range docker.containersByName {
|
2013-01-20 14:13:25 -08:00
|
|
|
names = append(names, name)
|
|
|
|
}
|
|
|
|
sort.Strings(names)
|
|
|
|
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
|
|
|
if (!*quiet) {
|
2013-01-22 18:29:44 -08:00
|
|
|
fmt.Fprintf(w, "NAME\tID\tCREATED\tSOURCE\tSIZE\tCHANGES\tRUNNING\tCOMMAND\n")
|
2013-01-20 14:13:25 -08:00
|
|
|
}
|
|
|
|
for _, name := range names {
|
|
|
|
if nameFilter != "" && nameFilter != name {
|
|
|
|
continue
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
for idx, container := range *docker.containersByName[name] {
|
2013-01-20 14:13:25 -08:00
|
|
|
if *limit > 0 && idx >= *limit {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if !*quiet {
|
2013-01-22 18:29:44 -08:00
|
|
|
for idx, field := range []string{
|
|
|
|
/* NAME */ container.Name,
|
|
|
|
/* ID */ container.Id,
|
|
|
|
/* CREATED */ humanDuration(time.Now().Sub(container.Created)) + " ago",
|
|
|
|
/* SOURCE */ container.Source,
|
|
|
|
/* SIZE */ fmt.Sprintf("%.1fM", float32(container.Size) / 1024 / 1024),
|
|
|
|
/* CHANGES */ fmt.Sprintf("%.1fM", float32(container.BytesChanged) / 1024 / 1024),
|
|
|
|
/* RUNNING */ fmt.Sprintf("%v", container.Running),
|
|
|
|
/* COMMAND */ container.CmdString(),
|
|
|
|
} {
|
|
|
|
if idx == 0 {
|
|
|
|
w.Write([]byte(field))
|
|
|
|
} else {
|
|
|
|
w.Write([]byte("\t" + field))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
w.Write([]byte{'\n'})
|
2013-01-20 14:13:25 -08:00
|
|
|
} else {
|
2013-01-22 18:29:44 -08:00
|
|
|
stdout.Write([]byte(container.Id + "\n"))
|
2013-01-20 00:37:52 -08:00
|
|
|
}
|
|
|
|
}
|
2013-01-20 14:13:25 -08:00
|
|
|
}
|
|
|
|
if (!*quiet) {
|
2013-01-20 00:37:52 -08:00
|
|
|
w.Flush()
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (docker *Docker) findContainer(name string) (*Container, bool) {
|
|
|
|
// 1: look for container by ID
|
|
|
|
if container, exists := docker.containers[name]; exists {
|
|
|
|
return container, true
|
2013-01-21 18:12:56 -08:00
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
// 2: look for a container by name (and pick the most recent)
|
|
|
|
if containers, exists := docker.containersByName[name]; exists {
|
|
|
|
return (*containers)[0], true
|
2013-01-21 18:12:56 -08:00
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (docker *Docker) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
2013-01-22 18:29:44 -08:00
|
|
|
flags := Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
|
2013-01-21 18:12:56 -08:00
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for _, name := range flags.Args() {
|
2013-01-22 18:29:44 -08:00
|
|
|
if _, err := docker.rm(name); err != nil {
|
2013-01-21 18:12:56 -08:00
|
|
|
fmt.Fprintln(stdout, "Error: " + err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-20 00:43:16 -08:00
|
|
|
func (docker *Docker) CmdGet(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
2013-01-19 18:24:01 -08:00
|
|
|
if len(args) < 1 {
|
|
|
|
return errors.New("Not enough arguments")
|
|
|
|
}
|
|
|
|
time.Sleep(2 * time.Second)
|
2013-01-22 18:29:44 -08:00
|
|
|
layer := docker.addContainer(args[0], "download", 0)
|
2013-01-20 00:58:18 -08:00
|
|
|
fmt.Fprintln(stdout, layer.Id)
|
2013-01-19 18:24:01 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-20 00:43:16 -08:00
|
|
|
func (docker *Docker) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
|
|
if len(args) < 1 {
|
|
|
|
return errors.New("Not enough arguments")
|
|
|
|
}
|
2013-01-19 18:24:01 -08:00
|
|
|
time.Sleep(1 * time.Second)
|
2013-01-22 18:29:44 -08:00
|
|
|
layer := docker.addContainer(args[0], "upload", 0)
|
2013-01-20 00:58:18 -08:00
|
|
|
fmt.Fprintln(stdout, layer.Id)
|
2013-01-19 16:07:19 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (docker *Docker) CmdFork(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
2013-01-22 09:54:56 -08:00
|
|
|
flags := Subcmd(stdout,
|
2013-01-22 18:29:44 -08:00
|
|
|
"fork", "[OPTIONS] CONTAINER [DEST]",
|
|
|
|
"Duplicate a container")
|
|
|
|
// FIXME "-r" to reset changes in the new container
|
2013-01-22 09:54:56 -08:00
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
srcName, dstName := flags.Arg(0), flags.Arg(1)
|
|
|
|
if srcName == "" {
|
2013-01-22 09:54:56 -08:00
|
|
|
flags.Usage()
|
|
|
|
return nil
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
if dstName == "" {
|
|
|
|
dstName = srcName
|
|
|
|
}
|
|
|
|
if src, exists := docker.findContainer(srcName); exists {
|
|
|
|
dst := docker.addContainer(dstName, "snapshot:" + src.Id, src.Size)
|
|
|
|
fmt.Fprintln(stdout, dst.Id)
|
2013-01-22 09:54:56 -08:00
|
|
|
return nil
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
return errors.New("No such container: " + srcName)
|
2013-01-22 09:54:56 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (docker *Docker) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
|
|
flags := Subcmd(stdout,
|
2013-01-22 18:29:44 -08:00
|
|
|
"tar", "CONTAINER",
|
|
|
|
"Stream the contents of a container as a tar archive")
|
2013-01-22 09:54:56 -08:00
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
name := flags.Arg(0)
|
2013-01-22 18:29:44 -08:00
|
|
|
if _, exists := docker.findContainer(name); exists {
|
2013-01-22 09:54:56 -08:00
|
|
|
// Stream the entire contents of the container (basically a volatile snapshot)
|
|
|
|
return WriteFakeTar(stdout)
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
return errors.New("No such container: " + name)
|
2013-01-22 09:54:56 -08:00
|
|
|
}
|
|
|
|
|
2013-01-20 22:21:26 -08:00
|
|
|
func (docker *Docker) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
2013-01-20 00:43:16 -08:00
|
|
|
flags := Subcmd(stdout,
|
2013-01-20 22:21:26 -08:00
|
|
|
"diff", "CONTAINER [OPTIONS]",
|
|
|
|
"Inspect changes on a container's filesystem")
|
2013-01-22 09:54:56 -08:00
|
|
|
fl_diff := flags.Bool("d", true, "Show changes in diff format")
|
|
|
|
fl_bytes := flags.Bool("b", false, "Show how many bytes have been changed")
|
2013-01-20 22:21:26 -08:00
|
|
|
fl_list := flags.Bool("l", false, "Show a list of changed files")
|
2013-01-20 00:43:16 -08:00
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2013-01-20 22:21:26 -08:00
|
|
|
if flags.NArg() < 1 {
|
2013-01-20 00:43:16 -08:00
|
|
|
return errors.New("Not enough arguments")
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
if container, exists := docker.findContainer(flags.Arg(0)); !exists {
|
2013-01-20 00:43:16 -08:00
|
|
|
return errors.New("No such container")
|
2013-01-20 22:21:26 -08:00
|
|
|
} else if *fl_bytes {
|
|
|
|
fmt.Fprintf(stdout, "%d\n", container.BytesChanged)
|
|
|
|
} else if *fl_list {
|
|
|
|
// FAKE
|
|
|
|
fmt.Fprintf(stdout, strings.Join([]string{
|
|
|
|
"/etc/postgres/pg.conf",
|
|
|
|
"/etc/passwd",
|
|
|
|
"/var/lib/postgres",
|
|
|
|
"/usr/bin/postgres",
|
|
|
|
"/usr/bin/psql",
|
|
|
|
"/var/log/postgres",
|
|
|
|
"/var/log/postgres/postgres.log",
|
|
|
|
"/var/log/postgres/postgres.log.0",
|
|
|
|
"/var/log/postgres/postgres.log.1.gz"}, "\n"))
|
2013-01-22 09:54:56 -08:00
|
|
|
} else if *fl_diff {
|
|
|
|
// Achievement unlocked: embed a diff of your code as a string in your code
|
|
|
|
fmt.Fprintf(stdout, `
|
|
|
|
diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go
|
|
|
|
index 2dae694..e43caca 100644
|
|
|
|
--- a/dockerd/dockerd.go
|
|
|
|
+++ b/dockerd/dockerd.go
|
|
|
|
@@ -158,6 +158,7 @@ func (docker *Docker) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...str
|
|
|
|
flags := Subcmd(stdout,
|
|
|
|
"diff", "CONTAINER [OPTIONS]",
|
|
|
|
"Inspect changes on a container's filesystem")
|
|
|
|
+ fl_diff := flags.Bool("d", true, "Show changes in diff format")
|
|
|
|
fl_bytes := flags.Bool("b", false, "Show how many bytes have been changes")
|
|
|
|
fl_list := flags.Bool("l", false, "Show a list of changed files")
|
|
|
|
fl_download := flags.Bool("d", false, "Download the changes as gzipped tar stream")
|
|
|
|
`)
|
2013-01-20 22:21:26 -08:00
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
flags.Usage()
|
|
|
|
return nil
|
2013-01-20 00:43:16 -08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-20 14:13:25 -08:00
|
|
|
// ByDate wraps an array of layers so they can be sorted by date (most recent first)
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
type ByDate []*Container
|
2013-01-20 14:13:25 -08:00
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (c *ByDate) Len() int {
|
|
|
|
return len(*c)
|
2013-01-20 14:13:25 -08:00
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (c *ByDate) Less(i, j int) bool {
|
|
|
|
containers := *c
|
|
|
|
return containers[j].Created.Before(containers[i].Created)
|
2013-01-20 14:13:25 -08:00
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (c *ByDate) Swap(i, j int) {
|
|
|
|
containers := *c
|
|
|
|
tmp := containers[i]
|
|
|
|
containers[i] = containers[j]
|
|
|
|
containers[j] = tmp
|
2013-01-20 14:13:25 -08:00
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (c *ByDate) Add(container *Container) {
|
|
|
|
*c = append(*c, container)
|
|
|
|
sort.Sort(c)
|
2013-01-20 14:13:25 -08:00
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (c *ByDate) Del(id string) {
|
|
|
|
for idx, container := range *c {
|
|
|
|
if container.Id == id {
|
|
|
|
*c = append((*c)[:idx], (*c)[idx + 1:]...)
|
2013-01-21 18:12:56 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-20 14:13:25 -08:00
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (docker *Docker) addContainer(name string, source string, size uint) *Container {
|
|
|
|
// Generate a fake random size
|
2013-01-20 00:43:16 -08:00
|
|
|
if size == 0 {
|
|
|
|
size = uint(rand.Int31n(142 * 1024 * 1024))
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
c := &Container{
|
|
|
|
Id: randomId(),
|
|
|
|
Name: name,
|
|
|
|
Created: time.Now(),
|
|
|
|
Source: source,
|
|
|
|
Size: size,
|
|
|
|
stdinLog: new(bytes.Buffer),
|
|
|
|
stdoutLog: new(bytes.Buffer),
|
|
|
|
}
|
|
|
|
docker.containers[c.Id] = c
|
|
|
|
if _, exists := docker.containersByName[c.Name]; !exists {
|
|
|
|
docker.containersByName[c.Name] = new(ByDate)
|
2013-01-20 14:13:25 -08:00
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
docker.containersByName[c.Name].Add(c)
|
|
|
|
return c
|
|
|
|
|
2013-01-20 00:43:16 -08:00
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
|
|
|
|
func (docker *Docker) rm(id string) (*Container, error) {
|
|
|
|
if container, exists := docker.containers[id]; exists {
|
|
|
|
if container.Running {
|
|
|
|
return nil, errors.New("Container is running: " + id)
|
2013-01-21 18:12:56 -08:00
|
|
|
} else {
|
|
|
|
// Remove from name lookup
|
2013-01-22 18:29:44 -08:00
|
|
|
docker.containersByName[container.Name].Del(container.Id)
|
2013-01-21 18:12:56 -08:00
|
|
|
// Remove from id lookup
|
2013-01-22 18:29:44 -08:00
|
|
|
delete(docker.containers, container.Id)
|
|
|
|
return container, nil
|
2013-01-21 18:12:56 -08:00
|
|
|
}
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
return nil, errors.New(fmt.Sprintf("No such container: %s", id))
|
2013-01-20 00:43:16 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-21 18:31:12 -08:00
|
|
|
func (docker *Docker) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
|
|
flags := Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
|
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if flags.NArg() != 1 {
|
|
|
|
flags.Usage()
|
|
|
|
return nil
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
name := flags.Arg(0)
|
|
|
|
if container, exists := docker.findContainer(name); exists {
|
2013-01-21 18:31:12 -08:00
|
|
|
if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
return nil
|
2013-01-21 18:31:12 -08:00
|
|
|
}
|
|
|
|
return errors.New("No such container: " + flags.Arg(0))
|
|
|
|
}
|
|
|
|
|
2013-01-19 16:07:19 -08:00
|
|
|
func (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
2013-01-22 18:29:44 -08:00
|
|
|
flags := Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
|
2013-01-20 22:22:51 -08:00
|
|
|
fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
|
2013-01-20 00:43:16 -08:00
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
if flags.NArg() < 2 {
|
2013-01-20 00:43:16 -08:00
|
|
|
flags.Usage()
|
|
|
|
return nil
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
name, cmd := flags.Arg(0), flags.Args()[1:]
|
|
|
|
if container, exists := docker.findContainer(name); exists {
|
|
|
|
if container.Running {
|
|
|
|
return errors.New("Already running: " + name)
|
|
|
|
}
|
|
|
|
if *fl_attach {
|
|
|
|
return container.Run(cmd[0], cmd[1:], stdin, stdout)
|
|
|
|
} else {
|
|
|
|
go container.Run(cmd[0], cmd[1:], ioutil.NopCloser(new(bytes.Buffer)), ioutil.Discard)
|
|
|
|
fmt.Fprintln(stdout, container.Id)
|
|
|
|
return nil
|
|
|
|
}
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
return errors.New("No such container: " + name)
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
|
|
|
|
if interactive {
|
|
|
|
term, err := pty.Start(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return term, term, nil
|
|
|
|
}
|
|
|
|
stdin, err := cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return stdin, stdout, nil
|
|
|
|
}
|
|
|
|
|
2013-01-23 23:14:46 -08:00
|
|
|
func (docker *Docker) ListenAndServeTCP(addr string) error {
|
|
|
|
listener, err := net.Listen("tcp", addr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer listener.Close()
|
|
|
|
for {
|
|
|
|
if conn, err := listener.Accept(); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
go func() {
|
|
|
|
if err := docker.serve(conn); err != nil {
|
|
|
|
log.Printf("Error: " + err.Error() + "\n")
|
|
|
|
fmt.Fprintf(conn, "Error: " + err.Error() + "\n")
|
|
|
|
}
|
|
|
|
conn.Close()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (docker *Docker) ListenAndServeHTTP(addr string) error {
|
|
|
|
return http.ListenAndServe(addr, docker)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-19 16:07:19 -08:00
|
|
|
func main() {
|
|
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
|
|
flag.Parse()
|
2013-01-23 23:14:46 -08:00
|
|
|
docker := New()
|
|
|
|
go func() {
|
|
|
|
if err := docker.ListenAndServeHTTP(":8080"); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
if err := docker.ListenAndServeTCP(":4242"); err != nil {
|
2013-01-19 16:07:19 -08:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-23 23:14:46 -08:00
|
|
|
func (docker *Docker) serve(conn net.Conn) error {
|
|
|
|
r := bufio.NewReader(conn)
|
|
|
|
var args []string
|
|
|
|
if line, err := r.ReadString('\n'); err != nil {
|
|
|
|
return err
|
|
|
|
} else if err := json.Unmarshal([]byte(line), &args); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
return docker.Call(ioutil.NopCloser(r), conn, args...)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-20 00:43:16 -08:00
|
|
|
func New() *Docker {
|
|
|
|
return &Docker{
|
2013-01-22 18:29:44 -08:00
|
|
|
containersByName: make(map[string]*ByDate),
|
|
|
|
containers: make(map[string]*Container),
|
2013-01-20 00:43:16 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-19 18:23:33 -08:00
|
|
|
type AutoFlush struct {
|
|
|
|
http.ResponseWriter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *AutoFlush) Write(data []byte) (int, error) {
|
|
|
|
ret, err := w.ResponseWriter.Write(data)
|
|
|
|
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
|
|
|
|
flusher.Flush()
|
|
|
|
}
|
|
|
|
return ret, err
|
|
|
|
}
|
|
|
|
|
2013-01-19 16:07:19 -08:00
|
|
|
func (docker *Docker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2013-01-23 23:14:46 -08:00
|
|
|
cmd, args := URLToCall(r.URL)
|
|
|
|
if err := docker.Call(r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
|
|
|
|
fmt.Fprintf(w, "Error: " + err.Error() + "\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (docker *Docker) Call(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
2013-01-20 00:47:39 -08:00
|
|
|
flags := flag.NewFlagSet("docker", flag.ContinueOnError)
|
|
|
|
flags.SetOutput(stdout)
|
|
|
|
flags.Usage = func() { docker.CmdHelp(stdin, stdout) }
|
2013-01-23 23:14:46 -08:00
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return err
|
2013-01-20 00:47:39 -08:00
|
|
|
}
|
2013-01-23 23:14:46 -08:00
|
|
|
cmd := flags.Arg(0)
|
|
|
|
log.Printf("%s\n", strings.Join(append(append([]string{"docker"}, cmd), args[1:]...), " "))
|
2013-01-19 17:43:42 -08:00
|
|
|
if cmd == "" {
|
2013-01-20 00:47:39 -08:00
|
|
|
cmd = "help"
|
2013-01-19 17:43:42 -08:00
|
|
|
}
|
2013-01-19 16:07:19 -08:00
|
|
|
method := docker.getMethod(cmd)
|
2013-01-23 23:14:46 -08:00
|
|
|
if method != nil {
|
|
|
|
return method(stdin, stdout, args[1:]...)
|
|
|
|
}
|
|
|
|
return errors.New("No such command: " + cmd)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (docker *Docker) CmdMirror(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
|
|
_, err := io.Copy(stdout, stdin)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (docker *Docker) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
|
|
for {
|
|
|
|
if line, err := bufio.NewReader(stdin).ReadString('\n'); err == nil {
|
|
|
|
fmt.Printf("--- %s", line)
|
|
|
|
} else if err == io.EOF {
|
|
|
|
if len(line) > 0 {
|
|
|
|
fmt.Printf("--- %s\n", line)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
return err
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
|
|
|
}
|
2013-01-23 23:14:46 -08:00
|
|
|
return nil
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
|
|
|
|
2013-01-20 15:55:00 -08:00
|
|
|
func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
2013-01-20 22:21:59 -08:00
|
|
|
flags := Subcmd(stdout, "web", "[OPTIONS]", "A web UI for docker")
|
|
|
|
showurl := flags.Bool("u", false, "Return the URL of the web UI")
|
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if *showurl {
|
|
|
|
fmt.Fprintln(stdout, "http://localhost:4242/web")
|
|
|
|
} else {
|
|
|
|
if file, err := os.Open("dockerweb.html"); err != nil {
|
|
|
|
return err
|
|
|
|
} else if _, err := io.Copy(stdout, file); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-01-20 15:55:00 -08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-19 16:07:19 -08:00
|
|
|
func (docker *Docker) getMethod(name string) Cmd {
|
|
|
|
methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:])
|
|
|
|
method, exists := reflect.TypeOf(docker).MethodByName(methodName)
|
|
|
|
if !exists {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
|
|
ret := method.Func.CallSlice([]reflect.Value{
|
|
|
|
reflect.ValueOf(docker),
|
|
|
|
reflect.ValueOf(stdin),
|
|
|
|
reflect.ValueOf(stdout),
|
|
|
|
reflect.ValueOf(args),
|
|
|
|
})[0].Interface()
|
|
|
|
if ret == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ret.(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Go(f func() error) chan error {
|
|
|
|
ch := make(chan error)
|
|
|
|
go func() {
|
|
|
|
ch <- f()
|
|
|
|
}()
|
|
|
|
return ch
|
|
|
|
}
|
|
|
|
|
|
|
|
type Docker struct {
|
2013-01-22 18:29:44 -08:00
|
|
|
containers map[string]*Container
|
|
|
|
containersByName map[string]*ByDate
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
type Container struct {
|
|
|
|
Id string
|
2013-01-22 18:29:44 -08:00
|
|
|
Name string
|
2013-01-19 17:43:42 -08:00
|
|
|
Created time.Time
|
2013-01-22 18:29:44 -08:00
|
|
|
Source string
|
|
|
|
Size uint
|
2013-01-19 17:43:42 -08:00
|
|
|
FilesChanged uint
|
|
|
|
BytesChanged uint
|
2013-01-20 00:43:16 -08:00
|
|
|
Running bool
|
2013-01-22 18:29:44 -08:00
|
|
|
Cmd string
|
|
|
|
Args []string
|
2013-01-21 18:31:12 -08:00
|
|
|
stdoutLog *bytes.Buffer
|
|
|
|
stdinLog *bytes.Buffer
|
2013-01-20 00:43:16 -08:00
|
|
|
}
|
|
|
|
|
2013-01-22 18:29:44 -08:00
|
|
|
func (c *Container) Run(command string, args []string, stdin io.ReadCloser, stdout io.Writer) error {
|
2013-01-20 00:43:16 -08:00
|
|
|
// Not thread-safe
|
|
|
|
if c.Running {
|
|
|
|
return errors.New("Already running")
|
|
|
|
}
|
2013-01-22 18:29:44 -08:00
|
|
|
c.Cmd = command
|
|
|
|
c.Args = args
|
|
|
|
// Reset logs
|
|
|
|
c.stdoutLog.Reset()
|
|
|
|
c.stdinLog.Reset()
|
2013-01-20 00:43:16 -08:00
|
|
|
c.Running = true
|
|
|
|
cmd := exec.Command(c.Cmd, c.Args...)
|
2013-01-23 23:14:46 -08:00
|
|
|
cmd_stdin, cmd_stdout, err := startCommand(cmd, true)
|
2013-01-22 18:29:44 -08:00
|
|
|
// ADD FAKE RANDOM CHANGES
|
|
|
|
c.FilesChanged = uint(rand.Int31n(42))
|
|
|
|
c.BytesChanged = uint(rand.Int31n(24 * 1024 * 1024))
|
2013-01-20 00:43:16 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
copy_out := Go(func() error {
|
2013-01-21 18:31:12 -08:00
|
|
|
_, err := io.Copy(io.MultiWriter(stdout, c.stdoutLog), cmd_stdout)
|
2013-01-20 00:43:16 -08:00
|
|
|
return err
|
|
|
|
})
|
2013-01-23 23:14:46 -08:00
|
|
|
Go(func() error {
|
|
|
|
_, err := io.Copy(io.MultiWriter(cmd_stdin, c.stdinLog), stdin)
|
2013-01-20 00:43:16 -08:00
|
|
|
cmd_stdin.Close()
|
|
|
|
stdin.Close()
|
|
|
|
return err
|
2013-01-23 23:14:46 -08:00
|
|
|
})
|
|
|
|
wait := Go(func() error {
|
|
|
|
err := cmd.Wait()
|
|
|
|
c.Running = false
|
2013-01-20 00:43:16 -08:00
|
|
|
return err
|
2013-01-23 23:14:46 -08:00
|
|
|
})
|
2013-01-20 00:43:16 -08:00
|
|
|
if err := <-copy_out; err != nil {
|
2013-01-23 23:14:46 -08:00
|
|
|
if c.Running {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := <-wait; err != nil {
|
|
|
|
if status, ok := err.(*exec.ExitError); ok {
|
|
|
|
fmt.Fprintln(stdout, status)
|
|
|
|
return nil
|
|
|
|
}
|
2013-01-20 00:43:16 -08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2013-01-19 17:43:42 -08:00
|
|
|
}
|
|
|
|
|
2013-01-21 18:31:12 -08:00
|
|
|
func (c *Container) StdoutLog() io.Reader {
|
|
|
|
return strings.NewReader(c.stdoutLog.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Container) StdinLog() io.Reader {
|
|
|
|
return strings.NewReader(c.stdinLog.String())
|
|
|
|
}
|
|
|
|
|
2013-01-19 17:43:42 -08:00
|
|
|
func (c *Container) CmdString() string {
|
|
|
|
return strings.Join(append([]string{c.Cmd}, c.Args...), " ")
|
2013-01-19 16:07:19 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
type Cmd func(io.ReadCloser, io.Writer, ...string) error
|
|
|
|
type CmdMethod func(*Docker, io.ReadCloser, io.Writer, ...string) error
|
|
|
|
|
|
|
|
// Use this key to encode an RPC call into an URL,
|
|
|
|
// eg. domain.tld/path/to/method?q=get_user&q=gordon
|
|
|
|
const ARG_URL_KEY = "q"
|
|
|
|
|
|
|
|
func URLToCall(u *url.URL) (method string, args []string) {
|
|
|
|
return path.Base(u.Path), u.Query()[ARG_URL_KEY]
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func randomBytes() io.Reader {
|
|
|
|
return bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int())))
|
|
|
|
}
|
|
|
|
|
|
|
|
func ComputeId(content io.Reader) (string, error) {
|
|
|
|
h := sha256.New()
|
|
|
|
if _, err := io.Copy(h, content); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%x", h.Sum(nil)[:8]), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func randomId() string {
|
|
|
|
id, _ := ComputeId(randomBytes()) // can't fail
|
|
|
|
return id
|
|
|
|
}
|
2013-01-19 17:43:42 -08:00
|
|
|
|
|
|
|
|
|
|
|
func humanDuration(d time.Duration) string {
|
|
|
|
if seconds := int(d.Seconds()); seconds < 1 {
|
|
|
|
return "Less than a second"
|
|
|
|
} else if seconds < 60 {
|
|
|
|
return fmt.Sprintf("%d seconds", seconds)
|
|
|
|
} else if minutes := int(d.Minutes()); minutes == 1 {
|
|
|
|
return "About a minute"
|
|
|
|
} else if minutes < 60 {
|
|
|
|
return fmt.Sprintf("%d minutes", minutes)
|
|
|
|
} else if hours := int(d.Hours()); hours == 1{
|
|
|
|
return "About an hour"
|
|
|
|
} else if hours < 48 {
|
|
|
|
return fmt.Sprintf("%d hours", hours)
|
|
|
|
} else if hours < 24 * 7 * 2 {
|
|
|
|
return fmt.Sprintf("%d days", hours / 24)
|
|
|
|
} else if hours < 24 * 30 * 3 {
|
|
|
|
return fmt.Sprintf("%d weeks", hours / 24 / 7)
|
|
|
|
} else if hours < 24 * 365 * 2 {
|
|
|
|
return fmt.Sprintf("%d months", hours / 24 / 30)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%d years", d.Hours() / 24 / 365)
|
|
|
|
}
|
2013-01-20 00:43:16 -08:00
|
|
|
|
|
|
|
func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet {
|
|
|
|
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
|
|
|
flags.SetOutput(output)
|
|
|
|
flags.Usage = func() {
|
|
|
|
fmt.Fprintf(output, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
|
|
|
|
flags.PrintDefaults()
|
|
|
|
}
|
|
|
|
return flags
|
|
|
|
}
|
|
|
|
|
2013-01-20 22:21:26 -08:00
|
|
|
|
2013-01-22 09:54:56 -08:00
|
|
|
func WriteFakeTar(dst io.Writer) error {
|
|
|
|
if data, err := FakeTar(); err != nil {
|
|
|
|
return err
|
|
|
|
} else if _, err := io.Copy(dst, data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-20 22:21:26 -08:00
|
|
|
func FakeTar() (io.Reader, error) {
|
|
|
|
content := []byte("Hello world!\n")
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
tw := tar.NewWriter(buf)
|
|
|
|
for _, name := range []string {"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres", "/var/log/postgres/postgres.conf"} {
|
|
|
|
hdr := new(tar.Header)
|
|
|
|
hdr.Size = int64(len(content))
|
|
|
|
hdr.Name = name
|
|
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tw.Write([]byte(content))
|
|
|
|
}
|
|
|
|
tw.Close()
|
|
|
|
return buf, nil
|
|
|
|
}
|