1
0
mirror of https://github.com/containers/podman.git synced 2026-02-05 06:45:31 +01:00

Add DELETE /libpod/quadlets

Fixes: https://issues.redhat.com/browse/RUN-3742

Signed-off-by: Nicola Sella <nsella@redhat.com>
This commit is contained in:
Nicola Sella
2025-12-16 19:43:49 +01:00
parent 7c9d4a42c5
commit 2e23fcc5a5
6 changed files with 279 additions and 2 deletions

View File

@@ -9,10 +9,12 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"go.podman.io/storage/pkg/archive"
"github.com/containers/podman/v6/libpod"
"github.com/containers/podman/v6/libpod/define"
"github.com/containers/podman/v6/pkg/api/handlers/utils"
api "github.com/containers/podman/v6/pkg/api/types"
"github.com/containers/podman/v6/pkg/domain/entities"
@@ -238,3 +240,115 @@ func InstallQuadlets(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, installReport)
}
// RemoveQuadlet handles DELETE /libpod/quadlets/{name} to remove a quadlet file
func RemoveQuadlet(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
Force bool `schema:"force"`
Ignore bool `schema:"ignore"`
ReloadSystemd bool `schema:"reload-systemd"`
}{
ReloadSystemd: true, // Default to true like CLI
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return
}
name := utils.GetName(r)
if name == "" {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("quadlet name must be provided"))
return
}
containerEngine := abi.ContainerEngine{Libpod: runtime}
removeOptions := entities.QuadletRemoveOptions{
Force: query.Force,
Ignore: query.Ignore,
ReloadSystemd: query.ReloadSystemd,
}
removeReport, err := containerEngine.QuadletRemove(r.Context(), []string{name}, removeOptions)
if err != nil {
// For systemd connection errors and other internal errors
utils.InternalServerError(w, err)
return
}
// Check if there are errors in the report for this specific quadlet
if err, ok := removeReport.Errors[name]; ok {
// If ignore=false and quadlet not found, return 404
if !query.Ignore && strings.Contains(err.Error(), "no such") {
utils.Error(w, http.StatusNotFound, fmt.Errorf("no such quadlet: %s: %w", name, err))
return
}
// If force=false and quadlet is running, return 400
if !query.Force && errors.Is(err, define.ErrQuadletRunning) {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("quadlet %s is running and force is not set, refusing to remove: %w", name, err))
return
}
}
utils.WriteResponse(w, http.StatusOK, removeReport)
}
// RemoveQuadlets handles DELETE /libpod/quadlets to remove quadlet files (batch operation)
func RemoveQuadlets(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
All bool `schema:"all"`
Force bool `schema:"force"`
Ignore bool `schema:"ignore"`
ReloadSystemd bool `schema:"reload-systemd"`
Quadlets []string `schema:"quadlets"`
}{
ReloadSystemd: true, // Default to true like CLI
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return
}
// Validate that either all=true OR at least one quadlet name is provided
if !query.All && len(query.Quadlets) == 0 {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("must provide at least 1 quadlet to remove or set all=true"))
return
}
// Validate that both all and quadlets are not provided together
if query.All && len(query.Quadlets) > 0 {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("when setting all=true, you may not pass any quadlet names"))
return
}
containerEngine := abi.ContainerEngine{Libpod: runtime}
removeOptions := entities.QuadletRemoveOptions{
Force: query.Force,
All: query.All,
Ignore: query.Ignore,
ReloadSystemd: query.ReloadSystemd,
}
removeReport, err := containerEngine.QuadletRemove(r.Context(), query.Quadlets, removeOptions)
if err != nil {
// Check if it's a "must provide at least 1 quadlet" error (shouldn't happen due to validation above, but handle it)
if strings.Contains(err.Error(), "must provide at least 1 quadlet") {
utils.Error(w, http.StatusBadRequest, err)
return
}
// For systemd connection errors and other internal errors
utils.InternalServerError(w, err)
return
}
// Return 200 with the report containing errors (if any)
// The CLI behavior returns success even with partial errors
utils.WriteResponse(w, http.StatusOK, removeReport)
}

