Merge pull request #24678 from rhatdan/manifest

Add podman manifest rm --ignore
This commit is contained in:
openshift-merge-bot[bot]
2025-01-27 14:52:05 +00:00
committed by GitHub
11 changed files with 68 additions and 20 deletions

View File

@ -6,13 +6,15 @@ import (
"github.com/containers/podman/v5/cmd/podman/common" "github.com/containers/podman/v5/cmd/podman/common"
"github.com/containers/podman/v5/cmd/podman/registry" "github.com/containers/podman/v5/cmd/podman/registry"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/podman/v5/pkg/errorhandling" "github.com/containers/podman/v5/pkg/errorhandling"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ( var (
rmCmd = &cobra.Command{ rmOptions = entities.ImageRemoveOptions{}
Use: "rm LIST [LIST...]", rmCmd = &cobra.Command{
Use: "rm [options] LIST [LIST...]",
Short: "Remove manifest list or image index from local storage", Short: "Remove manifest list or image index from local storage",
Long: "Remove manifest list or image index from local storage.", Long: "Remove manifest list or image index from local storage.",
RunE: rm, RunE: rm,
@ -27,10 +29,13 @@ func init() {
Command: rmCmd, Command: rmCmd,
Parent: manifestCmd, Parent: manifestCmd,
}) })
flags := rmCmd.Flags()
flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified manifest is missing")
} }
func rm(cmd *cobra.Command, args []string) error { func rm(cmd *cobra.Command, args []string) error {
report, rmErrors := registry.ImageEngine().ManifestRm(context.Background(), args) report, rmErrors := registry.ImageEngine().ManifestRm(context.Background(), args, rmOptions)
if report != nil { if report != nil {
for _, u := range report.Untagged { for _, u := range report.Untagged {
fmt.Println("Untagged: " + u) fmt.Println("Untagged: " + u)

View File

@ -4,11 +4,17 @@
podman\-manifest\-rm - Remove manifest list or image index from local storage podman\-manifest\-rm - Remove manifest list or image index from local storage
## SYNOPSIS ## SYNOPSIS
**podman manifest rm** *list-or-index* [...] **podman manifest rm** [*options*] *list-or-index* [...]
## DESCRIPTION ## DESCRIPTION
Removes one or more locally stored manifest lists. Removes one or more locally stored manifest lists.
## OPTIONS
#### **--ignore**, **-i**
If a specified manifest does not exist in the local storage, ignore it and do not throw an error.
## EXAMPLE ## EXAMPLE
podman manifest rm `<list>` podman manifest rm `<list>`

View File

@ -22,6 +22,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) {
query := struct { query := struct {
Force bool `schema:"force"` Force bool `schema:"force"`
NoPrune bool `schema:"noprune"` NoPrune bool `schema:"noprune"`
Ignore bool `schema:"ignore"`
}{ }{
// This is where you can override the golang default value for one of fields // This is where you can override the golang default value for one of fields
} }
@ -42,6 +43,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) {
options := entities.ImageRemoveOptions{ options := entities.ImageRemoveOptions{
Force: query.Force, Force: query.Force,
NoPrune: query.NoPrune, NoPrune: query.NoPrune,
Ignore: query.Ignore,
} }
report, rmerrors := imageEngine.Remove(r.Context(), []string{possiblyNormalizedName}, options) report, rmerrors := imageEngine.Remove(r.Context(), []string{possiblyNormalizedName}, options)
if len(rmerrors) > 0 && rmerrors[0] != nil { if len(rmerrors) > 0 && rmerrors[0] != nil {

View File

@ -668,6 +668,7 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) {
query := struct { query := struct {
Force bool `schema:"force"` Force bool `schema:"force"`
LookupManifest bool `schema:"lookupManifest"` LookupManifest bool `schema:"lookupManifest"`
Ignore bool `schema:"ignore"`
}{ }{
Force: false, Force: false,
} }
@ -677,7 +678,7 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) {
return return
} }
opts := entities.ImageRemoveOptions{Force: query.Force, LookupManifest: query.LookupManifest} opts := entities.ImageRemoveOptions{Force: query.Force, LookupManifest: query.LookupManifest, Ignore: query.Ignore}
imageEngine := abi.ImageEngine{Libpod: runtime} imageEngine := abi.ImageEngine{Libpod: runtime}
rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts) rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts)

View File

@ -745,20 +745,43 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) {
// ManifestDelete removes a manifest list from storage // ManifestDelete removes a manifest list from storage
func ManifestDelete(w http.ResponseWriter, r *http.Request) { func ManifestDelete(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
imageEngine := abi.ImageEngine{Libpod: runtime} imageEngine := abi.ImageEngine{Libpod: runtime}
name := utils.GetName(r) query := struct {
if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { Ignore bool `schema:"ignore"`
utils.Error(w, http.StatusNotFound, err) }{
return // Add defaults here once needed.
} }
results, errs := imageEngine.ManifestRm(r.Context(), []string{name}) if err := decoder.Decode(&query, r.URL.Query()); err != nil {
errsString := errorhandling.ErrorsToStrings(errs) utils.Error(w, http.StatusBadRequest,
report := handlers.LibpodImagesRemoveReport{ fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
ImageRemoveReport: *results, return
Errors: errsString, }
opts := entities.ImageRemoveOptions{
Ignore: query.Ignore,
}
name := utils.GetName(r)
rmReport, rmErrors := imageEngine.ManifestRm(r.Context(), []string{name}, opts)
// In contrast to batch-removal, where we're only setting the exit
// code, we need to have another closer look at the errors here and set
// the appropriate http status code.
switch rmReport.ExitCode {
case 0:
report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: []string{}}
utils.WriteResponse(w, http.StatusOK, report)
case 1:
// 404 - no such image
utils.Error(w, http.StatusNotFound, errorhandling.JoinErrors(rmErrors))
case 2:
// 409 - conflict error (in use by containers)
utils.Error(w, http.StatusConflict, errorhandling.JoinErrors(rmErrors))
default:
// 500 - internal error
utils.Error(w, http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors))
} }
utils.WriteResponse(w, http.StatusOK, report)
} }

View File

@ -322,6 +322,10 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
// type: string // type: string
// required: true // required: true
// description: The name or ID of the list to be deleted // description: The name or ID of the list to be deleted
// - in: query
// name: ignore
// description: Ignore if a specified manifest does not exist and do not throw an error.
// type: boolean
// responses: // responses:
// 200: // 200:
// $ref: "#/responses/imagesRemoveResponseLibpod" // $ref: "#/responses/imagesRemoveResponseLibpod"

View File

@ -45,7 +45,7 @@ type ImageEngine interface { //nolint:interfacebloat
ManifestAddArtifact(ctx context.Context, name string, files []string, opts ManifestAddArtifactOptions) (string, error) ManifestAddArtifact(ctx context.Context, name string, files []string, opts ManifestAddArtifactOptions) (string, error)
ManifestAnnotate(ctx context.Context, names, image string, opts ManifestAnnotateOptions) (string, error) ManifestAnnotate(ctx context.Context, names, image string, opts ManifestAnnotateOptions) (string, error)
ManifestRemoveDigest(ctx context.Context, names, image string) (string, error) ManifestRemoveDigest(ctx context.Context, names, image string) (string, error)
ManifestRm(ctx context.Context, names []string) (*ImageRemoveReport, []error) ManifestRm(ctx context.Context, names []string, imageRmOpts ImageRemoveOptions) (*ImageRemoveReport, []error)
ManifestPush(ctx context.Context, name, destination string, imagePushOpts ImagePushOptions) (string, error) ManifestPush(ctx context.Context, name, destination string, imagePushOpts ImagePushOptions) (string, error)
ManifestListClear(ctx context.Context, name string) (string, error) ManifestListClear(ctx context.Context, name string) (string, error)
Sign(ctx context.Context, names []string, options SignOptions) (*SignReport, error) Sign(ctx context.Context, names []string, options SignOptions) (*SignReport, error)

View File

@ -460,8 +460,8 @@ func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name, image str
} }
// ManifestRm removes the specified manifest list from storage // ManifestRm removes the specified manifest list from storage
func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string) (report *entities.ImageRemoveReport, rmErrors []error) { func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) {
return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true}) return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true, Ignore: opts.Ignore})
} }
// ManifestPush pushes a manifest list or image index to the destination // ManifestPush pushes a manifest list or image index to the destination

