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