github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/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/image/v5/manifest" 14 "github.com/containers/libpod/libpod" 15 image2 "github.com/containers/libpod/libpod/image" 16 "github.com/containers/libpod/pkg/api/handlers" 17 "github.com/containers/libpod/pkg/api/handlers/utils" 18 "github.com/containers/libpod/pkg/domain/entities" 19 "github.com/containers/libpod/pkg/util" 20 "github.com/docker/docker/api/types" 21 "github.com/gorilla/schema" 22 "github.com/pkg/errors" 23 ) 24 25 func ExportImage(w http.ResponseWriter, r *http.Request) { 26 // 200 ok 27 // 500 server 28 runtime := r.Context().Value("runtime").(*libpod.Runtime) 29 30 name := utils.GetName(r) 31 newImage, err := runtime.ImageRuntime().NewFromLocal(name) 32 if err != nil { 33 utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) 34 return 35 } 36 tmpfile, err := ioutil.TempFile("", "api.tar") 37 if err != nil { 38 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 39 return 40 } 41 if err := tmpfile.Close(); err != nil { 42 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 43 return 44 } 45 if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false); err != nil { 46 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) 47 return 48 } 49 rdr, err := os.Open(tmpfile.Name()) 50 if err != nil { 51 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) 52 return 53 } 54 defer rdr.Close() 55 defer os.Remove(tmpfile.Name()) 56 utils.WriteResponse(w, http.StatusOK, rdr) 57 } 58 59 func PruneImages(w http.ResponseWriter, r *http.Request) { 60 var ( 61 filters []string 62 ) 63 decoder := r.Context().Value("decoder").(*schema.Decoder) 64 runtime := r.Context().Value("runtime").(*libpod.Runtime) 65 66 query := struct { 67 All bool 68 Filters map[string][]string `schema:"filters"` 69 }{ 70 // This is where you can override the golang default value for one of fields 71 } 72 73 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 74 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 75 return 76 } 77 78 idr := []types.ImageDeleteResponseItem{} 79 for k, v := range query.Filters { 80 for _, val := range v { 81 filters = append(filters, fmt.Sprintf("%s=%s", k, val)) 82 } 83 } 84 pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters) 85 if err != nil { 86 utils.InternalServerError(w, err) 87 return 88 } 89 for _, p := range pruneCids { 90 idr = append(idr, types.ImageDeleteResponseItem{ 91 Deleted: p, 92 }) 93 } 94 95 //FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden 96 ipr := types.ImagesPruneReport{ 97 ImagesDeleted: idr, 98 SpaceReclaimed: 1, // TODO we cannot supply this right now 99 } 100 utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr}) 101 } 102 103 func CommitContainer(w http.ResponseWriter, r *http.Request) { 104 var ( 105 destImage string 106 ) 107 decoder := r.Context().Value("decoder").(*schema.Decoder) 108 runtime := r.Context().Value("runtime").(*libpod.Runtime) 109 110 query := struct { 111 Author string `schema:"author"` 112 Changes string `schema:"changes"` 113 Comment string `schema:"comment"` 114 Container string `schema:"container"` 115 //fromSrc string # fromSrc is currently unused 116 Pause bool `schema:"pause"` 117 Repo string `schema:"repo"` 118 Tag string `schema:"tag"` 119 }{ 120 // This is where you can override the golang default value for one of fields 121 } 122 123 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 124 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 125 return 126 } 127 rtc, err := runtime.GetConfig() 128 if err != nil { 129 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 130 return 131 } 132 sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) 133 tag := "latest" 134 options := libpod.ContainerCommitOptions{ 135 Pause: true, 136 } 137 options.CommitOptions = buildah.CommitOptions{ 138 SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, 139 ReportWriter: os.Stderr, 140 SystemContext: sc, 141 PreferredManifestType: manifest.DockerV2Schema2MediaType, 142 } 143 144 input := handlers.CreateContainerConfig{} 145 if err := json.NewDecoder(r.Body).Decode(&input); err != nil { 146 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 147 return 148 } 149 150 if len(query.Tag) > 0 { 151 tag = query.Tag 152 } 153 options.Message = query.Comment 154 options.Author = query.Author 155 options.Pause = query.Pause 156 options.Changes = strings.Fields(query.Changes) 157 ctr, err := runtime.LookupContainer(query.Container) 158 if err != nil { 159 utils.Error(w, "Something went wrong.", http.StatusNotFound, err) 160 return 161 } 162 163 // I know mitr hates this ... but doing for now 164 if len(query.Repo) > 1 { 165 destImage = fmt.Sprintf("%s:%s", query.Repo, tag) 166 } 167 168 commitImage, err := ctr.Commit(r.Context(), destImage, options) 169 if err != nil && !strings.Contains(err.Error(), "is not running") { 170 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) 171 return 172 } 173 utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint 174 } 175 176 func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { 177 // 200 no error 178 // 404 repo does not exist or no read access 179 // 500 internal 180 decoder := r.Context().Value("decoder").(*schema.Decoder) 181 runtime := r.Context().Value("runtime").(*libpod.Runtime) 182 183 query := struct { 184 FromSrc string `schema:"fromSrc"` 185 Changes []string `schema:"changes"` 186 }{ 187 // This is where you can override the golang default value for one of fields 188 } 189 190 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 191 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 192 return 193 } 194 // 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. 195 source := query.FromSrc 196 if source == "-" { 197 f, err := ioutil.TempFile("", "api_load.tar") 198 if err != nil { 199 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) 200 return 201 } 202 source = f.Name() 203 if err := SaveFromBody(f, r); err != nil { 204 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) 205 } 206 } 207 iid, err := runtime.Import(r.Context(), source, "", query.Changes, "", false) 208 if err != nil { 209 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) 210 return 211 } 212 tmpfile, err := ioutil.TempFile("", "fromsrc.tar") 213 if err != nil { 214 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 215 return 216 } 217 if err := tmpfile.Close(); err != nil { 218 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 219 return 220 } 221 // Success 222 utils.WriteResponse(w, http.StatusOK, struct { 223 Status string `json:"status"` 224 Progress string `json:"progress"` 225 ProgressDetail map[string]string `json:"progressDetail"` 226 Id string `json:"id"` 227 }{ 228 Status: iid, 229 ProgressDetail: map[string]string{}, 230 Id: iid, 231 }) 232 233 } 234 235 func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { 236 // 200 no error 237 // 404 repo does not exist or no read access 238 // 500 internal 239 decoder := r.Context().Value("decoder").(*schema.Decoder) 240 runtime := r.Context().Value("runtime").(*libpod.Runtime) 241 242 query := struct { 243 FromImage string `schema:"fromImage"` 244 Tag string `schema:"tag"` 245 }{ 246 // This is where you can override the golang default value for one of fields 247 } 248 249 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 250 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 251 return 252 } 253 254 /* 255 fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed. 256 repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image. 257 tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled. 258 */ 259 fromImage := query.FromImage 260 if len(query.Tag) >= 1 { 261 fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag) 262 } 263 264 // TODO 265 // We are eating the output right now because we haven't talked about how to deal with multiple responses yet 266 img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing) 267 if err != nil { 268 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) 269 return 270 } 271 272 // Success 273 utils.WriteResponse(w, http.StatusOK, struct { 274 Status string `json:"status"` 275 Error string `json:"error"` 276 Progress string `json:"progress"` 277 ProgressDetail map[string]string `json:"progressDetail"` 278 Id string `json:"id"` 279 }{ 280 Status: fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")), 281 ProgressDetail: map[string]string{}, 282 Id: img.ID(), 283 }) 284 } 285 286 func GetImage(w http.ResponseWriter, r *http.Request) { 287 // 200 no error 288 // 404 no such 289 // 500 internal 290 name := utils.GetName(r) 291 newImage, err := utils.GetImage(r, name) 292 if err != nil { 293 utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) 294 return 295 } 296 inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage) 297 if err != nil { 298 utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "Failed to convert ImageData to ImageInspect '%s'", inspect.ID)) 299 return 300 } 301 utils.WriteResponse(w, http.StatusOK, inspect) 302 } 303 304 func GetImages(w http.ResponseWriter, r *http.Request) { 305 images, err := utils.GetImages(w, r) 306 if err != nil { 307 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) 308 return 309 } 310 var summaries = make([]*entities.ImageSummary, len(images)) 311 for j, img := range images { 312 is, err := handlers.ImageToImageSummary(img) 313 if err != nil { 314 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) 315 return 316 } 317 summaries[j] = is 318 } 319 utils.WriteResponse(w, http.StatusOK, summaries) 320 } 321 322 func LoadImages(w http.ResponseWriter, r *http.Request) { 323 // TODO this is basically wrong 324 decoder := r.Context().Value("decoder").(*schema.Decoder) 325 runtime := r.Context().Value("runtime").(*libpod.Runtime) 326 327 query := struct { 328 Changes map[string]string `json:"changes"` 329 Message string `json:"message"` 330 Quiet bool `json:"quiet"` 331 }{ 332 // This is where you can override the golang default value for one of fields 333 } 334 335 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 336 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 337 return 338 } 339 340 var ( 341 err error 342 writer io.Writer 343 ) 344 f, err := ioutil.TempFile("", "api_load.tar") 345 if err != nil { 346 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) 347 return 348 } 349 if err := SaveFromBody(f, r); err != nil { 350 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) 351 return 352 } 353 id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "") 354 //id, err := runtime.Import(r.Context()) 355 if err != nil { 356 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) 357 return 358 } 359 utils.WriteResponse(w, http.StatusOK, struct { 360 Stream string `json:"stream"` 361 }{ 362 Stream: fmt.Sprintf("Loaded image: %s\n", id), 363 }) 364 }