refactor plugin install
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
(cherry picked from commit fa3b61a28f
)
Signed-off-by: Victor Vieux <vieux@docker.com>
This commit is contained in:
parent
696130c949
commit
728296b9ea
9 changed files with 185 additions and 120 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,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 {
|
||||
if resp.statusCode == http.StatusNotFound {
|
||||
return nil, nil, pluginNotFoundError{name}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 such 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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue