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 { | ||||||
| 	if err != nil { | 		c, err := tlsconfig.Client(*tlsConfig) | ||||||
| 		return nil, err | 		if err != nil { | ||||||
|  | 			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…
	
	Add table
		Add a link
		
	
		Reference in a new issue