github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/libpod/manifests.go (about) 1 package libpod 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "strconv" 11 "strings" 12 13 "github.com/containers/image/v5/docker/reference" 14 "github.com/containers/image/v5/manifest" 15 "github.com/containers/image/v5/types" 16 "github.com/hanks177/podman/v4/libpod" 17 "github.com/hanks177/podman/v4/pkg/api/handlers" 18 "github.com/hanks177/podman/v4/pkg/api/handlers/utils" 19 api "github.com/hanks177/podman/v4/pkg/api/types" 20 "github.com/hanks177/podman/v4/pkg/auth" 21 "github.com/hanks177/podman/v4/pkg/domain/entities" 22 "github.com/hanks177/podman/v4/pkg/domain/infra/abi" 23 "github.com/hanks177/podman/v4/pkg/errorhandling" 24 "github.com/gorilla/mux" 25 "github.com/gorilla/schema" 26 "github.com/opencontainers/go-digest" 27 "github.com/pkg/errors" 28 ) 29 30 func ManifestCreate(w http.ResponseWriter, r *http.Request) { 31 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 32 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 33 query := struct { 34 Name string `schema:"name"` 35 Images []string `schema:"images"` 36 All bool `schema:"all"` 37 }{ 38 // Add defaults here once needed. 39 } 40 41 // Support 3.x API calls, alias image to images 42 if image, ok := r.URL.Query()["image"]; ok { 43 query.Images = image 44 } 45 46 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 47 utils.Error(w, http.StatusBadRequest, 48 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 49 return 50 } 51 52 // Support 4.x API calls, map query parameter to path 53 if name, ok := mux.Vars(r)["name"]; ok { 54 n, err := url.QueryUnescape(name) 55 if err != nil { 56 utils.Error(w, http.StatusBadRequest, 57 errors.Wrapf(err, "failed to parse name parameter %q", name)) 58 return 59 } 60 query.Name = n 61 } 62 63 if _, err := reference.ParseNormalizedNamed(query.Name); err != nil { 64 utils.Error(w, http.StatusBadRequest, 65 errors.Wrapf(err, "invalid image name %s", query.Name)) 66 return 67 } 68 69 imageEngine := abi.ImageEngine{Libpod: runtime} 70 71 createOptions := entities.ManifestCreateOptions{All: query.All} 72 manID, err := imageEngine.ManifestCreate(r.Context(), query.Name, query.Images, createOptions) 73 if err != nil { 74 utils.InternalServerError(w, err) 75 return 76 } 77 78 status := http.StatusOK 79 if _, err := utils.SupportedVersion(r, "< 4.0.0"); err == utils.ErrVersionNotSupported { 80 status = http.StatusCreated 81 } 82 83 buffer, err := ioutil.ReadAll(r.Body) 84 if err != nil { 85 utils.InternalServerError(w, err) 86 return 87 } 88 89 // Treat \r\n as empty body 90 if len(buffer) < 3 { 91 utils.WriteResponse(w, status, entities.IDResponse{ID: manID}) 92 return 93 } 94 95 body := new(entities.ManifestModifyOptions) 96 if err := json.Unmarshal(buffer, body); err != nil { 97 utils.InternalServerError(w, errors.Wrap(err, "Decode()")) 98 return 99 } 100 101 // gather all images for manifest list 102 var images []string 103 if len(query.Images) > 0 { 104 images = query.Images 105 } 106 if len(body.Images) > 0 { 107 images = body.Images 108 } 109 110 id, err := imageEngine.ManifestAdd(r.Context(), query.Name, images, body.ManifestAddOptions) 111 if err != nil { 112 utils.InternalServerError(w, err) 113 return 114 } 115 116 utils.WriteResponse(w, status, entities.IDResponse{ID: id}) 117 } 118 119 // ManifestExists return true if manifest list exists. 120 func ManifestExists(w http.ResponseWriter, r *http.Request) { 121 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 122 name := utils.GetName(r) 123 124 imageEngine := abi.ImageEngine{Libpod: runtime} 125 report, err := imageEngine.ManifestExists(r.Context(), name) 126 if err != nil { 127 utils.Error(w, http.StatusInternalServerError, err) 128 return 129 } 130 if !report.Value { 131 utils.Error(w, http.StatusNotFound, errors.New("manifest not found")) 132 return 133 } 134 utils.WriteResponse(w, http.StatusNoContent, "") 135 } 136 137 func ManifestInspect(w http.ResponseWriter, r *http.Request) { 138 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 139 name := utils.GetName(r) 140 141 imageEngine := abi.ImageEngine{Libpod: runtime} 142 rawManifest, err := imageEngine.ManifestInspect(r.Context(), name) 143 if err != nil { 144 utils.Error(w, http.StatusNotFound, err) 145 return 146 } 147 148 var schema2List manifest.Schema2List 149 if err := json.Unmarshal(rawManifest, &schema2List); err != nil { 150 utils.Error(w, http.StatusInternalServerError, err) 151 return 152 } 153 154 utils.WriteResponse(w, http.StatusOK, schema2List) 155 } 156 157 // ManifestAddV3 remove digest from manifest list 158 // 159 // As of 4.0.0 use ManifestModify instead 160 func ManifestAddV3(w http.ResponseWriter, r *http.Request) { 161 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 162 163 // Wrapper to support 3.x with 4.x libpod 164 query := struct { 165 entities.ManifestAddOptions 166 TLSVerify bool `schema:"tlsVerify"` 167 }{} 168 if err := json.NewDecoder(r.Body).Decode(&query); err != nil { 169 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 170 return 171 } 172 173 authconf, authfile, err := auth.GetCredentials(r) 174 if err != nil { 175 utils.Error(w, http.StatusBadRequest, err) 176 return 177 } 178 defer auth.RemoveAuthfile(authfile) 179 var username, password string 180 if authconf != nil { 181 username = authconf.Username 182 password = authconf.Password 183 } 184 query.ManifestAddOptions.Authfile = authfile 185 query.ManifestAddOptions.Username = username 186 query.ManifestAddOptions.Password = password 187 if sys := runtime.SystemContext(); sys != nil { 188 query.ManifestAddOptions.CertDir = sys.DockerCertPath 189 } 190 if _, found := r.URL.Query()["tlsVerify"]; found { 191 query.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 192 } 193 194 name := utils.GetName(r) 195 if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { 196 utils.Error(w, http.StatusNotFound, err) 197 return 198 } 199 200 imageEngine := abi.ImageEngine{Libpod: runtime} 201 newID, err := imageEngine.ManifestAdd(r.Context(), name, query.Images, query.ManifestAddOptions) 202 if err != nil { 203 utils.InternalServerError(w, err) 204 return 205 } 206 utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: newID}) 207 } 208 209 // ManifestRemoveDigestV3 remove digest from manifest list 210 // 211 // As of 4.0.0 use ManifestModify instead 212 func ManifestRemoveDigestV3(w http.ResponseWriter, r *http.Request) { 213 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 214 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 215 query := struct { 216 Digest string `schema:"digest"` 217 }{ 218 // Add defaults here once needed. 219 } 220 name := utils.GetName(r) 221 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 222 utils.Error(w, http.StatusBadRequest, 223 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 224 return 225 } 226 manifestList, err := runtime.LibimageRuntime().LookupManifestList(name) 227 if err != nil { 228 utils.Error(w, http.StatusNotFound, err) 229 return 230 } 231 d, err := digest.Parse(query.Digest) 232 if err != nil { 233 utils.Error(w, http.StatusBadRequest, err) 234 return 235 } 236 if err := manifestList.RemoveInstance(d); err != nil { 237 utils.InternalServerError(w, err) 238 return 239 } 240 utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: manifestList.ID()}) 241 } 242 243 // ManifestPushV3 push image to registry 244 // 245 // As of 4.0.0 use ManifestPush instead 246 func ManifestPushV3(w http.ResponseWriter, r *http.Request) { 247 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 248 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 249 query := struct { 250 All bool `schema:"all"` 251 Destination string `schema:"destination"` 252 TLSVerify bool `schema:"tlsVerify"` 253 }{ 254 // Add defaults here once needed. 255 } 256 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 257 utils.Error(w, http.StatusBadRequest, 258 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 259 return 260 } 261 if err := utils.IsRegistryReference(query.Destination); err != nil { 262 utils.Error(w, http.StatusBadRequest, err) 263 return 264 } 265 266 source := utils.GetName(r) 267 authconf, authfile, err := auth.GetCredentials(r) 268 if err != nil { 269 utils.Error(w, http.StatusBadRequest, err) 270 return 271 } 272 defer auth.RemoveAuthfile(authfile) 273 var username, password string 274 if authconf != nil { 275 username = authconf.Username 276 password = authconf.Password 277 } 278 options := entities.ImagePushOptions{ 279 Authfile: authfile, 280 Username: username, 281 Password: password, 282 All: query.All, 283 } 284 if sys := runtime.SystemContext(); sys != nil { 285 options.CertDir = sys.DockerCertPath 286 } 287 if _, found := r.URL.Query()["tlsVerify"]; found { 288 options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 289 } 290 imageEngine := abi.ImageEngine{Libpod: runtime} 291 digest, err := imageEngine.ManifestPush(context.Background(), source, query.Destination, options) 292 if err != nil { 293 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", query.Destination)) 294 return 295 } 296 utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: digest}) 297 } 298 299 // ManifestPush push image to registry 300 // 301 // As of 4.0.0 302 func ManifestPush(w http.ResponseWriter, r *http.Request) { 303 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 304 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 305 306 query := struct { 307 All bool `schema:"all"` 308 TLSVerify bool `schema:"tlsVerify"` 309 }{ 310 // Add defaults here once needed. 311 } 312 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 313 utils.Error(w, http.StatusBadRequest, 314 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 315 return 316 } 317 318 destination := utils.GetVar(r, "destination") 319 if err := utils.IsRegistryReference(destination); err != nil { 320 utils.Error(w, http.StatusBadRequest, err) 321 return 322 } 323 324 authconf, authfile, err := auth.GetCredentials(r) 325 if err != nil { 326 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse registry header for %s", r.URL.String())) 327 return 328 } 329 defer auth.RemoveAuthfile(authfile) 330 var username, password string 331 if authconf != nil { 332 username = authconf.Username 333 password = authconf.Password 334 } 335 options := entities.ImagePushOptions{ 336 Authfile: authfile, 337 Username: username, 338 Password: password, 339 All: query.All, 340 } 341 if sys := runtime.SystemContext(); sys != nil { 342 options.CertDir = sys.DockerCertPath 343 } 344 if _, found := r.URL.Query()["tlsVerify"]; found { 345 options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 346 } 347 348 imageEngine := abi.ImageEngine{Libpod: runtime} 349 source := utils.GetName(r) 350 digest, err := imageEngine.ManifestPush(context.Background(), source, destination, options) 351 if err != nil { 352 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", destination)) 353 return 354 } 355 utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: digest}) 356 } 357 358 // ManifestModify efficiently updates the named manifest list 359 func ManifestModify(w http.ResponseWriter, r *http.Request) { 360 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 361 imageEngine := abi.ImageEngine{Libpod: runtime} 362 363 body := new(entities.ManifestModifyOptions) 364 if err := json.NewDecoder(r.Body).Decode(body); err != nil { 365 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 366 return 367 } 368 369 name := utils.GetName(r) 370 if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { 371 utils.Error(w, http.StatusNotFound, err) 372 return 373 } 374 375 if tlsVerify, ok := r.URL.Query()["tlsVerify"]; ok { 376 tls, err := strconv.ParseBool(tlsVerify[len(tlsVerify)-1]) 377 if err != nil { 378 utils.Error(w, http.StatusBadRequest, fmt.Errorf("tlsVerify param is not a bool: %w", err)) 379 return 380 } 381 body.SkipTLSVerify = types.NewOptionalBool(!tls) 382 } 383 384 authconf, authfile, err := auth.GetCredentials(r) 385 if err != nil { 386 utils.Error(w, http.StatusBadRequest, err) 387 return 388 } 389 defer auth.RemoveAuthfile(authfile) 390 var username, password string 391 if authconf != nil { 392 username = authconf.Username 393 password = authconf.Password 394 } 395 body.ManifestAddOptions.Authfile = authfile 396 body.ManifestAddOptions.Username = username 397 body.ManifestAddOptions.Password = password 398 if sys := runtime.SystemContext(); sys != nil { 399 body.ManifestAddOptions.CertDir = sys.DockerCertPath 400 } 401 402 var report entities.ManifestModifyReport 403 switch { 404 case strings.EqualFold("update", body.Operation): 405 id, err := imageEngine.ManifestAdd(r.Context(), name, body.Images, body.ManifestAddOptions) 406 if err != nil { 407 report.Errors = append(report.Errors, err) 408 break 409 } 410 report = entities.ManifestModifyReport{ 411 ID: id, 412 Images: body.Images, 413 } 414 case strings.EqualFold("remove", body.Operation): 415 for _, image := range body.Images { 416 id, err := imageEngine.ManifestRemoveDigest(r.Context(), name, image) 417 if err != nil { 418 report.Errors = append(report.Errors, err) 419 continue 420 } 421 report.ID = id 422 report.Images = append(report.Images, image) 423 } 424 case strings.EqualFold("annotate", body.Operation): 425 options := entities.ManifestAnnotateOptions{ 426 Annotation: body.Annotation, 427 Arch: body.Arch, 428 Features: body.Features, 429 OS: body.OS, 430 OSFeatures: body.OSFeatures, 431 OSVersion: body.OSVersion, 432 Variant: body.Variant, 433 } 434 for _, image := range body.Images { 435 id, err := imageEngine.ManifestAnnotate(r.Context(), name, image, options) 436 if err != nil { 437 report.Errors = append(report.Errors, err) 438 continue 439 } 440 report.ID = id 441 report.Images = append(report.Images, image) 442 } 443 default: 444 utils.Error(w, http.StatusBadRequest, fmt.Errorf("illegal operation %q for %q", body.Operation, r.URL.String())) 445 return 446 } 447 448 statusCode := http.StatusOK 449 switch { 450 case len(report.Errors) > 0 && len(report.Images) > 0: 451 statusCode = http.StatusConflict 452 case len(report.Errors) > 0: 453 statusCode = http.StatusBadRequest 454 } 455 utils.WriteResponse(w, statusCode, report) 456 } 457 458 // ManifestDelete removes a manifest list from storage 459 func ManifestDelete(w http.ResponseWriter, r *http.Request) { 460 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 461 imageEngine := abi.ImageEngine{Libpod: runtime} 462 463 name := utils.GetName(r) 464 if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { 465 utils.Error(w, http.StatusNotFound, err) 466 return 467 } 468 469 results, errs := imageEngine.ManifestRm(r.Context(), []string{name}) 470 errsString := errorhandling.ErrorsToStrings(errs) 471 report := handlers.LibpodImagesRemoveReport{ 472 ImageRemoveReport: *results, 473 Errors: errsString, 474 } 475 utils.WriteResponse(w, http.StatusOK, report) 476 }