summaryrefslogtreecommitdiffstats
path: root/graphdriver
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 16:15:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 16:15:20 +0000
commit9cce12a10d5c9a62cb86c1e2a2ce101d04d523bf (patch)
treedafe3c5d913024d6157de4a695b80fe2d6b3aec7 /graphdriver
parentInitial commit. (diff)
downloadgolang-github-docker-go-plugins-helpers-9cce12a10d5c9a62cb86c1e2a2ce101d04d523bf.tar.xz
golang-github-docker-go-plugins-helpers-9cce12a10d5c9a62cb86c1e2a2ce101d04d523bf.zip
Adding upstream version 0.20211224.upstream/0.20211224upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'graphdriver')
-rw-r--r--graphdriver/README.md27
-rw-r--r--graphdriver/api.go408
-rw-r--r--graphdriver/api_test.go320
-rw-r--r--graphdriver/shim/shim.go176
-rw-r--r--graphdriver/shim/shim_test.go98
5 files changed, 1029 insertions, 0 deletions
diff --git a/graphdriver/README.md b/graphdriver/README.md
new file mode 100644
index 0000000..8c55c8a
--- /dev/null
+++ b/graphdriver/README.md
@@ -0,0 +1,27 @@
+# Docker volume extension api.
+
+Go handler to create external graphdriver extensions for Docker.
+
+## Usage
+
+This library is designed to be integrated in your program.
+
+1. Implement the `graphdriver.Driver` interface.
+2. Initialize a `graphdriver.Handler` with your implementation.
+3. Call either `ServeTCP` or `ServeUnix` from the `graphdriver.Handler`.
+
+### Example using TCP sockets:
+
+```go
+ d := MyGraphDriver{}
+ h := graphdriver.NewHandler(d)
+ h.ServeTCP("test_graph", ":8080")
+```
+
+### Example using Unix sockets:
+
+```go
+ d := MyGraphDriver{}
+ h := graphdriver.NewHandler(d)
+ h.ServeUnix("root", "test_graph")
+```
diff --git a/graphdriver/api.go b/graphdriver/api.go
new file mode 100644
index 0000000..193f77a
--- /dev/null
+++ b/graphdriver/api.go
@@ -0,0 +1,408 @@
+package graphdriver
+
+// See https://github.com/docker/cli/blob/master/docs/extend/plugins_graphdriver.md
+
+import (
+ "io"
+ "net/http"
+
+ graphDriver "github.com/docker/docker/daemon/graphdriver"
+ "github.com/docker/docker/pkg/containerfs"
+ "github.com/docker/docker/pkg/idtools"
+ "github.com/docker/go-plugins-helpers/sdk"
+)
+
+const (
+ // DefaultDockerRootDirectory is the default directory where graph drivers will be created.
+ DefaultDockerRootDirectory = "/var/lib/docker/graph"
+
+ manifest = `{"Implements": ["GraphDriver"]}`
+ initPath = "/GraphDriver.Init"
+ createPath = "/GraphDriver.Create"
+ createRWPath = "/GraphDriver.CreateReadWrite"
+ removePath = "/GraphDriver.Remove"
+ getPath = "/GraphDriver.Get"
+ putPath = "/GraphDriver.Put"
+ existsPath = "/GraphDriver.Exists"
+ statusPath = "/GraphDriver.Status"
+ getMetadataPath = "/GraphDriver.GetMetadata"
+ cleanupPath = "/GraphDriver.Cleanup"
+ diffPath = "/GraphDriver.Diff"
+ changesPath = "/GraphDriver.Changes"
+ applyDiffPath = "/GraphDriver.ApplyDiff"
+ diffSizePath = "/GraphDriver.DiffSize"
+ capabilitiesPath = "/GraphDriver.Capabilities"
+)
+
+// Init
+
+// InitRequest is the structure that docker's init requests are deserialized to.
+type InitRequest struct {
+ Home string
+ Options []string `json:"Opts"`
+ UIDMaps []idtools.IDMap `json:"UIDMaps"`
+ GIDMaps []idtools.IDMap `json:"GIDMaps"`
+}
+
+// Create
+
+// CreateRequest is the structure that docker's create requests are deserialized to.
+type CreateRequest struct {
+ ID string
+ Parent string
+ MountLabel string
+ StorageOpt map[string]string
+}
+
+// Remove
+
+// RemoveRequest is the structure that docker's remove requests are deserialized to.
+type RemoveRequest struct {
+ ID string
+}
+
+// Get
+
+// GetRequest is the structure that docker's get requests are deserialized to.
+type GetRequest struct {
+ ID string
+ MountLabel string
+}
+
+// GetResponse is the strucutre that docker's remove responses are serialized to.
+type GetResponse struct {
+ Dir string
+}
+
+// Put
+
+// PutRequest is the structure that docker's put requests are deserialized to.
+type PutRequest struct {
+ ID string
+}
+
+// Exists
+
+// ExistsRequest is the structure that docker's exists requests are deserialized to.
+type ExistsRequest struct {
+ ID string
+}
+
+// ExistsResponse is the structure that docker's exists responses are serialized to.
+type ExistsResponse struct {
+ Exists bool
+}
+
+// Status
+
+// StatusRequest is the structure that docker's status requests are deserialized to.
+type StatusRequest struct{}
+
+// StatusResponse is the structure that docker's status responses are serialized to.
+type StatusResponse struct {
+ Status [][2]string
+}
+
+// GetMetadata
+
+// GetMetadataRequest is the structure that docker's getMetadata requests are deserialized to.
+type GetMetadataRequest struct {
+ ID string
+}
+
+// GetMetadataResponse is the structure that docker's getMetadata responses are serialized to.
+type GetMetadataResponse struct {
+ Metadata map[string]string
+}
+
+// Cleanup
+
+// CleanupRequest is the structure that docker's cleanup requests are deserialized to.
+type CleanupRequest struct{}
+
+// Diff
+
+// DiffRequest is the structure that docker's diff requests are deserialized to.
+type DiffRequest struct {
+ ID string
+ Parent string
+}
+
+// DiffResponse is the structure that docker's diff responses are serialized to.
+type DiffResponse struct {
+ Stream io.ReadCloser // TAR STREAM
+}
+
+// Changes
+
+// ChangesRequest is the structure that docker's changes requests are deserialized to.
+type ChangesRequest struct {
+ ID string
+ Parent string
+}
+
+// ChangesResponse is the structure that docker's changes responses are serialized to.
+type ChangesResponse struct {
+ Changes []Change
+}
+
+// ChangeKind represents the type of change mage
+type ChangeKind int
+
+const (
+ // Modified is a ChangeKind used when an item has been modified
+ Modified ChangeKind = iota
+ // Added is a ChangeKind used when an item has been added
+ Added
+ // Deleted is a ChangeKind used when an item has been deleted
+ Deleted
+)
+
+// Change is the structure that docker's individual changes are serialized to.
+type Change struct {
+ Path string
+ Kind ChangeKind
+}
+
+// ApplyDiff
+
+// ApplyDiffRequest is the structure that docker's applyDiff requests are deserialized to.
+type ApplyDiffRequest struct {
+ Stream io.Reader // TAR STREAM
+ ID string
+ Parent string
+}
+
+// ApplyDiffResponse is the structure that docker's applyDiff responses are serialized to.
+type ApplyDiffResponse struct {
+ Size int64
+}
+
+// DiffSize
+
+// DiffSizeRequest is the structure that docker's diffSize requests are deserialized to.
+type DiffSizeRequest struct {
+ ID string
+ Parent string
+}
+
+// DiffSizeResponse is the structure that docker's diffSize responses are serialized to.
+type DiffSizeResponse struct {
+ Size int64
+}
+
+// CapabilitiesRequest is the structure that docker's capabilities requests are deserialized to.
+type CapabilitiesRequest struct{}
+
+// CapabilitiesResponse is the structure that docker's capabilities responses are serialized to.
+type CapabilitiesResponse struct {
+ Capabilities graphDriver.Capabilities
+}
+
+// ErrorResponse is a formatted error message that docker can understand
+type ErrorResponse struct {
+ Err string
+}
+
+// NewErrorResponse creates an ErrorResponse with the provided message
+func NewErrorResponse(msg string) *ErrorResponse {
+ return &ErrorResponse{Err: msg}
+}
+
+// Driver represent the interface a driver must fulfill.
+type Driver interface {
+ Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) error
+ Create(id, parent, mountlabel string, storageOpt map[string]string) error
+ CreateReadWrite(id, parent, mountlabel string, storageOpt map[string]string) error
+ Remove(id string) error
+ Get(id, mountLabel string) (containerfs.ContainerFS, error)
+ Put(id string) error
+ Exists(id string) bool
+ Status() [][2]string
+ GetMetadata(id string) (map[string]string, error)
+ Cleanup() error
+ Diff(id, parent string) io.ReadCloser
+ Changes(id, parent string) ([]Change, error)
+ ApplyDiff(id, parent string, archive io.Reader) (int64, error)
+ DiffSize(id, parent string) (int64, error)
+ Capabilities() graphDriver.Capabilities
+}
+
+// Handler forwards requests and responses between the docker daemon and the plugin.
+type Handler struct {
+ driver Driver
+ sdk.Handler
+}
+
+// NewHandler initializes the request handler with a driver implementation.
+func NewHandler(driver Driver) *Handler {
+ h := &Handler{driver, sdk.NewHandler(manifest)}
+ h.initMux()
+ return h
+}
+
+func (h *Handler) initMux() {
+ h.HandleFunc(initPath, func(w http.ResponseWriter, r *http.Request) {
+ req := InitRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ err = h.driver.Init(req.Home, req.Options, req.UIDMaps, req.GIDMaps)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+ })
+ h.HandleFunc(createPath, func(w http.ResponseWriter, r *http.Request) {
+ req := CreateRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ err = h.driver.Create(req.ID, req.Parent, req.MountLabel, req.StorageOpt)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+ })
+ h.HandleFunc(createRWPath, func(w http.ResponseWriter, r *http.Request) {
+ req := CreateRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ err = h.driver.CreateReadWrite(req.ID, req.Parent, req.MountLabel, req.StorageOpt)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+ })
+ h.HandleFunc(removePath, func(w http.ResponseWriter, r *http.Request) {
+ req := RemoveRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ err = h.driver.Remove(req.ID)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+
+ })
+ h.HandleFunc(getPath, func(w http.ResponseWriter, r *http.Request) {
+ req := GetRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ dir, err := h.driver.Get(req.ID, req.MountLabel)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, &GetResponse{Dir: dir.Path()}, false)
+ })
+ h.HandleFunc(putPath, func(w http.ResponseWriter, r *http.Request) {
+ req := PutRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ err = h.driver.Put(req.ID)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+ })
+ h.HandleFunc(existsPath, func(w http.ResponseWriter, r *http.Request) {
+ req := ExistsRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ exists := h.driver.Exists(req.ID)
+ sdk.EncodeResponse(w, &ExistsResponse{Exists: exists}, false)
+ })
+ h.HandleFunc(statusPath, func(w http.ResponseWriter, r *http.Request) {
+ status := h.driver.Status()
+ sdk.EncodeResponse(w, &StatusResponse{Status: status}, false)
+ })
+ h.HandleFunc(getMetadataPath, func(w http.ResponseWriter, r *http.Request) {
+ req := GetMetadataRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ metadata, err := h.driver.GetMetadata(req.ID)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, &GetMetadataResponse{Metadata: metadata}, false)
+ })
+ h.HandleFunc(cleanupPath, func(w http.ResponseWriter, r *http.Request) {
+ err := h.driver.Cleanup()
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, struct{}{}, false)
+ })
+ h.HandleFunc(diffPath, func(w http.ResponseWriter, r *http.Request) {
+ req := DiffRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ stream := h.driver.Diff(req.ID, req.Parent)
+ sdk.StreamResponse(w, stream)
+ })
+ h.HandleFunc(changesPath, func(w http.ResponseWriter, r *http.Request) {
+ req := ChangesRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ changes, err := h.driver.Changes(req.ID, req.Parent)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, &ChangesResponse{Changes: changes}, false)
+ })
+ h.HandleFunc(applyDiffPath, func(w http.ResponseWriter, r *http.Request) {
+ params := r.URL.Query()
+ id := params.Get("id")
+ parent := params.Get("parent")
+ size, err := h.driver.ApplyDiff(id, parent, r.Body)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, &ApplyDiffResponse{Size: size}, false)
+ })
+ h.HandleFunc(diffSizePath, func(w http.ResponseWriter, r *http.Request) {
+ req := DiffRequest{}
+ err := sdk.DecodeRequest(w, r, &req)
+ if err != nil {
+ return
+ }
+ size, err := h.driver.DiffSize(req.ID, req.Parent)
+ if err != nil {
+ sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
+ return
+ }
+ sdk.EncodeResponse(w, &DiffSizeResponse{Size: size}, false)
+ })
+ h.HandleFunc(capabilitiesPath, func(w http.ResponseWriter, r *http.Request) {
+ caps := h.driver.Capabilities()
+ sdk.EncodeResponse(w, &CapabilitiesResponse{Capabilities: caps}, false)
+ })
+}
diff --git a/graphdriver/api_test.go b/graphdriver/api_test.go
new file mode 100644
index 0000000..b2c97c4
--- /dev/null
+++ b/graphdriver/api_test.go
@@ -0,0 +1,320 @@
+package graphdriver
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "testing"
+
+ graphDriver "github.com/docker/docker/daemon/graphdriver"
+ "github.com/docker/docker/pkg/containerfs"
+ "github.com/docker/docker/pkg/idtools"
+ "github.com/docker/go-connections/sockets"
+)
+
+const url = "http://localhost"
+
+func TestHandler(t *testing.T) {
+ p := &testPlugin{}
+ h := NewHandler(p)
+ l := sockets.NewInmemSocket("test", 0)
+ go h.Serve(l)
+ defer l.Close()
+
+ client := &http.Client{Transport: &http.Transport{
+ Dial: l.Dial,
+ }}
+
+ // Init
+ _, err := pluginRequest(client, initPath, &InitRequest{Home: "foo"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.init != 1 {
+ t.Fatalf("expected init 1, got %d", p.init)
+ }
+
+ // Create
+ _, err = pluginRequest(client, createPath, &CreateRequest{ID: "foo", Parent: "bar"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.create != 1 {
+ t.Fatalf("expected create 1, got %d", p.create)
+ }
+
+ // CreateReadWrite
+ _, err = pluginRequest(client, createRWPath, &CreateRequest{ID: "foo", Parent: "bar", MountLabel: "toto"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.createRW != 1 {
+ t.Fatalf("expected createReadWrite 1, got %d", p.createRW)
+ }
+
+ // Remove
+ _, err = pluginRequest(client, removePath, RemoveRequest{ID: "foo"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.remove != 1 {
+ t.Fatalf("expected remove 1, got %d", p.remove)
+ }
+
+ // Get
+ resp, err := pluginRequest(client, getPath, GetRequest{ID: "foo", MountLabel: "bar"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ var gResp *GetResponse
+ if err := json.NewDecoder(resp).Decode(&gResp); err != nil {
+ t.Fatal(err)
+ }
+ if gResp.Dir != "baz" {
+ t.Fatalf("expected dir = 'baz', got %s", gResp.Dir)
+ }
+ if p.get != 1 {
+ t.Fatalf("expected get 1, got %d", p.get)
+ }
+
+ // Put
+ _, err = pluginRequest(client, putPath, PutRequest{ID: "foo"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.put != 1 {
+ t.Fatalf("expected put 1, got %d", p.put)
+ }
+
+ // Exists
+ resp, err = pluginRequest(client, existsPath, ExistsRequest{ID: "foo"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ var eResp *ExistsResponse
+ if err := json.NewDecoder(resp).Decode(&eResp); err != nil {
+ t.Fatal(err)
+ }
+ if !eResp.Exists {
+ t.Fatalf("got error testing for existence of graph drivers: %v", eResp.Exists)
+ }
+ if p.exists != 1 {
+ t.Fatalf("expected exists 1, got %d", p.exists)
+ }
+
+ // Status
+ resp, err = pluginRequest(client, statusPath, StatusRequest{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ var sResp *StatusResponse
+ if err := json.NewDecoder(resp).Decode(&sResp); err != nil {
+ t.Fatal(err)
+ }
+ if p.status != 1 {
+ t.Fatalf("expected get 1, got %d", p.status)
+ }
+
+ // GetMetadata
+ resp, err = pluginRequest(client, getMetadataPath, GetMetadataRequest{ID: "foo"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ var gmResp *GetMetadataResponse
+ if err := json.NewDecoder(resp).Decode(&gmResp); err != nil {
+ t.Fatal(err)
+ }
+ if p.getMetadata != 1 {
+ t.Fatalf("expected getMetadata 1, got %d", p.getMetadata)
+ }
+
+ // Cleanup
+ _, err = pluginRequest(client, cleanupPath, CleanupRequest{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.cleanup != 1 {
+ t.Fatalf("expected cleanup 1, got %d", p.cleanup)
+ }
+
+ // Diff
+ _, err = pluginRequest(client, diffPath, DiffRequest{ID: "foo", Parent: "bar"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.diff != 1 {
+ t.Fatalf("expected diff 1, got %d", p.diff)
+ }
+
+ // Changes
+ resp, err = pluginRequest(client, changesPath, ChangesRequest{ID: "foo", Parent: "bar"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ var cResp *ChangesResponse
+ if err := json.NewDecoder(resp).Decode(&cResp); err != nil {
+ t.Fatal(err)
+ }
+ if p.status != 1 {
+ t.Fatalf("expected get 1, got %d", p.get)
+ }
+
+ // ApplyDiff
+ b := new(bytes.Buffer)
+ stream := bytes.NewReader(b.Bytes())
+ resp, err = pluginRequest(client, applyDiffPath, &ApplyDiffRequest{ID: "foo", Parent: "bar", Stream: stream})
+ if err != nil {
+ t.Fatal(err)
+ }
+ var adResp *ApplyDiffResponse
+ if err := json.NewDecoder(resp).Decode(&adResp); err != nil {
+ t.Fatal(err)
+ }
+ if p.status != 1 {
+ t.Fatalf("expected applyDiff 1, got %d", p.applyDiff)
+ }
+
+ // DiffSize
+ resp, err = pluginRequest(client, diffSizePath, DiffSizeRequest{ID: "foo", Parent: "bar"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ var dsResp *DiffSizeResponse
+ if err := json.NewDecoder(resp).Decode(&dsResp); err != nil {
+ t.Fatal(err)
+ }
+ if p.diffSize != 1 {
+ t.Fatalf("expected diffSize 1, got %d", p.diffSize)
+ }
+
+ // Capabilities
+ resp, err = pluginRequest(client, capabilitiesPath, CapabilitiesRequest{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ var caResp *CapabilitiesResponse
+ if err := json.NewDecoder(resp).Decode(&caResp); err != nil {
+ t.Fatal(err)
+ }
+ if caResp.Capabilities.ReproducesExactDiffs != true {
+ t.Fatalf("got error getting capabilities for graph drivers: %v", caResp.Capabilities)
+ }
+ if p.capabilities != 1 {
+ t.Fatalf("expected get 1, got %d", p.get)
+ }
+}
+
+func pluginRequest(client *http.Client, method string, req interface{}) (io.Reader, error) {
+ b, err := json.Marshal(req)
+ if err != nil {
+ return nil, err
+ }
+ if req == nil {
+ b = []byte{}
+ }
+ resp, err := client.Post("http://localhost"+method, "application/json", bytes.NewReader(b))
+ if err != nil {
+ return nil, err
+ }
+
+ return resp.Body, nil
+}
+
+type testPlugin struct {
+ init int
+ create int
+ createRW int
+ remove int
+ get int
+ put int
+ exists int
+ status int
+ getMetadata int
+ cleanup int
+ diff int
+ changes int
+ applyDiff int
+ diffSize int
+ capabilities int
+}
+
+var _ Driver = &testPlugin{}
+
+func (p *testPlugin) Init(string, []string, []idtools.IDMap, []idtools.IDMap) error {
+ p.init++
+ return nil
+}
+
+func (p *testPlugin) Create(string, string, string, map[string]string) error {
+ p.create++
+ return nil
+}
+
+func (p *testPlugin) CreateReadWrite(string, string, string, map[string]string) error {
+ p.createRW++
+ return nil
+}
+
+func (p *testPlugin) Remove(string) error {
+ p.remove++
+ return nil
+}
+
+func (p *testPlugin) Get(string, string) (containerfs.ContainerFS, error) {
+ p.get++
+ return containerfs.NewLocalContainerFS("baz"), nil
+}
+
+func (p *testPlugin) Put(string) error {
+ p.put++
+ return nil
+}
+
+func (p *testPlugin) Exists(string) bool {
+ p.exists++
+ return true
+}
+
+func (p *testPlugin) Status() [][2]string {
+ p.status++
+ return nil
+}
+
+func (p *testPlugin) GetMetadata(string) (map[string]string, error) {
+ p.getMetadata++
+ return nil, nil
+}
+
+func (p *testPlugin) Cleanup() error {
+ p.cleanup++
+ return nil
+}
+
+func (p *testPlugin) Diff(string, string) io.ReadCloser {
+ p.diff++
+ b := new(bytes.Buffer)
+ x := ioutil.NopCloser(bytes.NewReader(b.Bytes()))
+ return x
+}
+
+func (p *testPlugin) Changes(string, string) ([]Change, error) {
+ p.changes++
+ return nil, nil
+}
+
+func (p *testPlugin) ApplyDiff(string, string, io.Reader) (int64, error) {
+ p.applyDiff++
+ return 42, nil
+}
+
+func (p *testPlugin) DiffSize(string, string) (int64, error) {
+ p.diffSize++
+ return 42, nil
+}
+
+func (p *testPlugin) Capabilities() graphDriver.Capabilities {
+ p.capabilities++
+ return graphDriver.Capabilities{ReproducesExactDiffs: true}
+}
diff --git a/graphdriver/shim/shim.go b/graphdriver/shim/shim.go
new file mode 100644
index 0000000..bec1f59
--- /dev/null
+++ b/graphdriver/shim/shim.go
@@ -0,0 +1,176 @@
+package shim
+
+import (
+ "errors"
+ "io"
+ "log"
+
+ graphDriver "github.com/docker/docker/daemon/graphdriver"
+ "github.com/docker/docker/pkg/archive"
+ "github.com/docker/docker/pkg/containerfs"
+ "github.com/docker/docker/pkg/idtools"
+ graphPlugin "github.com/docker/go-plugins-helpers/graphdriver"
+)
+
+type shimDriver struct {
+ driver graphDriver.Driver
+ init graphDriver.InitFunc
+}
+
+// NewHandlerFromGraphDriver creates a plugin handler from an existing graph
+// driver. This could be used, for instance, by the `overlayfs` graph driver
+// built-in to Docker Engine and it would create a plugin from it that maps
+// plugin API calls directly to any volume driver that satifies the
+// graphdriver.Driver interface from Docker Engine.
+func NewHandlerFromGraphDriver(init graphDriver.InitFunc) *graphPlugin.Handler {
+ return graphPlugin.NewHandler(&shimDriver{driver: nil, init: init})
+}
+
+func (d *shimDriver) Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) error {
+ driver, err := d.init(home, options, uidMaps, gidMaps)
+ if err != nil {
+ return err
+ }
+ d.driver = driver
+ return nil
+}
+
+var errNotInitialized = errors.New("Not initialized")
+
+func (d *shimDriver) Create(id, parent, mountLabel string, storageOpt map[string]string) error {
+ if d == nil {
+ return errNotInitialized
+ }
+ opts := graphDriver.CreateOpts{
+ MountLabel: mountLabel,
+ StorageOpt: storageOpt,
+ }
+ return d.driver.Create(id, parent, &opts)
+}
+
+func (d *shimDriver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
+ if d == nil {
+ return errNotInitialized
+ }
+ opts := graphDriver.CreateOpts{
+ MountLabel: mountLabel,
+ StorageOpt: storageOpt,
+ }
+ return d.driver.CreateReadWrite(id, parent, &opts)
+}
+
+func (d *shimDriver) Remove(id string) error {
+ if d == nil {
+ return errNotInitialized
+ }
+ return d.driver.Remove(id)
+}
+
+func (d *shimDriver) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
+ if d == nil {
+ return nil, errNotInitialized
+ }
+ return d.driver.Get(id, mountLabel)
+}
+
+func (d *shimDriver) Put(id string) error {
+ if d == nil {
+ return errNotInitialized
+ }
+ return d.driver.Put(id)
+}
+
+func (d *shimDriver) Exists(id string) bool {
+ if d == nil {
+ return false
+ }
+ return d.driver.Exists(id)
+}
+
+func (d *shimDriver) Status() [][2]string {
+ if d == nil {
+ return nil
+ }
+ return d.driver.Status()
+}
+
+func (d *shimDriver) GetMetadata(id string) (map[string]string, error) {
+ if d == nil {
+ return nil, errNotInitialized
+ }
+ return d.driver.GetMetadata(id)
+}
+
+func (d *shimDriver) Cleanup() error {
+ if d == nil {
+ return errNotInitialized
+ }
+ return d.driver.Cleanup()
+}
+
+func (d *shimDriver) Diff(id, parent string) io.ReadCloser {
+ if d == nil {
+ return nil
+ }
+ // FIXME(samoht): how do we pass the error to the driver?
+ archive, err := d.driver.Diff(id, parent)
+ if err != nil {
+ log.Fatalf("Diff: error in stream %v", err)
+ }
+ return archive
+}
+
+func changeKind(c archive.ChangeType) graphPlugin.ChangeKind {
+ switch c {
+ case archive.ChangeModify:
+ return graphPlugin.Modified
+ case archive.ChangeAdd:
+ return graphPlugin.Added
+ case archive.ChangeDelete:
+ return graphPlugin.Deleted
+ }
+ return 0
+}
+
+func (d *shimDriver) Changes(id, parent string) ([]graphPlugin.Change, error) {
+ if d == nil {
+ return nil, errNotInitialized
+ }
+ cs, err := d.driver.Changes(id, parent)
+ if err != nil {
+ return nil, err
+ }
+ changes := make([]graphPlugin.Change, len(cs))
+ for _, c := range cs {
+ change := graphPlugin.Change{
+ Path: c.Path,
+ Kind: changeKind(c.Kind),
+ }
+ changes = append(changes, change)
+ }
+ return changes, nil
+}
+
+func (d *shimDriver) ApplyDiff(id, parent string, archive io.Reader) (int64, error) {
+ if d == nil {
+ return 0, errNotInitialized
+ }
+ return d.driver.ApplyDiff(id, parent, archive)
+}
+
+func (d *shimDriver) DiffSize(id, parent string) (int64, error) {
+ if d == nil {
+ return 0, errNotInitialized
+ }
+ return d.driver.DiffSize(id, parent)
+}
+
+func (d *shimDriver) Capabilities() graphDriver.Capabilities {
+ if d == nil {
+ return graphDriver.Capabilities{}
+ }
+ if capDriver, ok := d.driver.(graphDriver.CapabilityDriver); ok {
+ return capDriver.Capabilities()
+ }
+ return graphDriver.Capabilities{}
+}
diff --git a/graphdriver/shim/shim_test.go b/graphdriver/shim/shim_test.go
new file mode 100644
index 0000000..032301a
--- /dev/null
+++ b/graphdriver/shim/shim_test.go
@@ -0,0 +1,98 @@
+package shim
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "testing"
+
+ "github.com/docker/docker/pkg/containerfs"
+
+ "github.com/docker/docker/daemon/graphdriver"
+ "github.com/docker/docker/pkg/idtools"
+ "github.com/docker/go-connections/sockets"
+ graphPlugin "github.com/docker/go-plugins-helpers/graphdriver"
+)
+
+type testGraphDriver struct{}
+
+// ProtoDriver
+var _ graphdriver.ProtoDriver = &testGraphDriver{}
+
+func (t *testGraphDriver) String() string {
+ return ""
+}
+
+// FIXME(samoht): this doesn't seem to be called by the plugins
+func (t *testGraphDriver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
+ return nil
+}
+func (t *testGraphDriver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
+ return nil
+}
+func (t *testGraphDriver) Remove(id string) error {
+ return nil
+}
+func (t *testGraphDriver) Get(id, mountLabel string) (dir containerfs.ContainerFS, err error) {
+ return containerfs.NewLocalContainerFS(""), nil
+}
+func (t *testGraphDriver) Put(id string) error {
+ return nil
+}
+func (t *testGraphDriver) Exists(id string) bool {
+ return false
+}
+func (t *testGraphDriver) Status() [][2]string {
+ return nil
+}
+func (t *testGraphDriver) GetMetadata(id string) (map[string]string, error) {
+ return nil, nil
+}
+func (t *testGraphDriver) Cleanup() error {
+ return nil
+}
+func (t *testGraphDriver) Capabilities() graphdriver.Capabilities {
+ return graphdriver.Capabilities{}
+}
+
+func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+ d := graphdriver.NewNaiveDiffDriver(&testGraphDriver{}, uidMaps, gidMaps)
+ return d, nil
+}
+
+func TestGraphDriver(t *testing.T) {
+ h := NewHandlerFromGraphDriver(Init)
+ l := sockets.NewInmemSocket("test", 0)
+ go h.Serve(l)
+ defer l.Close()
+
+ client := &http.Client{Transport: &http.Transport{
+ Dial: l.Dial,
+ }}
+
+ resp, err := pluginRequest(client, "/GraphDriver.Init", &graphPlugin.InitRequest{Home: "foo"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if resp.Err != "" {
+ t.Fatalf("error while creating GraphDriver: %v", err)
+ }
+}
+
+func pluginRequest(client *http.Client, method string, req *graphPlugin.InitRequest) (*graphPlugin.ErrorResponse, error) {
+ b, err := json.Marshal(req)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := client.Post("http://localhost"+method, "application/json", bytes.NewReader(b))
+ if err != nil {
+ return nil, err
+ }
+ var gResp graphPlugin.ErrorResponse
+ err = json.NewDecoder(resp.Body).Decode(&gResp)
+ if err != nil {
+ return nil, err
+ }
+
+ return &gResp, nil
+}