mirror of https://github.com/docker/docs.git
Merged upstream changes in fs branch
This commit is contained in:
commit
2441edf1a3
11
container.go
11
container.go
|
@ -2,6 +2,7 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"./fs"
|
"./fs"
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/kr/pty"
|
"github.com/kr/pty"
|
||||||
|
@ -118,17 +119,25 @@ func createContainer(id string, root string, command string, args []string, imag
|
||||||
return container, nil
|
return container, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadContainer(containerPath string, netManager *NetworkManager) (*Container, error) {
|
func loadContainer(store *fs.Store, containerPath string, netManager *NetworkManager) (*Container, error) {
|
||||||
data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
|
data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
mountpoint, err := store.FetchMountpoint(
|
||||||
|
path.Join(containerPath, "rootfs"),
|
||||||
|
path.Join(containerPath, "rw"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
container := &Container{
|
container := &Container{
|
||||||
stdout: newWriteBroadcaster(),
|
stdout: newWriteBroadcaster(),
|
||||||
stderr: newWriteBroadcaster(),
|
stderr: newWriteBroadcaster(),
|
||||||
lxcConfigPath: path.Join(containerPath, "config.lxc"),
|
lxcConfigPath: path.Join(containerPath, "config.lxc"),
|
||||||
networkManager: netManager,
|
networkManager: netManager,
|
||||||
NetworkSettings: &NetworkSettings{},
|
NetworkSettings: &NetworkSettings{},
|
||||||
|
Mountpoint: mountpoint,
|
||||||
}
|
}
|
||||||
// Load container settings
|
// Load container settings
|
||||||
if err := json.Unmarshal(data, container); err != nil {
|
if err := json.Unmarshal(data, container); err != nil {
|
||||||
|
|
|
@ -94,12 +94,11 @@ func (docker *Docker) restore() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, v := range dir {
|
for _, v := range dir {
|
||||||
container, err := loadContainer(path.Join(docker.repository, v.Name()), docker.networkManager)
|
container, err := loadContainer(docker.Store, path.Join(docker.repository, v.Name()), docker.networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to load container %v: %v", v.Name(), err)
|
log.Printf("Failed to load container %v: %v", v.Name(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
container.Mountpoint.Store = docker.Store
|
|
||||||
docker.containers.PushBack(container)
|
docker.containers.PushBack(container)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
".."
|
".."
|
||||||
"../server"
|
"../server"
|
||||||
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ func FakeTar() (io.Reader, error) {
|
||||||
content := []byte("Hello world!\n")
|
content := []byte("Hello world!\n")
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
tw := tar.NewWriter(buf)
|
tw := tar.NewWriter(buf)
|
||||||
for _, name := range []string{"hello", "etc/postgres/postgres.conf", "etc/passwd", "var/log/postgres/postgres.conf"} {
|
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
||||||
hdr := new(tar.Header)
|
hdr := new(tar.Header)
|
||||||
hdr.Size = int64(len(content))
|
hdr.Size = int64(len(content))
|
||||||
hdr.Name = name
|
hdr.Name = name
|
||||||
|
|
192
fs/changes.go
192
fs/changes.go
|
@ -1,129 +1,129 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"os"
|
||||||
"os"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChangeType int
|
type ChangeType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ChangeModify = iota
|
ChangeModify = iota
|
||||||
ChangeAdd
|
ChangeAdd
|
||||||
ChangeDelete
|
ChangeDelete
|
||||||
)
|
)
|
||||||
|
|
||||||
type Change struct {
|
type Change struct {
|
||||||
Path string
|
Path string
|
||||||
Kind ChangeType
|
Kind ChangeType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (change *Change) String() string {
|
func (change *Change) String() string {
|
||||||
var kind string
|
var kind string
|
||||||
switch change.Kind {
|
switch change.Kind {
|
||||||
case ChangeModify:
|
case ChangeModify:
|
||||||
kind = "C"
|
kind = "C"
|
||||||
case ChangeAdd:
|
case ChangeAdd:
|
||||||
kind = "A"
|
kind = "A"
|
||||||
case ChangeDelete:
|
case ChangeDelete:
|
||||||
kind = "D"
|
kind = "D"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s %s", kind, change.Path)
|
return fmt.Sprintf("%s %s", kind, change.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) Changes(mp *Mountpoint) ([]Change, error) {
|
func (store *Store) Changes(mp *Mountpoint) ([]Change, error) {
|
||||||
var changes []Change
|
var changes []Change
|
||||||
image, err := store.Get(mp.Image)
|
image, err := store.Get(mp.Image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
layers, err := image.layers()
|
layers, err := image.layers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = filepath.Walk(mp.Rw, func(path string, f os.FileInfo, err error) error {
|
err = filepath.Walk(mp.Rw, func(path string, f os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebase path
|
// Rebase path
|
||||||
path, err = filepath.Rel(mp.Rw, path)
|
path, err = filepath.Rel(mp.Rw, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
path = filepath.Join("/", path)
|
path = filepath.Join("/", path)
|
||||||
|
|
||||||
// Skip root
|
// Skip root
|
||||||
if path == "/" {
|
if path == "/" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip AUFS metadata
|
// Skip AUFS metadata
|
||||||
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
change := Change{
|
change := Change{
|
||||||
Path: path,
|
Path: path,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find out what kind of modification happened
|
// Find out what kind of modification happened
|
||||||
file := filepath.Base(path)
|
file := filepath.Base(path)
|
||||||
// If there is a whiteout, then the file was removed
|
// If there is a whiteout, then the file was removed
|
||||||
if strings.HasPrefix(file, ".wh.") {
|
if strings.HasPrefix(file, ".wh.") {
|
||||||
originalFile := strings.TrimLeft(file, ".wh.")
|
originalFile := strings.TrimLeft(file, ".wh.")
|
||||||
change.Path = filepath.Join(filepath.Dir(path), originalFile)
|
change.Path = filepath.Join(filepath.Dir(path), originalFile)
|
||||||
change.Kind = ChangeDelete
|
change.Kind = ChangeDelete
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, the file was added
|
// Otherwise, the file was added
|
||||||
change.Kind = ChangeAdd
|
change.Kind = ChangeAdd
|
||||||
|
|
||||||
// ...Unless it already existed in a top layer, in which case, it's a modification
|
// ...Unless it already existed in a top layer, in which case, it's a modification
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
stat, err := os.Stat(filepath.Join(layer, path))
|
stat, err := os.Stat(filepath.Join(layer, path))
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// The file existed in the top layer, so that's a modification
|
// The file existed in the top layer, so that's a modification
|
||||||
|
|
||||||
// However, if it's a directory, maybe it wasn't actually modified.
|
// However, if it's a directory, maybe it wasn't actually modified.
|
||||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||||
if stat.IsDir() && f.IsDir() {
|
if stat.IsDir() && f.IsDir() {
|
||||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
||||||
// Both directories are the same, don't record the change
|
// Both directories are the same, don't record the change
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
change.Kind = ChangeModify
|
change.Kind = ChangeModify
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record change
|
// Record change
|
||||||
changes = append(changes, change)
|
changes = append(changes, change)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return changes, nil
|
return changes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset removes all changes to the filesystem, reverting it to its initial state.
|
// Reset removes all changes to the filesystem, reverting it to its initial state.
|
||||||
func (mp *Mountpoint) Reset() error {
|
func (mp *Mountpoint) Reset() error {
|
||||||
if err := os.RemoveAll(mp.Rw); err != nil {
|
if err := os.RemoveAll(mp.Rw); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// We removed the RW directory itself along with its content: let's re-create an empty one.
|
// We removed the RW directory itself along with its content: let's re-create an empty one.
|
||||||
if err := mp.createFolders(); err != nil {
|
if err := mp.createFolders(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens the named file for reading.
|
// Open opens the named file for reading.
|
||||||
|
|
|
@ -2,7 +2,6 @@ package fs
|
||||||
|
|
||||||
import "syscall"
|
import "syscall"
|
||||||
|
|
||||||
|
|
||||||
func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
||||||
return syscall.Mount(source, target, fstype, flags, data)
|
return syscall.Mount(source, target, fstype, flags, data)
|
||||||
}
|
}
|
||||||
|
|
14
fs/store.go
14
fs/store.go
|
@ -4,9 +4,9 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/coopernurse/gorp"
|
|
||||||
"github.com/dotcloud/docker/future"
|
"github.com/dotcloud/docker/future"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/shykes/gorp" //Forked to implement CreateTablesOpts
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -29,8 +29,6 @@ func New(root string) (*Store, error) {
|
||||||
|
|
||||||
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if os.IsExist(err) {
|
|
||||||
isNewStore = false
|
|
||||||
}
|
}
|
||||||
db, err := sql.Open("sqlite3", path.Join(root, "db"))
|
db, err := sql.Open("sqlite3", path.Join(root, "db"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -42,7 +40,7 @@ func New(root string) (*Store, error) {
|
||||||
orm.AddTableWithName(Mountpoint{}, "mountpoints").SetKeys(false, "Root")
|
orm.AddTableWithName(Mountpoint{}, "mountpoints").SetKeys(false, "Root")
|
||||||
orm.AddTableWithName(Tag{}, "tags").SetKeys(false, "TagName")
|
orm.AddTableWithName(Tag{}, "tags").SetKeys(false, "TagName")
|
||||||
if isNewStore {
|
if isNewStore {
|
||||||
if err := orm.CreateTables(); err != nil {
|
if err := orm.CreateTablesOpts(true); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,7 +225,7 @@ func (image *Image) Mountpoints() ([]*Mountpoint, error) {
|
||||||
|
|
||||||
func (image *Image) Mount(root, rw string) (*Mountpoint, error) {
|
func (image *Image) Mount(root, rw string) (*Mountpoint, error) {
|
||||||
var mountpoint *Mountpoint
|
var mountpoint *Mountpoint
|
||||||
if mp, err := image.fetchMountpoint(root, rw); err != nil {
|
if mp, err := image.store.FetchMountpoint(root, rw); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if mp == nil {
|
} else if mp == nil {
|
||||||
mountpoint, err = image.Mountpoint(root, rw)
|
mountpoint, err = image.Mountpoint(root, rw)
|
||||||
|
@ -345,8 +343,8 @@ func (mp *Mountpoint) Deregister() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (image *Image) fetchMountpoint(root, rw string) (*Mountpoint, error) {
|
func (store *Store) FetchMountpoint(root, rw string) (*Mountpoint, error) {
|
||||||
res, err := image.store.orm.Select(Mountpoint{}, "select * from mountpoints where Image=? and Root=? and Rw=?", image.Id, root, rw)
|
res, err := store.orm.Select(Mountpoint{}, "select * from mountpoints where Root=? and Rw=?", root, rw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if len(res) < 1 || res[0] == nil {
|
} else if len(res) < 1 || res[0] == nil {
|
||||||
|
@ -354,7 +352,7 @@ func (image *Image) fetchMountpoint(root, rw string) (*Mountpoint, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mp := res[0].(*Mountpoint)
|
mp := res[0].(*Mountpoint)
|
||||||
mp.Store = image.store
|
mp.Store = store
|
||||||
return mp, nil
|
return mp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"../fake"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"../fake"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
362
image/image.go
362
image/image.go
|
@ -1,362 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"github.com/dotcloud/docker/future"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(abspath, 0700); err != nil && !os.IsExist(err) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import creates a new image from the contents of `archive` and registers it in the store as `name`.
|
|
||||||
// If `parent` is not nil, it will registered as the parent of the new image.
|
|
||||||
func (store *Store) Import(name string, archive io.Reader, parent *Image) (*Image, error) {
|
|
||||||
layer, err := store.Layers.AddLayer(archive)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
layers := []string{layer}
|
|
||||||
if parent != nil {
|
|
||||||
layers = append(layers, parent.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)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes all images with the name `name`
|
|
||||||
func (index *Index) Delete(name string) error {
|
|
||||||
// Load
|
|
||||||
if err := index.load(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, exists := index.ByName[name]; !exists {
|
|
||||||
return errors.New("No such image: " + name)
|
|
||||||
}
|
|
||||||
// Remove from index lookup
|
|
||||||
for _, image := range *index.ByName[name] {
|
|
||||||
delete(index.ById, image.Id)
|
|
||||||
}
|
|
||||||
// Remove from name lookup
|
|
||||||
delete(index.ByName, name)
|
|
||||||
// Save
|
|
||||||
if err := index.save(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMatch deletes all images whose name matches `pattern`
|
|
||||||
func (index *Index) DeleteMatch(pattern string) error {
|
|
||||||
// Load
|
|
||||||
if err := index.load(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for name, history := range index.ByName {
|
|
||||||
if match, err := regexp.MatchString(pattern, name); err != nil {
|
|
||||||
return err
|
|
||||||
} else if match {
|
|
||||||
// Remove from index lookup
|
|
||||||
for _, image := range *history {
|
|
||||||
delete(index.ById, image.Id)
|
|
||||||
}
|
|
||||||
// Remove from name lookup
|
|
||||||
delete(index.ByName, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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,13 +1,12 @@
|
||||||
package rcli
|
package rcli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// Use this key to encode an RPC call into an URL,
|
// Use this key to encode an RPC call into an URL,
|
||||||
// eg. domain.tld/path/to/method?q=get_user&q=gordon
|
// eg. domain.tld/path/to/method?q=get_user&q=gordon
|
||||||
const ARG_URL_KEY = "q"
|
const ARG_URL_KEY = "q"
|
||||||
|
@ -16,18 +15,16 @@ func URLToCall(u *url.URL) (method string, args []string) {
|
||||||
return path.Base(u.Path), u.Query()[ARG_URL_KEY]
|
return path.Base(u.Path), u.Query()[ARG_URL_KEY]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func ListenAndServeHTTP(addr string, service Service) error {
|
func ListenAndServeHTTP(addr string, service Service) error {
|
||||||
return http.ListenAndServe(addr, http.HandlerFunc(
|
return http.ListenAndServe(addr, http.HandlerFunc(
|
||||||
func (w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
cmd, args := URLToCall(r.URL)
|
cmd, args := URLToCall(r.URL)
|
||||||
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
|
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
|
||||||
fmt.Fprintf(w, "Error: " + err.Error() + "\n")
|
fmt.Fprintf(w, "Error: "+err.Error()+"\n")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type AutoFlush struct {
|
type AutoFlush struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
}
|
}
|
||||||
|
|
12
rcli/tcp.go
12
rcli/tcp.go
|
@ -1,13 +1,13 @@
|
||||||
package rcli
|
package rcli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"log"
|
"log"
|
||||||
"fmt"
|
"net"
|
||||||
"encoding/json"
|
|
||||||
"bufio"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connect to a remote endpoint using protocol `proto` and address `addr`,
|
// Connect to a remote endpoint using protocol `proto` and address `addr`,
|
||||||
|
@ -44,7 +44,7 @@ func ListenAndServe(proto, addr string, service Service) error {
|
||||||
go func() {
|
go func() {
|
||||||
if err := Serve(conn, service); err != nil {
|
if err := Serve(conn, service); err != nil {
|
||||||
log.Printf("Error: " + err.Error() + "\n")
|
log.Printf("Error: " + err.Error() + "\n")
|
||||||
fmt.Fprintf(conn, "Error: " + err.Error() + "\n")
|
fmt.Fprintf(conn, "Error: "+err.Error()+"\n")
|
||||||
}
|
}
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}()
|
}()
|
||||||
|
@ -53,7 +53,6 @@ func ListenAndServe(proto, addr string, service Service) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Parse an rcli call on a new connection, and pass it to `service` if it
|
// Parse an rcli call on a new connection, and pass it to `service` if it
|
||||||
// is valid.
|
// is valid.
|
||||||
func Serve(conn io.ReadWriter, service Service) error {
|
func Serve(conn io.ReadWriter, service Service) error {
|
||||||
|
@ -68,4 +67,3 @@ func Serve(conn io.ReadWriter, service Service) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,13 @@ package rcli
|
||||||
// are the usual suspects.
|
// are the usual suspects.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
|
||||||
"flag"
|
|
||||||
"log"
|
"log"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
|
@ -25,7 +25,6 @@ type Service interface {
|
||||||
type Cmd func(io.ReadCloser, io.Writer, ...string) error
|
type Cmd func(io.ReadCloser, io.Writer, ...string) error
|
||||||
type CmdMethod func(Service, 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 {
|
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
args = []string{"help"}
|
args = []string{"help"}
|
||||||
|
@ -63,7 +62,7 @@ func getMethod(service Service, name string) Cmd {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:])
|
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
|
||||||
method, exists := reflect.TypeOf(service).MethodByName(methodName)
|
method, exists := reflect.TypeOf(service).MethodByName(methodName)
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil
|
return nil
|
||||||
|
@ -91,4 +90,3 @@ func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet
|
||||||
}
|
}
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue