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
|
||||
func createTestPlugin(t *testing.T) *authorizationPlugin {
|
||||
plugin := &plugins.Plugin{Name: "authz"}
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
|
|
|
@ -35,7 +35,7 @@ func NewPlugins(names []string) []Plugin {
|
|||
|
||||
// authorizationPlugin is an internal adapter to docker plugin system
|
||||
type authorizationPlugin struct {
|
||||
plugin *plugins.Plugin
|
||||
plugin *plugins.Client
|
||||
name string
|
||||
once sync.Once
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error)
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ func (a *authorizationPlugin) AuthZResponse(authReq *Request) (*Response, error)
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,12 @@ func (a *authorizationPlugin) initPlugin() error {
|
|||
var err error
|
||||
a.once.Do(func() {
|
||||
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
|
||||
|
|
|
@ -20,14 +20,16 @@ const (
|
|||
)
|
||||
|
||||
// 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{}
|
||||
|
||||
c, err := tlsconfig.Client(tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if tlsConfig != nil {
|
||||
c, err := tlsconfig.Client(*tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.TLSClientConfig = c
|
||||
}
|
||||
tr.TLSClientConfig = c
|
||||
|
||||
u, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
|
|
|
@ -31,7 +31,7 @@ func teardownRemotePluginServer() {
|
|||
}
|
||||
|
||||
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)
|
||||
if err == nil {
|
||||
t.Fatal("Unexpected successful connection")
|
||||
|
@ -55,7 +55,7 @@ func TestEchoInputOutput(t *testing.T) {
|
|||
io.Copy(w, r.Body)
|
||||
})
|
||||
|
||||
c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
|
||||
c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true})
|
||||
var output Manifest
|
||||
err := c.Call("Test.Echo", m, &output)
|
||||
if err != nil {
|
||||
|
|
|
@ -64,7 +64,7 @@ func (l *localRegistry) Plugin(name string) (*Plugin, error) {
|
|||
|
||||
for _, p := range socketpaths {
|
||||
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 newLocalPlugin(name, addr), nil
|
||||
return NewLocalPlugin(name, addr), nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
p.Name = name
|
||||
p.name = name
|
||||
if len(p.TLSConfig.CAFile) == 0 {
|
||||
p.TLSConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ func TestFileSpecPlugin(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ func TestFileJSONSpecPlugin(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if plugin.Name != "example" {
|
||||
if plugin.name != "example" {
|
||||
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)
|
||||
}
|
||||
|
||||
if p.Name != "echo" {
|
||||
if p.name != "echo" {
|
||||
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.
|
||||
type Plugin struct {
|
||||
// Name of the plugin
|
||||
Name string `json:"-"`
|
||||
name string
|
||||
// Address of the plugin
|
||||
Addr string
|
||||
// TLS configuration of the plugin
|
||||
TLSConfig tlsconfig.Options
|
||||
TLSConfig *tlsconfig.Options
|
||||
// Client attached to the plugin
|
||||
Client *Client `json:"-"`
|
||||
client *Client
|
||||
// Manifest of the plugin (see above)
|
||||
Manifest *Manifest `json:"-"`
|
||||
|
||||
|
@ -73,11 +73,23 @@ type Plugin struct {
|
|||
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{
|
||||
Name: name,
|
||||
Addr: addr,
|
||||
TLSConfig: tlsconfig.Options{InsecureSkipVerify: true},
|
||||
name: name,
|
||||
Addr: addr,
|
||||
// TODO: change to nil
|
||||
TLSConfig: &tlsconfig.Options{InsecureSkipVerify: true},
|
||||
activateWait: sync.NewCond(&sync.Mutex{}),
|
||||
}
|
||||
}
|
||||
|
@ -102,10 +114,10 @@ func (p *Plugin) activateWithLock() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Client = c
|
||||
p.client = c
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -116,7 +128,7 @@ func (p *Plugin) activateWithLock() error {
|
|||
if !handled {
|
||||
continue
|
||||
}
|
||||
handler(p.Name, p.Client)
|
||||
handler(p.name, p.client)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue