github.com/rumpl/bof@v23.0.0-rc.2+incompatible/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 "time" 11 12 "github.com/containerd/containerd/platforms" 13 "github.com/docker/distribution/reference" 14 "github.com/docker/docker/api/server/httputils" 15 "github.com/docker/docker/api/types" 16 "github.com/docker/docker/api/types/filters" 17 "github.com/docker/docker/api/types/versions" 18 "github.com/docker/docker/errdefs" 19 "github.com/docker/docker/image" 20 "github.com/docker/docker/layer" 21 "github.com/docker/docker/pkg/ioutils" 22 "github.com/docker/docker/pkg/streamformatter" 23 specs "github.com/opencontainers/image-spec/specs-go/v1" 24 "github.com/pkg/errors" 25 ) 26 27 // Creates an image from Pull or from Import 28 func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 29 30 if err := httputils.ParseForm(r); err != nil { 31 return err 32 } 33 34 var ( 35 image = r.Form.Get("fromImage") 36 repo = r.Form.Get("repo") 37 tag = r.Form.Get("tag") 38 message = r.Form.Get("message") 39 progressErr error 40 output = ioutils.NewWriteFlusher(w) 41 platform *specs.Platform 42 ) 43 defer output.Close() 44 45 w.Header().Set("Content-Type", "application/json") 46 47 version := httputils.VersionFromContext(ctx) 48 if versions.GreaterThanOrEqualTo(version, "1.32") { 49 if p := r.FormValue("platform"); p != "" { 50 sp, err := platforms.Parse(p) 51 if err != nil { 52 return err 53 } 54 platform = &sp 55 } 56 } 57 58 if image != "" { // pull 59 metaHeaders := map[string][]string{} 60 for k, v := range r.Header { 61 if strings.HasPrefix(k, "X-Meta-") { 62 metaHeaders[k] = v 63 } 64 } 65 66 authEncoded := r.Header.Get("X-Registry-Auth") 67 authConfig := &types.AuthConfig{} 68 if authEncoded != "" { 69 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 70 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 71 // for a pull it is not an error if no auth was given 72 // to increase compatibility with the existing api it is defaulting to be empty 73 authConfig = &types.AuthConfig{} 74 } 75 } 76 progressErr = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output) 77 } else { // import 78 src := r.Form.Get("fromSrc") 79 progressErr = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"]) 80 } 81 if progressErr != nil { 82 if !output.Flushed() { 83 return progressErr 84 } 85 _, _ = output.Write(streamformatter.FormatError(progressErr)) 86 } 87 88 return nil 89 } 90 91 func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 92 metaHeaders := map[string][]string{} 93 for k, v := range r.Header { 94 if strings.HasPrefix(k, "X-Meta-") { 95 metaHeaders[k] = v 96 } 97 } 98 if err := httputils.ParseForm(r); err != nil { 99 return err 100 } 101 authConfig := &types.AuthConfig{} 102 103 authEncoded := r.Header.Get("X-Registry-Auth") 104 if authEncoded != "" { 105 // the new format is to handle the authConfig as a header 106 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 107 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 108 // to increase compatibility to existing api it is defaulting to be empty 109 authConfig = &types.AuthConfig{} 110 } 111 } else { 112 // the old format is supported for compatibility if there was no authConfig header 113 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 114 return errors.Wrap(errdefs.InvalidParameter(err), "Bad parameters and missing X-Registry-Auth") 115 } 116 } 117 118 image := vars["name"] 119 tag := r.Form.Get("tag") 120 121 output := ioutils.NewWriteFlusher(w) 122 defer output.Close() 123 124 w.Header().Set("Content-Type", "application/json") 125 126 if err := s.backend.PushImage(ctx, image, tag, metaHeaders, authConfig, output); err != nil { 127 if !output.Flushed() { 128 return err 129 } 130 _, _ = output.Write(streamformatter.FormatError(err)) 131 } 132 return nil 133 } 134 135 func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 136 if err := httputils.ParseForm(r); err != nil { 137 return err 138 } 139 140 w.Header().Set("Content-Type", "application/x-tar") 141 142 output := ioutils.NewWriteFlusher(w) 143 defer output.Close() 144 var names []string 145 if name, ok := vars["name"]; ok { 146 names = []string{name} 147 } else { 148 names = r.Form["names"] 149 } 150 151 if err := s.backend.ExportImage(names, output); err != nil { 152 if !output.Flushed() { 153 return err 154 } 155 _, _ = output.Write(streamformatter.FormatError(err)) 156 } 157 return nil 158 } 159 160 func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 161 if err := httputils.ParseForm(r); err != nil { 162 return err 163 } 164 quiet := httputils.BoolValueOrDefault(r, "quiet", true) 165 166 w.Header().Set("Content-Type", "application/json") 167 168 output := ioutils.NewWriteFlusher(w) 169 defer output.Close() 170 if err := s.backend.LoadImage(r.Body, output, quiet); err != nil { 171 _, _ = output.Write(streamformatter.FormatError(err)) 172 } 173 return nil 174 } 175 176 type missingImageError struct{} 177 178 func (missingImageError) Error() string { 179 return "image name cannot be blank" 180 } 181 182 func (missingImageError) InvalidParameter() {} 183 184 func (s *imageRouter) deleteImages(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 name := vars["name"] 190 191 if strings.TrimSpace(name) == "" { 192 return missingImageError{} 193 } 194 195 force := httputils.BoolValue(r, "force") 196 prune := !httputils.BoolValue(r, "noprune") 197 198 list, err := s.backend.ImageDelete(name, force, prune) 199 if err != nil { 200 return err 201 } 202 203 return httputils.WriteJSON(w, http.StatusOK, list) 204 } 205 206 func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 207 image, err := s.backend.GetImage(vars["name"], nil) 208 if err != nil { 209 return err 210 } 211 212 imageInspect, err := s.toImageInspect(image) 213 if err != nil { 214 return err 215 } 216 217 return httputils.WriteJSON(w, http.StatusOK, imageInspect) 218 } 219 220 func (s *imageRouter) toImageInspect(img *image.Image) (*types.ImageInspect, error) { 221 refs := s.referenceBackend.References(img.ID().Digest()) 222 repoTags := []string{} 223 repoDigests := []string{} 224 for _, ref := range refs { 225 switch ref.(type) { 226 case reference.NamedTagged: 227 repoTags = append(repoTags, reference.FamiliarString(ref)) 228 case reference.Canonical: 229 repoDigests = append(repoDigests, reference.FamiliarString(ref)) 230 } 231 } 232 233 var size int64 234 var layerMetadata map[string]string 235 layerID := img.RootFS.ChainID() 236 if layerID != "" { 237 l, err := s.layerStore.Get(layerID) 238 if err != nil { 239 return nil, err 240 } 241 defer layer.ReleaseAndLog(s.layerStore, l) 242 size = l.Size() 243 layerMetadata, err = l.Metadata() 244 if err != nil { 245 return nil, err 246 } 247 } 248 249 comment := img.Comment 250 if len(comment) == 0 && len(img.History) > 0 { 251 comment = img.History[len(img.History)-1].Comment 252 } 253 254 lastUpdated, err := s.imageStore.GetLastUpdated(img.ID()) 255 if err != nil { 256 return nil, err 257 } 258 259 return &types.ImageInspect{ 260 ID: img.ID().String(), 261 RepoTags: repoTags, 262 RepoDigests: repoDigests, 263 Parent: img.Parent.String(), 264 Comment: comment, 265 Created: img.Created.Format(time.RFC3339Nano), 266 Container: img.Container, 267 ContainerConfig: &img.ContainerConfig, 268 DockerVersion: img.DockerVersion, 269 Author: img.Author, 270 Config: img.Config, 271 Architecture: img.Architecture, 272 Variant: img.Variant, 273 Os: img.OperatingSystem(), 274 OsVersion: img.OSVersion, 275 Size: size, 276 VirtualSize: size, // TODO: field unused, deprecate 277 GraphDriver: types.GraphDriverData{ 278 Name: s.layerStore.DriverName(), 279 Data: layerMetadata, 280 }, 281 RootFS: rootFSToAPIType(img.RootFS), 282 Metadata: types.ImageMetadata{ 283 LastTagTime: lastUpdated, 284 }, 285 }, nil 286 } 287 288 func rootFSToAPIType(rootfs *image.RootFS) types.RootFS { 289 var layers []string 290 for _, l := range rootfs.DiffIDs { 291 layers = append(layers, l.String()) 292 } 293 return types.RootFS{ 294 Type: rootfs.Type, 295 Layers: layers, 296 } 297 } 298 299 func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 300 if err := httputils.ParseForm(r); err != nil { 301 return err 302 } 303 304 imageFilters, err := filters.FromJSON(r.Form.Get("filters")) 305 if err != nil { 306 return err 307 } 308 309 version := httputils.VersionFromContext(ctx) 310 if versions.LessThan(version, "1.41") { 311 // NOTE: filter is a shell glob string applied to repository names. 312 filterParam := r.Form.Get("filter") 313 if filterParam != "" { 314 imageFilters.Add("reference", filterParam) 315 } 316 } 317 318 var sharedSize bool 319 if versions.GreaterThanOrEqualTo(version, "1.42") { 320 // NOTE: Support for the "shared-size" parameter was added in API 1.42. 321 sharedSize = httputils.BoolValue(r, "shared-size") 322 } 323 324 images, err := s.backend.Images(ctx, types.ImageListOptions{ 325 All: httputils.BoolValue(r, "all"), 326 Filters: imageFilters, 327 SharedSize: sharedSize, 328 }) 329 if err != nil { 330 return err 331 } 332 333 return httputils.WriteJSON(w, http.StatusOK, images) 334 } 335 336 func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 337 name := vars["name"] 338 history, err := s.backend.ImageHistory(name) 339 if err != nil { 340 return err 341 } 342 343 return httputils.WriteJSON(w, http.StatusOK, history) 344 } 345 346 func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 347 if err := httputils.ParseForm(r); err != nil { 348 return err 349 } 350 if _, err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil { 351 return err 352 } 353 w.WriteHeader(http.StatusCreated) 354 return nil 355 } 356 357 func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 358 if err := httputils.ParseForm(r); err != nil { 359 return err 360 } 361 var ( 362 config *types.AuthConfig 363 authEncoded = r.Header.Get("X-Registry-Auth") 364 headers = map[string][]string{} 365 ) 366 367 if authEncoded != "" { 368 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 369 if err := json.NewDecoder(authJSON).Decode(&config); err != nil { 370 // for a search it is not an error if no auth was given 371 // to increase compatibility with the existing api it is defaulting to be empty 372 config = &types.AuthConfig{} 373 } 374 } 375 for k, v := range r.Header { 376 if strings.HasPrefix(k, "X-Meta-") { 377 headers[k] = v 378 } 379 } 380 381 var limit int 382 if r.Form.Get("limit") != "" { 383 var err error 384 limit, err = strconv.Atoi(r.Form.Get("limit")) 385 if err != nil || limit < 0 { 386 return errdefs.InvalidParameter(errors.Wrap(err, "invalid limit specified")) 387 } 388 } 389 searchFilters, err := filters.FromJSON(r.Form.Get("filters")) 390 if err != nil { 391 return err 392 } 393 394 query, err := s.backend.SearchRegistryForImages(ctx, searchFilters, r.Form.Get("term"), limit, config, headers) 395 if err != nil { 396 return err 397 } 398 return httputils.WriteJSON(w, http.StatusOK, query.Results) 399 } 400 401 func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 402 if err := httputils.ParseForm(r); err != nil { 403 return err 404 } 405 406 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 407 if err != nil { 408 return err 409 } 410 411 pruneReport, err := s.backend.ImagesPrune(ctx, pruneFilters) 412 if err != nil { 413 return err 414 } 415 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 416 }