plugins: experimental support for new plugin management
This patch introduces a new experimental engine-level plugin management with a new API and command line. Plugins can be distributed via a Docker registry, and their lifecycle is managed by the engine. This makes plugins a first-class construct. For more background, have a look at issue #20363. Documentation is in a separate commit. If you want to understand how the new plugin system works, you can start by reading the documentation. Note: backwards compatibility with existing plugins is maintained, albeit they won't benefit from the advantages of the new system. Signed-off-by: Tibor Vass <tibor@docker.com> Signed-off-by: Anusha Ragunathan <anusha@docker.com>
This commit is contained in:
parent
789aee497c
commit
59c8eda724
8 changed files with 48 additions and 30 deletions
|
@ -203,18 +203,17 @@ func TestResponseModifierOverride(t *testing.T) {
|
||||||
|
|
||||||
// createTestPlugin creates a new sample authorization plugin
|
// createTestPlugin creates a new sample authorization plugin
|
||||||
func createTestPlugin(t *testing.T) *authorizationPlugin {
|
func createTestPlugin(t *testing.T) *authorizationPlugin {
|
||||||
plugin := &plugins.Plugin{Name: "authz"}
|
|
||||||
pwd, err := os.Getwd()
|
pwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.Client, err = plugins.NewClient("unix:///"+path.Join(pwd, pluginAddress), tlsconfig.Options{InsecureSkipVerify: true})
|
client, err := plugins.NewClient("unix:///"+path.Join(pwd, pluginAddress), &tlsconfig.Options{InsecureSkipVerify: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create client %v", err)
|
t.Fatalf("Failed to create client %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &authorizationPlugin{name: "plugin", plugin: plugin}
|
return &authorizationPlugin{name: "plugin", plugin: client}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthZPluginTestServer is a simple server that implements the authZ plugin interface
|
// AuthZPluginTestServer is a simple server that implements the authZ plugin interface
|
||||||
|
|
|
@ -35,7 +35,7 @@ func NewPlugins(names []string) []Plugin {
|
||||||
|
|
||||||
// authorizationPlugin is an internal adapter to docker plugin system
|
// authorizationPlugin is an internal adapter to docker plugin system
|
||||||
type authorizationPlugin struct {
|
type authorizationPlugin struct {
|
||||||
plugin *plugins.Plugin
|
plugin *plugins.Client
|
||||||
name string
|
name string
|
||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
authRes := &Response{}
|
authRes := &Response{}
|
||||||
if err := a.plugin.Client.Call(AuthZApiRequest, authReq, authRes); err != nil {
|
if err := a.plugin.Call(AuthZApiRequest, authReq, authRes); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func (a *authorizationPlugin) AuthZResponse(authReq *Request) (*Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
authRes := &Response{}
|
authRes := &Response{}
|
||||||
if err := a.plugin.Client.Call(AuthZApiResponse, authReq, authRes); err != nil {
|
if err := a.plugin.Call(AuthZApiResponse, authReq, authRes); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,12 @@ func (a *authorizationPlugin) initPlugin() error {
|
||||||
var err error
|
var err error
|
||||||
a.once.Do(func() {
|
a.once.Do(func() {
|
||||||
if a.plugin == nil {
|
if a.plugin == nil {
|
||||||
a.plugin, err = plugins.Get(a.name, AuthZApiImplements)
|
plugin, e := plugins.Get(a.name, AuthZApiImplements)
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.plugin = plugin.Client()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -20,14 +20,16 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClient creates a new plugin client (http).
|
// NewClient creates a new plugin client (http).
|
||||||
func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
|
func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
|
||||||
tr := &http.Transport{}
|
tr := &http.Transport{}
|
||||||
|
|
||||||
c, err := tlsconfig.Client(tlsConfig)
|
if tlsConfig != nil {
|
||||||
|
c, err := tlsconfig.Client(*tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tr.TLSClientConfig = c
|
tr.TLSClientConfig = c
|
||||||
|
}
|
||||||
|
|
||||||
u, err := url.Parse(addr)
|
u, err := url.Parse(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -31,7 +31,7 @@ func teardownRemotePluginServer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailedConnection(t *testing.T) {
|
func TestFailedConnection(t *testing.T) {
|
||||||
c, _ := NewClient("tcp://127.0.0.1:1", tlsconfig.Options{InsecureSkipVerify: true})
|
c, _ := NewClient("tcp://127.0.0.1:1", &tlsconfig.Options{InsecureSkipVerify: true})
|
||||||
_, err := c.callWithRetry("Service.Method", nil, false)
|
_, err := c.callWithRetry("Service.Method", nil, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Unexpected successful connection")
|
t.Fatal("Unexpected successful connection")
|
||||||
|
@ -55,7 +55,7 @@ func TestEchoInputOutput(t *testing.T) {
|
||||||
io.Copy(w, r.Body)
|
io.Copy(w, r.Body)
|
||||||
})
|
})
|
||||||
|
|
||||||
c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
|
c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true})
|
||||||
var output Manifest
|
var output Manifest
|
||||||
err := c.Call("Test.Echo", m, &output)
|
err := c.Call("Test.Echo", m, &output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -64,7 +64,7 @@ func (l *localRegistry) Plugin(name string) (*Plugin, error) {
|
||||||
|
|
||||||
for _, p := range socketpaths {
|
for _, p := range socketpaths {
|
||||||
if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 {
|
if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 {
|
||||||
return newLocalPlugin(name, "unix://"+p), nil
|
return NewLocalPlugin(name, "unix://"+p), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func readPluginInfo(name, path string) (*Plugin, error) {
|
||||||
return nil, fmt.Errorf("Unknown protocol")
|
return nil, fmt.Errorf("Unknown protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
return newLocalPlugin(name, addr), nil
|
return NewLocalPlugin(name, addr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPluginJSONInfo(name, path string) (*Plugin, error) {
|
func readPluginJSONInfo(name, path string) (*Plugin, error) {
|
||||||
|
@ -115,7 +115,7 @@ func readPluginJSONInfo(name, path string) (*Plugin, error) {
|
||||||
if err := json.NewDecoder(f).Decode(&p); err != nil {
|
if err := json.NewDecoder(f).Decode(&p); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p.Name = name
|
p.name = name
|
||||||
if len(p.TLSConfig.CAFile) == 0 {
|
if len(p.TLSConfig.CAFile) == 0 {
|
||||||
p.TLSConfig.InsecureSkipVerify = true
|
p.TLSConfig.InsecureSkipVerify = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ func TestFileSpecPlugin(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Name != c.name {
|
if p.name != c.name {
|
||||||
t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
|
t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ func TestFileJSONSpecPlugin(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if plugin.Name != "example" {
|
if plugin.name != "example" {
|
||||||
t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name)
|
t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ func TestLocalSocket(t *testing.T) {
|
||||||
t.Fatalf("Expected %v, was %v\n", p, pp)
|
t.Fatalf("Expected %v, was %v\n", p, pp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Name != "echo" {
|
if p.name != "echo" {
|
||||||
t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
|
t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,13 +55,13 @@ type Manifest struct {
|
||||||
// Plugin is the definition of a docker plugin.
|
// Plugin is the definition of a docker plugin.
|
||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
// Name of the plugin
|
// Name of the plugin
|
||||||
Name string `json:"-"`
|
name string
|
||||||
// Address of the plugin
|
// Address of the plugin
|
||||||
Addr string
|
Addr string
|
||||||
// TLS configuration of the plugin
|
// TLS configuration of the plugin
|
||||||
TLSConfig tlsconfig.Options
|
TLSConfig *tlsconfig.Options
|
||||||
// Client attached to the plugin
|
// Client attached to the plugin
|
||||||
Client *Client `json:"-"`
|
client *Client
|
||||||
// Manifest of the plugin (see above)
|
// Manifest of the plugin (see above)
|
||||||
Manifest *Manifest `json:"-"`
|
Manifest *Manifest `json:"-"`
|
||||||
|
|
||||||
|
@ -73,11 +73,23 @@ type Plugin struct {
|
||||||
activateWait *sync.Cond
|
activateWait *sync.Cond
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalPlugin(name, addr string) *Plugin {
|
// Name returns the name of the plugin.
|
||||||
|
func (p *Plugin) Name() string {
|
||||||
|
return p.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns a ready-to-use plugin client that can be used to communicate with the plugin.
|
||||||
|
func (p *Plugin) Client() *Client {
|
||||||
|
return p.client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalPlugin creates a new local plugin.
|
||||||
|
func NewLocalPlugin(name, addr string) *Plugin {
|
||||||
return &Plugin{
|
return &Plugin{
|
||||||
Name: name,
|
name: name,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
TLSConfig: tlsconfig.Options{InsecureSkipVerify: true},
|
// TODO: change to nil
|
||||||
|
TLSConfig: &tlsconfig.Options{InsecureSkipVerify: true},
|
||||||
activateWait: sync.NewCond(&sync.Mutex{}),
|
activateWait: sync.NewCond(&sync.Mutex{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,10 +114,10 @@ func (p *Plugin) activateWithLock() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.Client = c
|
p.client = c
|
||||||
|
|
||||||
m := new(Manifest)
|
m := new(Manifest)
|
||||||
if err = p.Client.Call("Plugin.Activate", nil, m); err != nil {
|
if err = p.client.Call("Plugin.Activate", nil, m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +128,7 @@ func (p *Plugin) activateWithLock() error {
|
||||||
if !handled {
|
if !handled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handler(p.Name, p.Client)
|
handler(p.name, p.client)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue