Browse code

refactor plugin install

Signed-off-by: Victor Vieux <victorvieux@gmail.com>

Victor Vieux authored on 2016/11/24 10:29:21
Showing 9 changed files
... ...
@@ -16,7 +16,8 @@ type Backend interface {
16 16
 	Inspect(name string) (enginetypes.Plugin, error)
17 17
 	Remove(name string, config *enginetypes.PluginRmConfig) error
18 18
 	Set(name string, args []string) error
19
-	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
19
+	Privileges(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
20
+	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, privileges enginetypes.PluginPrivileges) error
20 21
 	Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
21 22
 	CreateFromContext(ctx context.Context, tarCtx io.Reader, options *enginetypes.PluginCreateOptions) error
22 23
 }
... ...
@@ -25,7 +25,8 @@ func (r *pluginRouter) Routes() []router.Route {
25 25
 func (r *pluginRouter) initRoutes() {
26 26
 	r.routes = []router.Route{
27 27
 		router.NewGetRoute("/plugins", r.listPlugins),
28
-		router.NewGetRoute("/plugins/{name:.*}", r.inspectPlugin),
28
+		router.NewGetRoute("/plugins/{name:.*}/json", r.inspectPlugin),
29
+		router.NewGetRoute("/plugins/privileges", r.getPrivileges),
29 30
 		router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin),
30 31
 		router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH?
31 32
 		router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin),
... ...
@@ -12,20 +12,17 @@ import (
12 12
 	"golang.org/x/net/context"
13 13
 )
14 14
 
15
-func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
16
-	if err := httputils.ParseForm(r); err != nil {
17
-		return err
18
-	}
15
+func parseHeaders(headers http.Header) (map[string][]string, *types.AuthConfig) {
19 16
 
20 17
 	metaHeaders := map[string][]string{}
21
-	for k, v := range r.Header {
18
+	for k, v := range headers {
22 19
 		if strings.HasPrefix(k, "X-Meta-") {
23 20
 			metaHeaders[k] = v
24 21
 		}
25 22
 	}
26 23
 
27 24
 	// Get X-Registry-Auth
28
-	authEncoded := r.Header.Get("X-Registry-Auth")
25
+	authEncoded := headers.Get("X-Registry-Auth")
29 26
 	authConfig := &types.AuthConfig{}
30 27
 	if authEncoded != "" {
31 28
 		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
... ...
@@ -34,13 +31,42 @@ func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r
34 34
 		}
35 35
 	}
36 36
 
37
-	privileges, err := pr.backend.Pull(r.FormValue("name"), metaHeaders, authConfig)
37
+	return metaHeaders, authConfig
38
+}
39
+
40
+func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
41
+	if err := httputils.ParseForm(r); err != nil {
42
+		return err
43
+	}
44
+
45
+	metaHeaders, authConfig := parseHeaders(r.Header)
46
+
47
+	privileges, err := pr.backend.Privileges(r.FormValue("name"), metaHeaders, authConfig)
38 48
 	if err != nil {
39 49
 		return err
40 50
 	}
41 51
 	return httputils.WriteJSON(w, http.StatusOK, privileges)
42 52
 }
43 53
 
54
+func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
55
+	if err := httputils.ParseForm(r); err != nil {
56
+		return err
57
+	}
58
+
59
+	var privileges types.PluginPrivileges
60
+	if err := json.NewDecoder(r.Body).Decode(&privileges); err != nil {
61
+		return err
62
+	}
63
+
64
+	metaHeaders, authConfig := parseHeaders(r.Header)
65
+
66
+	if err := pr.backend.Pull(r.FormValue("name"), metaHeaders, authConfig, privileges); err != nil {
67
+		return err
68
+	}
69
+	w.WriteHeader(http.StatusCreated)
70
+	return nil
71
+}
72
+
44 73
 func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
45 74
 	if err := httputils.ParseForm(r); err != nil {
46 75
 		return err
... ...
@@ -52,6 +78,7 @@ func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter,
52 52
 	if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
53 53
 		return err
54 54
 	}
55
+	//TODO: send progress bar
55 56
 	w.WriteHeader(http.StatusNoContent)
56 57
 	return nil
57 58
 }
... ...
@@ -92,22 +119,8 @@ func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r
92 92
 		return err
93 93
 	}
94 94
 
