From fa3b61a28f55d84afbbb978785ce9632123d12fa Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 23 Nov 2016 17:29:21 -0800 Subject: [PATCH] refactor plugin install Signed-off-by: Victor Vieux --- api/server/router/plugin/backend.go | 3 +- api/server/router/plugin/plugin.go | 3 +- api/server/router/plugin/plugin_routes.go | 57 ++++---- client/plugin_inspect.go | 2 +- client/plugin_install.go | 33 +++-- plugin/backend_linux.go | 150 +++++++++++++++++----- plugin/backend_unsupported.go | 9 +- plugin/distribution/pull.go | 1 - plugin/v2/plugin.go | 47 ------- 9 files changed, 185 insertions(+), 120 deletions(-) diff --git a/api/server/router/plugin/backend.go b/api/server/router/plugin/backend.go index cc931fe411..fba42f3e81 100644 --- a/api/server/router/plugin/backend.go +++ b/api/server/router/plugin/backend.go @@ -16,7 +16,8 @@ type Backend interface { Inspect(name string) (enginetypes.Plugin, error) Remove(name string, config *enginetypes.PluginRmConfig) 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 CreateFromContext(ctx context.Context, tarCtx io.Reader, options *enginetypes.PluginCreateOptions) error } diff --git a/api/server/router/plugin/plugin.go b/api/server/router/plugin/plugin.go index 15bdcb3cd5..3f6ff566c8 100644 --- a/api/server/router/plugin/plugin.go +++ b/api/server/router/plugin/plugin.go @@ -25,7 +25,8 @@ func (r *pluginRouter) Routes() []router.Route { func (r *pluginRouter) initRoutes() { r.routes = []router.Route{ 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.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH? router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin), diff --git a/api/server/router/plugin/plugin_routes.go b/api/server/router/plugin/plugin_routes.go index ffa05dc984..6a2ba7dc4f 100644 --- a/api/server/router/plugin/plugin_routes.go +++ b/api/server/router/plugin/plugin_routes.go @@ -12,20 +12,17 @@ import ( "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{} - for k, v := range r.Header { + for k, v := range headers { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } // Get X-Registry-Auth - authEncoded := r.Header.Get("X-Registry-Auth") + authEncoded := headers.Get("X-Registry-Auth") authConfig := &types.AuthConfig{} if 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 { return err } 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 { if err := httputils.ParseForm(r); err != nil { 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 { return err } + //TODO: send progress bar w.WriteHeader(http.StatusNoContent) return nil } @@ -92,22 +119,8 @@ func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r 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) } diff --git a/client/plugin_inspect.go b/client/plugin_inspect.go index e9474b5a98..1fb40624ca 100644 --- a/client/plugin_inspect.go +++ b/client/plugin_inspect.go @@ -11,7 +11,7 @@ import ( // PluginInspectWithRaw inspects an existing plugin 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 { return nil, nil, err } diff --git a/client/plugin_install.go b/client/plugin_install.go index f73362ccd3..e7b67f2051 100644 --- a/client/plugin_install.go +++ b/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. query := url.Values{} 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 { newAuthHeader, privilegeErr := options.PrivilegeFunc() if privilegeErr != nil { ensureReaderClosed(resp) return privilegeErr } - resp, err = cli.tryPluginPull(ctx, query, newAuthHeader) + options.RegistryAuth = newAuthHeader + resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth) } if err != nil { ensureReaderClosed(resp) return err } - defer func() { - if err != nil { - delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil) - ensureReaderClosed(delResp) - } - }() - var privileges types.PluginPrivileges if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil { 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 err := cli.PluginSet(ctx, name, options.Args); err != nil { 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}) } -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.post(ctx, "/plugins/pull", query, nil, headers) + 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}} + return cli.post(ctx, "/plugins/pull", query, privileges, headers) } diff --git a/plugin/backend_linux.go b/plugin/backend_linux.go index 8c1a152d9b..80270b3eaf 100644 --- a/plugin/backend_linux.go +++ b/plugin/backend_linux.go @@ -5,12 +5,14 @@ package plugin import ( "bytes" "encoding/json" + "errors" "fmt" "io" "io/ioutil" "net/http" "os" "path/filepath" + "reflect" "regexp" "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) } -func (pm *Manager) pull(ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig, pluginID string) (types.PluginPrivileges, error) { - pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig) - if err != nil { - logrus.Debugf("error in distribution.Pull(): %v", err) - return nil, err - } - - if err := distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true); err != nil { - logrus.Debugf("error in distribution.WritePullData(): %v", 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 { - return nil, err - } - pm.pluginStore.Add(p) - - pm.pluginEventLogger(pluginID, ref.String(), "pull") - return p.ComputePrivileges(), 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) { +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, err + return nil, nil, err } name = ref.String() if p, _ := pm.pluginStore.GetByName(name); p != nil { logrus.Debug("plugin already exists") - return nil, fmt.Errorf("%s exists", name) + return nil, nil, fmt.Errorf("%s exists", name) + } + + pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig) + if err != nil { + logrus.Debugf("error in distribution.Pull(): %v", err) + return nil, nil, err + } + return ref, pd, nil +} + +func computePrivileges(pd distribution.PullData) (types.PluginPrivileges, error) { + config, err := pd.Config() + if err != nil { + return nil, err + } + + var c types.PluginConfig + if err := json.Unmarshal(config, &c); err != nil { + return nil, err + } + + 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 +} + +// 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 { + return nil, err + } + return computePrivileges(pd) +} + +// 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() pluginDir := filepath.Join(pm.libRoot, pluginID) if err := os.MkdirAll(pluginDir, 0755); err != nil { 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 } - return priv, nil + 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) + + pm.pluginEventLogger(pluginID, ref.String(), "pull") + + return nil } // List displays the list of plugins and associated metadata. diff --git a/plugin/backend_unsupported.go b/plugin/backend_unsupported.go index e54994fe75..0e07cd679a 100644 --- a/plugin/backend_unsupported.go +++ b/plugin/backend_unsupported.go @@ -28,11 +28,16 @@ func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) { 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 } +// 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. func (pm *Manager) List() ([]types.Plugin, error) { return nil, errNotSupported diff --git a/plugin/distribution/pull.go b/plugin/distribution/pull.go index 5694be0573..dba750f2a5 100644 --- a/plugin/distribution/pull.go +++ b/plugin/distribution/pull.go @@ -178,7 +178,6 @@ func WritePullData(pd PullData, dest string, extract bool) error { return err } logrus.Debugf("%#v", p) - if err := os.MkdirAll(dest, 0700); err != nil { return err } diff --git a/plugin/v2/plugin.go b/plugin/v2/plugin.go index 4679498ee6..7ea115cb39 100644 --- a/plugin/v2/plugin.go +++ b/plugin/v2/plugin.go @@ -216,53 +216,6 @@ next: 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. func (p *Plugin) IsEnabled() bool { p.RLock()