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