Merge pull request #27025 from StefanNienhuis/feat/api-autoupdate

feat: Implement autoupdate endpoint in libpod REST API
This commit is contained in:
Jan Rodák 2026-09-17 14:32:57 +02:00 • committed by GitHub
commit 9de95641de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 325 additions and 4 deletions

View file

@ -31,7 +31,6 @@ var (
or similar units that create new containers in order to run the updated images.
Please refer to the podman-auto-update(1) man page for details.`
autoUpdateCommand = &cobra.Command{
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
Use: "auto-update [options]",
Short: "Auto update containers according to their auto-update policy",
Long: autoUpdateDescription,

View file

@ -424,6 +424,11 @@ sub operation_name {
$main = 'image';
$action = $1;
}
# Top-level autoupdate endpoint
elsif ($main eq 'autoupdate') {
$main = 'autoupdate';
$action = '';
}
# Top-level system endpoints
elsif ($main =~ /^(auth|event|info|version)$/) {
$main = 'system';

View file

@ -0,0 +1,81 @@
//go:build !remote && (linux || freebsd)
package libpod
import (
"fmt"
"net/http"
"github.com/gorilla/schema"
"go.podman.io/image/v5/types"
"go.podman.io/podman/v6/libpod"
"go.podman.io/podman/v6/pkg/api/handlers"
"go.podman.io/podman/v6/pkg/api/handlers/utils"
api "go.podman.io/podman/v6/pkg/api/types"
"go.podman.io/podman/v6/pkg/auth"
"go.podman.io/podman/v6/pkg/domain/entities"
"go.podman.io/podman/v6/pkg/domain/infra/abi"
"go.podman.io/podman/v6/pkg/errorhandling"
)
func AutoUpdate(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {
DryRun bool `schema:"dryRun"`
Rollback bool `schema:"rollback"`
TLSVerify types.OptionalBool `schema:"tlsVerify"`
}{
Rollback: true,
}
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
}
_, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.AutoUpdateOptions{
Authfile: authfile,
DryRun: query.DryRun,
Rollback: query.Rollback,
InsecureSkipTLSVerify: types.OptionalBoolUndefined,
}
// If TLS verification is explicitly specified (True or False) in the query,
// set the InsecureSkipTLSVerify option accordingly.
// If TLSVerify was not set in the query, OptionalBoolUndefined is used and
// handled later based off the target registry configuration.
switch query.TLSVerify {
case types.OptionalBoolTrue:
options.InsecureSkipTLSVerify = types.NewOptionalBool(false)
case types.OptionalBoolFalse:
options.InsecureSkipTLSVerify = types.NewOptionalBool(true)
case types.OptionalBoolUndefined:
// If the user doesn't define TLSVerify in the query, do nothing and pass
// it to the backend code to handle.
default: // Should never happen
panic("Unexpected handling occurred for TLSVerify")
}
autoUpdateReports, autoUpdateFailures := containerEngine.AutoUpdate(r.Context(), options)
if autoUpdateReports == nil {
if err := errorhandling.JoinErrors(autoUpdateFailures); err != nil {
utils.Error(w, http.StatusInternalServerError, err)
return
}
}
reports := handlers.LibpodAutoUpdateReports{Reports: autoUpdateReports, Errors: errorhandling.ErrorsToStrings(autoUpdateFailures)}
utils.WriteResponse(w, http.StatusOK, reports)
}

View file

@ -532,3 +532,10 @@ type quadletRemoveResponse struct {
// in:body
Body entities.QuadletRemoveReport
}
// Auto Update
// swagger:response
type autoupdateResponse struct {
// in:body
Body handlers.LibpodAutoUpdateReports
}

View file

@ -249,3 +249,10 @@ type ExecStartConfig struct {
type ExecRemoveConfig struct {
Force bool `json:"Force"`
}
// LibpodAutoUpdateReport is the return type for auto update via the rest api.
type LibpodAutoUpdateReports struct {
Reports []*entities.AutoUpdateReport
// Auto update returns data and possible errors.
Errors []string
}

View file

@ -0,0 +1,52 @@
//go:build !remote
package server
import (
"net/http"
"github.com/gorilla/mux"
"go.podman.io/podman/v6/pkg/api/handlers/libpod"
)
func (s *APIServer) registerAutoUpdateHandlers(r *mux.Router) error {
// swagger:operation POST /libpod/autoupdate libpod AutoupdateLibpod
// ---
// tags:
// - autoupdate
// summary: Auto update
// description: |
// Auto update containers according to their auto-update policy.
//
// Auto-update policies are specified with the "io.containers.autoupdate" label.
// Containers are expected to run in systemd units created with "podman-generate-systemd --new",
// or similar units that create new containers in order to run the updated images.
// Please refer to the podman-auto-update(1) man page for details.
// parameters:
// - in: query
// name: authfile
// type: string
// description: Authfile to use when contacting registries.
// - in: query
// name: dryRun
// type: boolean
// description: Only check for but do not perform any update. If an update is pending, it will be indicated in the Updated field.
// - in: query
// name: rollback
// type: boolean
// description: If restarting the service with the new image failed, restart it another time with the previous image.
// - in: query
// name: tlsVerify
// type: boolean
// default: true
// description: Require HTTPS and verify signatures when contacting registries.
// produces:
// - application/json
// responses:
// 200:
// $ref: "#/responses/autoupdateResponse"
// 500:
// $ref: '#/responses/internalError'
r.HandleFunc(VersionedPath("/libpod/autoupdate"), s.APIHandler(libpod.AutoUpdate)).Methods(http.MethodPost)
return nil
}

View file

@ -148,6 +148,7 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser
server.registerAuthHandlers,
server.registerArtifactHandlers,
server.registerArchiveHandlers,
server.registerAutoUpdateHandlers,
server.registerContainersHandlers,
server.registerDistributionHandlers,
server.registerEventsHandlers,

View file

@ -1,6 +1,8 @@
tags:
- name: artifacts
description: Actions related to artifacts
- name: autoupdate
description: Actions related to auto update
- name: containers
description: Actions related to containers
- name: exec

View file

@ -0,0 +1,54 @@
package autoupdate
import (
"context"
"net/http"
"strconv"
imageTypes "go.podman.io/image/v5/types"
"go.podman.io/podman/v6/pkg/api/handlers"
"go.podman.io/podman/v6/pkg/auth"
"go.podman.io/podman/v6/pkg/bindings"
"go.podman.io/podman/v6/pkg/domain/entities"
"go.podman.io/podman/v6/pkg/errorhandling"
)
func AutoUpdate(ctx context.Context, options *AutoUpdateOptions) ([]*entities.AutoUpdateReport, []error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, []error{err}
}
if options == nil {
options = new(AutoUpdateOptions)
}
params, err := options.ToParams()
if err != nil {
return nil, []error{err}
}
// InsecureSkipTLSVerify is special. We need to delete the param added by
// ToParams() and change the key and flip the bool
if options.InsecureSkipTLSVerify != nil {
params.Del("SkipTLSVerify")
params.Set("tlsVerify", strconv.FormatBool(!options.GetInsecureSkipTLSVerify()))
}
header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, "", "")
if err != nil {
return nil, []error{err}
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/autoupdate", params, header)
if err != nil {
return nil, []error{err}
}
defer response.Body.Close()
var reports handlers.LibpodAutoUpdateReports
if err := response.Process(&reports); err != nil {
return nil, []error{err}
}
return reports.Reports, errorhandling.StringsToErrors(reports.Errors)
}

View file

@ -0,0 +1,19 @@
package autoupdate
// AutoUpdateOptions are the options for running auto-update
//
//go:generate go run ../generator/generator.go AutoUpdateOptions
type AutoUpdateOptions struct {
// Authfile to use when contacting registries.
Authfile *string
// Only check for but do not perform any update. If an update is
// pending, it will be indicated in the Updated field of
// AutoUpdateReport.
DryRun *bool
// If restarting the service with the new image failed, restart it
// another time with the previous image.
Rollback *bool
// Allow contacting registries over HTTP, or HTTPS with failed TLS
// verification. Note that this does not affect other TLS connections.
InsecureSkipTLSVerify *bool
}

View file

@ -0,0 +1,78 @@
// Code generated by go generate; DO NOT EDIT.
package autoupdate
import (
"net/url"
"go.podman.io/podman/v6/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *AutoUpdateOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *AutoUpdateOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
// WithAuthfile set field Authfile to given value
func (o *AutoUpdateOptions) WithAuthfile(value string) *AutoUpdateOptions {
o.Authfile = &value
return o
}
// GetAuthfile returns value of field Authfile
func (o *AutoUpdateOptions) GetAuthfile() string {
if o.Authfile == nil {
var z string
return z
}
return *o.Authfile
}
// WithDryRun set field DryRun to given value
func (o *AutoUpdateOptions) WithDryRun(value bool) *AutoUpdateOptions {
o.DryRun = &value
return o
}
// GetDryRun returns value of field DryRun
func (o *AutoUpdateOptions) GetDryRun() bool {
if o.DryRun == nil {
var z bool
return z
}
return *o.DryRun
}
// WithRollback set field Rollback to given value
func (o *AutoUpdateOptions) WithRollback(value bool) *AutoUpdateOptions {
o.Rollback = &value
return o
}
// GetRollback returns value of field Rollback
func (o *AutoUpdateOptions) GetRollback() bool {
if o.Rollback == nil {
var z bool
return z
}
return *o.Rollback
}
// WithInsecureSkipTLSVerify set field InsecureSkipTLSVerify to given value
func (o *AutoUpdateOptions) WithInsecureSkipTLSVerify(value bool) *AutoUpdateOptions {
o.InsecureSkipTLSVerify = &value
return o
}
// GetInsecureSkipTLSVerify returns value of field InsecureSkipTLSVerify
func (o *AutoUpdateOptions) GetInsecureSkipTLSVerify() bool {
if o.InsecureSkipTLSVerify == nil {
var z bool
return z
}
return *o.InsecureSkipTLSVerify
}

View file

@ -2,11 +2,21 @@ package tunnel
import (
"context"
"errors"
"go.podman.io/image/v5/types"
autoupdate "go.podman.io/podman/v6/pkg/bindings/auto-update"
"go.podman.io/podman/v6/pkg/domain/entities"
)
func (ic *ContainerEngine) AutoUpdate(_ context.Context, _ entities.AutoUpdateOptions) ([]*entities.AutoUpdateReport, []error) {
return nil, []error{errors.New("not implemented")}
func (ic *ContainerEngine) AutoUpdate(_ context.Context, opts entities.AutoUpdateOptions) ([]*entities.AutoUpdateReport, []error) {
options := new(autoupdate.AutoUpdateOptions).WithAuthfile(opts.Authfile).WithDryRun(opts.DryRun).WithRollback(opts.Rollback)
if s := opts.InsecureSkipTLSVerify; s != types.OptionalBoolUndefined {
if s == types.OptionalBoolTrue {
options.WithInsecureSkipTLSVerify(true)
} else {
options.WithInsecureSkipTLSVerify(false)
}
}
return autoupdate.AutoUpdate(ic.ClientCtx, options)
}

View file

@ -0,0 +1,6 @@
# -*- sh -*-
#
# test 'autoupdate' endpoint
#
t POST libpod/autoupdate 200