github.com/rawahars/moby@v24.0.4+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 "io" 6 "net/http" 7 "net/url" 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 opts "github.com/docker/docker/api/types/image" 18 "github.com/docker/docker/api/types/registry" 19 "github.com/docker/docker/api/types/versions" 20 "github.com/docker/docker/builder/remotecontext" 21 "github.com/docker/docker/dockerversion" 22 "github.com/docker/docker/errdefs" 23 "github.com/docker/docker/image" 24 "github.com/docker/docker/pkg/ioutils" 25 "github.com/docker/docker/pkg/progress" 26 "github.com/docker/docker/pkg/streamformatter" 27 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 28 "github.com/pkg/errors" 29 ) 30 31 // Creates an image from Pull or from Import 32 func (ir *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 33 if err := httputils.ParseForm(r); err != nil { 34 return err 35 } 36 37 var ( 38 img = r.Form.Get("fromImage") 39 repo = r.Form.Get("repo") 40 tag = r.Form.Get("tag") 41 comment = r.Form.Get("message") 42 progressErr error 43 output = ioutils.NewWriteFlusher(w) 44 platform *ocispec.Platform 45 ) 46 defer output.Close() 47 48 w.Header().Set("Content-Type", "application/json") 49 50 version := httputils.VersionFromContext(ctx) 51 if versions.GreaterThanOrEqualTo(version, "1.32") { 52 if p := r.FormValue("platform"); p != "" { 53 sp, err := platforms.Parse(p) 54 if err != nil { 55 return err 56 } 57 platform = &sp 58 } 59 } 60 61 if img != "" { // pull 62 metaHeaders := map[string][]string{} 63 for k, v := range r.Header { 64 if strings.HasPrefix(k, "X-Meta-") { 65 metaHeaders[k] = v 66 } 67 } 68 69 // For a pull it is not an error if no auth was given. Ignore invalid 70 // AuthConfig to increase compatibility with the existing API. 71 authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader)) 72 progressErr = ir.backend.PullImage(ctx, img, tag, platform, metaHeaders, authConfig, output) 73 } else { // import 74 src := r.Form.Get("fromSrc") 75 76 tagRef, err := httputils.RepoTagReference(repo, tag) 77 if err != nil { 78 return errdefs.InvalidParameter(err) 79 } 80 81 if len(comment) == 0 { 82 comment = "Imported from " + src 83 } 84 85 var layerReader io.ReadCloser 86 defer r.Body.Close() 87 if src == "-" { 88 layerReader = r.Body 89 } else { 90 if len(strings.Split(src, "://")) == 1 { 91 src = "http://" + src 92 } 93 u, err := url.Parse(src) 94 if err != nil { 95 return errdefs.InvalidParameter(err) 96 } 97 98 resp, err := remotecontext.GetWithStatusError(u.String()) 99 if err != nil { 100 return err 101 } 102 output.Write(streamformatter.FormatStatus("", "Downloading from %s", u)) 103 progressOutput := streamformatter.NewJSONProgressOutput(output, true) 104 layerReader = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing") 105 defer layerReader.Close() 106 } 107 108 var id image.ID 109 id, progressErr = ir.backend.ImportImage(ctx, tagRef, platform, comment, layerReader, r.Form["changes"]) 110 111 if progressErr == nil { 112 output.Write(streamformatter.FormatStatus("", id.String())) 113 } 114 } 115 if progressErr != nil { 116 if !output.Flushed() { 117 return progressErr 118 } 119 _, _ = output.Write(streamformatter.FormatError(progressErr)) 120 } 121 122 return nil 123 } 124 125 func (ir *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 126 metaHeaders := map[string][]string{} 127 for k, v := range r.Header { 128 if strings.HasPrefix(k, "X-Meta-") { 129 metaHeaders[k] = v 130 } 131 } 132 if err := httputils.ParseForm(r); err != nil { 133 return err 134 } 135 136 var authConfig *registry.AuthConfig 137 if authEncoded := r.Header.Get(registry.AuthHeader); authEncoded != "" { 138 // the new format is to handle the authConfig as a header. Ignore invalid 139 // AuthConfig to increase compatibility with the existing API. 140 authConfig, _ = registry.DecodeAuthConfig(authEncoded) 141 } else { 142 // the old format is supported for compatibility if there was no authConfig header 143 var err error 144 authConfig, err = registry.DecodeAuthConfigBody(r.Body) 145 if err != nil { 146 return errors.Wrap(err, "bad parameters and missing X-Registry-Auth") 147 } 148 } 149 150 output := ioutils.NewWriteFlusher(w) 151 defer output.Close() 152 153 w.Header().Set("Content-Type", "application/json") 154 155 img := vars["name"] 156 tag := r.Form.Get("tag") 157 158 var ref reference.Named 159 160 // Tag is empty only in case ImagePushOptions.All is true. 161 if tag != "" { 162 r, err := httputils.RepoTagReference(img, tag) 163 if err != nil { 164 return errdefs.InvalidParameter(err) 165 } 166 ref = r 167 } else { 168 r, err := reference.ParseNormalizedNamed(img) 169 if err != nil { 170 return errdefs.InvalidParameter(err) 171 } 172 ref = r 173 } 174 175 if err := ir.backend.PushImage(ctx, ref, metaHeaders, authConfig, output); err != nil { 176 if !output.Flushed() { 177 return err 178 } 179 _, _ = output.Write(streamformatter.FormatError(err)) 180 } 181 return nil 182 } 183 184 func (ir *imageRouter) getImagesGet(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 w.Header().Set("Content-Type", "application/x-tar") 190 191 output := ioutils.NewWriteFlusher(w) 192 defer output.Close() 193 var names []string 194 if name, ok := vars["name"]; ok { 195 names = []string{name} 196 } else { 197 names = r.Form["names"] 198 } 199 200 if err := ir.backend.ExportImage(ctx, names, output); err != nil { 201 if !output.Flushed() { 202 return err 203 } 204 _, _ = output.Write(streamformatter.FormatError(err)) 205 } 206 return nil 207 } 208 209 func (ir *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 210 if err := httputils.ParseForm(r); err != nil { 211 return err 212 } 213 quiet := httputils.BoolValueOrDefault(r, "quiet", true) 214 215 w.Header().Set("Content-Type", "application/json") 216 217 output := ioutils.NewWriteFlusher(w) 218 defer output.Close() 219 if err := ir.backend.LoadImage(ctx, r.Body, output, quiet); err != nil { 220 _, _ = output.Write(streamformatter.FormatError(err)) 221 } 222 return nil 223 } 224 225 type missingImageError struct{} 226 227 func (missingImageError) Error() string { 228 return "image name cannot be blank" 229 } 230 231 func (missingImageError) InvalidParameter() {} 232 233 func (ir *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 234 if err := httputils.ParseForm(r); err != nil { 235 return err 236 } 237 238 name := vars["name"] 239 240 if strings.TrimSpace(name) == "" { 241 return missingImageError{} 242 } 243 244 force := httputils.BoolValue(r, "force") 245 prune := !httputils.BoolValue(r, "noprune") 246 247 list, err := ir.backend.ImageDelete(ctx, name, force, prune) 248 if err != nil { 249 return err 250 } 251 252 return httputils.WriteJSON(w, http.StatusOK, list) 253 } 254 255 func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 256 img, err := ir.backend.GetImage(ctx, vars["name"], opts.GetImageOpts{Details: true}) 257 if err != nil { 258 return err 259 } 260 261 imageInspect, err := ir.toImageInspect(img) 262 if err != nil { 263 return err 264 } 265 266 return httputils.WriteJSON(w, http.StatusOK, imageInspect) 267 } 268 269 func (ir *imageRouter) toImageInspect(img *image.Image) (*types.ImageInspect, error) { 270 var repoTags, repoDigests []string 271 for _, ref := range img.Details.References { 272 switch ref.(type) { 273 case reference.NamedTagged: 274 repoTags = append(repoTags, reference.FamiliarString(ref)) 275 case reference.Canonical: 276 repoDigests = append(repoDigests, reference.FamiliarString(ref)) 277 } 278 } 279 280 comment := img.Comment 281 if len(comment) == 0 && len(img.History) > 0 { 282 comment = img.History[len(img.History)-1].Comment 283 } 284 285 // Make sure we output empty arrays instead of nil. 286 if repoTags == nil { 287 repoTags = []string{} 288 } 289 if repoDigests == nil { 290 repoDigests = []string{} 291 } 292 293 return &types.ImageInspect{ 294 ID: img.ID().String(), 295 RepoTags: repoTags, 296 RepoDigests: repoDigests, 297 Parent: img.Parent.String(), 298 Comment: comment, 299 Created: img.Created.Format(time.RFC3339Nano), 300 Container: img.Container, 301 ContainerConfig: &img.ContainerConfig, 302 DockerVersion: img.DockerVersion, 303 Author: img.Author, 304 Config: img.Config, 305 Architecture: img.Architecture, 306 Variant: img.Variant, 307 Os: img.OperatingSystem(), 308 OsVersion: img.OSVersion, 309 Size: img.Details.Size, 310 VirtualSize: img.Details.Size, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44. 311 GraphDriver: types.GraphDriverData{ 312 Name: img.Details.Driver, 313 Data: img.Details.Metadata, 314 }, 315 RootFS: rootFSToAPIType(img.RootFS), 316 Metadata: types.ImageMetadata{ 317 LastTagTime: img.Details.LastUpdated, 318 }, 319 }, nil 320 } 321 322 func rootFSToAPIType(rootfs *image.RootFS) types.RootFS { 323 var layers []string 324 for _, l := range rootfs.DiffIDs { 325 layers = append(layers, l.String()) 326 } 327 return types.RootFS{ 328 Type: rootfs.Type, 329 Layers: layers, 330 } 331 } 332 333 func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 334 if err := httputils.ParseForm(r); err != nil { 335 return err 336 } 337 338 imageFilters, err := filters.FromJSON(r.Form.Get("filters")) 339 if err != nil { 340 return err 341 } 342 343 version := httputils.VersionFromContext(ctx) 344 if versions.LessThan(version, "1.41") { 345 // NOTE: filter is a shell glob string applied to repository names. 346 filterParam := r.Form.Get("filter") 347 if filterParam != "" { 348 imageFilters.Add("reference", filterParam) 349 } 350 } 351 352 var sharedSize bool 353 if versions.GreaterThanOrEqualTo(version, "1.42") { 354 // NOTE: Support for the "shared-size" parameter was added in API 1.42. 355 sharedSize = httputils.BoolValue(r, "shared-size") 356 } 357 358 images, err := ir.backend.Images(ctx, types.ImageListOptions{ 359 All: httputils.BoolValue(r, "all"), 360 Filters: imageFilters, 361 SharedSize: sharedSize, 362 }) 363 if err != nil { 364 return err 365 } 366 367 useNone := versions.LessThan(version, "1.43") 368 for _, img := range images { 369 if useNone { 370 if len(img.RepoTags) == 0 && len(img.RepoDigests) == 0 { 371 img.RepoTags = append(img.RepoTags, "<none>:<none>") 372 img.RepoDigests = append(img.RepoDigests, "<none>@<none>") 373 } 374 } else { 375 if img.RepoTags == nil { 376 img.RepoTags = []string{} 377 } 378 if img.RepoDigests == nil { 379 img.RepoDigests = []string{} 380 } 381 } 382 } 383 384 return httputils.WriteJSON(w, http.StatusOK, images) 385 } 386 387 func (ir *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 388 history, err := ir.backend.ImageHistory(ctx, vars["name"]) 389 if err != nil { 390 return err 391 } 392 393 return httputils.WriteJSON(w, http.StatusOK, history) 394 } 395 396 func (ir *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 397 if err := httputils.ParseForm(r); err != nil { 398 return err 399 } 400 401 ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag")) 402 if ref == nil || err != nil { 403 return errdefs.InvalidParameter(err) 404 } 405 406 img, err := ir.backend.GetImage(ctx, vars["name"], opts.GetImageOpts{}) 407 if err != nil { 408 return errdefs.NotFound(err) 409 } 410 411 if err := ir.backend.TagImage(ctx, img.ID(), ref); err != nil { 412 return err 413 } 414 w.WriteHeader(http.StatusCreated) 415 return nil 416 } 417 418 func (ir *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 419 if err := httputils.ParseForm(r); err != nil { 420 return err 421 } 422 423 var limit int 424 if r.Form.Get("limit") != "" { 425 var err error 426 limit, err = strconv.Atoi(r.Form.Get("limit")) 427 if err != nil || limit < 0 { 428 return errdefs.InvalidParameter(errors.Wrap(err, "invalid limit specified")) 429 } 430 } 431 searchFilters, err := filters.FromJSON(r.Form.Get("filters")) 432 if err != nil { 433 return err 434 } 435 436 // For a search it is not an error if no auth was given. Ignore invalid 437 // AuthConfig to increase compatibility with the existing API. 438 authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader)) 439 440 var headers = http.Header{} 441 for k, v := range r.Header { 442 k = http.CanonicalHeaderKey(k) 443 if strings.HasPrefix(k, "X-Meta-") { 444 headers[k] = v 445 } 446 } 447 headers.Set("User-Agent", dockerversion.DockerUserAgent(ctx)) 448 res, err := ir.searcher.Search(ctx, searchFilters, r.Form.Get("term"), limit, authConfig, headers) 449 if err != nil { 450 return err 451 } 452 return httputils.WriteJSON(w, http.StatusOK, res) 453 } 454 455 func (ir *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 456 if err := httputils.ParseForm(r); err != nil { 457 return err 458 } 459 460 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 461 if err != nil { 462 return err 463 } 464 465 pruneReport, err := ir.backend.ImagesPrune(ctx, pruneFilters) 466 if err != nil { 467 return err 468 } 469 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 470 }