github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/api/server/router/image/image_routes.go (about) 1 package image // import "github.com/docker/docker/api/server/router/image" 2 3 import ( 4 "context" 5 "encoding/base64" 6 "encoding/json" 7 "net/http" 8 "strconv" 9 "strings" 10 11 "github.com/containerd/containerd/platforms" 12 "github.com/docker/docker/api/server/httputils" 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/api/types/filters" 15 "github.com/docker/docker/api/types/versions" 16 "github.com/docker/docker/errdefs" 17 "github.com/docker/docker/pkg/ioutils" 18 "github.com/docker/docker/pkg/streamformatter" 19 "github.com/docker/docker/registry" 20 specs "github.com/opencontainers/image-spec/specs-go/v1" 21 "github.com/pkg/errors" 22 ) 23 24 // Creates an image from Pull or from Import 25 func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 26 27 if err := httputils.ParseForm(r); err != nil { 28 return err 29 } 30 31 var ( 32 image = r.Form.Get("fromImage") 33 repo = r.Form.Get("repo") 34 tag = r.Form.Get("tag") 35 message = r.Form.Get("message") 36 err error 37 output = ioutils.NewWriteFlusher(w) 38 platform *specs.Platform 39 ) 40 defer output.Close() 41 42 w.Header().Set("Content-Type", "application/json") 43 44 version := httputils.VersionFromContext(ctx) 45 if versions.GreaterThanOrEqualTo(version, "1.32") { 46 apiPlatform := r.FormValue("platform") 47 if apiPlatform != "" { 48 sp, err := platforms.Parse(apiPlatform) 49 if err != nil { 50 return err 51 } 52 platform = &sp 53 } 54 } 55 56 if image != "" { // pull 57 metaHeaders := map[string][]string{} 58 for k, v := range r.Header { 59 if strings.HasPrefix(k, "X-Meta-") { 60 metaHeaders[k] = v 61 } 62 } 63 64 authEncoded := r.Header.Get("X-Registry-Auth") 65 authConfig := &types.AuthConfig{} 66 if authEncoded != "" { 67 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 68 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 69 // for a pull it is not an error if no auth was given 70 // to increase compatibility with the existing api it is defaulting to be empty 71 authConfig = &types.AuthConfig{} 72 } 73 } 74 err = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output) 75 } else { // import 76 src := r.Form.Get("fromSrc") 77 // 'err' MUST NOT be defined within this block, we need any error 78 // generated from the download to be available to the output 79 // stream processing below 80 os := "" 81 if platform != nil { 82 os = platform.OS 83 } 84 err = s.backend.ImportImage(src, repo, os, tag, message, r.Body, output, r.Form["changes"]) 85 } 86 if err != nil { 87 if !output.Flushed() { 88 return err 89 } 90 _, _ = output.Write(streamformatter.FormatError(err)) 91 } 92 93 return nil 94 } 95 96 func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 97 metaHeaders := map[string][]string{} 98 for k, v := range r.Header { 99 if strings.HasPrefix(k, "X-Meta-") { 100 metaHeaders[k] = v 101 } 102 } 103 if err := httputils.ParseForm(r); err != nil { 104 return err 105 } 106 authConfig := &types.AuthConfig{} 107 108 authEncoded := r.Header.Get("X-Registry-Auth") 109 if authEncoded != "" { 110 // the new format is to handle the authConfig as a header 111 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 112 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 113 // to increase compatibility to existing api it is defaulting to be empty 114 authConfig = &types.AuthConfig{} 115 } 116 } else { 117 // the old format is supported for compatibility if there was no authConfig header 118 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 119 return errors.Wrap(errdefs.InvalidParameter(err), "Bad parameters and missing X-Registry-Auth") 120 } 121 } 122 123 image := vars["name"] 124 tag := r.Form.Get("tag") 125 126 output := ioutils.NewWriteFlusher(w) 127 defer output.Close() 128 129 w.Header().Set("Content-Type", "application/json") 130 131 if err := s.backend.PushImage(ctx, image, tag, metaHeaders, authConfig, output); err != nil { 132 if !output.Flushed() { 133 return err 134 } 135 _, _ = output.Write(streamformatter.FormatError(err)) 136 } 137 return nil 138 } 139 140 func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 141 if err := httputils.ParseForm(r); err != nil { 142 return err 143 } 144 145 w.Header().Set("Content-Type", "application/x-tar") 146 147 output := ioutils.NewWriteFlusher(w) 148 defer output.Close() 149 var names []string 150 if name, ok := vars["name"]; ok { 151 names = []string{name} 152 } else { 153 names = r.Form["names"] 154 } 155 156 if err := s.backend.ExportImage(names, output); err != nil { 157 if !output.Flushed() { 158 return err 159 } 160 _, _ = output.Write(streamformatter.FormatError(err)) 161 } 162 return nil 163 } 164 165 func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 166 if err := httputils.ParseForm(r); err != nil { 167 return err 168 } 169 quiet := httputils.BoolValueOrDefault(r, "quiet", true) 170 171 w.Header().Set("Content-Type", "application/json") 172 173 output := ioutils.NewWriteFlusher(w) 174 defer output.Close() 175 if err := s.backend.LoadImage(r.Body, output, quiet); err != nil { 176 _, _ = output.Write(streamformatter.FormatError(err)) 177 } 178 return nil 179 } 180 181 type missingImageError struct{} 182 183 func (missingImageError) Error() string { 184 return "image name cannot be blank" 185 } 186 187 func (missingImageError) InvalidParameter() {} 188 189 func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 190 if err := httputils.ParseForm(r); err != nil { 191 return err 192 } 193 194 name := vars["name"] 195 196 if strings.TrimSpace(name) == "" { 197 return missingImageError{} 198 } 199 200 force := httputils.BoolValue(r, "force") 201 prune := !httputils.BoolValue(r, "noprune") 202 203 list, err := s.backend.ImageDelete(name, force, prune) 204 if err != nil { 205 return err 206 } 207 208 return httputils.WriteJSON(w, http.StatusOK, list) 209 } 210 211 func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 212 imageInspect, err := s.backend.LookupImage(vars["name"]) 213 if err != nil { 214 return err 215 } 216 217 return httputils.WriteJSON(w, http.StatusOK, imageInspect) 218 } 219 220 func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 221 if err := httputils.ParseForm(r); err != nil { 222 return err 223 } 224 225 imageFilters, err := filters.FromJSON(r.Form.Get("filters")) 226 if err != nil { 227 return err 228 } 229 230 version := httputils.VersionFromContext(ctx) 231 if versions.LessThan(version, "1.41") { 232 filterParam := r.Form.Get("filter") 233 if filterParam != "" { 234 imageFilters.Add("reference", filterParam) 235 } 236 } 237 238 images, err := s.backend.Images(imageFilters, httputils.BoolValue(r, "all"), false) 239 if err != nil { 240 return err 241 } 242 243 return httputils.WriteJSON(w, http.StatusOK, images) 244 } 245 246 func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 247 name := vars["name"] 248 history, err := s.backend.ImageHistory(name) 249 if err != nil { 250 return err 251 } 252 253 return httputils.WriteJSON(w, http.StatusOK, history) 254 } 255 256 func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 257 if err := httputils.ParseForm(r); err != nil { 258 return err 259 } 260 if _, err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil { 261 return err 262 } 263 w.WriteHeader(http.StatusCreated) 264 return nil 265 } 266 267 func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 268 if err := httputils.ParseForm(r); err != nil { 269 return err 270 } 271 var ( 272 config *types.AuthConfig 273 authEncoded = r.Header.Get("X-Registry-Auth") 274 headers = map[string][]string{} 275 ) 276 277 if authEncoded != "" { 278 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 279 if err := json.NewDecoder(authJSON).Decode(&config); err != nil { 280 // for a search it is not an error if no auth was given 281 // to increase compatibility with the existing api it is defaulting to be empty 282 config = &types.AuthConfig{} 283 } 284 } 285 for k, v := range r.Header { 286 if strings.HasPrefix(k, "X-Meta-") { 287 headers[k] = v 288 } 289 } 290 limit := registry.DefaultSearchLimit 291 if r.Form.Get("limit") != "" { 292 limitValue, err := strconv.Atoi(r.Form.Get("limit")) 293 if err != nil { 294 return err 295 } 296 limit = limitValue 297 } 298 query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), limit, config, headers) 299 if err != nil { 300 return err 301 } 302 return httputils.WriteJSON(w, http.StatusOK, query.Results) 303 } 304 305 func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 306 if err := httputils.ParseForm(r); err != nil { 307 return err 308 } 309 310 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 311 if err != nil { 312 return err 313 } 314 315 pruneReport, err := s.backend.ImagesPrune(ctx, pruneFilters) 316 if err != nil { 317 return err 318 } 319 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 320 }