Experimenting with a UI which differentiates images and containers
This commit is contained in:
parent
6df37208be
commit
904b0ab52b
5 changed files with 598 additions and 285 deletions
|
@ -59,16 +59,15 @@ func createContainer(id string, root string, command string, args []string, laye
|
||||||
stdoutLog: new(bytes.Buffer),
|
stdoutLog: new(bytes.Buffer),
|
||||||
stderrLog: new(bytes.Buffer),
|
stderrLog: new(bytes.Buffer),
|
||||||
}
|
}
|
||||||
if err := container.Filesystem.createMountPoints(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
|
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
|
||||||
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
|
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
|
||||||
|
|
||||||
if err := os.Mkdir(root, 0700); err != nil {
|
if err := os.Mkdir(root, 0700); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := container.Filesystem.createMountPoints(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := container.save(); err != nil {
|
if err := container.save(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/dotcloud/docker"
|
"github.com/dotcloud/docker"
|
||||||
"github.com/dotcloud/docker/rcli"
|
"github.com/dotcloud/docker/rcli"
|
||||||
|
"github.com/dotcloud/docker/image"
|
||||||
"github.com/dotcloud/docker/future"
|
"github.com/dotcloud/docker/future"
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -12,7 +13,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"sort"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -60,7 +60,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
if container := srv.docker.Get(name); container != nil {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
if err := container.Stop(); err != nil {
|
if err := container.Stop(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ func (srv *Server) CmdUmount(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
if err := container.Filesystem.Umount(); err != nil {
|
if err := container.Filesystem.Umount(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
if err := container.Filesystem.Mount(); err != nil {
|
if err := container.Filesystem.Mount(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ func (srv *Server) CmdCat(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
name, path := cmd.Arg(0), cmd.Arg(1)
|
name, path := cmd.Arg(0), cmd.Arg(1)
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
if f, err := container.Filesystem.OpenFile(path, os.O_RDONLY, 0); err != nil {
|
if f, err := container.Filesystem.OpenFile(path, os.O_RDONLY, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err := io.Copy(stdout, f); err != nil {
|
} else if _, err := io.Copy(stdout, f); err != nil {
|
||||||
|
@ -151,7 +151,7 @@ func (srv *Server) CmdWrite(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
name, path := cmd.Arg(0), cmd.Arg(1)
|
name, path := cmd.Arg(0), cmd.Arg(1)
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
if f, err := container.Filesystem.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600); err != nil {
|
if f, err := container.Filesystem.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err := io.Copy(f, stdin); err != nil {
|
} else if _, err := io.Copy(f, stdin); err != nil {
|
||||||
|
@ -174,7 +174,7 @@ func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
name, path := cmd.Arg(0), cmd.Arg(1)
|
name, path := cmd.Arg(0), cmd.Arg(1)
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
if files, err := container.Filesystem.ReadDir(path); err != nil {
|
if files, err := container.Filesystem.ReadDir(path); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
|
@ -198,7 +198,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
name := cmd.Arg(0)
|
name := cmd.Arg(0)
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
data, err := json.Marshal(container)
|
data, err := json.Marshal(container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -215,90 +215,18 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
|
||||||
return errors.New("No such container: " + name)
|
return errors.New("No such container: " + name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
||||||
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)
|
|
||||||
if flags.NArg() > 1 {
|
|
||||||
flags.Usage()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var nameFilter string
|
|
||||||
if flags.NArg() == 1 {
|
|
||||||
nameFilter = flags.Arg(0)
|
|
||||||
}
|
|
||||||
var names []string
|
|
||||||
for name := range srv.containersByName {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
|
||||||
if (!*quiet) {
|
|
||||||
fmt.Fprintf(w, "NAME\tID\tCREATED\tSOURCE\tRUNNING\tMOUNTED\tCOMMAND\tPID\tEXIT\n")
|
|
||||||
}
|
|
||||||
for _, name := range names {
|
|
||||||
if nameFilter != "" && nameFilter != name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for idx, container := range *srv.containersByName[name] {
|
|
||||||
if *limit > 0 && idx >= *limit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !*quiet {
|
|
||||||
for idx, field := range []string{
|
|
||||||
/* NAME */ container.GetUserData("name"),
|
|
||||||
/* ID */ container.Id,
|
|
||||||
/* CREATED */ future.HumanDuration(time.Now().Sub(container.Created)) + " ago",
|
|
||||||
/* SOURCE */ container.GetUserData("source"),
|
|
||||||
/* RUNNING */ fmt.Sprintf("%v", container.State.Running),
|
|
||||||
/* MOUNTED */ fmt.Sprintf("%v", container.Filesystem.IsMounted()),
|
|
||||||
/* COMMAND */ fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
|
|
||||||
/* PID */ fmt.Sprintf("%v", container.State.Pid),
|
|
||||||
/* EXIT CODE */ fmt.Sprintf("%v", container.State.ExitCode),
|
|
||||||
} {
|
|
||||||
if idx == 0 {
|
|
||||||
w.Write([]byte(field))
|
|
||||||
} else {
|
|
||||||
w.Write([]byte("\t" + field))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write([]byte{'\n'})
|
|
||||||
} else {
|
|
||||||
stdout.Write([]byte(container.Id + "\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!*quiet) {
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (srv *Server) findContainer(name string) (*docker.Container, bool) {
|
|
||||||
// 1: look for container by ID
|
|
||||||
if container := srv.docker.Get(name); container != nil {
|
|
||||||
return container, true
|
|
||||||
}
|
|
||||||
// 2: look for a container by name (and pick the most recent)
|
|
||||||
if containers, exists := srv.containersByName[name]; exists {
|
|
||||||
return (*containers)[0], true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
flags := rcli.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 {
|
if err := flags.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, name := range flags.Args() {
|
for _, name := range flags.Args() {
|
||||||
if _, err := srv.rm(name); err != nil {
|
container := srv.containers.Get(name)
|
||||||
fmt.Fprintln(stdout, "Error: " + err.Error())
|
if container == nil {
|
||||||
|
return errors.New("No such container: " + name)
|
||||||
|
}
|
||||||
|
if err := srv.containers.Destroy(container); err != nil {
|
||||||
|
fmt.Fprintln(stdout, "Error destroying container " + name + ": " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -312,15 +240,11 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
layer, err := srv.layers.AddLayer(resp.Body, stdout)
|
img, err := srv.images.Import(args[0], resp.Body, stdout, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container, err := srv.addContainer(layer.Id(), []string{layer.Path}, args[0], "download")
|
fmt.Fprintln(stdout, img.Id)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintln(stdout, container.Id)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,61 +252,180 @@ func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return errors.New("Not enough arguments")
|
return errors.New("Not enough arguments")
|
||||||
}
|
}
|
||||||
fmt.Printf("Adding layer\n")
|
img, err := srv.images.Import(args[0], stdin, stdout, nil)
|
||||||
layer, err := srv.layers.AddLayer(stdin, stdout)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
id := layer.Id()
|
fmt.Fprintln(stdout, img.Id)
|
||||||
if !srv.docker.Exists(id) {
|
return nil
|
||||||
log.Println("Creating new container: " + id)
|
}
|
||||||
log.Printf("%v\n", srv.docker.List())
|
|
||||||
_, err := srv.addContainer(id, []string{layer.Path}, args[0], "upload")
|
func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
if err != nil {
|
flags := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
|
||||||
return err
|
limit := flags.Int("l", 0, "Only show the N most recent versions of each image")
|
||||||
|
quiet := flags.Bool("q", false, "only show numeric IDs")
|
||||||
|
flags.Parse(args)
|
||||||
|
if flags.NArg() > 1 {
|
||||||
|
flags.Usage()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var nameFilter string
|
||||||
|
if flags.NArg() == 1 {
|
||||||
|
nameFilter = flags.Arg(0)
|
||||||
|
}
|
||||||
|
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
||||||
|
if (!*quiet) {
|
||||||
|
fmt.Fprintf(w, "NAME\tID\tCREATED\tPARENT\n")
|
||||||
|
}
|
||||||
|
for _, name := range srv.images.Names() {
|
||||||
|
if nameFilter != "" && nameFilter != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for idx, img := range *srv.images.ByName[name] {
|
||||||
|
if *limit > 0 && idx >= *limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !*quiet {
|
||||||
|
id := img.Id
|
||||||
|
if !img.IdIsFinal() {
|
||||||
|
id += "..."
|
||||||
|
}
|
||||||
|
for idx, field := range []string{
|
||||||
|
/* NAME */ name,
|
||||||
|
/* ID */ id,
|
||||||
|
/* CREATED */ future.HumanDuration(time.Now().Sub(img.Created)) + " ago",
|
||||||
|
/* PARENT */ img.Parent,
|
||||||
|
} {
|
||||||
|
if idx == 0 {
|
||||||
|
w.Write([]byte(field))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte("\t" + field))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte{'\n'})
|
||||||
|
} else {
|
||||||
|
stdout.Write([]byte(img.Id + "\n"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintln(stdout, id)
|
if (!*quiet) {
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) CmdContainers(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
|
cmd := rcli.Subcmd(stdout,
|
||||||
|
"containers", "[OPTIONS]",
|
||||||
|
"List containers")
|
||||||
|
quiet := cmd.Bool("q", false, "Only display numeric IDs")
|
||||||
|
if err := cmd.Parse(args); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
||||||
|
if (!*quiet) {
|
||||||
|
fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\n")
|
||||||
|
}
|
||||||
|
for _, container := range srv.containers.List() {
|
||||||
|
if !*quiet {
|
||||||
|
for idx, field := range[]string {
|
||||||
|
/* ID */ container.Id,
|
||||||
|
/* IMAGE */ container.GetUserData("image"),
|
||||||
|
/* COMMAND */ fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
|
||||||
|
/* CREATED */ future.HumanDuration(time.Now().Sub(container.Created)) + " ago",
|
||||||
|
/* STATUS */ container.State.String(),
|
||||||
|
} {
|
||||||
|
if idx == 0 {
|
||||||
|
w.Write([]byte(field))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte("\t" + field))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte{'\n'})
|
||||||
|
} else {
|
||||||
|
stdout.Write([]byte(container.Id + "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!*quiet) {
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) CmdLayers(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
|
flags := rcli.Subcmd(stdout,
|
||||||
|
"layers", "[OPTIONS]",
|
||||||
|
"List filesystem layers (debug only)")
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, layer := range srv.images.Layers.List() {
|
||||||
|
fmt.Fprintln(stdout, layer)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdCp(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
flags := rcli.Subcmd(stdout,
|
flags := rcli.Subcmd(stdout,
|
||||||
"fork", "[OPTIONS] CONTAINER [DEST]",
|
"cp", "[OPTIONS] IMAGE NAME",
|
||||||
"Duplicate a container")
|
"Create a copy of IMAGE and call it NAME")
|
||||||
// FIXME "-r" to reset changes in the new container
|
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
srcName, dstName := flags.Arg(0), flags.Arg(1)
|
if newImage, err := srv.images.Copy(flags.Arg(0), flags.Arg(1)); err != nil {
|
||||||
if srcName == "" {
|
return err
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(stdout, newImage.Id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
|
flags := rcli.Subcmd(stdout,
|
||||||
|
"commit", "[OPTIONS] CONTAINER [DEST]",
|
||||||
|
"Create a new image from a container's changes")
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
containerName, imgName := flags.Arg(0), flags.Arg(1)
|
||||||
|
if containerName == "" || imgName == "" {
|
||||||
flags.Usage()
|
flags.Usage()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if dstName == "" {
|
if container := srv.containers.Get(containerName); container != nil {
|
||||||
dstName = srcName
|
// FIXME: freeze the container before copying it to avoid data corruption?
|
||||||
}
|
rwTar, err := docker.Tar(container.Filesystem.RWPath)
|
||||||
/*
|
if err != nil {
|
||||||
if src, exists := srv.findContainer(srcName); exists {
|
return err
|
||||||
baseLayer := src.Filesystem.Layers[0]
|
}
|
||||||
//dst := srv.addContainer(dstName, "snapshot:" + src.Id, src.Size)
|
// Create a new image from the container's base layers + a new layer from container changes
|
||||||
//fmt.Fprintln(stdout, dst.Id)
|
parentImg := srv.images.Find(container.GetUserData("image"))
|
||||||
|
img, err := srv.images.Import(imgName, rwTar, stdout, parentImg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintln(stdout, img.Id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
*/
|
return errors.New("No such container: " + containerName)
|
||||||
return errors.New("No such container: " + srcName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
flags := rcli.Subcmd(stdout,
|
flags := rcli.Subcmd(stdout,
|
||||||
"tar", "CONTAINER",
|
"tar", "CONTAINER",
|
||||||
"Stream the contents of a container as a tar archive")
|
"Stream the contents of a container as a tar archive")
|
||||||
|
fl_sparse := flags.Bool("s", false, "Generate a sparse tar stream (top layer + reference to bottom layers)")
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if *fl_sparse {
|
||||||
|
return errors.New("Sparse mode not yet implemented") // FIXME
|
||||||
|
}
|
||||||
name := flags.Arg(0)
|
name := flags.Arg(0)
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
data, err := container.Filesystem.Tar()
|
data, err := container.Filesystem.Tar()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -406,7 +449,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||||
if flags.NArg() < 1 {
|
if flags.NArg() < 1 {
|
||||||
return errors.New("Not enough arguments")
|
return errors.New("Not enough arguments")
|
||||||
}
|
}
|
||||||
if container, exists := srv.findContainer(flags.Arg(0)); !exists {
|
if container := srv.containers.Get(flags.Arg(0)); container == nil {
|
||||||
return errors.New("No such container")
|
return errors.New("No such container")
|
||||||
} else {
|
} else {
|
||||||
changes, err := container.Filesystem.Changes()
|
changes, err := container.Filesystem.Changes()
|
||||||
|
@ -431,7 +474,7 @@ func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||||
return errors.New("Not enough arguments")
|
return errors.New("Not enough arguments")
|
||||||
}
|
}
|
||||||
for _, name := range flags.Args() {
|
for _, name := range flags.Args() {
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
if err := container.Filesystem.Reset(); err != nil {
|
if err := container.Filesystem.Reset(); err != nil {
|
||||||
return errors.New("Reset " + container.Id + ": " + err.Error())
|
return errors.New("Reset " + container.Id + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
@ -440,70 +483,6 @@ func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByDate wraps an array of layers so they can be sorted by date (most recent first)
|
|
||||||
|
|
||||||
type ByDate []*docker.Container
|
|
||||||
|
|
||||||
func (c *ByDate) Len() int {
|
|
||||||
return len(*c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ByDate) Less(i, j int) bool {
|
|
||||||
containers := *c
|
|
||||||
return containers[j].Created.Before(containers[i].Created)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ByDate) Swap(i, j int) {
|
|
||||||
containers := *c
|
|
||||||
tmp := containers[i]
|
|
||||||
containers[i] = containers[j]
|
|
||||||
containers[j] = tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ByDate) Add(container *docker.Container) {
|
|
||||||
*c = append(*c, container)
|
|
||||||
sort.Sort(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ByDate) Del(id string) {
|
|
||||||
for idx, container := range *c {
|
|
||||||
if container.Id == id {
|
|
||||||
*c = append((*c)[:idx], (*c)[idx + 1:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) addContainer(id string, layers []string, name string, source string) (*docker.Container, error) {
|
|
||||||
c, err := srv.docker.Create(id, "", nil, layers, &docker.Config{Hostname: id, Ram: 512 * 1024 * 1024})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.SetUserData("name", name); err != nil {
|
|
||||||
srv.docker.Destroy(c)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, exists := srv.containersByName[name]; !exists {
|
|
||||||
srv.containersByName[name] = new(ByDate)
|
|
||||||
}
|
|
||||||
srv.containersByName[name].Add(c)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) rm(id string) (*docker.Container, error) {
|
|
||||||
container := srv.docker.Get(id)
|
|
||||||
if container == nil {
|
|
||||||
return nil, errors.New("No such continer: " + id)
|
|
||||||
}
|
|
||||||
// Remove from name lookup
|
|
||||||
srv.containersByName[container.GetUserData("name")].Del(container.Id)
|
|
||||||
if err := srv.docker.Destroy(container); err != nil {
|
|
||||||
return container, err
|
|
||||||
}
|
|
||||||
return container, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
flags := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
|
flags := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
|
||||||
|
@ -515,7 +494,7 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
name := flags.Arg(0)
|
name := flags.Arg(0)
|
||||||
if container, exists := srv.findContainer(name); exists {
|
if container := srv.containers.Get(name); container != nil {
|
||||||
if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
|
if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -528,8 +507,21 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (srv *Server) CreateContainer(img *image.Image, cmd string, args ...string) (*docker.Container, error) {
|
||||||
|
id := future.RandomId()
|
||||||
|
container, err := srv.containers.Create(id, cmd, args, img.Layers, &docker.Config{Hostname: id})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := container.SetUserData("image", img.Id); err != nil {
|
||||||
|
srv.containers.Destroy(container)
|
||||||
|
return nil, errors.New("Error setting container userdata: " + err.Error())
|
||||||
|
}
|
||||||
|
return container, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
flags := rcli.Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
|
flags := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
|
||||||
fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
|
fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
|
||||||
//fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
|
//fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
|
@ -540,40 +532,44 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
name, cmd := flags.Arg(0), flags.Args()[1:]
|
name, cmd := flags.Arg(0), flags.Args()[1:]
|
||||||
if container, exists := srv.findContainer(name); exists {
|
// Find the image
|
||||||
log.Printf("Running container %#v\n", container)
|
img := srv.images.Find(name)
|
||||||
container.Path = cmd[0]
|
if img == nil {
|
||||||
container.Args = cmd[1:]
|
return errors.New("No such image: " + name)
|
||||||
if *fl_attach {
|
|
||||||
cmd_stdout, err := container.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd_stderr, err := container.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := container.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err })
|
|
||||||
sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err })
|
|
||||||
err_sending_stdout := <-sending_stdout
|
|
||||||
err_sending_stderr := <-sending_stderr
|
|
||||||
if err_sending_stdout != nil {
|
|
||||||
return err_sending_stdout
|
|
||||||
}
|
|
||||||
return err_sending_stderr
|
|
||||||
} else {
|
|
||||||
if output, err := container.Output(); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
fmt.Printf("-->|%s|\n", output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return errors.New("No such container: " + name)
|
// Create new container
|
||||||
|
container, err := srv.CreateContainer(img, cmd[0], cmd[1:]...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Error creating container: " + err.Error())
|
||||||
|
}
|
||||||
|
// Run the container
|
||||||
|
if *fl_attach {
|
||||||
|
cmd_stdout, err := container.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd_stderr, err := container.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := container.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err })
|
||||||
|
sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err })
|
||||||
|
err_sending_stdout := <-sending_stdout
|
||||||
|
err_sending_stderr := <-sending_stderr
|
||||||
|
if err_sending_stdout != nil {
|
||||||
|
return err_sending_stdout
|
||||||
|
}
|
||||||
|
return err_sending_stderr
|
||||||
|
} else {
|
||||||
|
if err := container.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintln(stdout, container.Id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -594,30 +590,18 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (*Server, error) {
|
func New() (*Server, error) {
|
||||||
store, err := future.NewStore("/var/lib/docker/layers")
|
images, err := image.New("/var/lib/docker/images")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := store.Init(); err != nil {
|
containers, err := docker.New()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
d, err := docker.New()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
containersByName: make(map[string]*ByDate),
|
images: images,
|
||||||
layers: store,
|
containers: containers,
|
||||||
docker: d,
|
|
||||||
}
|
}
|
||||||
for _, container := range srv.docker.List() {
|
|
||||||
name := container.GetUserData("name")
|
|
||||||
if _, exists := srv.containersByName[name]; !exists {
|
|
||||||
srv.containersByName[name] = new(ByDate)
|
|
||||||
}
|
|
||||||
srv.containersByName[name].Add(container)
|
|
||||||
}
|
|
||||||
log.Printf("Done building index\n")
|
|
||||||
return srv, nil
|
return srv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,8 +646,7 @@ func (srv *Server) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
containersByName map[string]*ByDate
|
containers *docker.Docker
|
||||||
layers *future.Store
|
images *image.Store
|
||||||
docker *docker.Docker
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
316
image/image.go
Normal file
316
image/image.go
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"os"
|
||||||
|
"github.com/dotcloud/docker/future"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
*Index
|
||||||
|
Root string
|
||||||
|
Layers *LayerStore
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func New(root string) (*Store, error) {
|
||||||
|
abspath, err := filepath.Abs(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
layers, err := NewLayerStore(path.Join(root, "layers"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := layers.Init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Store{
|
||||||
|
Root: abspath,
|
||||||
|
Index: NewIndex(path.Join(root, "index.json")),
|
||||||
|
Layers: layers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (store *Store) Import(name string, archive io.Reader, stderr io.Writer, parent *Image) (*Image, error) {
|
||||||
|
layer, err := store.Layers.AddLayer(archive, stderr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
layers := []string{layer}
|
||||||
|
if parent != nil {
|
||||||
|
layers = append(parent.Layers, layers...)
|
||||||
|
}
|
||||||
|
var parentId string
|
||||||
|
if parent != nil {
|
||||||
|
parentId = parent.Id
|
||||||
|
}
|
||||||
|
return store.Create(name, parentId, layers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *Store) Create(name string, source string, layers ...string) (*Image, error) {
|
||||||
|
image, err := NewImage(name, layers, source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := store.Index.Add(name, image); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Index
|
||||||
|
|
||||||
|
type Index struct {
|
||||||
|
Path string
|
||||||
|
ByName map[string]*History
|
||||||
|
ById map[string]*Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIndex(path string) *Index {
|
||||||
|
return &Index{
|
||||||
|
Path: path,
|
||||||
|
ByName: make(map[string]*History),
|
||||||
|
ById: make(map[string]*Image),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *Index) Exists(id string) bool {
|
||||||
|
_, exists := index.ById[id]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *Index) Find(idOrName string) *Image {
|
||||||
|
// Load
|
||||||
|
if err := index.load(); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Lookup by ID
|
||||||
|
if image, exists := index.ById[idOrName]; exists {
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
// Lookup by name
|
||||||
|
if history, exists := index.ByName[idOrName]; exists && history.Len() > 0 {
|
||||||
|
return (*history)[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *Index) Add(name string, image *Image) error {
|
||||||
|
// Load
|
||||||
|
if err := index.load(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, exists := index.ByName[name]; !exists {
|
||||||
|
index.ByName[name] = new(History)
|
||||||
|
} else {
|
||||||
|
// If this image is already the latest version, don't add it.
|
||||||
|
if (*index.ByName[name])[0].Id == image.Id {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index.ByName[name].Add(image)
|
||||||
|
index.ById[image.Id] = image
|
||||||
|
// Save
|
||||||
|
if err := index.save(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *Index) Copy(srcNameOrId, dstName string) (*Image, error) {
|
||||||
|
if srcNameOrId == "" || dstName == "" {
|
||||||
|
return nil, errors.New("Illegal image name")
|
||||||
|
}
|
||||||
|
// Load
|
||||||
|
if err := index.load(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
src := index.Find(srcNameOrId)
|
||||||
|
if src == nil {
|
||||||
|
return nil, errors.New("No such image: " + srcNameOrId)
|
||||||
|
}
|
||||||
|
if index.Find(dstName) != nil {
|
||||||
|
return nil, errors.New(dstName + ": image already exists.")
|
||||||
|
}
|
||||||
|
dst, err := NewImage(dstName, src.Layers, src.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := index.Add(dstName, dst); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Save
|
||||||
|
if err := index.save(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *Index) Rename(oldName, newName string) error {
|
||||||
|
// Load
|
||||||
|
if err := index.load(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, exists := index.ByName[oldName]; !exists {
|
||||||
|
return errors.New("Can't rename " + oldName + ": no such image.")
|
||||||
|
}
|
||||||
|
if _, exists := index.ByName[newName]; exists {
|
||||||
|
return errors.New("Can't rename to " + newName + ": name is already in use.")
|
||||||
|
}
|
||||||
|
index.ByName[newName] = index.ByName[oldName]
|
||||||
|
delete(index.ByName, oldName)
|
||||||
|
// Change the ID of all images, since they include the name
|
||||||
|
for _, image := range *index.ByName[newName] {
|
||||||
|
if id, err := generateImageId(newName, image.Layers); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
oldId := image.Id
|
||||||
|
image.Id = id
|
||||||
|
index.ById[id] = image
|
||||||
|
delete(index.ById, oldId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save
|
||||||
|
if err := index.save(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *Index) Names() []string {
|
||||||
|
if err := index.load(); err != nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
var names[]string
|
||||||
|
for name := range index.ByName {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *Index) load() error {
|
||||||
|
jsonData, err := ioutil.ReadFile(index.Path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
path := index.Path
|
||||||
|
if err := json.Unmarshal(jsonData, index); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
index.Path = path
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *Index) save() error {
|
||||||
|
jsonData, err := json.Marshal(index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(index.Path, jsonData, 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// History wraps an array of images so they can be sorted by date (most recent first)
|
||||||
|
|
||||||
|
type History []*Image
|
||||||
|
|
||||||
|
func (history *History) Len() int {
|
||||||
|
return len(*history)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (history *History) Less(i, j int) bool {
|
||||||
|
images := *history
|
||||||
|
return images[j].Created.Before(images[i].Created)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (history *History) Swap(i, j int) {
|
||||||
|
images := *history
|
||||||
|
tmp := images[i]
|
||||||
|
images[i] = images[j]
|
||||||
|
images[j] = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (history *History) Add(image *Image) {
|
||||||
|
*history = append(*history, image)
|
||||||
|
sort.Sort(history)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (history *History) Del(id string) {
|
||||||
|
for idx, image := range *history {
|
||||||
|
if image.Id == id {
|
||||||
|
*history = append((*history)[:idx], (*history)[idx + 1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
Id string // Globally unique identifier
|
||||||
|
Layers []string // Absolute paths
|
||||||
|
Created time.Time
|
||||||
|
Parent string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *Image) IdParts() (string, string) {
|
||||||
|
if len(image.Id) < 8 {
|
||||||
|
return "", image.Id
|
||||||
|
}
|
||||||
|
hash := image.Id[len(image.Id)-8:len(image.Id)]
|
||||||
|
name := image.Id[:len(image.Id)-9]
|
||||||
|
return name, hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *Image) IdIsFinal() bool {
|
||||||
|
return len(image.Layers) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateImageId(name string, layers []string) (string, error) {
|
||||||
|
if len(layers) == 0 {
|
||||||
|
return "", errors.New("No layers provided.")
|
||||||
|
}
|
||||||
|
var hash string
|
||||||
|
if len(layers) == 1 {
|
||||||
|
hash = path.Base(layers[0])
|
||||||
|
} else {
|
||||||
|
var ids string
|
||||||
|
for _, layer := range layers {
|
||||||
|
ids += path.Base(layer)
|
||||||
|
}
|
||||||
|
if h, err := future.ComputeId(strings.NewReader(ids)); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
hash = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name + ":" + hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImage(name string, layers []string, parent string) (*Image, error) {
|
||||||
|
id, err := generateImageId(name, layers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Image{
|
||||||
|
Id: id,
|
||||||
|
Layers: layers,
|
||||||
|
Created: time.Now(),
|
||||||
|
Parent: parent,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -1,38 +1,52 @@
|
||||||
package future
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"github.com/dotcloud/docker/future"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Store struct {
|
type LayerStore struct {
|
||||||
Root string
|
Root string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewLayerStore(root string) (*LayerStore, error) {
|
||||||
func NewStore(root string) (*Store, error) {
|
|
||||||
abspath, err := filepath.Abs(root)
|
abspath, err := filepath.Abs(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Store{
|
return &LayerStore{
|
||||||
Root: abspath,
|
Root: abspath,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) Get(id string) (*Layer, bool) {
|
func (store *LayerStore) List() []string {
|
||||||
layer := &Layer{Path: store.layerPath(id)}
|
files, err := ioutil.ReadDir(store.Root)
|
||||||
if !layer.Exists() {
|
if err != nil {
|
||||||
return nil, false
|
return []string{}
|
||||||
}
|
}
|
||||||
return layer, true
|
var layers []string
|
||||||
|
for _, st := range files {
|
||||||
|
if st.IsDir() {
|
||||||
|
layers = append(layers, path.Join(store.Root, st.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return layers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) Exists() (bool, error) {
|
func (store *LayerStore) Get(id string) string {
|
||||||
|
if !store.Exists(id) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return store.layerPath(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *LayerStore) rootExists() (bool, error) {
|
||||||
if stat, err := os.Stat(store.Root); err != nil {
|
if stat, err := os.Stat(store.Root); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -44,8 +58,8 @@ func (store *Store) Exists() (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) Init() error {
|
func (store *LayerStore) Init() error {
|
||||||
if exists, err := store.Exists(); err != nil {
|
if exists, err := store.rootExists(); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if exists {
|
} else if exists {
|
||||||
return nil
|
return nil
|
||||||
|
@ -54,8 +68,8 @@ func (store *Store) Init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (store *Store) Mktemp() (string, error) {
|
func (store *LayerStore) Mktemp() (string, error) {
|
||||||
tmpName := RandomId()
|
tmpName := future.RandomId()
|
||||||
tmpPath := path.Join(store.Root, "tmp-" + tmpName)
|
tmpPath := path.Join(store.Root, "tmp-" + tmpName)
|
||||||
if err := os.Mkdir(tmpPath, 0700); err != nil {
|
if err := os.Mkdir(tmpPath, 0700); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -63,73 +77,63 @@ func (store *Store) Mktemp() (string, error) {
|
||||||
return tmpPath, nil
|
return tmpPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) layerPath(id string) string {
|
func (store *LayerStore) layerPath(id string) string {
|
||||||
return path.Join(store.Root, id)
|
return path.Join(store.Root, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (store *Store) AddLayer(archive io.Reader, stderr io.Writer) (*Layer, error) {
|
func (store *LayerStore) AddLayer(archive io.Reader, stderr io.Writer) (string, error) {
|
||||||
tmp, err := store.Mktemp()
|
tmp, err := store.Mktemp()
|
||||||
defer os.RemoveAll(tmp)
|
defer os.RemoveAll(tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
untarCmd := exec.Command("tar", "-C", tmp, "-x")
|
untarCmd := exec.Command("tar", "-C", tmp, "-x")
|
||||||
untarW, err := untarCmd.StdinPipe()
|
untarW, err := untarCmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
untarStderr, err := untarCmd.StderrPipe()
|
untarStderr, err := untarCmd.StderrPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
go io.Copy(stderr, untarStderr)
|
go io.Copy(stderr, untarStderr)
|
||||||
untarStdout, err := untarCmd.StdoutPipe()
|
untarStdout, err := untarCmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
go io.Copy(stderr, untarStdout)
|
go io.Copy(stderr, untarStdout)
|
||||||
untarCmd.Start()
|
untarCmd.Start()
|
||||||
hashR, hashW := io.Pipe()
|
hashR, hashW := io.Pipe()
|
||||||
job_copy := Go(func() error {
|
job_copy := future.Go(func() error {
|
||||||
_, err := io.Copy(io.MultiWriter(hashW, untarW), archive)
|
_, err := io.Copy(io.MultiWriter(hashW, untarW), archive)
|
||||||
hashW.Close()
|
hashW.Close()
|
||||||
untarW.Close()
|
untarW.Close()
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
id, err := ComputeId(hashR)
|
id, err := future.ComputeId(hashR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := untarCmd.Wait(); err != nil {
|
if err := untarCmd.Wait(); err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := <-job_copy; err != nil {
|
if err := <-job_copy; err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
layer := &Layer{Path: store.layerPath(id)}
|
layer := store.layerPath(id)
|
||||||
if !layer.Exists() {
|
if !store.Exists(id) {
|
||||||
if err := os.Rename(tmp, layer.Path); err != nil {
|
if err := os.Rename(tmp, layer); err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return layer, nil
|
return layer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *LayerStore) Exists(id string) bool {
|
||||||
type Layer struct {
|
st, err := os.Stat(store.layerPath(id))
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (layer *Layer) Exists() bool {
|
|
||||||
st, err := os.Stat(layer.Path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return st.IsDir()
|
return st.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layer *Layer) Id() string {
|
|
||||||
return path.Base(layer.Path)
|
|
||||||
}
|
|
11
state.go
11
state.go
|
@ -3,6 +3,8 @@ package docker
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"fmt"
|
||||||
|
"github.com/dotcloud/docker/future"
|
||||||
)
|
)
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
|
@ -15,6 +17,7 @@ type State struct {
|
||||||
stateChangeCond *sync.Cond
|
stateChangeCond *sync.Cond
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func newState() *State {
|
func newState() *State {
|
||||||
lock := new(sync.Mutex)
|
lock := new(sync.Mutex)
|
||||||
return &State{
|
return &State{
|
||||||
|
@ -23,6 +26,14 @@ func newState() *State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a human-readable description of the state
|
||||||
|
func (s *State) String() string {
|
||||||
|
if s.Running {
|
||||||
|
return fmt.Sprintf("Running for %s", future.HumanDuration(time.Now().Sub(s.StartedAt)))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Exited with %d", s.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) setRunning(pid int) {
|
func (s *State) setRunning(pid int) {
|
||||||
s.Running = true
|
s.Running = true
|
||||||
s.ExitCode = 0
|
s.ExitCode = 0
|
||||||
|
|
Loading…
Reference in a new issue