github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/api/handlers/compat/images.go (about) 1 package compat 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "strings" 11 12 "github.com/containers/buildah" 13 "github.com/containers/common/pkg/config" 14 "github.com/containers/image/v5/manifest" 15 "github.com/containers/podman/v2/libpod" 16 image2 "github.com/containers/podman/v2/libpod/image" 17 "github.com/containers/podman/v2/pkg/api/handlers" 18 "github.com/containers/podman/v2/pkg/api/handlers/utils" 19 "github.com/containers/podman/v2/pkg/auth" 20 "github.com/containers/podman/v2/pkg/domain/entities" 21 "github.com/docker/docker/api/types" 22 "github.com/gorilla/schema" 23 "github.com/opencontainers/go-digest" 24 "github.com/pkg/errors" 25 ) 26 27 // mergeNameAndTagOrDigest creates an image reference as string from the 28 // provided image name and tagOrDigest which can be a tag, a digest or empty. 29 func mergeNameAndTagOrDigest(name, tagOrDigest string) string { 30 if len(tagOrDigest) == 0 { 31 return name 32 } 33 34 separator := ":" // default to tag 35 if _, err := digest.Parse(tagOrDigest); err == nil { 36 // We have a digest, so let's change the separator. 37 separator = "@" 38 } 39 return fmt.Sprintf("%s%s%s", name, separator, tagOrDigest) 40 } 41 42 func ExportImage(w http.ResponseWriter, r *http.Request) { 43 // 200 ok 44 // 500 server 45 runtime := r.Context().Value("runtime").(*libpod.Runtime) 46 47 name := utils.GetName(r) 48 newImage, err := runtime.ImageRuntime().NewFromLocal(name) 49 if err != nil { 50 utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) 51 return 52 } 53 tmpfile, err := ioutil.TempFile("", "api.tar") 54 if err != nil { 55 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 56 return 57 } 58 defer os.Remove(tmpfile.Name()) 59 if err := tmpfile.Close(); err != nil { 60 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 61 return 62 } 63 if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false, true); err != nil { 64 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) 65 return 66 } 67 rdr, err := os.Open(tmpfile.Name()) 68 if err != nil { 69 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) 70 return 71 } 72 defer rdr.Close() 73 utils.WriteResponse(w, http.StatusOK, rdr) 74 } 75 76 func PruneImages(w http.ResponseWriter, r *http.Request) { 77 var ( 78 filters []string 79 ) 80 decoder := r.Context().Value("decoder").(*schema.Decoder) 81 runtime := r.Context().Value("runtime").(*libpod.Runtime) 82 83 query := struct { 84 All bool 85 Filters map[string][]string `schema:"filters"` 86 }{ 87 // This is where you can override the golang default value for one of fields 88 } 89 90 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 91 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 92 return 93 } 94 95 idr := []types.ImageDeleteResponseItem{} 96 for k, v := range query.Filters { 97 for _, val := range v { 98 filters = append(filters, fmt.Sprintf("%s=%s", k, val)) 99 } 100 } 101 pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters) 102 if err != nil { 103 utils.InternalServerError(w, err) 104 return 105 } 106 for _, p := range pruneCids { 107 idr = append(idr, types.ImageDeleteResponseItem{ 108 Deleted: p, 109 }) 110 } 111 112 // FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden 113 ipr := types.ImagesPruneReport{ 114 ImagesDeleted: idr, 115 SpaceReclaimed: 1, // TODO we cannot supply this right now 116 } 117 utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr}) 118 } 119 120 func CommitContainer(w http.ResponseWriter, r *http.Request) { 121 var ( 122 destImage string 123 ) 124 decoder := r.Context().Value("decoder").(*schema.Decoder) 125 runtime := r.Context().Value("runtime").(*libpod.Runtime) 126 127 query := struct { 128 Author string `schema:"author"` 129 Changes string `schema:"changes"` 130 Comment string `schema:"comment"` 131 Container string `schema:"container"` 132 // fromSrc string # fromSrc is currently unused 133 Pause bool `schema:"pause"` 134 Repo string `schema:"repo"` 135 Tag string `schema:"tag"` 136 }{ 137 // This is where you can override the golang default value for one of fields 138 } 139 140 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 141 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 142 return 143 } 144 rtc, err := runtime.GetConfig() 145 if err != nil { 146 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 147 return 148 } 149 sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) 150 tag := "latest" 151 options := libpod.ContainerCommitOptions{ 152 Pause: true, 153 } 154 options.CommitOptions = buildah.CommitOptions{ 155 SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, 156 ReportWriter: os.Stderr, 157 SystemContext: sc, 158 PreferredManifestType: manifest.DockerV2Schema2MediaType, 159 } 160 161 input := handlers.CreateContainerConfig{} 162 if err := json.NewDecoder(r.Body).Decode(&input); err != nil { 163 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 164 return 165 } 166 167 if len(query.Tag) > 0 { 168 tag = query.Tag 169 } 170 options.Message = query.Comment 171 options.Author = query.Author 172 options.Pause = query.Pause 173 options.Changes = strings.Fields(query.Changes) 174 ctr, err := runtime.LookupContainer(query.Container) 175 if err != nil { 176 utils.Error(w, "Something went wrong.", http.StatusNotFound, err) 177 return 178 } 179 180 // I know mitr hates this ... but doing for now 181 if len(query.Repo) > 1 { 182 destImage = fmt.Sprintf("%s:%s", query.Repo, tag) 183 } 184 185 commitImage, err := ctr.Commit(r.Context(), destImage, options) 186 if err != nil && !strings.Contains(err.Error(), "is not running") { 187 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) 188 return 189 } 190 utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint 191 } 192 193 func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { 194 // 200 no error 195 // 404 repo does not exist or no read access 196 // 500 internal 197 decoder := r.Context().Value("decoder").(*schema.Decoder) 198 runtime := r.Context().Value("runtime").(*libpod.Runtime) 199 200 query := struct { 201 FromSrc string `schema:"fromSrc"` 202 Changes []string `schema:"changes"` 203 }{ 204 // This is where you can override the golang default value for one of fields 205 } 206 207 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 208 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 209 return 210 } 211 // fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image. 212 source := query.FromSrc 213 if source == "-" { 214 f, err := ioutil.TempFile("", "api_load.tar") 215 if err != nil { 216 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) 217 return 218 } 219 source = f.Name() 220 if err := SaveFromBody(f, r); err != nil { 221 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) 222 } 223 } 224 iid, err := runtime.Import(r.Context(), source, "", "", query.Changes, "", false) 225 if err != nil { 226 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) 227 return 228 } 229 tmpfile, err := ioutil.TempFile("", "fromsrc.tar") 230 if err != nil { 231 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 232 return 233 } 234 if err := tmpfile.Close(); err != nil { 235 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 236 return 237 } 238 // Success 239 utils.WriteResponse(w, http.StatusOK, struct { 240 Status string `json:"status"` 241 Progress string `json:"progress"` 242 ProgressDetail map[string]string `json:"progressDetail"` 243 Id string `json:"id"` // nolint 244 }{ 245 Status: iid, 246 ProgressDetail: map[string]string{}, 247 Id: iid, 248 }) 249 250 } 251 252 func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { 253 // 200 no error 254 // 404 repo does not exist or no read access 255 // 500 internal 256 decoder := r.Context().Value("decoder").(*schema.Decoder) 257 runtime := r.Context().Value("runtime").(*libpod.Runtime) 258 259 query := struct { 260 FromImage string `schema:"fromImage"` 261 Tag string `schema:"tag"` 262 }{ 263 // This is where you can override the golang default value for one of fields 264 } 265 266 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 267 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 268 return 269 } 270 271 fromImage := mergeNameAndTagOrDigest(query.FromImage, query.Tag) 272 273 authConf, authfile, key, err := auth.GetCredentials(r) 274 if err != nil { 275 utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) 276 return 277 } 278 defer auth.RemoveAuthfile(authfile) 279 280 registryOpts := image2.DockerRegistryOptions{DockerRegistryCreds: authConf} 281 if sys := runtime.SystemContext(); sys != nil { 282 registryOpts.DockerCertPath = sys.DockerCertPath 283 } 284 rtc, err := runtime.GetConfig() 285 if err != nil { 286 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 287 return 288 } 289 pullPolicy, err := config.ValidatePullPolicy(rtc.Engine.PullPolicy) 290 if err != nil { 291 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 292 return 293 } 294 img, err := runtime.ImageRuntime().New(r.Context(), 295 fromImage, 296 "", // signature policy 297 authfile, 298 nil, // writer 299 ®istryOpts, 300 image2.SigningOptions{}, 301 nil, // label 302 pullPolicy, 303 ) 304 if err != nil { 305 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) 306 return 307 } 308 309 // Success 310 utils.WriteResponse(w, http.StatusOK, struct { 311 Status string `json:"status"` 312 Error string `json:"error"` 313 Progress string `json:"progress"` 314 ProgressDetail map[string]string `json:"progressDetail"` 315 Id string `json:"id"` // nolint 316 }{ 317 Status: fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")), 318 ProgressDetail: map[string]string{}, 319 Id: img.ID(), 320 }) 321 } 322 323 func GetImage(w http.ResponseWriter, r *http.Request) { 324 // 200 no error 325 // 404 no such 326 // 500 internal 327 name := utils.GetName(r) 328 newImage, err := utils.GetImage(r, name) 329 if err != nil { 330 // Here we need to fiddle with the error message because docker-py is looking for "No 331 // such image" to determine on how to raise the correct exception. 332 errMsg := strings.ReplaceAll(err.Error(), "no such image", "No such image") 333 utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Errorf("failed to find image %s: %s", name, errMsg)) 334 return 335 } 336 inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage) 337 if err != nil { 338 utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed to convert ImageData to ImageInspect '%s'", inspect.ID)) 339 return 340 } 341 utils.WriteResponse(w, http.StatusOK, inspect) 342 } 343 344 func GetImages(w http.ResponseWriter, r *http.Request) { 345 images, err := utils.GetImages(w, r) 346 if err != nil { 347 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) 348 return 349 } 350 var summaries = make([]*entities.ImageSummary, len(images)) 351 for j, img := range images { 352 is, err := handlers.ImageToImageSummary(img) 353 if err != nil { 354 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) 355 return 356 } 357 summaries[j] = is 358 } 359 utils.WriteResponse(w, http.StatusOK, summaries) 360 } 361 362 func LoadImages(w http.ResponseWriter, r *http.Request) { 363 // TODO this is basically wrong 364 decoder := r.Context().Value("decoder").(*schema.Decoder) 365 runtime := r.Context().Value("runtime").(*libpod.Runtime) 366 367 query := struct { 368 Changes map[string]string `json:"changes"` 369 Message string `json:"message"` 370 Quiet bool `json:"quiet"` 371 }{ 372 // This is where you can override the golang default value for one of fields 373 } 374 375 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 376 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 377 return 378 } 379 380 var ( 381 err error 382 writer io.Writer 383 ) 384 f, err := ioutil.TempFile("", "api_load.tar") 385 if err != nil { 386 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) 387 return 388 } 389 if err := SaveFromBody(f, r); err != nil { 390 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) 391 return 392 } 393 id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "") 394 if err != nil { 395 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) 396 return 397 } 398 utils.WriteResponse(w, http.StatusOK, struct { 399 Stream string `json:"stream"` 400 }{ 401 Stream: fmt.Sprintf("Loaded image: %s\n", id), 402 }) 403 } 404 405 func ExportImages(w http.ResponseWriter, r *http.Request) { 406 // 200 OK 407 // 500 Error 408 decoder := r.Context().Value("decoder").(*schema.Decoder) 409 runtime := r.Context().Value("runtime").(*libpod.Runtime) 410 411 query := struct { 412 Names string `schema:"names"` 413 }{ 414 // This is where you can override the golang default value for one of fields 415 } 416 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 417 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 418 return 419 } 420 images := make([]string, 0) 421 images = append(images, strings.Split(query.Names, ",")...) 422 tmpfile, err := ioutil.TempFile("", "api.tar") 423 if err != nil { 424 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 425 return 426 } 427 defer os.Remove(tmpfile.Name()) 428 if err := tmpfile.Close(); err != nil { 429 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 430 return 431 } 432 if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false, true); err != nil { 433 utils.InternalServerError(w, err) 434 return 435 } 436 rdr, err := os.Open(tmpfile.Name()) 437 if err != nil { 438 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) 439 return 440 } 441 defer rdr.Close() 442 utils.WriteResponse(w, http.StatusOK, rdr) 443 }