View File

@@ -534,3 +534,10 @@ type quadletFileResponse struct {
// in:body
Body string
}
// Quadlet remove
// swagger:response
type quadletRemoveResponse struct {
// in:body
Body entities.QuadletRemoveReport
}

View File

@@ -109,5 +109,90 @@ func (s *APIServer) registerQuadletHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/quadlets"), s.APIHandler(libpod.InstallQuadlets)).Methods(http.MethodPost)
// swagger:operation DELETE /libpod/quadlets libpod QuadletDeleteAllLibpod
// ---
// tags:
// - quadlets
// summary: Remove quadlet files (batch operation)
// description: |
// Remove one or more quadlet files. Supports removing specific quadlets by name or all quadlets
// for the current user. Can force removal of running quadlets and control systemd reload behavior.
// produces:
// - application/json
// parameters:
// - in: query
// name: quadlets
// type: array
// items:
// type: string
// description: Names of quadlets to remove (e.g., "myapp.container"). Required unless all=true
// - in: query
// name: all
// type: boolean
// default: false
// description: Remove all quadlets for the current user
// - in: query
// name: force
// type: boolean
// default: false
// description: Remove running quadlets by stopping them first
// - in: query
// name: ignore
// type: boolean
// default: false
// description: Do not error for quadlets that do not exist
// - in: query
// name: reload-systemd
// type: boolean
// default: true
// description: Reload systemd after removing quadlets
// responses:
// 200:
// $ref: "#/responses/quadletRemoveResponse"
// 400:
// $ref: "#/responses/badParamError"
// 500:
// $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/quadlets"), s.APIHandler(libpod.RemoveQuadlets)).Methods(http.MethodDelete)
// swagger:operation DELETE /libpod/quadlets/{name} libpod QuadletDeleteLibpod
// ---
// tags:
// - quadlets
// summary: Remove a quadlet file
// description: |
// Remove a quadlet file by name. Can force removal of running quadlets and control systemd reload behavior.
// produces:
// - application/json
// parameters:
// - in: path
// name: name
// type: string
// required: true
// description: the name of the quadlet with extension (e.g., "myapp.container")
// - in: query
// name: force
// type: boolean
// default: false
// description: Remove running quadlet by stopping it first
// - in: query
// name: ignore
// type: boolean
// default: false
// description: Do not error if the quadlet does not exist
// - in: query
// name: reload-systemd
// type: boolean
// default: true
// description: Reload systemd after removing the quadlet
// responses:
// 200:
// $ref: "#/responses/quadletRemoveResponse"
// 400:
// $ref: "#/responses/badParamError"
// 404:
// $ref: "#/responses/quadletNotFound"
// 500:
// $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/quadlets/{name}"), s.APIHandler(libpod.RemoveQuadlet)).Methods(http.MethodDelete)
return nil
}

View File

@@ -17,6 +17,7 @@ import (
"slices"
"strings"
"github.com/containers/podman/v6/libpod/define"
"github.com/containers/podman/v6/pkg/domain/entities"
"github.com/containers/podman/v6/pkg/rootless"
"github.com/containers/podman/v6/pkg/systemd"
@@ -896,7 +897,7 @@ func (ic *ContainerEngine) QuadletRemove(ctx context.Context, quadlets []string,
needReload = options.ReloadSystemd
if unitStatus.ActiveState == "active" {
if !options.Force {
report.Errors[quadletName] = fmt.Errorf("quadlet %s is running and force is not set, refusing to remove", quadletName)
report.Errors[quadletName] = fmt.Errorf("quadlet %s is running and force is not set, refusing to remove: %w", quadletName, define.ErrQuadletRunning)
runningQuadlets = append(runningQuadlets, quadletName)
continue
}