95
-	metaHeaders := map[string][]string{}
96
-	for k, v := range r.Header {
97
-		if strings.HasPrefix(k, "X-Meta-") {
98
-			metaHeaders[k] = v
99
-		}
100
-	}
95
+	metaHeaders, authConfig := parseHeaders(r.Header)
101 96
 
102
-	// Get X-Registry-Auth
103
-	authEncoded := r.Header.Get("X-Registry-Auth")
104
-	authConfig := &types.AuthConfig{}
105
-	if authEncoded != "" {
106
-		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
107
-		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
108
-			authConfig = &types.AuthConfig{}
109
-		}
110
-	}
111 97
 	return pr.backend.Push(vars["name"], metaHeaders, authConfig)
112 98
 }
113 99
 
... ...
@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 // PluginInspectWithRaw inspects an existing plugin
13 13
 func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
14
-	resp, err := cli.get(ctx, "/plugins/"+name, nil, nil)
14
+	resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
15 15
 	if err != nil {
16 16
 		return nil, nil, err
17 17
 	}
... ...
@@ -14,27 +14,21 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
14 14
 	// FIXME(vdemeester) name is a ref, we might want to parse/validate it here.
15 15
 	query := url.Values{}
16 16
 	query.Set("name", name)
17
-	resp, err := cli.tryPluginPull(ctx, query, options.RegistryAuth)
17
+	resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
18 18
 	if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
19 19
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
20 20
 		if privilegeErr != nil {
21 21
 			ensureReaderClosed(resp)
22 22
 			return privilegeErr
23 23
 		}
24
-		resp, err = cli.tryPluginPull(ctx, query, newAuthHeader)
24
+		options.RegistryAuth = newAuthHeader
25
+		resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
25 26
 	}
26 27
 	if err != nil {
27 28
 		ensureReaderClosed(resp)
28 29
 		return err
29 30
 	}
30 31
 
31
-	defer func() {
32
-		if err != nil {
33
-			delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil)
34
-			ensureReaderClosed(delResp)
35
-		}
36
-	}()
37
-
38 32
 	var privileges types.PluginPrivileges
39 33
 	if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil {
40 34
 		ensureReaderClosed(resp)
... ...
@@ -52,6 +46,18 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
52 52
 		}
53 53
 	}
54 54
 
55
+	_, err = cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth)
56
+	if err != nil {
57
+		return err
58
+	}
59
+
60
+	defer func() {
61
+		if err != nil {
62
+			delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil)
63
+			ensureReaderClosed(delResp)
64
+		}
65
+	}()
66
+
55 67
 	if len(options.Args) > 0 {
56 68
 		if err := cli.PluginSet(ctx, name, options.Args); err != nil {
57 69
 			return err
... ...
@@ -65,7 +71,12 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
65 65
 	return cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
66 66
 }
67 67
 
68
-func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
68
+func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
69
+	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
70
+	return cli.get(ctx, "/plugins/privileges", query, headers)
71
+}
72
+
73
+func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) {
69 74
 	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
70
-	return cli.post(ctx, "/plugins/pull", query, nil, headers)
75
+	return cli.post(ctx, "/plugins/pull", query, privileges, headers)
71 76
 }
