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