Introduce dynamic plugins
go1.8 Plugin package brings a mechanism for dynamyc loading. StorageDriver or AccessController can be compiled as plugin and can be loaded at runtime. Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>
This commit is contained in:
parent
beabc206e1
commit
9b1e893755
13 changed files with 214 additions and 10 deletions
|
@ -1,10 +1,10 @@
|
||||||
FROM golang:1.8rc2-alpine
|
FROM golang:1.8-alpine
|
||||||
|
|
||||||
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
|
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
|
||||||
ENV DOCKER_BUILDTAGS include_oss include_gcs
|
ENV DOCKER_BUILDTAGS include_oss include_gcs
|
||||||
|
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk add --no-cache make git
|
&& apk add --no-cache make git build-base
|
||||||
|
|
||||||
WORKDIR $DISTRIBUTION_DIR
|
WORKDIR $DISTRIBUTION_DIR
|
||||||
COPY . $DISTRIBUTION_DIR
|
COPY . $DISTRIBUTION_DIR
|
||||||
|
|
|
@ -8,7 +8,7 @@ machine:
|
||||||
|
|
||||||
post:
|
post:
|
||||||
# go
|
# go
|
||||||
- gvm install go1.8rc2 --prefer-binary --name=stable
|
- gvm install go1.8 --prefer-binary --name=stable
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
# Convenient shortcuts to "common" locations
|
# Convenient shortcuts to "common" locations
|
||||||
|
|
|
@ -48,6 +48,9 @@ type Configuration struct {
|
||||||
// deprecated. Please use Log.Level in the future.
|
// deprecated. Please use Log.Level in the future.
|
||||||
Loglevel Loglevel `yaml:"loglevel,omitempty"`
|
Loglevel Loglevel `yaml:"loglevel,omitempty"`
|
||||||
|
|
||||||
|
// Plugins is a path where plugins are expected to be found
|
||||||
|
Plugins []string `yaml:"plugins,omitempty"`
|
||||||
|
|
||||||
// Storage is the configuration for the registry's storage driver
|
// Storage is the configuration for the registry's storage driver
|
||||||
Storage Storage `yaml:"storage"`
|
Storage Storage `yaml:"storage"`
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,10 @@ log:
|
||||||
to:
|
to:
|
||||||
- errors@example.com
|
- errors@example.com
|
||||||
loglevel: debug # deprecated: use "log"
|
loglevel: debug # deprecated: use "log"
|
||||||
|
plugins:
|
||||||
|
- /plugins/
|
||||||
|
- /plugin/driver1.so
|
||||||
|
- /plugin/auth1.so
|
||||||
storage:
|
storage:
|
||||||
filesystem:
|
filesystem:
|
||||||
rootdirectory: /var/lib/registry
|
rootdirectory: /var/lib/registry
|
||||||
|
@ -359,6 +363,21 @@ loglevel: debug
|
||||||
Permitted values are `error`, `warn`, `info` and `debug`. The default is
|
Permitted values are `error`, `warn`, `info` and `debug`. The default is
|
||||||
`info`.
|
`info`.
|
||||||
|
|
||||||
|
## `plugins`
|
||||||
|
|
||||||
|
```none
|
||||||
|
plugins:
|
||||||
|
- /plugins/
|
||||||
|
- /plugin/driver1.so
|
||||||
|
- /plugin/auth1.so
|
||||||
|
```
|
||||||
|
|
||||||
|
> Requires golang >= 1.8
|
||||||
|
|
||||||
|
Directory with plugins or paths point to plugins. Plugin are loaded before trying to initialize
|
||||||
|
any driver or authcontroller. If a path is a directory, it is scanned for plugins loading all files
|
||||||
|
with a is common shared library extension on the platform (`.so`, `.dylib`, `dll`).
|
||||||
|
|
||||||
## `storage`
|
## `storage`
|
||||||
|
|
||||||
```none
|
```none
|
||||||
|
|
|
@ -198,5 +198,14 @@ func GetAccessController(name string, options map[string]interface{}) (AccessCon
|
||||||
return initFunc(options)
|
return initFunc(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no access controller registered with name: %s", name)
|
return nil, InvalidAccessControllerError{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidAccessControllerError records an attempt to construct an unregistered storage driver
|
||||||
|
type InvalidAccessControllerError struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err InvalidAccessControllerError) Error() string {
|
||||||
|
return fmt.Sprintf("no access controller registered with name: %s", err.Name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/docker/distribution/registry/auth"
|
"github.com/docker/distribution/registry/auth"
|
||||||
registrymiddleware "github.com/docker/distribution/registry/middleware/registry"
|
registrymiddleware "github.com/docker/distribution/registry/middleware/registry"
|
||||||
repositorymiddleware "github.com/docker/distribution/registry/middleware/repository"
|
repositorymiddleware "github.com/docker/distribution/registry/middleware/repository"
|
||||||
|
"github.com/docker/distribution/registry/pluginloader"
|
||||||
"github.com/docker/distribution/registry/proxy"
|
"github.com/docker/distribution/registry/proxy"
|
||||||
"github.com/docker/distribution/registry/storage"
|
"github.com/docker/distribution/registry/storage"
|
||||||
memorycache "github.com/docker/distribution/registry/storage/cache/memory"
|
memorycache "github.com/docker/distribution/registry/storage/cache/memory"
|
||||||
|
@ -117,9 +118,6 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
|
||||||
var err error
|
var err error
|
||||||
app.driver, err = factory.Create(config.Storage.Type(), storageParams)
|
app.driver, err = factory.Create(config.Storage.Type(), storageParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(stevvooe): Move the creation of a service into a protected
|
|
||||||
// method, where this is created lazily. Its status can be queried via
|
|
||||||
// a health check.
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +155,12 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
|
||||||
app.configureRedis(config)
|
app.configureRedis(config)
|
||||||
app.configureLogHook(config)
|
app.configureLogHook(config)
|
||||||
|
|
||||||
|
if len(config.Plugins) != 0 {
|
||||||
|
if err = pluginloader.LoadPlugins(app, config.Plugins); err != nil {
|
||||||
|
ctxu.GetLogger(app).Errorf("could not load plugins from %s: %v", config.Plugins, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
options := registrymiddleware.GetRegistryOptions()
|
options := registrymiddleware.GetRegistryOptions()
|
||||||
if config.Compatibility.Schema1.TrustKey != "" {
|
if config.Compatibility.Schema1.TrustKey != "" {
|
||||||
app.trustKey, err = libtrust.LoadKeyFile(config.Compatibility.Schema1.TrustKey)
|
app.trustKey, err = libtrust.LoadKeyFile(config.Compatibility.Schema1.TrustKey)
|
||||||
|
@ -301,11 +305,10 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
|
||||||
authType := config.Auth.Type()
|
authType := config.Auth.Type()
|
||||||
|
|
||||||
if authType != "" {
|
if authType != "" {
|
||||||
accessController, err := auth.GetAccessController(config.Auth.Type(), config.Auth.Parameters())
|
app.accessController, err = auth.GetAccessController(authType, config.Auth.Parameters())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err))
|
panic(err)
|
||||||
}
|
}
|
||||||
app.accessController = accessController
|
|
||||||
ctxu.GetLogger(app).Debugf("configured %q access controller", authType)
|
ctxu.GetLogger(app).Debugf("configured %q access controller", authType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
80
registry/pluginloader/loaders_18_test.go
Normal file
80
registry/pluginloader/loaders_18_test.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// +build go1.8,!race
|
||||||
|
|
||||||
|
package pluginloader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
check "gopkg.in/check.v1"
|
||||||
|
|
||||||
|
ctxu "github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/registry/auth"
|
||||||
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
|
"github.com/docker/distribution/registry/storage/driver/factory"
|
||||||
|
"github.com/docker/distribution/registry/storage/driver/testsuites"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlugins(t *testing.T) {
|
||||||
|
tempdir, err := ioutil.TempDir("", "ditributiontestplugins")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can not create tempdirectory %v\n", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempdir)
|
||||||
|
|
||||||
|
pluginpath := filepath.Join(tempdir, "testplugin"+suffix)
|
||||||
|
gobinary, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LookPath can not locate go binary: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("compile plugin into %s by %s\n", pluginpath, gobinary)
|
||||||
|
cmd := exec.Command(gobinary, "build", "-o", pluginpath, "-buildmode", "plugin", "github.com/docker/distribution/registry/pluginloader/testplugin")
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
output, _ := cmd.CombinedOutput()
|
||||||
|
t.Fatalf("plugin compilation failed %v %s\n", err, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("TestLoadPlugin", func(t *testing.T) {
|
||||||
|
_, err := factory.Create("inmemory", nil)
|
||||||
|
if _, ok := err.(factory.InvalidStorageDriverError); !ok {
|
||||||
|
t.Fatalf("inmemory driver is not expected to be built in: %T\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = auth.GetAccessController("silly", nil)
|
||||||
|
if _, ok := err.(auth.InvalidAccessControllerError); !ok {
|
||||||
|
t.Fatalf("silly plugin is not expected to be built in: %T\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = LoadPlugins(ctxu.Background(), []string{pluginpath})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("loading failed %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dr, err := factory.Create("inmemory", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("dynamic driver construction failed %v\n", err)
|
||||||
|
}
|
||||||
|
if dr == nil {
|
||||||
|
t.Fatal("driver is not expected to be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
ac, err := auth.GetAccessController("silly", map[string]interface{}{"realm": "realm", "service": "dummy"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AccessController construction failed %v\n", err)
|
||||||
|
}
|
||||||
|
if ac == nil {
|
||||||
|
t.Fatal("AccessController is not expected to be nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InmemoryDriverTestSuite", func(t *testing.T) {
|
||||||
|
inmemoryDriverConstructor := func() (storagedriver.StorageDriver, error) {
|
||||||
|
return factory.Create("inmemory", nil)
|
||||||
|
}
|
||||||
|
testsuites.RegisterSuite(inmemoryDriverConstructor, testsuites.NeverSkip)
|
||||||
|
check.TestingT(t)
|
||||||
|
})
|
||||||
|
}
|
54
registry/pluginloader/pluginloader.go
Normal file
54
registry/pluginloader/pluginloader.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package pluginloader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"plugin"
|
||||||
|
|
||||||
|
ctxu "github.com/docker/distribution/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadPlugins loads plugins pointed by paths. If a path points to a directory
|
||||||
|
// this directory is scanned for files with a platform specific shared library suffix (like .so, .dylib, .dll).
|
||||||
|
// NOTE: Plugins are expected to register themselves the same way as built-ins do.
|
||||||
|
// Storage drivers should use `factory.Register`, AccessControllers `auth.Register` in init().
|
||||||
|
func LoadPlugins(ctx ctxu.Context, paths []string) error {
|
||||||
|
for _, pluginpath := range paths {
|
||||||
|
fi, err := os.Stat(pluginpath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
ctxu.GetLogger(ctx).Errorf("plugin file %s does not exist", pluginpath)
|
||||||
|
} else {
|
||||||
|
ctxu.GetLogger(ctx).Errorf("could not Stat plugin file %s: %v", pluginpath, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
if err = loadplugin(pluginpath); err != nil {
|
||||||
|
ctxu.GetLogger(ctx).Errorf("could not load plugin %s: %v", pluginpath, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// To preserve the order we do not append this plugins to the paths
|
||||||
|
matches, err := filepath.Glob(filepath.Join(pluginpath, "*"+suffix))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pluginpath := range matches {
|
||||||
|
if err = loadplugin(pluginpath); err != nil {
|
||||||
|
ctxu.GetLogger(ctx).Errorf("could not load plugin %s: %v", pluginpath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadplugin(path string) error {
|
||||||
|
_, err := plugin.Open(path)
|
||||||
|
return err
|
||||||
|
}
|
13
registry/pluginloader/pluginloader_pre18.go
Normal file
13
registry/pluginloader/pluginloader_pre18.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package pluginloader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadPlugins(ctx context.Context, paths []string) error {
|
||||||
|
return fmt.Errorf("only golang >= 1.8 supports dynamic plugins")
|
||||||
|
}
|
5
registry/pluginloader/suffix.go
Normal file
5
registry/pluginloader/suffix.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// +build !darwin,!windows
|
||||||
|
|
||||||
|
package pluginloader
|
||||||
|
|
||||||
|
const suffix = ".so"
|
5
registry/pluginloader/suffix_darwin.go
Normal file
5
registry/pluginloader/suffix_darwin.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package pluginloader
|
||||||
|
|
||||||
|
const suffix = ".dylib"
|
5
registry/pluginloader/suffix_windows.go
Normal file
5
registry/pluginloader/suffix_windows.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package pluginloader
|
||||||
|
|
||||||
|
const suffix = ".dll"
|
8
registry/pluginloader/testplugin/testplugin.go
Normal file
8
registry/pluginloader/testplugin/testplugin.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/docker/distribution/registry/auth/silly"
|
||||||
|
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {}
|
Loading…
Reference in a new issue