b6ba9b46c7
Adds POST /api/webhooks/container-automation/{token} which kicks a full
auto-update pass immediately, instead of waiting up to PollInterval (6h) for the
poll. Registry-agnostic 'general kick': the payload is not parsed; the pass
compares digests and updates only stale containers. Polling stays as a fallback.
- Service.TriggerUpdate(): a buffer-1 non-blocking kick channel + a worker
goroutine on baseCtx that debounces ~10s (collapsing a multi-arch/multi-tag
push burst into one pass). update() is split into runUpdatePass() bool, which
returns false when the updateRunning CAS is not acquired; the worker backs off
and re-runs so a kick landing during a scheduled pass is never lost. The poll
timer keeps calling the same pass.
- Worker panics are recovered (poll parity — the poll path runs under the
scheduler's cron.Recover), so a panic in the pass cannot crash the daemon from
this bare goroutine. runUpdatePass also re-checks AutoUpdate.Enabled as
defense-in-depth against a disable during the debounce window.
- Route via bouncer.PublicAccess (distinct from /webhooks/{token} by segment
count); token compared with subtle.ConstantTimeCompare; empty/mismatched token
-> 404, disabled auto-update -> 409, success -> 202. Token is server-generated
(uuid) via RegenerateWebhookToken / ClearWebhookToken settings actions; never
accepted from the client; excluded from /settings/public (present in admin GET
for the UI).
- Frontend: an AutoUpdatePanel 'Update trigger webhook' section (readonly URL,
copy, Generate/Regenerate, Clear, registry-setup hint).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
59 lines
2.3 KiB
Go
59 lines
2.3 KiB
Go
package webhooks
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/portainer/portainer/api/dataservices"
|
|
dockerclient "github.com/portainer/portainer/api/docker/client"
|
|
"github.com/portainer/portainer/api/http/security"
|
|
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// ContainerAutomationTrigger triggers an immediate container auto-update pass. It
|
|
// is a minimal interface (mirroring the settings handler's
|
|
// ContainerAutomationReloader) so the webhooks handler does not depend on the
|
|
// concrete container-automation service. It is satisfied by
|
|
// *containerautomation.Service.
|
|
type ContainerAutomationTrigger interface {
|
|
TriggerUpdate()
|
|
}
|
|
|
|
// Handler is the HTTP handler used to handle webhook operations.
|
|
type Handler struct {
|
|
*mux.Router
|
|
requestBouncer security.BouncerService
|
|
DataStore dataservices.DataStore
|
|
DockerClientFactory *dockerclient.ClientFactory
|
|
// ContainerAutomationService receives the inbound registry-push kick that
|
|
// triggers an immediate auto-update pass. It may be nil (the endpoint then
|
|
// reports the feature as unavailable rather than panicking).
|
|
ContainerAutomationService ContainerAutomationTrigger
|
|
}
|
|
|
|
// NewHandler creates a handler to manage webhooks operations.
|
|
func NewHandler(bouncer security.BouncerService) *Handler {
|
|
h := &Handler{
|
|
Router: mux.NewRouter(),
|
|
requestBouncer: bouncer,
|
|
}
|
|
h.Handle("/webhooks",
|
|
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookCreate))).Methods(http.MethodPost)
|
|
h.Handle("/webhooks/{id}",
|
|
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookUpdate))).Methods(http.MethodPut)
|
|
h.Handle("/webhooks",
|
|
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookList))).Methods(http.MethodGet)
|
|
h.Handle("/webhooks/{id}",
|
|
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookDelete))).Methods(http.MethodDelete)
|
|
h.Handle("/webhooks/{token}",
|
|
bouncer.PublicAccess(httperror.LoggerHandler(h.webhookExecute))).Methods(http.MethodPost)
|
|
// Inbound registry-push trigger for native container auto-update. The extra path
|
|
// segment ("container-automation") keeps it distinct from the resource webhook
|
|
// above, which matches a single {token} segment.
|
|
h.Handle("/webhooks/container-automation/{token}",
|
|
bouncer.PublicAccess(httperror.LoggerHandler(h.webhookContainerAutomation))).Methods(http.MethodPost)
|
|
|
|
return h
|
|
}
|