github.com/getong/docker@v1.13.1/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 distreference "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/pkg/ioutils" 14 "github.com/docker/docker/pkg/streamformatter" 15 "github.com/docker/docker/reference" 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 // NOTE: Using distribution reference to handle references 50 // containing both a name and digest 51 remoteRef, err := distreference.ParseNamed(remote) 52 if err != nil { 53 return nil, "", err 54 } 55 56 var tag string 57 if t, ok := remoteRef.(distreference.Tagged); ok { 58 tag = t.Tag() 59 } 60 61 // Convert distribution reference to docker reference 62 // TODO: remove when docker reference changes reconciled upstream 63 ref, err := reference.WithName(remoteRef.Name()) 64 if err != nil { 65 return nil, "", err 66 } 67 if d, ok := remoteRef.(distreference.Digested); ok { 68 ref, err = reference.WithDigest(ref, d.Digest()) 69 if err != nil { 70 return nil, "", err 71 } 72 } else if tag != "" { 73 ref, err = reference.WithTag(ref, tag) 74 if err != nil { 75 return nil, "", err 76 } 77 } else { 78 ref = reference.WithDefaultTag(ref) 79 } 80 81 return ref, tag, nil 82 } 83 84 func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 85 if err := httputils.ParseForm(r); err != nil { 86 return err 87 } 88 89 metaHeaders, authConfig := parseHeaders(r.Header) 90 91 ref, _, err := parseRemoteRef(r.FormValue("remote")) 92 if err != nil { 93 return err 94 } 95 96 privileges, err := pr.backend.Privileges(ctx, ref, metaHeaders, authConfig) 97 if err != nil { 98 return err 99 } 100 return httputils.WriteJSON(w, http.StatusOK, privileges) 101 } 102 103 func (pr *pluginRouter) upgradePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 104 if err := httputils.ParseForm(r); err != nil { 105 return errors.Wrap(err, "failed to parse form") 106 } 107 108 var privileges types.PluginPrivileges 109 dec := json.NewDecoder(r.Body) 110 if err := dec.Decode(&privileges); err != nil { 111 return errors.Wrap(err, "failed to parse privileges") 112 } 113 if dec.More() { 114 return errors.New("invalid privileges") 115 } 116 117 metaHeaders, authConfig := parseHeaders(r.Header) 118 ref, tag, err := parseRemoteRef(r.FormValue("remote")) 119 if err != nil { 120 return err 121 } 122 123 name, err := getName(ref, tag, vars["name"]) 124 if err != nil { 125 return err 126 } 127 w.Header().Set("Docker-Plugin-Name", name) 128 129 w.Header().Set("Content-Type", "application/json") 130 output := ioutils.NewWriteFlusher(w) 131 132 if err := pr.backend.Upgrade(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil { 133 if !output.Flushed() { 134 return err 135 } 136 output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err)) 137 } 138 139 return nil 140 } 141 142 func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 143 if err := httputils.ParseForm(r); err != nil { 144 return errors.Wrap(err, "failed to parse form") 145 } 146 147 var privileges types.PluginPrivileges 148 dec := json.NewDecoder(r.Body) 149 if err := dec.Decode(&privileges); err != nil { 150 return errors.Wrap(err, "failed to parse privileges") 151 } 152 if dec.More() { 153 return errors.New("invalid privileges") 154 } 155 156 metaHeaders, authConfig := parseHeaders(r.Header) 157 ref, tag, err := parseRemoteRef(r.FormValue("remote")) 158 if err != nil { 159 return err 160 } 161 162 name, err := getName(ref, tag, r.FormValue("name")) 163 if err != nil { 164 return err 165 } 166 w.Header().Set("Docker-Plugin-Name", name) 167 168 w.Header().Set("Content-Type", "application/json") 169 output := ioutils.NewWriteFlusher(w) 170 171 if err := pr.backend.Pull(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil { 172 if !output.Flushed() { 173 return err 174 } 175 output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err)) 176 } 177 178 return nil 179 } 180 181 func getName(ref reference.Named, tag, name string) (string, error) { 182 if name == "" { 183 if _, ok := ref.(reference.Canonical); ok { 184 trimmed := reference.TrimNamed(ref) 185 if tag != "" { 186 nt, err := reference.WithTag(trimmed, tag) 187 if err != nil { 188 return "", err 189 } 190 name = nt.String() 191 } else { 192 name = reference.WithDefaultTag(trimmed).String() 193 } 194 } else { 195 name = ref.String() 196 } 197 } else { 198 localRef, err := reference.ParseNamed(name) 199 if err != nil { 200 return "", err 201 } 202 if _, ok := localRef.(reference.Canonical); ok { 203 return "", errors.New("cannot use digest in plugin tag") 204 } 205 if distreference.IsNameOnly(localRef) { 206 // TODO: log change in name to out stream 207 name = reference.WithDefaultTag(localRef).String() 208 } 209 } 210 return name, nil 211 } 212 213 func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 214 if err := httputils.ParseForm(r); err != nil { 215 return err 216 } 217 218 options := &types.PluginCreateOptions{ 219 RepoName: r.FormValue("name")} 220 221 if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil { 222 return err 223 } 224 //TODO: send progress bar 225 w.WriteHeader(http.StatusNoContent) 226 return nil 227 } 228 229 func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 230 if err := httputils.ParseForm(r); err != nil { 231 return err 232 } 233 234 name := vars["name"] 235 timeout, err := strconv.Atoi(r.Form.Get("timeout")) 236 if err != nil { 237 return err 238 } 239 config := &types.PluginEnableConfig{Timeout: timeout} 240 241 return pr.backend.Enable(name, config) 242 } 243 244 func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 245 if err := httputils.ParseForm(r); err != nil { 246 return err 247 } 248 249 name := vars["name"] 250 config := &types.PluginDisableConfig{ 251 ForceDisable: httputils.BoolValue(r, "force"), 252 } 253 254 return pr.backend.Disable(name, config) 255 } 256 257 func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 258 if err := httputils.ParseForm(r); err != nil { 259 return err 260 } 261 262 name := vars["name"] 263 config := &types.PluginRmConfig{ 264 ForceRemove: httputils.BoolValue(r, "force"), 265 } 266 return pr.backend.Remove(name, config) 267 } 268 269 func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 270 if err := httputils.ParseForm(r); err != nil { 271 return errors.Wrap(err, "failed to parse form") 272 } 273 274 metaHeaders, authConfig := parseHeaders(r.Header) 275 276 w.Header().Set("Content-Type", "application/json") 277 output := ioutils.NewWriteFlusher(w) 278 279 if err := pr.backend.Push(ctx, vars["name"], metaHeaders, authConfig, output); err != nil { 280 if !output.Flushed() { 281 return err 282 } 283 output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err)) 284 } 285 return nil 286 } 287 288 func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 289 var args []string 290 if err := json.NewDecoder(r.Body).Decode(&args); err != nil { 291 return err 292 } 293 if err := pr.backend.Set(vars["name"], args); err != nil { 294 return err 295 } 296 w.WriteHeader(http.StatusNoContent) 297 return nil 298 } 299 300 func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 301 l, err := pr.backend.List() 302 if err != nil { 303 return err 304 } 305 return httputils.WriteJSON(w, http.StatusOK, l) 306 } 307 308 func (pr *pluginRouter) inspectPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 309 result, err := pr.backend.Inspect(vars["name"]) 310 if err != nil { 311 return err 312 } 313 return httputils.WriteJSON(w, http.StatusOK, result) 314 }