Przeglądaj źródła

refactor plugin install

Signed-off-by: Victor Vieux <victorvieux@gmail.com>
Victor Vieux 8 lat temu
rodzic
commit
fa3b61a28f

+ 2 - 1
api/server/router/plugin/backend.go

@@ -16,7 +16,8 @@ type Backend interface {
 	Inspect(name string) (enginetypes.Plugin, error)
 	Inspect(name string) (enginetypes.Plugin, error)
 	Remove(name string, config *enginetypes.PluginRmConfig) error
 	Remove(name string, config *enginetypes.PluginRmConfig) error
 	Set(name string, args []string) error
 	Set(name string, args []string) error
-	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
+	Privileges(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
+	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, privileges enginetypes.PluginPrivileges) error
 	Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
 	Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
 	CreateFromContext(ctx context.Context, tarCtx io.Reader, options *enginetypes.PluginCreateOptions) error
 	CreateFromContext(ctx context.Context, tarCtx io.Reader, options *enginetypes.PluginCreateOptions) error
 }
 }

+ 2 - 1
api/server/router/plugin/plugin.go

@@ -25,7 +25,8 @@ func (r *pluginRouter) Routes() []router.Route {
 func (r *pluginRouter) initRoutes() {
 func (r *pluginRouter) initRoutes() {
 	r.routes = []router.Route{
 	r.routes = []router.Route{
 		router.NewGetRoute("/plugins", r.listPlugins),
 		router.NewGetRoute("/plugins", r.listPlugins),
-		router.NewGetRoute("/plugins/{name:.*}", r.inspectPlugin),
+		router.NewGetRoute("/plugins/{name:.*}/json", r.inspectPlugin),
+		router.NewGetRoute("/plugins/privileges", r.getPrivileges),
 		router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin),
 		router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin),
 		router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH?
 		router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH?
 		router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin),
 		router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin),

+ 35 - 22
api/server/router/plugin/plugin_routes.go

@@ -12,20 +12,17 @@ import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
-func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	if err := httputils.ParseForm(r); err != nil {
-		return err
-	}
+func parseHeaders(headers http.Header) (map[string][]string, *types.AuthConfig) {
 
 
 	metaHeaders := map[string][]string{}
 	metaHeaders := map[string][]string{}
-	for k, v := range r.Header {
+	for k, v := range headers {
 		if strings.HasPrefix(k, "X-Meta-") {
 		if strings.HasPrefix(k, "X-Meta-") {
 			metaHeaders[k] = v
 			metaHeaders[k] = v
 		}
 		}
 	}
 	}
 
 
 	// Get X-Registry-Auth
 	// Get X-Registry-Auth
-	authEncoded := r.Header.Get("X-Registry-Auth")
+	authEncoded := headers.Get("X-Registry-Auth")
 	authConfig := &types.AuthConfig{}
 	authConfig := &types.AuthConfig{}
 	if authEncoded != "" {
 	if authEncoded != "" {
 		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
 		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
@@ -34,13 +31,42 @@ func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r
 		}
 		}
 	}
 	}
 
 
-	privileges, err := pr.backend.Pull(r.FormValue("name"), metaHeaders, authConfig)
+	return metaHeaders, authConfig
+}
+
+func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	metaHeaders, authConfig := parseHeaders(r.Header)
+
+	privileges, err := pr.backend.Privileges(r.FormValue("name"), metaHeaders, authConfig)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	return httputils.WriteJSON(w, http.StatusOK, privileges)
 	return httputils.WriteJSON(w, http.StatusOK, privileges)
 }
 }
 
 
+func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	var privileges types.PluginPrivileges
+	if err := json.NewDecoder(r.Body).Decode(&privileges); err != nil {
+		return err
+	}
+
+	metaHeaders, authConfig := parseHeaders(r.Header)
+
+	if err := pr.backend.Pull(r.FormValue("name"), metaHeaders, authConfig, privileges); err != nil {
+		return err
+	}
+	w.WriteHeader(http.StatusCreated)
+	return nil
+}
+
 func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
@@ -52,6 +78,7 @@ func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter,
 	if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
 	if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
 		return err
 		return err
 	}
 	}
+	//TODO: send progress bar
 	w.WriteHeader(http.StatusNoContent)
 	w.WriteHeader(http.StatusNoContent)
 	return nil
 	return nil
 }
 }
