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