Factored out docker/rcli (remote cli protocol), docker/fake (mocking utilities) and docker/future (real utilities which don't yet fit in the core)
This commit is contained in:
parent
553ca56d7e
commit
f3ffba7afe
7 changed files with 344 additions and 255 deletions
|
@ -1,14 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"io"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"net"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
||||
|
@ -172,17 +170,10 @@ func main() {
|
|||
}
|
||||
defer Restore(0, oldState)
|
||||
}
|
||||
cmd, err := json.Marshal(os.Args[1:])
|
||||
conn, err := rcli.CallTCP(os.Getenv("DOCKER"), os.Args[1:]...)
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
conn, err := net.Dial("tcp", os.Getenv("DOCKER"))
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
go func() {
|
||||
if _, err := io.Copy(os.Stdout, conn); err != nil {
|
||||
Fatal(err)
|
||||
|
|
|
@ -1,64 +1,56 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"github.com/dotcloud/docker/fake"
|
||||
"github.com/dotcloud/docker/future"
|
||||
"bufio"
|
||||
"errors"
|
||||
"log"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"flag"
|
||||
"reflect"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
"math/rand"
|
||||
"crypto/sha256"
|
||||
"bytes"
|
||||
"text/tabwriter"
|
||||
"sort"
|
||||
"os"
|
||||
"archive/tar"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
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"},
|
||||
{"get", "Download a tarball and create a container from it"},
|
||||
{"put", "Upload a tarball and create a container from it"},
|
||||
{"rm", "Remove containers"},
|
||||
{"wait", "Wait for the state of a container to change"},
|
||||
{"stop", "Stop a running container"},
|
||||
{"logs", "Fetch the logs of a container"},
|
||||
{"diff", "Inspect changes on a container's filesystem"},
|
||||
{"fork", "Duplicate a container"},
|
||||
{"attach", "Attach to the standard inputs and outputs of a running container"},
|
||||
{"info", "Display system-wide information"},
|
||||
{"web", "Generate a web UI"},
|
||||
} {
|
||||
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")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
func (docker *Docker) Name() string {
|
||||
return "docker"
|
||||
}
|
||||
|
||||
func (docker *Docker) Help() string {
|
||||
help := "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"},
|
||||
{"pull", "Download a tarball and create a container from it"},
|
||||
{"put", "Upload a tarball and create a container from it"},
|
||||
{"rm", "Remove containers"},
|
||||
{"wait", "Wait for the state of a container to change"},
|
||||
{"stop", "Stop a running container"},
|
||||
{"logs", "Fetch the logs of a container"},
|
||||
{"diff", "Inspect changes on a container's filesystem"},
|
||||
{"commit", "Save the state of a container"},
|
||||
{"attach", "Attach to the standard inputs and outputs of a running container"},
|
||||
{"info", "Display system-wide information"},
|
||||
{"web", "Generate a web UI"},
|
||||
} {
|
||||
help += fmt.Sprintf(" %-10.10s%s\n", cmd...)
|
||||
}
|
||||
return help
|
||||
}
|
||||
|
||||
|
||||
func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := Subcmd(stdout, "list", "[OPTIONS] [NAME]", "List containers")
|
||||
flags := rcli.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")
|
||||
flags.Parse(args)
|
||||
|
@ -91,7 +83,7 @@ func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...str
|
|||
for idx, field := range []string{
|
||||
/* NAME */ container.Name,
|
||||
/* ID */ container.Id,
|
||||
/* CREATED */ humanDuration(time.Now().Sub(container.Created)) + " ago",
|
||||
/* CREATED */ future.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),
|
||||
|
@ -130,7 +122,7 @@ func (docker *Docker) findContainer(name string) (*Container, bool) {
|
|||
|
||||
|
||||
func (docker *Docker) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
|
||||
flags := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -142,7 +134,7 @@ func (docker *Docker) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
|||
return nil
|
||||
}
|
||||
|
||||
func (docker *Docker) CmdGet(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (docker *Docker) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("Not enough arguments")
|
||||
}
|
||||
|
@ -162,8 +154,8 @@ func (docker *Docker) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
|||
return nil
|
||||
}
|
||||
|
||||
func (docker *Docker) CmdFork(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := Subcmd(stdout,
|
||||
func (docker *Docker) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := rcli.Subcmd(stdout,
|
||||
"fork", "[OPTIONS] CONTAINER [DEST]",
|
||||
"Duplicate a container")
|
||||
// FIXME "-r" to reset changes in the new container
|
||||
|
@ -187,7 +179,7 @@ func (docker *Docker) CmdFork(stdin io.ReadCloser, stdout io.Writer, args ...str
|
|||
}
|
||||
|
||||
func (docker *Docker) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := Subcmd(stdout,
|
||||
flags := rcli.Subcmd(stdout,
|
||||
"tar", "CONTAINER",
|
||||
"Stream the contents of a container as a tar archive")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
|
@ -196,13 +188,13 @@ func (docker *Docker) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
|||
name := flags.Arg(0)
|
||||
if _, exists := docker.findContainer(name); exists {
|
||||
// Stream the entire contents of the container (basically a volatile snapshot)
|
||||
return WriteFakeTar(stdout)
|
||||
return fake.WriteFakeTar(stdout)
|
||||
}
|
||||
return errors.New("No such container: " + name)
|
||||
}
|
||||
|
||||
func (docker *Docker) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := Subcmd(stdout,
|
||||
flags := rcli.Subcmd(stdout,
|
||||
"diff", "CONTAINER [OPTIONS]",
|
||||
"Inspect changes on a container's filesystem")
|
||||
fl_diff := flags.Bool("d", true, "Show changes in diff format")
|
||||
|
@ -238,7 +230,7 @@ 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,
|
||||
flags := rcli.Subcmd(stdout,
|
||||
"diff", "CONTAINER [OPTIONS]",
|
||||
"Inspect changes on a container's filesystem")
|
||||
+ fl_diff := flags.Bool("d", true, "Show changes in diff format")
|
||||
|
@ -290,12 +282,11 @@ func (c *ByDate) Del(id string) {
|
|||
|
||||
|
||||
func (docker *Docker) addContainer(name string, source string, size uint) *Container {
|
||||
// Generate a fake random size
|
||||
if size == 0 {
|
||||
size = uint(rand.Int31n(142 * 1024 * 1024))
|
||||
size = fake.RandomContainerSize()
|
||||
}
|
||||
c := &Container{
|
||||
Id: randomId(),
|
||||
Id: fake.RandomId(),
|
||||
Name: name,
|
||||
Created: time.Now(),
|
||||
Source: source,
|
||||
|
@ -330,7 +321,7 @@ func (docker *Docker) rm(id string) (*Container, error) {
|
|||
|
||||
|
||||
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")
|
||||
flags := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -349,7 +340,7 @@ func (docker *Docker) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...str
|
|||
}
|
||||
|
||||
func (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
|
||||
flags := rcli.Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
|
||||
fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -396,60 +387,21 @@ func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadClose
|
|||
return stdin, stdout, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
fake.Seed()
|
||||
flag.Parse()
|
||||
docker := New()
|
||||
go func() {
|
||||
if err := docker.ListenAndServeHTTP(":8080"); err != nil {
|
||||
if err := rcli.ListenAndServeHTTP(":8080", docker); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
if err := docker.ListenAndServeTCP(":4242"); err != nil {
|
||||
if err := rcli.ListenAndServeTCP(":4242", docker); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func New() *Docker {
|
||||
return &Docker{
|
||||
containersByName: make(map[string]*ByDate),
|
||||
|
@ -457,43 +409,6 @@ func New() *Docker {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (docker *Docker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
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 {
|
||||
flags := flag.NewFlagSet("docker", flag.ContinueOnError)
|
||||
flags.SetOutput(stdout)
|
||||
flags.Usage = func() { docker.CmdHelp(stdin, stdout) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := flags.Arg(0)
|
||||
log.Printf("%s\n", strings.Join(append(append([]string{"docker"}, cmd), args[1:]...), " "))
|
||||
if cmd == "" {
|
||||
cmd = "help"
|
||||
}
|
||||
method := docker.getMethod(cmd)
|
||||
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)
|
||||
|
@ -517,7 +432,7 @@ func (docker *Docker) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...st
|
|||
}
|
||||
|
||||
func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := Subcmd(stdout, "web", "[OPTIONS]", "A web UI for docker")
|
||||
flags := rcli.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
|
||||
|
@ -534,25 +449,6 @@ func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
|||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -596,8 +492,8 @@ func (c *Container) Run(command string, args []string, stdin io.ReadCloser, stdo
|
|||
cmd := exec.Command(c.Cmd, c.Args...)
|
||||
cmd_stdin, cmd_stdout, err := startCommand(cmd, true)
|
||||
// ADD FAKE RANDOM CHANGES
|
||||
c.FilesChanged = uint(rand.Int31n(42))
|
||||
c.BytesChanged = uint(rand.Int31n(24 * 1024 * 1024))
|
||||
c.FilesChanged = fake.RandomFilesChanged()
|
||||
c.BytesChanged = fake.RandomBytesChanged()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -643,92 +539,3 @@ func (c *Container) CmdString() string {
|
|||
return strings.Join(append([]string{c.Cmd}, c.Args...), " ")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
65
fake/fake.go
Normal file
65
fake/fake.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package fake
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/future"
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"time"
|
||||
"io"
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Seed() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
|
||||
func randomBytes() io.Reader {
|
||||
return bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int())))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
func RandomId() string {
|
||||
id, _ := future.ComputeId(randomBytes()) // can't fail
|
||||
return id
|
||||
}
|
||||
|
||||
|
||||
func RandomBytesChanged() uint {
|
||||
return uint(rand.Int31n(24 * 1024 * 1024))
|
||||
}
|
||||
|
||||
func RandomFilesChanged() uint {
|
||||
return uint(rand.Int31n(42))
|
||||
}
|
||||
|
||||
func RandomContainerSize() uint {
|
||||
return uint(rand.Int31n(142 * 1024 * 1024))
|
||||
}
|
39
future/future.go
Normal file
39
future/future.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package future
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 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)
|
||||
}
|
41
rcli/http.go
Normal file
41
rcli/http.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package rcli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
||||
// 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 ListenAndServeHTTP(addr string, service Service) error {
|
||||
return http.ListenAndServe(addr, http.HandlerFunc(
|
||||
func (w http.ResponseWriter, r *http.Request) {
|
||||
cmd, args := URLToCall(r.URL)
|
||||
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
|
||||
fmt.Fprintf(w, "Error: " + err.Error() + "\n")
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
62
rcli/tcp.go
Normal file
62
rcli/tcp.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package rcli
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"log"
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"bufio"
|
||||
)
|
||||
|
||||
func CallTCP(addr string, args ...string) (io.ReadWriteCloser, error) {
|
||||
cmd, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func ListenAndServeTCP(addr string, service Service) 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 := Serve(conn, service); err != nil {
|
||||
log.Printf("Error: " + err.Error() + "\n")
|
||||
fmt.Fprintf(conn, "Error: " + err.Error() + "\n")
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Serve(conn io.ReadWriter, service Service) 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 call(service, ioutil.NopCloser(r), conn, args...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
84
rcli/types.go
Normal file
84
rcli/types.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package rcli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"flag"
|
||||
"log"
|
||||
"strings"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Name() string
|
||||
Help() string
|
||||
}
|
||||
|
||||
type Cmd func(io.ReadCloser, io.Writer, ...string) error
|
||||
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
|
||||
|
||||
|
||||
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
flags := flag.NewFlagSet("main", flag.ContinueOnError)
|
||||
flags.SetOutput(stdout)
|
||||
flags.Usage = func() { stdout.Write([]byte(service.Help())) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := flags.Arg(0)
|
||||
log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))
|
||||
if cmd == "" {
|
||||
cmd = "help"
|
||||
}
|
||||
method := getMethod(service, cmd)
|
||||
if method != nil {
|
||||
return method(stdin, stdout, args[1:]...)
|
||||
}
|
||||
return errors.New("No such command: " + cmd)
|
||||
}
|
||||
|
||||
func getMethod(service Service, name string) Cmd {
|
||||
if name == "help" {
|
||||
return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
if len(args) == 0 {
|
||||
stdout.Write([]byte(service.Help()))
|
||||
} else {
|
||||
if method := getMethod(service, args[0]); method == nil {
|
||||
return errors.New("No such command: " + args[0])
|
||||
} else {
|
||||
method(stdin, stdout, "--help")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:])
|
||||
method, exists := reflect.TypeOf(service).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(service),
|
||||
reflect.ValueOf(stdin),
|
||||
reflect.ValueOf(stdout),
|
||||
reflect.ValueOf(args),
|
||||
})[0].Interface()
|
||||
if ret == nil {
|
||||
return nil
|
||||
}
|
||||
return ret.(error)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
Loading…
Reference in a new issue