View File

@ -178,8 +178,8 @@ func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name string, im
} }
// ManifestRm removes the specified manifest list from storage // ManifestRm removes the specified manifest list from storage
func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string) (*entities.ImageRemoveReport, []error) { func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) {
return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true}) return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true, Ignore: opts.Ignore})
} }
// ManifestPush pushes a manifest list or image index to the destination // ManifestPush pushes a manifest list or image index to the destination

View File

@ -66,6 +66,9 @@ t POST "/v4.0.0/libpod/manifests/xyz:latest/registry/localhost:$REGISTRY_PORT%2F
# /v3.x cannot delete a manifest list # /v3.x cannot delete a manifest list
t DELETE /v4.0.0/libpod/manifests/$id_abc 200 t DELETE /v4.0.0/libpod/manifests/$id_abc 200
t DELETE /v4.0.0/libpod/manifests/$id_xyz 200 t DELETE /v4.0.0/libpod/manifests/$id_xyz 200
t GET /v4.0.0/libpod/manifests/$id_xyz/exists 404
t DELETE /v4.0.0/libpod/manifests/$id_xyz 404
t DELETE /v4.0.0/libpod/manifests/$id_xyz?ignore=true 200
# manifest add --artifact tests # manifest add --artifact tests
truncate -s 20M $WORKDIR/zeroes truncate -s 20M $WORKDIR/zeroes

View File

@ -94,6 +94,10 @@ function validate_instance_compression {
--tls-verify=false $mid \ --tls-verify=false $mid \
$manifest1 $manifest1
run_podman manifest rm $manifest1 run_podman manifest rm $manifest1
run_podman 1 manifest rm $manifest1
is "$output" "Error: $manifest1: image not known" "Missing manifest is reported"
run_podman manifest rm --ignore $manifest1
is "$output" "" "Missing manifest is ignored"
# Default is to require TLS; also test explicit opts # Default is to require TLS; also test explicit opts
for opt in '' '--insecure=false' '--tls-verify=true' "--authfile=$authfile"; do for opt in '' '--insecure=false' '--tls-verify=true' "--authfile=$authfile"; do