Experimenting with a UI which differentiates images and containers

This commit is contained in:
Solomon Hykes 2013-01-27 15:42:42 -08:00
parent 6df37208be
commit 904b0ab52b
5 changed files with 598 additions and 285 deletions

View file

@ -59,16 +59,15 @@ func createContainer(id string, root string, command string, args []string, laye
stdoutLog: new(bytes.Buffer),
stderrLog: new(bytes.Buffer),
}
if err := container.Filesystem.createMountPoints(); err != nil {
return nil, err
}
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
if err := os.Mkdir(root, 0700); err != nil {
return nil, err
}
if err := container.Filesystem.createMountPoints(); err != nil {
return nil, err
}
if err := container.save(); err != nil {
return nil, err
}

View file

@ -3,6 +3,7 @@ package main
import (
"github.com/dotcloud/docker"
"github.com/dotcloud/docker/rcli"
"github.com/dotcloud/docker/image"
"github.com/dotcloud/docker/future"
"bufio"
"errors"
@ -12,7 +13,6 @@ import (
"fmt"
"strings"
"text/tabwriter"
"sort"
"os"
"time"
"net/http"
@ -60,7 +60,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
return nil
}
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 {
return err
}
@ -83,7 +83,7 @@ func (srv *Server) CmdUmount(stdin io.ReadCloser, stdout io.Writer, args ...stri
return nil
}
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 {
return err
}
@ -106,7 +106,7 @@ func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...strin
return nil
}
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 {
return err
}
@ -129,7 +129,7 @@ func (srv *Server) CmdCat(stdin io.ReadCloser, stdout io.Writer, args ...string)
return nil
}
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 {
return err
} 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
}
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 {
return err
} 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
}
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 {
return err
} else {
@ -198,7 +198,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
return nil
}
name := cmd.Arg(0)
if container, exists := srv.findContainer(name); exists {
if container := srv.containers.Get(name); container != nil {
data, err := json.Marshal(container)
if err != nil {
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)
}
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 {
flags := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
if err := flags.Parse(args); err != nil {
return nil
}
for _, name := range flags.Args() {
if _, err := srv.rm(name); err != nil {
fmt.Fprintln(stdout, "Error: " + err.Error())
container := srv.containers.Get(name)
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
@ -312,15 +240,11 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
if err != nil {
return err
}
layer, err := srv.layers.AddLayer(resp.Body, stdout)
img, err := srv.images.Import(args[0], resp.Body, stdout, nil)
if err != nil {
return err
}
container, err := srv.addContainer(layer.Id(), []string{layer.Path}, args[0], "download")
if err != nil {
return err
}
fmt.Fprintln(stdout, container.Id)
fmt.Fprintln(stdout, img.Id)
return nil
}
@ -328,61 +252,180 @@ func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string)
if len(args) < 1 {
return errors.New("Not enough arguments")
}
fmt.Printf("Adding layer\n")
layer, err := srv.layers.AddLayer(stdin, stdout)
img, err := srv.images.Import(args[0], stdin, stdout, nil)
if err != nil {
return err
}
id := layer.Id()
if !srv.docker.Exists(id) {
log.Println("Creating new container: " + id)
log.Printf("%v\n", srv.docker.List())
_, err := srv.addContainer(id, []string{layer.Path}, args[0], "upload")
if err != nil {
return err
fmt.Fprintln(stdout, img.Id)
return nil
}
func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
flags := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
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
}
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,
"fork", "[OPTIONS] CONTAINER [DEST]",
"Duplicate a container")
// FIXME "-r" to reset changes in the new container
"cp", "[OPTIONS] IMAGE NAME",
"Create a copy of IMAGE and call it NAME")
if err := flags.Parse(args); err != nil {
return nil
}
srcName, dstName := flags.Arg(0), flags.Arg(1)
if srcName == "" {
if newImage, err := srv.images.Copy(flags.Arg(0), flags.Arg(1)); err != nil {
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()
return nil
}
if dstName == "" {
dstName = srcName
}
/*
if src, exists := srv.findContainer(srcName); exists {
baseLayer := src.Filesystem.Layers[0]
//dst := srv.addContainer(dstName, "snapshot:" + src.Id, src.Size)
//fmt.Fprintln(stdout, dst.Id)
if container := srv.containers.Get(containerName); container != nil {
// FIXME: freeze the container before copying it to avoid data corruption?
rwTar, err := docker.Tar(container.Filesystem.RWPath)
if err != nil {
return err
}
// Create a new image from the container's base layers + a new layer from container changes
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 errors.New("No such container: " + srcName)
return errors.New("No such container: " + containerName)
}
func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
flags := rcli.Subcmd(stdout,
"tar", "CONTAINER",
"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 {
return nil
}
if *fl_sparse {
return errors.New("Sparse mode not yet implemented") // FIXME
}
name := flags.Arg(0)
if container, exists := srv.findContainer(name); exists {
if container := srv.containers.Get(name); container != nil {
data, err := container.Filesystem.Tar()
if err != nil {
return err
@ -406,7 +449,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
if flags.NArg() < 1 {
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")
} else {
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")
}
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 {
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
}
// 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 {
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
}
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 {
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 {
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_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
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
}
name, cmd := flags.Arg(0), flags.Args()[1:]
if container, exists := srv.findContainer(name); exists {
log.Printf("Running container %#v\n", container)
container.Path = cmd[0]
container.Args = cmd[1:]
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
// Find the image
img := srv.images.Find(name)
if img == nil {
return errors.New("No such image: " + name)
}
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() {
@ -594,30 +590,18 @@ func main() {
}
func New() (*Server, error) {
store, err := future.NewStore("/var/lib/docker/layers")
images, err := image.New("/var/lib/docker/images")
if err != nil {
return nil, err
}
if err := store.Init(); err != nil {
return nil, err
}
d, err := docker.New()
containers, err := docker.New()
if err != nil {
return nil, err
}
srv := &Server{
containersByName: make(map[string]*ByDate),
layers: store,
docker: d,
images: images,
containers: containers,
}
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
}
@ -662,8 +646,7 @@ func (srv *Server) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string)
type Server struct {
containersByName map[string]*ByDate
layers *future.Store
docker *docker.Docker
containers *docker.Docker
images *image.Store
}

316
image/image.go Normal file
View 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
}

View file

@ -1,38 +1,52 @@
package future
package image
import (
"errors"
"path"
"path/filepath"
"io"
"io/ioutil"
"os"
"os/exec"
"github.com/dotcloud/docker/future"
)
type Store struct {
type LayerStore struct {
Root string
}
func NewStore(root string) (*Store, error) {
func NewLayerStore(root string) (*LayerStore, error) {
abspath, err := filepath.Abs(root)
if err != nil {
return nil, err
}
return &Store{
return &LayerStore{
Root: abspath,
}, nil
}
func (store *Store) Get(id string) (*Layer, bool) {
layer := &Layer{Path: store.layerPath(id)}
if !layer.Exists() {
return nil, false
func (store *LayerStore) List() []string {
files, err := ioutil.ReadDir(store.Root)
if err != nil {
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 os.IsNotExist(err) {
return false, nil
@ -44,8 +58,8 @@ func (store *Store) Exists() (bool, error) {
return true, nil
}
func (store *Store) Init() error {
if exists, err := store.Exists(); err != nil {
func (store *LayerStore) Init() error {
if exists, err := store.rootExists(); err != nil {
return err
} else if exists {
return nil
@ -54,8 +68,8 @@ func (store *Store) Init() error {
}
func (store *Store) Mktemp() (string, error) {
tmpName := RandomId()
func (store *LayerStore) Mktemp() (string, error) {
tmpName := future.RandomId()
tmpPath := path.Join(store.Root, "tmp-" + tmpName)
if err := os.Mkdir(tmpPath, 0700); err != nil {
return "", err
@ -63,73 +77,63 @@ func (store *Store) Mktemp() (string, error) {
return tmpPath, nil
}
func (store *Store) layerPath(id string) string {
func (store *LayerStore) layerPath(id string) string {
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()
defer os.RemoveAll(tmp)
if err != nil {
return nil, err
return "", err
}
untarCmd := exec.Command("tar", "-C", tmp, "-x")
untarW, err := untarCmd.StdinPipe()
if err != nil {
return nil, err
return "", err
}
untarStderr, err := untarCmd.StderrPipe()
if err != nil {
return nil, err
return "", err
}
go io.Copy(stderr, untarStderr)
untarStdout, err := untarCmd.StdoutPipe()
if err != nil {
return nil, err
return "", err
}
go io.Copy(stderr, untarStdout)
untarCmd.Start()
hashR, hashW := io.Pipe()
job_copy := Go(func() error {
job_copy := future.Go(func() error {
_, err := io.Copy(io.MultiWriter(hashW, untarW), archive)
hashW.Close()
untarW.Close()
return err
})
id, err := ComputeId(hashR)
id, err := future.ComputeId(hashR)
if err != nil {
return nil, err
return "", err
}
if err := untarCmd.Wait(); err != nil {
return nil, err
return "", err
}
if err := <-job_copy; err != nil {
return nil, err
return "", err
}
layer := &Layer{Path: store.layerPath(id)}
if !layer.Exists() {
if err := os.Rename(tmp, layer.Path); err != nil {
return nil, err
layer := store.layerPath(id)
if !store.Exists(id) {
if err := os.Rename(tmp, layer); err != nil {
return "", err
}
}
return layer, nil
}
type Layer struct {
Path string
}
func (layer *Layer) Exists() bool {
st, err := os.Stat(layer.Path)
func (store *LayerStore) Exists(id string) bool {
st, err := os.Stat(store.layerPath(id))
if err != nil {
return false
}
return st.IsDir()
}
func (layer *Layer) Id() string {
return path.Base(layer.Path)
}

View file

@ -3,6 +3,8 @@ package docker
import (
"sync"
"time"
"fmt"
"github.com/dotcloud/docker/future"
)
type State struct {
@ -15,6 +17,7 @@ type State struct {
stateChangeCond *sync.Cond
}
func newState() *State {
lock := new(sync.Mutex)
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) {
s.Running = true
s.ExitCode = 0