@@ -92,22 +119,8 @@ func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r
 		return err
 		return err
 	}
 	}
 
 
-	metaHeaders := map[string][]string{}
-	for k, v := range r.Header {
-		if strings.HasPrefix(k, "X-Meta-") {
-			metaHeaders[k] = v
-		}
-	}
+	metaHeaders, authConfig := parseHeaders(r.Header)
 
 
-	// Get X-Registry-Auth
-	authEncoded := r.Header.Get("X-Registry-Auth")
-	authConfig := &types.AuthConfig{}
-	if authEncoded != "" {
-		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
-		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
-			authConfig = &types.AuthConfig{}
-		}
-	}
 	return pr.backend.Push(vars["name"], metaHeaders, authConfig)
 	return pr.backend.Push(vars["name"], metaHeaders, authConfig)
 }
 }
 
 

+ 1 - 1
client/plugin_inspect.go

@@ -11,7 +11,7 @@ import (
 
 
 // PluginInspectWithRaw inspects an existing plugin
 // PluginInspectWithRaw inspects an existing plugin
 func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
 func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
-	resp, err := cli.get(ctx, "/plugins/"+name, nil, nil)
+	resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}

+ 22 - 11
client/plugin_install.go

@@ -14,27 +14,21 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
 	// FIXME(vdemeester) name is a ref, we might want to parse/validate it here.
 	// FIXME(vdemeester) name is a ref, we might want to parse/validate it here.
 	query := url.Values{}
 	query := url.Values{}
 	query.Set("name", name)
 	query.Set("name", name)
-	resp, err := cli.tryPluginPull(ctx, query, options.RegistryAuth)
+	resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
 	if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
 	if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
 		if privilegeErr != nil {
 		if privilegeErr != nil {
 			ensureReaderClosed(resp)
 			ensureReaderClosed(resp)
 			return privilegeErr
 			return privilegeErr
 		}
 		}
-		resp, err = cli.tryPluginPull(ctx, query, newAuthHeader)
+		options.RegistryAuth = newAuthHeader
+		resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
 	}
 	}
 	if err != nil {
 	if err != nil {
 		ensureReaderClosed(resp)
 		ensureReaderClosed(resp)
 		return err
 		return err
 	}
 	}
 
 
-	defer func() {
-		if err != nil {
-			delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil)
-			ensureReaderClosed(delResp)
-		}
-	}()
-
 	var privileges types.PluginPrivileges
 	var privileges types.PluginPrivileges
 	if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil {
 	if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil {
 		ensureReaderClosed(resp)
 		ensureReaderClosed(resp)
@@ -52,6 +46,18 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
 		}
 		}
 	}
 	}
 
 
+	_, err = cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth)
+	if err != nil {
+		return err
+	}
+
+	defer func() {
+		if err != nil {
+			delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil)
+			ensureReaderClosed(delResp)
+		}
+	}()
+
 	if len(options.Args) > 0 {
 	if len(options.Args) > 0 {
 		if err := cli.PluginSet(ctx, name, options.Args); err != nil {
 		if err := cli.PluginSet(ctx, name, options.Args); err != nil {
 			return err
 			return err
@@ -65,7 +71,12 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
 	return cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
 	return cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
 }
 }
 
 
-func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
+func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
+	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
+	return cli.get(ctx, "/plugins/privileges", query, headers)
+}
+
+func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) {
 	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
 	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
-	return cli.post(ctx, "/plugins/pull", query, nil, headers)
+	return cli.post(ctx, "/plugins/pull", query, privileges, headers)
 }
 }

+ 107 - 25
plugin/backend_linux.go

@@ -5,12 +5,14 @@ package plugin
 import (
 import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
+	"reflect"
 	"regexp"
 	"regexp"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
@@ -87,59 +89,139 @@ func (pm *Manager) Inspect(refOrID string) (tp types.Plugin, err error) {
 	return tp, fmt.Errorf("no plugin name or ID associated with %q", refOrID)
 	return tp, fmt.Errorf("no plugin name or ID associated with %q", refOrID)
 }
 }
 
 
