1package plugin // import "github.com/docker/docker/api/server/router/plugin"
2
3import (
4	"context"
5	"encoding/base64"
6	"encoding/json"
7	"io"
8	"net/http"
9	"strconv"
10	"strings"
11
12	"github.com/docker/distribution/reference"
13	"github.com/docker/docker/api/server/httputils"
14	"github.com/docker/docker/api/types"
15	"github.com/docker/docker/api/types/filters"
16	"github.com/docker/docker/errdefs"
17	"github.com/docker/docker/pkg/ioutils"
18	"github.com/docker/docker/pkg/streamformatter"
19	"github.com/pkg/errors"
20)
21
22func parseHeaders(headers http.Header) (map[string][]string, *types.AuthConfig) {
23
24	metaHeaders := map[string][]string{}
25	for k, v := range headers {
26		if strings.HasPrefix(k, "X-Meta-") {
27			metaHeaders[k] = v
28		}
29	}
30
31	// Get X-Registry-Auth
32	authEncoded := headers.Get("X-Registry-Auth")
33	authConfig := &types.AuthConfig{}
34	if authEncoded != "" {
35		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
36		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
37			authConfig = &types.AuthConfig{}
38		}
39	}
40
41	return metaHeaders, authConfig
42}
43
44// parseRemoteRef parses the remote reference into a reference.Named
45// returning the tag associated with the reference. In the case the
46// given reference string includes both digest and tag, the returned
47// reference will have the digest without the tag, but the tag will
48// be returned.
49func parseRemoteRef(remote string) (reference.Named, string, error) {
50	// Parse remote reference, supporting remotes with name and tag
51	remoteRef, err := reference.ParseNormalizedNamed(remote)
52	if err != nil {
53		return nil, "", err
54	}
55
56	type canonicalWithTag interface {
57		reference.Canonical
58		Tag() string
59	}
60
61	if canonical, ok := remoteRef.(canonicalWithTag); ok {
62		remoteRef, err = reference.WithDigest(reference.TrimNamed(remoteRef), canonical.Digest())
63		if err != nil {
64			return nil, "", err
65		}
66		return remoteRef, canonical.Tag(), nil
67	}
68
69	remoteRef = reference.TagNameOnly(remoteRef)
70
71	return remoteRef, "", nil
72}
73
74func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
75	if err := httputils.ParseForm(r); err != nil {
76		return err
77	}
78
79	metaHeaders, authConfig := parseHeaders(r.Header)
80
81	ref, _, err := parseRemoteRef(r.FormValue("remote"))
82	if err != nil {
83		return err
84	}
85
86	privileges, err := pr.backend.Privileges(ctx, ref, metaHeaders, authConfig)
87	if err != nil {
88		return err
89	}
90	return httputils.WriteJSON(w, http.StatusOK, privileges)
91}
92
93func (pr *pluginRouter) upgradePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
94	if err := httputils.ParseForm(r); err != nil {
95		return errors.Wrap(err, "failed to parse form")
96	}
97
98	var privileges types.PluginPrivileges
99	dec := json.NewDecoder(r.Body)
100	if err := dec.Decode(&privileges); err != nil {
101		return errors.Wrap(err, "failed to parse privileges")
102	}
103	if dec.More() {
104		return errors.New("invalid privileges")
105	}
106
107	metaHeaders, authConfig := parseHeaders(r.Header)
108	ref, tag, err := parseRemoteRef(r.FormValue("remote"))
109	if err != nil {
110		return err
111	}
112
113	name, err := getName(ref, tag, vars["name"])
114	if err != nil {
115		return err
116	}
117	w.Header().Set("Docker-Plugin-Name", name)
118
119	w.Header().Set("Content-Type", "application/json")
120	output := ioutils.NewWriteFlusher(w)
121
122	if err := pr.backend.Upgrade(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil {
123		if !output.Flushed() {
124			return err
125		}
126		_, _ = output.Write(streamformatter.FormatError(err))
127	}
128
129	return nil
130}
131
132func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
133	if err := httputils.ParseForm(r); err != nil {
134		return errors.Wrap(err, "failed to parse form")
135	}
136
137	var privileges types.PluginPrivileges
138	dec := json.NewDecoder(r.Body)
139	if err := dec.Decode(&privileges); err != nil {
140		return errors.Wrap(err, "failed to parse privileges")
141	}
142	if dec.More() {
143		return errors.New("invalid privileges")
144	}
145
146	metaHeaders, authConfig := parseHeaders(r.Header)
147	ref, tag, err := parseRemoteRef(r.FormValue("remote"))
148	if err != nil {
149		return err
150	}
151
152	name, err := getName(ref, tag, r.FormValue("name"))
153	if err != nil {
154		return err
155	}
156	w.Header().Set("Docker-Plugin-Name", name)
157
158	w.Header().Set("Content-Type", "application/json")
159	output := ioutils.NewWriteFlusher(w)
160
161	if err := pr.backend.Pull(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil {
162		if !output.Flushed() {
163			return err
164		}
165		_, _ = output.Write(streamformatter.FormatError(err))
166	}
167
168	return nil
169}
170
171func getName(ref reference.Named, tag, name string) (string, error) {
172	if name == "" {
173		if _, ok := ref.(reference.Canonical); ok {
174			trimmed := reference.TrimNamed(ref)
175			if tag != "" {
176				nt, err := reference.WithTag(trimmed, tag)
177				if err != nil {
178					return "", err
179				}
180				name = reference.FamiliarString(nt)
181			} else {
182				name = reference.FamiliarString(reference.TagNameOnly(trimmed))
183			}
184		} else {
185			name = reference.FamiliarString(ref)
186		}
187	} else {
188		localRef, err := reference.ParseNormalizedNamed(name)
189		if err != nil {
190			return "", err
191		}
192		if _, ok := localRef.(reference.Canonical); ok {
193			return "", errors.New("cannot use digest in plugin tag")
194		}
195		if reference.IsNameOnly(localRef) {
196			// TODO: log change in name to out stream
197			name = reference.FamiliarString(reference.TagNameOnly(localRef))
198		}
199	}
200	return name, nil
201}
202
203func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
204	if err := httputils.ParseForm(r); err != nil {
205		return err
206	}
207
208	options := &types.PluginCreateOptions{
209		RepoName: r.FormValue("name")}
210
211	if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
212		return err
213	}
214	// TODO: send progress bar
215	w.WriteHeader(http.StatusNoContent)
216	return nil
217}
218
219func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
220	if err := httputils.ParseForm(r); err != nil {
221		return err
222	}
223
224	name := vars["name"]
225	timeout, err := strconv.Atoi(r.Form.Get("timeout"))
226	if err != nil {
227		return err
228	}
229	config := &types.PluginEnableConfig{Timeout: timeout}
230
231	return pr.backend.Enable(name, config)
232}
233
234func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
235	if err := httputils.ParseForm(r); err != nil {
236		return err
237	}
238
239	name := vars["name"]
240	config := &types.PluginDisableConfig{
241		ForceDisable: httputils.BoolValue(r, "force"),
242	}
243
244	return pr.backend.Disable(name, config)
245}
246
247func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
248	if err := httputils.ParseForm(r); err != nil {
249		return err
250	}
251
252	name := vars["name"]
253	config := &types.PluginRmConfig{
254		ForceRemove: httputils.BoolValue(r, "force"),
255	}
256	return pr.backend.Remove(name, config)
257}
258
259func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
260	if err := httputils.ParseForm(r); err != nil {
261		return errors.Wrap(err, "failed to parse form")
262	}
263
264	metaHeaders, authConfig := parseHeaders(r.Header)
265
266	w.Header().Set("Content-Type", "application/json")
267	output := ioutils.NewWriteFlusher(w)
268
269	if err := pr.backend.Push(ctx, vars["name"], metaHeaders, authConfig, output); err != nil {
270		if !output.Flushed() {
271			return err
272		}
273		_, _ = output.Write(streamformatter.FormatError(err))
274	}
275	return nil
276}
277
278func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
279	var args []string
280	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
281		if err == io.EOF {
282			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
283		}
284		return errdefs.InvalidParameter(err)
285	}
286	if err := pr.backend.Set(vars["name"], args); err != nil {
287		return err
288	}
289	w.WriteHeader(http.StatusNoContent)
290	return nil
291}
292
293func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
294	if err := httputils.ParseForm(r); err != nil {
295		return err
296	}
297
298	pluginFilters, err := filters.FromJSON(r.Form.Get("filters"))
299	if err != nil {
300		return err
301	}
302	l, err := pr.backend.List(pluginFilters)
303	if err != nil {
304		return err
305	}
306	return httputils.WriteJSON(w, http.StatusOK, l)
307}
308
309func (pr *pluginRouter) inspectPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
310	result, err := pr.backend.Inspect(vars["name"])
311	if err != nil {
312		return err
313	}
314	return httputils.WriteJSON(w, http.StatusOK, result)
315}
316