... ...
@@ -5,12 +5,14 @@ package plugin
5 5
 import (
6 6
 	"bytes"
7 7
 	"encoding/json"
8
+	"errors"
8 9
 	"fmt"
9 10
 	"io"
10 11
 	"io/ioutil"
11 12
 	"net/http"
12 13
 	"os"
13 14
 	"path/filepath"
15
+	"reflect"
14 16
 	"regexp"
15 17
 
16 18
 	"github.com/Sirupsen/logrus"
... ...
@@ -87,59 +89,139 @@ func (pm *Manager) Inspect(refOrID string) (tp types.Plugin, err error) {
87 87
 	return tp, fmt.Errorf("no plugin name or ID associated with %q", refOrID)
88 88
 }
89 89
 
90
-func (pm *Manager) pull(ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig, pluginID string) (types.PluginPrivileges, error) {
90
+func (pm *Manager) pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (reference.Named, distribution.PullData, error) {
91
+	ref, err := distribution.GetRef(name)
92
+	if err != nil {
93
+		logrus.Debugf("error in distribution.GetRef: %v", err)
94
+		return nil, nil, err
95
+	}
96
+	name = ref.String()
97
+
98
+	if p, _ := pm.pluginStore.GetByName(name); p != nil {
99
+		logrus.Debug("plugin already exists")
100
+		return nil, nil, fmt.Errorf("%s exists", name)
101
+	}
102
+
91 103
 	pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig)
92 104
 	if err != nil {
93 105
 		logrus.Debugf("error in distribution.Pull(): %v", err)
94
-		return nil, err
106
+		return nil, nil, err
95 107
 	}
108
+	return ref, pd, nil
109
+}
96 110
 
97
-	if err := distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true); err != nil {
98
-		logrus.Debugf("error in distribution.WritePullData(): %v", err)
111
+func computePrivileges(pd distribution.PullData) (types.PluginPrivileges, error) {
112
+	config, err := pd.Config()
113
+	if err != nil {
99 114
 		return nil, err
100 115
 	}
101 116
 
102
-	tag := distribution.GetTag(ref)
103
-	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag)
104
-	if err := p.InitPlugin(); err != nil {
117
+	var c types.PluginConfig
118
+	if err := json.Unmarshal(config, &c); err != nil {
105 119
 		return nil, err
106 120
 	}
107
-	pm.pluginStore.Add(p)
108 121
 
109
-	pm.pluginEventLogger(pluginID, ref.String(), "pull")
110
-	return p.ComputePrivileges(), nil
122
+	var privileges types.PluginPrivileges
123
+	if c.Network.Type != "null" && c.Network.Type != "bridge" {
124
+		privileges = append(privileges, types.PluginPrivilege{
125
+			Name:        "network",
126
+			Description: "permissions to access a network",
127
+			Value:       []string{c.Network.Type},
128
+		})
129
+	}
130
+	for _, mount := range c.Mounts {
131
+		if mount.Source != nil {
132
+			privileges = append(privileges, types.PluginPrivilege{
133
+				Name:        "mount",
134
+				Description: "host path to mount",
135
+				Value:       []string{*mount.Source},
136
+			})
137
+		}
138
+	}
139
+	for _, device := range c.Linux.Devices {
140
+		if device.Path != nil {
141
+			privileges = append(privileges, types.PluginPrivilege{
142
+				Name:        "device",
143
+				Description: "host device to access",
144
+				Value:       []string{*device.Path},
145
+			})
146
+		}
147
+	}
148
+	if c.Linux.DeviceCreation {
149
+		privileges = append(privileges, types.PluginPrivilege{
150
+			Name:        "device-creation",
151
+			Description: "allow creating devices inside plugin",
152
+			Value:       []string{"true"},
153
+		})
154
+	}
155
+	if len(c.Linux.Capabilities) > 0 {
156
+		privileges = append(privileges, types.PluginPrivilege{
157
+			Name:        "capabilities",
158
+			Description: "list of additional capabilities required",
159
+			Value:       c.Linux.Capabilities,
160
+		})
161
+	}
162
+
163
+	return privileges, nil
111 164
 }
112 165
 
113
-// Pull pulls a plugin and computes the privileges required to install it.
114
-func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
115
-	ref, err := distribution.GetRef(name)
166
+// Privileges pulls a plugin config and computes the privileges required to install it.
167
+func (pm *Manager) Privileges(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
168
+	_, pd, err := pm.pull(name, metaHeader, authConfig)
116 169
 	if err != nil {
117
-		logrus.Debugf("error in distribution.GetRef: %v", err)
118 170
 		return nil, err
119 171
 	}
120
-	name = ref.String()
172
+	return computePrivileges(pd)
173
+}
121 174
 
122
-	if p, _ := pm.pluginStore.GetByName(name); p != nil {
123
-		logrus.Debug("plugin already exists")
124
-		return nil, fmt.Errorf("%s exists", name)
175
+// Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
176
+func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges) (err error) {
177
+	ref, pd, err := pm.pull(name, metaHeader, authConfig)
178
+	if err != nil {
179
+		return err
180
+	}
181
+
182
+	requiredPrivileges, err := computePrivileges(pd)
183
+	if err != nil {
184
+		return err
185
+	}
186
+
187
+	if !reflect.DeepEqual(privileges, requiredPrivileges) {
188
+		return errors.New("incorrect privileges")
125 189
 	}
126 190
 
127 191
 	pluginID := stringid.GenerateNonCryptoID()
128 192
 	pluginDir := filepath.Join(pm.libRoot, pluginID)
129 193
 	if err := os.MkdirAll(pluginDir, 0755); err != nil {
130 194
 		logrus.Debugf("error in MkdirAll: %v", err)
131
-		return nil, err
195
+		return err
132 196
 	}
133 197
 
134
-	priv, err := pm.pull(ref, metaHeader, authConfig, pluginID)
135
-	if err != nil {
136
-		if err := os.RemoveAll(pluginDir); err != nil {
137
-			logrus.Warnf("unable to remove %q from failed plugin pull: %v", pluginDir, err)
198
+	defer func() {
199
+		if err != nil {
200
+			if delErr := os.RemoveAll(pluginDir); delErr != nil {
201
+				logrus.Warnf("unable to remove %q from failed plugin pull: %v", pluginDir, delErr)
202
+			}
138 203
 		}
139
-		return nil, err
204
+	}()
205
+
206
+	err = distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true)
207
+	if err != nil {
208
+		logrus.Debugf("error in distribution.WritePullData(): %v", err)
209
+		return err
210
+	}
211
+
212
+	tag := distribution.GetTag(ref)
213
+	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag)
214
+	err = p.InitPlugin()
215
+	if err != nil {
216
+		return err
140 217
 	}
218
+	pm.pluginStore.Add(p)
141 219
 
142
-	return priv, nil
220
+	pm.pluginEventLogger(pluginID, ref.String(), "pull")
221
+
222
+	return nil
143 223
 }
144 224
 
145 225
 // List displays the list of plugins and associated metadata.
... ...
@@ -28,11 +28,16 @@ func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
28 28
 	return tp, errNotSupported
29 29
 }