-func (pm *Manager) pull(ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig, pluginID string) (types.PluginPrivileges, error) {
+func (pm *Manager) pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (reference.Named, distribution.PullData, error) {
+	ref, err := distribution.GetRef(name)
+	if err != nil {
+		logrus.Debugf("error in distribution.GetRef: %v", err)
+		return nil, nil, err
+	}
+	name = ref.String()
+
+	if p, _ := pm.pluginStore.GetByName(name); p != nil {
+		logrus.Debug("plugin already exists")
+		return nil, nil, fmt.Errorf("%s exists", name)
+	}
+
 	pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig)
 	pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig)
 	if err != nil {
 	if err != nil {
 		logrus.Debugf("error in distribution.Pull(): %v", err)
 		logrus.Debugf("error in distribution.Pull(): %v", err)
-		return nil, err
+		return nil, nil, err
 	}
 	}
+	return ref, pd, nil
+}
 
 
-	if err := distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true); err != nil {
-		logrus.Debugf("error in distribution.WritePullData(): %v", err)
+func computePrivileges(pd distribution.PullData) (types.PluginPrivileges, error) {
+	config, err := pd.Config()
+	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	tag := distribution.GetTag(ref)
-	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag)
-	if err := p.InitPlugin(); err != nil {
+	var c types.PluginConfig
+	if err := json.Unmarshal(config, &c); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	pm.pluginStore.Add(p)
 
 
-	pm.pluginEventLogger(pluginID, ref.String(), "pull")
-	return p.ComputePrivileges(), nil
+	var privileges types.PluginPrivileges
+	if c.Network.Type != "null" && c.Network.Type != "bridge" {
+		privileges = append(privileges, types.PluginPrivilege{
+			Name:        "network",
+			Description: "permissions to access a network",
+			Value:       []string{c.Network.Type},
+		})
+	}
+	for _, mount := range c.Mounts {
+		if mount.Source != nil {
+			privileges = append(privileges, types.PluginPrivilege{
+				Name:        "mount",
+				Description: "host path to mount",
+				Value:       []string{*mount.Source},
+			})
+		}
+	}
+	for _, device := range c.Linux.Devices {
+		if device.Path != nil {
+			privileges = append(privileges, types.PluginPrivilege{
+				Name:        "device",
+				Description: "host device to access",
+				Value:       []string{*device.Path},
+			})
+		}
+	}
+	if c.Linux.DeviceCreation {
+		privileges = append(privileges, types.PluginPrivilege{
+			Name:        "device-creation",
+			Description: "allow creating devices inside plugin",
+			Value:       []string{"true"},
+		})
+	}
+	if len(c.Linux.Capabilities) > 0 {
+		privileges = append(privileges, types.PluginPrivilege{
+			Name:        "capabilities",
+			Description: "list of additional capabilities required",
+			Value:       c.Linux.Capabilities,
+		})
+	}
+
+	return privileges, nil
 }
 }
 
 
-// Pull pulls a plugin and computes the privileges required to install it.
-func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
-	ref, err := distribution.GetRef(name)
+// Privileges pulls a plugin config and computes the privileges required to install it.
+func (pm *Manager) Privileges(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
+	_, pd, err := pm.pull(name, metaHeader, authConfig)
 	if err != nil {
 	if err != nil {
-		logrus.Debugf("error in distribution.GetRef: %v", err)
 		return nil, err
 		return nil, err
 	}
 	}
-	name = ref.String()
+	return computePrivileges(pd)
+}
 
 
-	if p, _ := pm.pluginStore.GetByName(name); p != nil {
-		logrus.Debug("plugin already exists")
-		return nil, fmt.Errorf("%s exists", name)
+// Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
+func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges) (err error) {
+	ref, pd, err := pm.pull(name, metaHeader, authConfig)
+	if err != nil {
+		return err
+	}
+
+	requiredPrivileges, err := computePrivileges(pd)
+	if err != nil {
+		return err
+	}
+
+	if !reflect.DeepEqual(privileges, requiredPrivileges) {
+		return errors.New("incorrect privileges")
 	}
 	}
 
 
 	pluginID := stringid.GenerateNonCryptoID()
 	pluginID := stringid.GenerateNonCryptoID()
 	pluginDir := filepath.Join(pm.libRoot, pluginID)
 	pluginDir := filepath.Join(pm.libRoot, pluginID)
 	if err := os.MkdirAll(pluginDir, 0755); err != nil {
 	if err := os.MkdirAll(pluginDir, 0755); err != nil {
 		logrus.Debugf("error in MkdirAll: %v", err)
 		logrus.Debugf("error in MkdirAll: %v", err)
-		return nil, err
+		return err
 	}
 	}
 
 
