mirror of https://github.com/docker/docs.git
Merge pull request #22077 from cpuguy83/remote_volplugin_caps
Add support for volume scopes
This commit is contained in:
commit
28dde09cdf
|
|
@ -745,7 +745,9 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
volumedrivers.Register(volumesDriver, volumesDriver.Name())
|
if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) {
|
||||||
|
return nil, fmt.Errorf("local volume driver could not be registered")
|
||||||
|
}
|
||||||
return store.New(config.Root)
|
return store.New(config.Root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,13 @@ func volumeToAPIType(v volume.Volume) *types.Volume {
|
||||||
Name: v.Name(),
|
Name: v.Name(),
|
||||||
Driver: v.DriverName(),
|
Driver: v.DriverName(),
|
||||||
}
|
}
|
||||||
if v, ok := v.(interface {
|
if v, ok := v.(volume.LabeledVolume); ok {
|
||||||
Labels() map[string]string
|
|
||||||
}); ok {
|
|
||||||
tv.Labels = v.Labels()
|
tv.Labels = v.Labels()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := v.(volume.ScopedVolume); ok {
|
||||||
|
tv.Scope = v.Scope()
|
||||||
|
}
|
||||||
return tv
|
return tv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ documentation](plugins.md) for more information.
|
||||||
### 1.12.0
|
### 1.12.0
|
||||||
|
|
||||||
- Add `Status` field to `VolumeDriver.Get` response ([#21006](https://github.com/docker/docker/pull/21006#))
|
- Add `Status` field to `VolumeDriver.Get` response ([#21006](https://github.com/docker/docker/pull/21006#))
|
||||||
|
- Add `VolumeDriver.Capabilities` to get capabilities of the volume driver([#22077](https://github.com/docker/docker/pull/22077))
|
||||||
|
|
||||||
### 1.10.0
|
### 1.10.0
|
||||||
|
|
||||||
|
|
@ -236,3 +237,29 @@ Get the list of volumes registered with the plugin.
|
||||||
```
|
```
|
||||||
|
|
||||||
Respond with a string error if an error occurred.
|
Respond with a string error if an error occurred.
|
||||||
|
|
||||||
|
### /VolumeDriver.Capabilities
|
||||||
|
|
||||||
|
**Request**:
|
||||||
|
```json
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
|
Get the list of capabilities the driver supports.
|
||||||
|
The driver is not required to implement this endpoint, however in such cases
|
||||||
|
the default values will be taken.
|
||||||
|
|
||||||
|
**Response**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Capabilities": {
|
||||||
|
"Scope": "global"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported scopes are `global` and `local`. Any other value in `Scope` will be
|
||||||
|
ignored and assumed to be `local`. Scope allows cluster managers to handle the
|
||||||
|
volume differently, for instance with a scope of `global`, the cluster manager
|
||||||
|
knows it only needs to create the volume once instead of on every engine. More
|
||||||
|
capabilities may be added in the future.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/integration/checker"
|
"github.com/docker/docker/pkg/integration/checker"
|
||||||
|
"github.com/docker/docker/volume"
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
)
|
)
|
||||||
|
|
@ -35,6 +36,7 @@ type eventCounter struct {
|
||||||
paths int
|
paths int
|
||||||
lists int
|
lists int
|
||||||
gets int
|
gets int
|
||||||
|
caps int
|
||||||
}
|
}
|
||||||
|
|
||||||
type DockerExternalVolumeSuite struct {
|
type DockerExternalVolumeSuite struct {
|
||||||
|
|
@ -225,6 +227,18 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
||||||
send(w, nil)
|
send(w, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/VolumeDriver.Capabilities", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s.ec.caps++
|
||||||
|
|
||||||
|
_, err := read(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
send(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
send(w, `{"Capabilities": { "Scope": "global" }}`)
|
||||||
|
})
|
||||||
|
|
||||||
err := os.MkdirAll("/etc/docker/plugins", 0755)
|
err := os.MkdirAll("/etc/docker/plugins", 0755)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
|
@ -491,3 +505,18 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C)
|
||||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||||
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
|
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that VolumeDriver.Capabilities gets called, and only called once
|
||||||
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverCapabilities(c *check.C) {
|
||||||
|
c.Assert(s.d.Start(), checker.IsNil)
|
||||||
|
c.Assert(s.ec.caps, checker.Equals, 0)
|
||||||
|
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
out, err := s.d.Cmd("volume", "create", "-d", "test-external-volume-driver", "--name", fmt.Sprintf("test%d", i))
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||||
|
c.Assert(s.ec.caps, checker.Equals, 1)
|
||||||
|
out, err = s.d.Cmd("volume", "inspect", "--format={{.Scope}}", fmt.Sprintf("test%d", i))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Equals, volume.GlobalScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,6 @@ supplying `--tag`. This flag can be specified multiple times.
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
|
|
||||||
The parser can currently only handle types which are not specifically a map or
|
|
||||||
a slice.
|
|
||||||
You can, however, create a type that uses a map or a slice internally, for instance:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type opts map[string]string
|
|
||||||
```
|
|
||||||
|
|
||||||
This `opts` type will work, whreas using a `map[string]string` directly will not.
|
|
||||||
|
|
||||||
## go-generate
|
## go-generate
|
||||||
|
|
||||||
You can also use this with go-generate, which is pretty awesome.
|
You can also use this with go-generate, which is pretty awesome.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
aliasedio "io"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/plugins/pluginrpc-gen/fixtures/otherfixture"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errFakeImport = fmt.Errorf("just to import fmt for imports tests")
|
||||||
|
)
|
||||||
|
|
||||||
type wobble struct {
|
type wobble struct {
|
||||||
Some string
|
Some string
|
||||||
Val string
|
Val string
|
||||||
|
|
@ -22,6 +34,7 @@ type Fooer3 interface {
|
||||||
Qux(a, b string) (val string, err error)
|
Qux(a, b string) (val string, err error)
|
||||||
Wobble() (w *wobble)
|
Wobble() (w *wobble)
|
||||||
Wiggle() (w wobble)
|
Wiggle() (w wobble)
|
||||||
|
WiggleWobble(a []*wobble, b []wobble, c map[string]*wobble, d map[*wobble]wobble, e map[string][]wobble, f []*otherfixture.Spaceship) (g map[*wobble]wobble, h [][]*wobble, i otherfixture.Spaceship, j *otherfixture.Spaceship, k map[*otherfixture.Spaceship]otherfixture.Spaceship, l []otherfixture.Spaceship)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fooer4 is an interface used for tests.
|
// Fooer4 is an interface used for tests.
|
||||||
|
|
@ -39,3 +52,38 @@ type Fooer5 interface {
|
||||||
Foo()
|
Foo()
|
||||||
Bar
|
Bar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fooer6 is an interface used for tests.
|
||||||
|
type Fooer6 interface {
|
||||||
|
Foo(a otherfixture.Spaceship)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fooer7 is an interface used for tests.
|
||||||
|
type Fooer7 interface {
|
||||||
|
Foo(a *otherfixture.Spaceship)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fooer8 is an interface used for tests.
|
||||||
|
type Fooer8 interface {
|
||||||
|
Foo(a map[string]otherfixture.Spaceship)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fooer9 is an interface used for tests.
|
||||||
|
type Fooer9 interface {
|
||||||
|
Foo(a map[string]*otherfixture.Spaceship)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fooer10 is an interface used for tests.
|
||||||
|
type Fooer10 interface {
|
||||||
|
Foo(a []otherfixture.Spaceship)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fooer11 is an interface used for tests.
|
||||||
|
type Fooer11 interface {
|
||||||
|
Foo(a []*otherfixture.Spaceship)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fooer12 is an interface used for tests.
|
||||||
|
type Fooer12 interface {
|
||||||
|
Foo(a aliasedio.Reader)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package otherfixture
|
||||||
|
|
||||||
|
// Spaceship is a fixture for tests
|
||||||
|
type Spaceship struct{}
|
||||||
|
|
@ -78,7 +78,7 @@ func main() {
|
||||||
|
|
||||||
errorOut("parser error", generatedTempl.Execute(&buf, analysis))
|
errorOut("parser error", generatedTempl.Execute(&buf, analysis))
|
||||||
src, err := format.Source(buf.Bytes())
|
src, err := format.Source(buf.Bytes())
|
||||||
errorOut("error formating generated source", err)
|
errorOut("error formatting generated source:\n"+buf.String(), err)
|
||||||
errorOut("error writing file", ioutil.WriteFile(*outputFile, src, 0644))
|
errorOut("error writing file", ioutil.WriteFile(*outputFile, src, 0644))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errBadReturn = errors.New("found return arg with no name: all args must be named")
|
var errBadReturn = errors.New("found return arg with no name: all args must be named")
|
||||||
|
|
@ -25,6 +27,7 @@ func (e errUnexpectedType) Error() string {
|
||||||
type ParsedPkg struct {
|
type ParsedPkg struct {
|
||||||
Name string
|
Name string
|
||||||
Functions []function
|
Functions []function
|
||||||
|
Imports []importSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
type function struct {
|
type function struct {
|
||||||
|
|
@ -35,14 +38,29 @@ type function struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type arg struct {
|
type arg struct {
|
||||||
Name string
|
Name string
|
||||||
ArgType string
|
ArgType string
|
||||||
|
PackageSelector string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *arg) String() string {
|
func (a *arg) String() string {
|
||||||
return a.Name + " " + a.ArgType
|
return a.Name + " " + a.ArgType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type importSpec struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *importSpec) String() string {
|
||||||
|
var ss string
|
||||||
|
if len(s.Name) != 0 {
|
||||||
|
ss += s.Name
|
||||||
|
}
|
||||||
|
ss += s.Path
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parses the given file for an interface definition with the given name.
|
// Parse parses the given file for an interface definition with the given name.
|
||||||
func Parse(filePath string, objName string) (*ParsedPkg, error) {
|
func Parse(filePath string, objName string) (*ParsedPkg, error) {
|
||||||
fs := token.NewFileSet()
|
fs := token.NewFileSet()
|
||||||
|
|
@ -73,6 +91,44 @@ func Parse(filePath string, objName string) (*ParsedPkg, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// figure out what imports will be needed
|
||||||
|
imports := make(map[string]importSpec)
|
||||||
|
for _, f := range p.Functions {
|
||||||
|
args := append(f.Args, f.Returns...)
|
||||||
|
for _, arg := range args {
|
||||||
|
if len(arg.PackageSelector) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range pkg.Imports {
|
||||||
|
if i.Name != nil {
|
||||||
|
if i.Name.Name != arg.PackageSelector {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
imports[i.Path.Value] = importSpec{Name: arg.PackageSelector, Path: i.Path.Value}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
_, name := path.Split(i.Path.Value)
|
||||||
|
splitName := strings.Split(name, "-")
|
||||||
|
if len(splitName) > 1 {
|
||||||
|
name = splitName[len(splitName)-1]
|
||||||
|
}
|
||||||
|
// import paths have quotes already added in, so need to remove them for name comparison
|
||||||
|
name = strings.TrimPrefix(name, `"`)
|
||||||
|
name = strings.TrimSuffix(name, `"`)
|
||||||
|
if name == arg.PackageSelector {
|
||||||
|
imports[i.Path.Value] = importSpec{Path: i.Path.Value}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, spec := range imports {
|
||||||
|
p.Imports = append(p.Imports, spec)
|
||||||
|
}
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,22 +198,66 @@ func parseArgs(fields []*ast.Field) ([]arg, error) {
|
||||||
return nil, errBadReturn
|
return nil, errBadReturn
|
||||||
}
|
}
|
||||||
for _, name := range f.Names {
|
for _, name := range f.Names {
|
||||||
var typeName string
|
p, err := parseExpr(f.Type)
|
||||||
switch argType := f.Type.(type) {
|
if err != nil {
|
||||||
case *ast.Ident:
|
return nil, err
|
||||||
typeName = argType.Name
|
|
||||||
case *ast.StarExpr:
|
|
||||||
i, ok := argType.X.(*ast.Ident)
|
|
||||||
if !ok {
|
|
||||||
return nil, errUnexpectedType{"*ast.Ident", f.Type}
|
|
||||||
}
|
|
||||||
typeName = "*" + i.Name
|
|
||||||
default:
|
|
||||||
return nil, errUnexpectedType{"*ast.Ident or *ast.StarExpr", f.Type}
|
|
||||||
}
|
}
|
||||||
|
args = append(args, arg{name.Name, p.value, p.pkg})
|
||||||
args = append(args, arg{name.Name, typeName})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type parsedExpr struct {
|
||||||
|
value string
|
||||||
|
pkg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseExpr(e ast.Expr) (parsedExpr, error) {
|
||||||
|
var parsed parsedExpr
|
||||||
|
switch i := e.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
parsed.value += i.Name
|
||||||
|
case *ast.StarExpr:
|
||||||
|
p, err := parseExpr(i.X)
|
||||||
|
if err != nil {
|
||||||
|
return parsed, err
|
||||||
|
}
|
||||||
|
parsed.value += "*"
|
||||||
|
parsed.value += p.value
|
||||||
|
parsed.pkg = p.pkg
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
p, err := parseExpr(i.X)
|
||||||
|
if err != nil {
|
||||||
|
return parsed, err
|
||||||
|
}
|
||||||
|
parsed.pkg = p.value
|
||||||
|
parsed.value += p.value + "."
|
||||||
|
parsed.value += i.Sel.Name
|
||||||
|
case *ast.MapType:
|
||||||
|
parsed.value += "map["
|
||||||
|
p, err := parseExpr(i.Key)
|
||||||
|
if err != nil {
|
||||||
|
return parsed, err
|
||||||
|
}
|
||||||
|
parsed.value += p.value
|
||||||
|
parsed.value += "]"
|
||||||
|
p, err = parseExpr(i.Value)
|
||||||
|
if err != nil {
|
||||||
|
return parsed, err
|
||||||
|
}
|
||||||
|
parsed.value += p.value
|
||||||
|
parsed.pkg = p.pkg
|
||||||
|
case *ast.ArrayType:
|
||||||
|
parsed.value += "[]"
|
||||||
|
p, err := parseExpr(i.Elt)
|
||||||
|
if err != nil {
|
||||||
|
return parsed, err
|
||||||
|
}
|
||||||
|
parsed.value += p.value
|
||||||
|
parsed.pkg = p.pkg
|
||||||
|
default:
|
||||||
|
return parsed, errUnexpectedType{"*ast.Ident or *ast.StarExpr", i}
|
||||||
|
}
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func TestParseWithMultipleFuncs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assertName(t, "foo", pkg.Name)
|
assertName(t, "foo", pkg.Name)
|
||||||
assertNum(t, 6, len(pkg.Functions))
|
assertNum(t, 7, len(pkg.Functions))
|
||||||
|
|
||||||
f := pkg.Functions[0]
|
f := pkg.Functions[0]
|
||||||
assertName(t, "Foo", f.Name)
|
assertName(t, "Foo", f.Name)
|
||||||
|
|
@ -105,6 +105,35 @@ func TestParseWithMultipleFuncs(t *testing.T) {
|
||||||
arg = f.Returns[0]
|
arg = f.Returns[0]
|
||||||
assertName(t, "w", arg.Name)
|
assertName(t, "w", arg.Name)
|
||||||
assertName(t, "wobble", arg.ArgType)
|
assertName(t, "wobble", arg.ArgType)
|
||||||
|
|
||||||
|
f = pkg.Functions[6]
|
||||||
|
assertName(t, "WiggleWobble", f.Name)
|
||||||
|
assertNum(t, 6, len(f.Args))
|
||||||
|
assertNum(t, 6, len(f.Returns))
|
||||||
|
expectedArgs := [][]string{
|
||||||
|
{"a", "[]*wobble"},
|
||||||
|
{"b", "[]wobble"},
|
||||||
|
{"c", "map[string]*wobble"},
|
||||||
|
{"d", "map[*wobble]wobble"},
|
||||||
|
{"e", "map[string][]wobble"},
|
||||||
|
{"f", "[]*otherfixture.Spaceship"},
|
||||||
|
}
|
||||||
|
for i, arg := range f.Args {
|
||||||
|
assertName(t, expectedArgs[i][0], arg.Name)
|
||||||
|
assertName(t, expectedArgs[i][1], arg.ArgType)
|
||||||
|
}
|
||||||
|
expectedReturns := [][]string{
|
||||||
|
{"g", "map[*wobble]wobble"},
|
||||||
|
{"h", "[][]*wobble"},
|
||||||
|
{"i", "otherfixture.Spaceship"},
|
||||||
|
{"j", "*otherfixture.Spaceship"},
|
||||||
|
{"k", "map[*otherfixture.Spaceship]otherfixture.Spaceship"},
|
||||||
|
{"l", "[]otherfixture.Spaceship"},
|
||||||
|
}
|
||||||
|
for i, ret := range f.Returns {
|
||||||
|
assertName(t, expectedReturns[i][0], ret.Name)
|
||||||
|
assertName(t, expectedReturns[i][1], ret.ArgType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseWithUnamedReturn(t *testing.T) {
|
func TestParseWithUnamedReturn(t *testing.T) {
|
||||||
|
|
@ -150,6 +179,31 @@ func TestEmbeddedInterface(t *testing.T) {
|
||||||
assertName(t, "error", arg.ArgType)
|
assertName(t, "error", arg.ArgType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParsedImports(t *testing.T) {
|
||||||
|
cases := []string{"Fooer6", "Fooer7", "Fooer8", "Fooer9", "Fooer10", "Fooer11"}
|
||||||
|
for _, testCase := range cases {
|
||||||
|
pkg, err := Parse(testFixture, testCase)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNum(t, 1, len(pkg.Imports))
|
||||||
|
importPath := strings.Split(pkg.Imports[0].Path, "/")
|
||||||
|
assertName(t, "otherfixture\"", importPath[len(importPath)-1])
|
||||||
|
assertName(t, "", pkg.Imports[0].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAliasedImports(t *testing.T) {
|
||||||
|
pkg, err := Parse(testFixture, "Fooer12")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNum(t, 1, len(pkg.Imports))
|
||||||
|
assertName(t, "aliasedio", pkg.Imports[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
func assertName(t *testing.T, expected, actual string) {
|
func assertName(t *testing.T, expected, actual string) {
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
fatalOut(t, fmt.Sprintf("expected name to be `%s`, got: %s", expected, actual))
|
fatalOut(t, fmt.Sprintf("expected name to be `%s`, got: %s", expected, actual))
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,19 @@ func printArgs(args []arg) string {
|
||||||
return strings.Join(argStr, ", ")
|
return strings.Join(argStr, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildImports(specs []importSpec) string {
|
||||||
|
if len(specs) == 0 {
|
||||||
|
return `import "errors"`
|
||||||
|
}
|
||||||
|
imports := "import(\n"
|
||||||
|
imports += "\t\"errors\"\n"
|
||||||
|
for _, i := range specs {
|
||||||
|
imports += "\t" + i.String() + "\n"
|
||||||
|
}
|
||||||
|
imports += ")"
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
func marshalType(t string) string {
|
func marshalType(t string) string {
|
||||||
switch t {
|
switch t {
|
||||||
case "error":
|
case "error":
|
||||||
|
|
@ -44,6 +57,7 @@ var templFuncs = template.FuncMap{
|
||||||
"lower": strings.ToLower,
|
"lower": strings.ToLower,
|
||||||
"title": title,
|
"title": title,
|
||||||
"tag": buildTag,
|
"tag": buildTag,
|
||||||
|
"imports": buildImports,
|
||||||
}
|
}
|
||||||
|
|
||||||
func title(s string) string {
|
func title(s string) string {
|
||||||
|
|
@ -60,7 +74,7 @@ var generatedTempl = template.Must(template.New("rpc_cient").Funcs(templFuncs).P
|
||||||
|
|
||||||
package {{ .Name }}
|
package {{ .Name }}
|
||||||
|
|
||||||
import "errors"
|
{{ imports .Imports }}
|
||||||
|
|
||||||
type client interface{
|
type client interface{
|
||||||
Call(string, interface{}, interface{}) error
|
Call(string, interface{}, interface{}) error
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,22 @@
|
||||||
package volumedrivers
|
package volumedrivers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidScope = errors.New("invalid scope")
|
||||||
|
errNoSuchVolume = errors.New("no such volume")
|
||||||
|
)
|
||||||
|
|
||||||
type volumeDriverAdapter struct {
|
type volumeDriverAdapter struct {
|
||||||
name string
|
name string
|
||||||
proxy *volumeDriverProxy
|
capabilities *volume.Capability
|
||||||
|
proxy *volumeDriverProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *volumeDriverAdapter) Name() string {
|
func (a *volumeDriverAdapter) Name() string {
|
||||||
|
|
@ -56,7 +64,7 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
|
||||||
|
|
||||||
// plugin may have returned no volume and no error
|
// plugin may have returned no volume and no error
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return nil, fmt.Errorf("no such volume")
|
return nil, errNoSuchVolume
|
||||||
}
|
}
|
||||||
|
|
||||||
return &volumeAdapter{
|
return &volumeAdapter{
|
||||||
|
|
@ -68,6 +76,38 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *volumeDriverAdapter) Scope() string {
|
||||||
|
cap := a.getCapabilities()
|
||||||
|
return cap.Scope
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
|
||||||
|
if a.capabilities != nil {
|
||||||
|
return *a.capabilities
|
||||||
|
}
|
||||||
|
cap, err := a.proxy.Capabilities()
|
||||||
|
if err != nil {
|
||||||
|
// `GetCapabilities` is a not a required endpoint.
|
||||||
|
// On error assume it's a local-only driver
|
||||||
|
logrus.Warnf("Volume driver %s returned an error while trying to query it's capabilities, using default capabilties: %v", a.name, err)
|
||||||
|
return volume.Capability{Scope: volume.LocalScope}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't spam the warn log below just because the plugin didn't provide a scope
|
||||||
|
if len(cap.Scope) == 0 {
|
||||||
|
cap.Scope = volume.LocalScope
|
||||||
|
}
|
||||||
|
|
||||||
|
cap.Scope = strings.ToLower(cap.Scope)
|
||||||
|
if cap.Scope != volume.LocalScope && cap.Scope != volume.GlobalScope {
|
||||||
|
logrus.Warnf("Volume driver %q returned an invalid scope: %q", a.Name(), cap.Scope)
|
||||||
|
cap.Scope = volume.LocalScope
|
||||||
|
}
|
||||||
|
|
||||||
|
a.capabilities = &cap
|
||||||
|
return cap
|
||||||
|
}
|
||||||
|
|
||||||
type volumeAdapter struct {
|
type volumeAdapter struct {
|
||||||
proxy *volumeDriverProxy
|
proxy *volumeDriverProxy
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,12 @@ func NewVolumeDriver(name string, c client) volume.Driver {
|
||||||
return &volumeDriverAdapter{name: name, proxy: proxy}
|
return &volumeDriverAdapter{name: name, proxy: proxy}
|
||||||
}
|
}
|
||||||
|
|
||||||
type opts map[string]string
|
|
||||||
type list []*proxyVolume
|
|
||||||
|
|
||||||
// volumeDriver defines the available functions that volume plugins must implement.
|
// volumeDriver defines the available functions that volume plugins must implement.
|
||||||
// This interface is only defined to generate the proxy objects.
|
// This interface is only defined to generate the proxy objects.
|
||||||
// It's not intended to be public or reused.
|
// It's not intended to be public or reused.
|
||||||
type volumeDriver interface {
|
type volumeDriver interface {
|
||||||
// Create a volume with the given name
|
// Create a volume with the given name
|
||||||
Create(name string, opts opts) (err error)
|
Create(name string, opts map[string]string) (err error)
|
||||||
// Remove the volume with the given name
|
// Remove the volume with the given name
|
||||||
Remove(name string) (err error)
|
Remove(name string) (err error)
|
||||||
// Get the mountpoint of the given volume
|
// Get the mountpoint of the given volume
|
||||||
|
|
@ -42,9 +39,11 @@ type volumeDriver interface {
|
||||||
// Unmount the given volume
|
// Unmount the given volume
|
||||||
Unmount(name, id string) (err error)
|
Unmount(name, id string) (err error)
|
||||||
// List lists all the volumes known to the driver
|
// List lists all the volumes known to the driver
|
||||||
List() (volumes list, err error)
|
List() (volumes []*proxyVolume, err error)
|
||||||
// Get retrieves the volume with the requested name
|
// Get retrieves the volume with the requested name
|
||||||
Get(name string) (volume *proxyVolume, err error)
|
Get(name string) (volume *proxyVolume, err error)
|
||||||
|
// Capabilities gets the list of capabilities of the driver
|
||||||
|
Capabilities() (capabilities volume.Capability, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type driverExtpoint struct {
|
type driverExtpoint struct {
|
||||||
|
|
@ -67,6 +66,11 @@ func Register(extension volume.Driver, name string) bool {
|
||||||
if exists {
|
if exists {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validateDriver(extension); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
drivers.extensions[name] = extension
|
drivers.extensions[name] = extension
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -110,10 +114,22 @@ func Lookup(name string) (volume.Driver, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
d := NewVolumeDriver(name, pl.Client)
|
d := NewVolumeDriver(name, pl.Client)
|
||||||
|
if err := validateDriver(d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
drivers.extensions[name] = d
|
drivers.extensions[name] = d
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateDriver(vd volume.Driver) error {
|
||||||
|
scope := vd.Scope()
|
||||||
|
if scope != volume.LocalScope && scope != volume.GlobalScope {
|
||||||
|
return fmt.Errorf("Driver %q provided an invalid capability scope: %s", vd.Name(), scope)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetDriver returns a volume driver by its name.
|
// GetDriver returns a volume driver by its name.
|
||||||
// If the driver is empty, it looks for the local driver.
|
// If the driver is empty, it looks for the local driver.
|
||||||
func GetDriver(name string) (volume.Driver, error) {
|
func GetDriver(name string) (volume.Driver, error) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
package volumedrivers
|
package volumedrivers
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/docker/docker/volume"
|
||||||
|
)
|
||||||
|
|
||||||
type client interface {
|
type client interface {
|
||||||
Call(string, interface{}, interface{}) error
|
Call(string, interface{}, interface{}) error
|
||||||
|
|
@ -14,14 +17,14 @@ type volumeDriverProxy struct {
|
||||||
|
|
||||||
type volumeDriverProxyCreateRequest struct {
|
type volumeDriverProxyCreateRequest struct {
|
||||||
Name string
|
Name string
|
||||||
Opts opts
|
Opts map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type volumeDriverProxyCreateResponse struct {
|
type volumeDriverProxyCreateResponse struct {
|
||||||
Err string
|
Err string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *volumeDriverProxy) Create(name string, opts opts) (err error) {
|
func (pp *volumeDriverProxy) Create(name string, opts map[string]string) (err error) {
|
||||||
var (
|
var (
|
||||||
req volumeDriverProxyCreateRequest
|
req volumeDriverProxyCreateRequest
|
||||||
ret volumeDriverProxyCreateResponse
|
ret volumeDriverProxyCreateResponse
|
||||||
|
|
@ -158,11 +161,11 @@ type volumeDriverProxyListRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type volumeDriverProxyListResponse struct {
|
type volumeDriverProxyListResponse struct {
|
||||||
Volumes list
|
Volumes []*proxyVolume
|
||||||
Err string
|
Err string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *volumeDriverProxy) List() (volumes list, err error) {
|
func (pp *volumeDriverProxy) List() (volumes []*proxyVolume, err error) {
|
||||||
var (
|
var (
|
||||||
req volumeDriverProxyListRequest
|
req volumeDriverProxyListRequest
|
||||||
ret volumeDriverProxyListResponse
|
ret volumeDriverProxyListResponse
|
||||||
|
|
@ -209,3 +212,30 @@ func (pp *volumeDriverProxy) Get(name string) (volume *proxyVolume, err error) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type volumeDriverProxyCapabilitiesRequest struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type volumeDriverProxyCapabilitiesResponse struct {
|
||||||
|
Capabilities volume.Capability
|
||||||
|
Err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *volumeDriverProxy) Capabilities() (capabilities volume.Capability, err error) {
|
||||||
|
var (
|
||||||
|
req volumeDriverProxyCapabilitiesRequest
|
||||||
|
ret volumeDriverProxyCapabilitiesResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = pp.Call("VolumeDriver.Capabilities", req, &ret); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
capabilities = ret.Capabilities
|
||||||
|
|
||||||
|
if ret.Err != "" {
|
||||||
|
err = errors.New(ret.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ func TestVolumeRequestError(t *testing.T) {
|
||||||
fmt.Fprintln(w, `{"Err": "Cannot get volume"}`)
|
fmt.Fprintln(w, `{"Err": "Cannot get volume"}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/VolumeDriver.Capabilities", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||||
|
http.Error(w, "error", 500)
|
||||||
|
})
|
||||||
|
|
||||||
u, _ := url.Parse(server.URL)
|
u, _ := url.Parse(server.URL)
|
||||||
client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true})
|
client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -119,4 +124,9 @@ func TestVolumeRequestError(t *testing.T) {
|
||||||
if !strings.Contains(err.Error(), "Cannot get volume") {
|
if !strings.Contains(err.Error(), "Cannot get volume") {
|
||||||
t.Fatalf("Unexpected error: %v\n", err)
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = driver.Capabilities()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,11 @@ func (r *Root) Get(name string) (volume.Volume, error) {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scope returns the local volume scope
|
||||||
|
func (r *Root) Scope() string {
|
||||||
|
return volume.LocalScope
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Root) validateName(name string) error {
|
func (r *Root) validateName(name string) error {
|
||||||
if !volumeNameRegex.MatchString(name) {
|
if !volumeNameRegex.MatchString(name) {
|
||||||
return validationError{fmt.Errorf("%q includes invalid characters for a local volume name, only %q are allowed", name, utils.RestrictedNameChars)}
|
return validationError{fmt.Errorf("%q includes invalid characters for a local volume name, only %q are allowed", name, utils.RestrictedNameChars)}
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,29 @@ type volumeMetadata struct {
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type volumeWithLabels struct {
|
type volumeWrapper struct {
|
||||||
volume.Volume
|
volume.Volume
|
||||||
labels map[string]string
|
labels map[string]string
|
||||||
|
scope string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v volumeWithLabels) Labels() map[string]string {
|
func (v volumeWrapper) Labels() map[string]string {
|
||||||
return v.labels
|
return v.labels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v volumeWrapper) Scope() string {
|
||||||
|
return v.scope
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v volumeWrapper) CachedPath() string {
|
||||||
|
if vv, ok := v.Volume.(interface {
|
||||||
|
CachedPath() string
|
||||||
|
}); ok {
|
||||||
|
return vv.CachedPath()
|
||||||
|
}
|
||||||
|
return v.Volume.Path()
|
||||||
|
}
|
||||||
|
|
||||||
// New initializes a VolumeStore to keep
|
// New initializes a VolumeStore to keep
|
||||||
// reference counting of volumes in the system.
|
// reference counting of volumes in the system.
|
||||||
func New(rootPath string) (*VolumeStore, error) {
|
func New(rootPath string) (*VolumeStore, error) {
|
||||||
|
|
@ -166,6 +180,10 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
|
||||||
chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
|
chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for i, v := range vs {
|
||||||
|
vs[i] = volumeWrapper{v, s.labels[v.Name()], d.Scope()}
|
||||||
|
}
|
||||||
|
|
||||||
chVols <- vols{vols: vs}
|
chVols <- vols{vols: vs}
|
||||||
}(vd)
|
}(vd)
|
||||||
}
|
}
|
||||||
|
|
@ -291,7 +309,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return volumeWithLabels{v, labels}, nil
|
return volumeWrapper{v, labels, vd.Scope()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWithRef gets a volume with the given name from the passed in driver and stores the ref
|
// GetWithRef gets a volume with the given name from the passed in driver and stores the ref
|
||||||
|
|
@ -313,10 +331,8 @@ func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, e
|
||||||
}
|
}
|
||||||
|
|
||||||
s.setNamed(v, ref)
|
s.setNamed(v, ref)
|
||||||
if labels, ok := s.labels[name]; ok {
|
|
||||||
return volumeWithLabels{v, labels}, nil
|
return volumeWrapper{v, s.labels[name], vd.Scope()}, nil
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get looks if a volume with the given name exists and returns it if so
|
// Get looks if a volume with the given name exists and returns it if so
|
||||||
|
|
@ -376,7 +392,7 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return volumeWithLabels{vol, labels}, nil
|
return volumeWrapper{vol, labels, vd.Scope()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("Probing all drivers for volume with name: %s", name)
|
logrus.Debugf("Probing all drivers for volume with name: %s", name)
|
||||||
|
|
@ -391,7 +407,7 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return volumeWithLabels{v, labels}, nil
|
return volumeWrapper{v, labels, d.Scope()}, nil
|
||||||
}
|
}
|
||||||
return nil, errNoSuchVolume
|
return nil, errNoSuchVolume
|
||||||
}
|
}
|
||||||
|
|
@ -412,7 +428,7 @@ func (s *VolumeStore) Remove(v volume.Volume) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
||||||
vol := withoutLabels(v)
|
vol := unwrapVolume(v)
|
||||||
if err := vd.Remove(vol); err != nil {
|
if err := vd.Remove(vol); err != nil {
|
||||||
return &OpErr{Err: err, Name: name, Op: "remove"}
|
return &OpErr{Err: err, Name: name, Op: "remove"}
|
||||||
}
|
}
|
||||||
|
|
@ -465,6 +481,9 @@ func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &OpErr{Err: err, Name: name, Op: "list"}
|
return nil, &OpErr{Err: err, Name: name, Op: "list"}
|
||||||
}
|
}
|
||||||
|
for i, v := range ls {
|
||||||
|
ls[i] = volumeWrapper{v, s.labels[v.Name()], vd.Scope()}
|
||||||
|
}
|
||||||
return ls, nil
|
return ls, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -497,8 +516,8 @@ func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume
|
||||||
return ls
|
return ls
|
||||||
}
|
}
|
||||||
|
|
||||||
func withoutLabels(v volume.Volume) volume.Volume {
|
func unwrapVolume(v volume.Volume) volume.Volume {
|
||||||
if vol, ok := v.(volumeWithLabels); ok {
|
if vol, ok := v.(volumeWrapper); ok {
|
||||||
return vol.Volume
|
return vol.Volume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,3 +109,8 @@ func (d *FakeDriver) Get(name string) (volume.Volume, error) {
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no such volume")
|
return nil, fmt.Errorf("no such volume")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scope returns the local scope
|
||||||
|
func (*FakeDriver) Scope() string {
|
||||||
|
return "local"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,14 @@ import (
|
||||||
|
|
||||||
// DefaultDriverName is the driver name used for the driver
|
// DefaultDriverName is the driver name used for the driver
|
||||||
// implemented in the local package.
|
// implemented in the local package.
|
||||||
const DefaultDriverName string = "local"
|
const DefaultDriverName = "local"
|
||||||
|
|
||||||
|
// Scopes define if a volume has is cluster-wide (global) or local only.
|
||||||
|
// Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume
|
||||||
|
const (
|
||||||
|
LocalScope = "local"
|
||||||
|
GlobalScope = "global"
|
||||||
|
)
|
||||||
|
|
||||||
// Driver is for creating and removing volumes.
|
// Driver is for creating and removing volumes.
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
|
|
@ -27,6 +34,18 @@ type Driver interface {
|
||||||
List() ([]Volume, error)
|
List() ([]Volume, error)
|
||||||
// Get retrieves the volume with the requested name
|
// Get retrieves the volume with the requested name
|
||||||
Get(name string) (Volume, error)
|
Get(name string) (Volume, error)
|
||||||
|
// Scope returns the scope of the driver (e.g. `golbal` or `local`).
|
||||||
|
// Scope determines how the driver is handled at a cluster level
|
||||||
|
Scope() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capability defines a set of capabilities that a driver is able to handle.
|
||||||
|
type Capability struct {
|
||||||
|
// Scope is the scope of the driver, `global` or `local`
|
||||||
|
// A `global` scope indicates that the driver manages volumes across the cluster
|
||||||
|
// A `local` scope indicates that the driver only manages volumes resources local to the host
|
||||||
|
// Scope is declared by the driver
|
||||||
|
Scope string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume is a place to store data. It is backed by a specific driver, and can be mounted.
|
// Volume is a place to store data. It is backed by a specific driver, and can be mounted.
|
||||||
|
|
@ -46,6 +65,18 @@ type Volume interface {
|
||||||
Status() map[string]interface{}
|
Status() map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LabeledVolume wraps a Volume with user-defined labels
|
||||||
|
type LabeledVolume interface {
|
||||||
|
Labels() map[string]string
|
||||||
|
Volume
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScopedVolume wraps a volume with a cluster scope (e.g., `local` or `global`)
|
||||||
|
type ScopedVolume interface {
|
||||||
|
Scope() string
|
||||||
|
Volume
|
||||||
|
}
|
||||||
|
|
||||||
// MountPoint is the intersection point between a volume and a container. It
|
// MountPoint is the intersection point between a volume and a container. It
|
||||||
// specifies which volume is to be used and where inside a container it should
|
// specifies which volume is to be used and where inside a container it should
|
||||||
// be mounted.
|
// be mounted.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue