diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 16:15:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 16:15:20 +0000 |
commit | 9cce12a10d5c9a62cb86c1e2a2ce101d04d523bf (patch) | |
tree | dafe3c5d913024d6157de4a695b80fe2d6b3aec7 /graphdriver | |
parent | Initial commit. (diff) | |
download | golang-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.md | 27 | ||||
-rw-r--r-- | graphdriver/api.go | 408 | ||||
-rw-r--r-- | graphdriver/api_test.go | 320 | ||||
-rw-r--r-- | graphdriver/shim/shim.go | 176 | ||||
-rw-r--r-- | graphdriver/shim/shim_test.go | 98 |
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 +} |