30 30
 
31
-// Pull pulls a plugin and computes the privileges required to install it.
32
-func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
31
+// Privileges pulls a plugin config and computes the privileges required to install it.
32
+func (pm *Manager) Privileges(name string, metaHeaders http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
33 33
 	return nil, errNotSupported
34 34
 }
35 35
 
36
+// Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
37
+func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges) error {
38
+	return errNotSupported
39
+}
40
+
36 41
 // List displays the list of plugins and associated metadata.
37 42
 func (pm *Manager) List() ([]types.Plugin, error) {
38 43
 	return nil, errNotSupported
... ...
@@ -178,7 +178,6 @@ func WritePullData(pd PullData, dest string, extract bool) error {
178 178
 		return err
179 179
 	}
180 180
 	logrus.Debugf("%#v", p)
181
-
182 181
 	if err := os.MkdirAll(dest, 0700); err != nil {
183 182
 		return err
184 183
 	}
... ...
@@ -216,53 +216,6 @@ next:
216 216
 	return p.writeSettings()
217 217
 }
218 218
 
219
-// ComputePrivileges takes the config file and computes the list of access necessary
220
-// for the plugin on the host.
221
-func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
222
-	c := p.PluginObj.Config
223
-	var privileges types.PluginPrivileges
224
-	if c.Network.Type != "null" && c.Network.Type != "bridge" {
225
-		privileges = append(privileges, types.PluginPrivilege{
226
-			Name:        "network",
227
-			Description: "permissions to access a network",
228
-			Value:       []string{c.Network.Type},
229
-		})
230
-	}
231
-	for _, mount := range c.Mounts {
232
-		if mount.Source != nil {
233
-			privileges = append(privileges, types.PluginPrivilege{
234
-				Name:        "mount",
235
-				Description: "host path to mount",
236
-				Value:       []string{*mount.Source},
237
-			})
238
-		}
239
-	}
240
-	for _, device := range c.Linux.Devices {
241
-		if device.Path != nil {
242
-			privileges = append(privileges, types.PluginPrivilege{
243
-				Name:        "device",
244
-				Description: "host device to access",
245
-				Value:       []string{*device.Path},
246
-			})
247
-		}
248
-	}
249
-	if c.Linux.DeviceCreation {
250
-		privileges = append(privileges, types.PluginPrivilege{
251
-			Name:        "device-creation",
252
-			Description: "allow creating devices inside plugin",
253
-			Value:       []string{"true"},
254
-		})
255
-	}
256
-	if len(c.Linux.Capabilities) > 0 {
257
-		privileges = append(privileges, types.PluginPrivilege{
258
-			Name:        "capabilities",
259
-			Description: "list of additional capabilities required",
260
-			Value:       c.Linux.Capabilities,
261
-		})
262
-	}
263
-	return privileges
264
-}
265
-
266 219
 // IsEnabled returns the active state of the plugin.
267 220
 func (p *Plugin) IsEnabled() bool {
268 221
 	p.RLock()