-	priv, err := pm.pull(ref, metaHeader, authConfig, pluginID)
-	if err != nil {
-		if err := os.RemoveAll(pluginDir); err != nil {
-			logrus.Warnf("unable to remove %q from failed plugin pull: %v", pluginDir, err)
+	defer func() {
+		if err != nil {
+			if delErr := os.RemoveAll(pluginDir); delErr != nil {
+				logrus.Warnf("unable to remove %q from failed plugin pull: %v", pluginDir, delErr)
+			}
 		}
 		}
-		return nil, err
+	}()
+
+	err = distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true)
+	if err != nil {
+		logrus.Debugf("error in distribution.WritePullData(): %v", err)
+		return err
+	}
+
+	tag := distribution.GetTag(ref)
+	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag)
+	err = p.InitPlugin()
+	if err != nil {
+		return err
 	}
 	}
+	pm.pluginStore.Add(p)
 
 
-	return priv, nil
+	pm.pluginEventLogger(pluginID, ref.String(), "pull")
+
+	return nil
 }
 }
 
 
 // List displays the list of plugins and associated metadata.
 // List displays the list of plugins and associated metadata.

+ 7 - 2
plugin/backend_unsupported.go

@@ -28,11 +28,16 @@ func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
 	return tp, errNotSupported
 	return tp, errNotSupported
 }
 }
 
 
-// Pull pulls a plugin and computes the privileges required to install it.
-func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
+// Privileges pulls a plugin config and computes the privileges required to install it.
+func (pm *Manager) Privileges(name string, metaHeaders http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
 	return nil, errNotSupported
 	return nil, errNotSupported
 }
 }
 
 
+// Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
+func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges) error {
+	return errNotSupported
+}
+
 // List displays the list of plugins and associated metadata.
 // List displays the list of plugins and associated metadata.
 func (pm *Manager) List() ([]types.Plugin, error) {
 func (pm *Manager) List() ([]types.Plugin, error) {
 	return nil, errNotSupported
 	return nil, errNotSupported

+ 0 - 1
plugin/distribution/pull.go

@@ -178,7 +178,6 @@ func WritePullData(pd PullData, dest string, extract bool) error {
 		return err
 		return err
 	}
 	}
 	logrus.Debugf("%#v", p)
 	logrus.Debugf("%#v", p)
-
 	if err := os.MkdirAll(dest, 0700); err != nil {
 	if err := os.MkdirAll(dest, 0700); err != nil {
 		return err
 		return err
 	}
 	}

+ 0 - 47
plugin/v2/plugin.go

@@ -216,53 +216,6 @@ next:
 	return p.writeSettings()
 	return p.writeSettings()
 }
 }
 
 
-// ComputePrivileges takes the config file and computes the list of access necessary
-// for the plugin on the host.
-func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
-	c := p.PluginObj.Config
-	var privileges types.PluginPrivileges
-	if c.Network.Type != "null" && c.Network.Type != "bridge" {
-		privileges = append(privileges, types.PluginPrivilege{
-			Name:        "network",
-			Description: "permissions to access a network",
-			Value:       []string{c.Network.Type},
-		})
-	}
-	for _, mount := range c.Mounts {
-		if mount.Source != nil {
-			privileges = append(privileges, types.PluginPrivilege{
-				Name:        "mount",
-				Description: "host path to mount",
-				Value:       []string{*mount.Source},
-			})
-		}
-	}
-	for _, device := range c.Linux.Devices {
-		if device.Path != nil {
-			privileges = append(privileges, types.PluginPrivilege{
-				Name:        "device",
-				Description: "host device to access",
-				Value:       []string{*device.Path},
-			})
-		}
-	}
-	if c.Linux.DeviceCreation {
-		privileges = append(privileges, types.PluginPrivilege{
-			Name:        "device-creation",
-			Description: "allow creating devices inside plugin",
-			Value:       []string{"true"},
-		})
-	}
-	if len(c.Linux.Capabilities) > 0 {
-		privileges = append(privileges, types.PluginPrivilege{
-			Name:        "capabilities",
-			Description: "list of additional capabilities required",
-			Value:       c.Linux.Capabilities,
-		})
-	}
-	return privileges
-}
-
 // IsEnabled returns the active state of the plugin.
 // IsEnabled returns the active state of the plugin.
 func (p *Plugin) IsEnabled() bool {
 func (p *Plugin) IsEnabled() bool {
 	p.RLock()
 	p.RLock()