mirror of
https://github.com/containrrr/watchtower.git
synced 2025-12-16 15:10:12 +01:00
feat(api): implement new api handler
This commit is contained in:
parent
72e437f173
commit
47091761a5
17 changed files with 571 additions and 294 deletions
119
pkg/api/api.go
119
pkg/api/api.go
|
|
@ -1,8 +1,14 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/containrrr/watchtower/pkg/api/metrics"
|
||||
"github.com/containrrr/watchtower/pkg/api/middleware"
|
||||
"github.com/containrrr/watchtower/pkg/api/prelude"
|
||||
"github.com/containrrr/watchtower/pkg/api/updates"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
|
@ -11,46 +17,52 @@ const tokenMissingMsg = "api token is empty or has not been set. exiting"
|
|||
|
||||
// API is the http server responsible for serving the HTTP API endpoints
|
||||
type API struct {
|
||||
Token string
|
||||
hasHandlers bool
|
||||
Token string
|
||||
hasHandlers bool
|
||||
mux *http.ServeMux
|
||||
server *http.Server
|
||||
running *sync.Mutex
|
||||
router router
|
||||
authMiddleware prelude.Middleware
|
||||
registered bool
|
||||
}
|
||||
|
||||
// New is a factory function creating a new API instance
|
||||
func New(token string) *API {
|
||||
return &API{
|
||||
Token: token,
|
||||
hasHandlers: false,
|
||||
Token: token,
|
||||
hasHandlers: false,
|
||||
mux: http.NewServeMux(),
|
||||
running: &sync.Mutex{},
|
||||
router: router{},
|
||||
authMiddleware: middleware.RequireToken(token),
|
||||
registered: false,
|
||||
}
|
||||
}
|
||||
|
||||
// RequireToken is wrapper around http.HandleFunc that checks token validity
|
||||
func (api *API) RequireToken(fn http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
want := fmt.Sprintf("Bearer %s", api.Token)
|
||||
if auth != want {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
func (api *API) route(route string) methodHandlers {
|
||||
return api.router.route(route)
|
||||
}
|
||||
|
||||
func (api *API) registerHandlers() {
|
||||
if api.registered {
|
||||
return
|
||||
}
|
||||
for path, route := range api.router {
|
||||
if len(route) < 1 {
|
||||
continue
|
||||
}
|
||||
log.Debug("Valid token found.")
|
||||
fn(w, r)
|
||||
api.hasHandlers = true
|
||||
api.mux.Handle(path, api.authMiddleware(route.Handler))
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterFunc is a wrapper around http.HandleFunc that also sets the flag used to determine whether to launch the API
|
||||
func (api *API) RegisterFunc(path string, fn http.HandlerFunc) {
|
||||
api.hasHandlers = true
|
||||
http.HandleFunc(path, api.RequireToken(fn))
|
||||
}
|
||||
|
||||
// RegisterHandler is a wrapper around http.Handler that also sets the flag used to determine whether to launch the API
|
||||
func (api *API) RegisterHandler(path string, handler http.Handler) {
|
||||
api.hasHandlers = true
|
||||
http.Handle(path, api.RequireToken(handler.ServeHTTP))
|
||||
api.registered = true
|
||||
return
|
||||
}
|
||||
|
||||
// Start the API and serve over HTTP. Requires an API Token to be set.
|
||||
func (api *API) Start(block bool) error {
|
||||
func (api *API) Start() error {
|
||||
|
||||
api.registerHandlers()
|
||||
|
||||
if !api.hasHandlers {
|
||||
log.Debug("Watchtower HTTP API skipped.")
|
||||
|
|
@ -61,16 +73,49 @@ func (api *API) Start(block bool) error {
|
|||
log.Fatal(tokenMissingMsg)
|
||||
}
|
||||
|
||||
if block {
|
||||
runHTTPServer()
|
||||
} else {
|
||||
go func() {
|
||||
runHTTPServer()
|
||||
}()
|
||||
}
|
||||
api.running.Lock()
|
||||
go func() {
|
||||
defer api.running.Unlock()
|
||||
api.server = &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: api.mux,
|
||||
}
|
||||
|
||||
if err := api.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Errorf("HTTP Server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runHTTPServer() {
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
// Stop tells the api server to shut down (if its running) and returns a sync.Mutex that is locked
|
||||
// until the server has handled all remaining requests and shut down
|
||||
func (api *API) Stop() *sync.Mutex {
|
||||
|
||||
if api.server != nil {
|
||||
go func() {
|
||||
if err := api.server.Shutdown(context.Background()); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Errorf("Error stopping HTTP Server: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return api.running
|
||||
}
|
||||
|
||||
// Handler is used to get a http.Handler for testing
|
||||
func (api *API) Handler() http.Handler {
|
||||
api.registerHandlers()
|
||||
return api.mux
|
||||
}
|
||||
|
||||
// EnableUpdates registers the `updates` endpoints
|
||||
func (api *API) EnableUpdates(f updates.InvokedFunc, updateLock *sync.Mutex) {
|
||||
api.route("/v1/updates").post(updates.PostV1(f, updateLock))
|
||||
}
|
||||
|
||||
// EnableMetrics registers the `metrics` endpoints
|
||||
func (api *API) EnableMetrics() {
|
||||
api.route("/v1/metrics").get(metrics.GetV1())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,14 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
. "github.com/containrrr/watchtower/pkg/api/prelude"
|
||||
"github.com/containrrr/watchtower/pkg/metrics"
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// Handler is an HTTP handle for serving metric data
|
||||
type Handler struct {
|
||||
Path string
|
||||
Handle http.HandlerFunc
|
||||
Metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
// New is a factory function creating a new Metrics instance
|
||||
func New() *Handler {
|
||||
m := metrics.Default()
|
||||
handler := promhttp.Handler()
|
||||
|
||||
return &Handler{
|
||||
Path: "/v1/metrics",
|
||||
Handle: handler.ServeHTTP,
|
||||
Metrics: m,
|
||||
}
|
||||
// GetV1 creates a new metrics http handler
|
||||
func GetV1() HandlerFunc {
|
||||
// Initialize watchtower metrics
|
||||
metrics.Init()
|
||||
return WrapHandler(promhttp.Handler().ServeHTTP)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containrrr/watchtower/pkg/api"
|
||||
metricsAPI "github.com/containrrr/watchtower/pkg/api/metrics"
|
||||
"github.com/containrrr/watchtower/pkg/metrics"
|
||||
)
|
||||
|
||||
|
|
@ -51,10 +50,9 @@ func getWithToken(handler http.Handler) map[string]string {
|
|||
|
||||
var _ = Describe("the metrics API", func() {
|
||||
httpAPI := api.New(token)
|
||||
m := metricsAPI.New()
|
||||
httpAPI.EnableMetrics()
|
||||
|
||||
handleReq := httpAPI.RequireToken(m.Handle)
|
||||
tryGetMetrics := func() map[string]string { return getWithToken(handleReq) }
|
||||
tryGetMetrics := func() map[string]string { return getWithToken(httpAPI.Handler()) }
|
||||
|
||||
It("should serve metrics", func() {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package api
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"github.com/containrrr/watchtower/pkg/api/prelude"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -11,55 +11,58 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
token = "123123123"
|
||||
token = "123123123"
|
||||
)
|
||||
|
||||
func TestAPI(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "API Suite")
|
||||
RunSpecs(t, "Middleware Suite")
|
||||
}
|
||||
|
||||
var _ = Describe("API", func() {
|
||||
api := New(token)
|
||||
requireToken := RequireToken(token)
|
||||
|
||||
Describe("RequireToken middleware", func() {
|
||||
It("should return 401 Unauthorized when token is not provided", func() {
|
||||
handlerFunc := api.RequireToken(testHandler)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/hello", nil)
|
||||
|
||||
handlerFunc(rec, req)
|
||||
requireToken(testHandler).ServeHTTP(rec, req)
|
||||
|
||||
Expect(rec.Code).To(Equal(http.StatusUnauthorized))
|
||||
Expect(rec.Body).To(MatchJSON(`{
|
||||
"code": "MISSING_TOKEN",
|
||||
"error": "No authentication token was supplied"
|
||||
}`))
|
||||
})
|
||||
|
||||
It("should return 401 Unauthorized when token is invalid", func() {
|
||||
handlerFunc := api.RequireToken(testHandler)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/hello", nil)
|
||||
req.Header.Set("Authorization", "Bearer 123")
|
||||
|
||||
handlerFunc(rec, req)
|
||||
requireToken(testHandler).ServeHTTP(rec, req)
|
||||
|
||||
Expect(rec.Code).To(Equal(http.StatusUnauthorized))
|
||||
Expect(rec.Body).To(MatchJSON(`{
|
||||
"code": "INVALID_TOKEN",
|
||||
"error": "The supplied token does not match the configured auth token"
|
||||
}`))
|
||||
})
|
||||
|
||||
It("should return 200 OK when token is valid", func() {
|
||||
handlerFunc := api.RequireToken(testHandler)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/hello", nil)
|
||||
req.Header.Set("Authorization", "Bearer " + token)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
handlerFunc(rec, req)
|
||||
requireToken(testHandler).ServeHTTP(rec, req)
|
||||
|
||||
Expect(rec.Code).To(Equal(http.StatusOK))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func testHandler(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = io.WriteString(w, "Hello!")
|
||||
func testHandler(_ *prelude.Context) prelude.Response {
|
||||
return prelude.OK("Hello!")
|
||||
}
|
||||
24
pkg/api/middleware/require_token.go
Normal file
24
pkg/api/middleware/require_token.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/containrrr/watchtower/pkg/api/prelude"
|
||||
)
|
||||
|
||||
// RequireToken returns a prelude.Middleware that checks token validity
|
||||
func RequireToken(token string) Middleware {
|
||||
return func(next HandlerFunc) HandlerFunc {
|
||||
want := fmt.Sprintf("Bearer %s", token)
|
||||
return func(c *Context) Response {
|
||||
auth := c.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return Error(ErrMissingToken)
|
||||
}
|
||||
|
||||
if auth != want {
|
||||
return Error(ErrInvalidToken)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
62
pkg/api/prelude/context.go
Normal file
62
pkg/api/prelude/context.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package prelude
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Request *http.Request
|
||||
Log *logrus.Entry
|
||||
writer http.ResponseWriter
|
||||
}
|
||||
|
||||
func newContext(w http.ResponseWriter, req *http.Request) *Context {
|
||||
reqLog := localLog.WithField("endpoint", fmt.Sprintf("%v %v", req.Method, req.URL.Path))
|
||||
return &Context{
|
||||
Log: reqLog,
|
||||
Request: req,
|
||||
writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) Headers() http.Header {
|
||||
return c.writer.Header()
|
||||
}
|
||||
|
||||
type contextWrapper struct {
|
||||
context *Context
|
||||
body bytes.Buffer
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (cw *contextWrapper) Header() http.Header {
|
||||
return cw.context.writer.Header()
|
||||
}
|
||||
|
||||
func (cw *contextWrapper) Write(bytes []byte) (int, error) {
|
||||
return cw.body.Write(bytes)
|
||||
}
|
||||
|
||||
func (cw *contextWrapper) WriteHeader(statusCode int) {
|
||||
cw.statusCode = statusCode
|
||||
}
|
||||
|
||||
func WrapHandler(next http.HandlerFunc) HandlerFunc {
|
||||
return func(c *Context) Response {
|
||||
wrapper := contextWrapper{
|
||||
context: c,
|
||||
body: bytes.Buffer{},
|
||||
}
|
||||
|
||||
next(&wrapper, c.Request)
|
||||
|
||||
return Response{
|
||||
Status: wrapper.statusCode,
|
||||
Body: wrapper.body.Bytes(),
|
||||
Raw: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
36
pkg/api/prelude/errors.go
Normal file
36
pkg/api/prelude/errors.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package prelude
|
||||
|
||||
import "net/http"
|
||||
|
||||
type errorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Code ErrorCode `json:"code"`
|
||||
Status int `json:"-"`
|
||||
}
|
||||
|
||||
const internalErrorPayload string = `{ "error": "API internal error, check logs", "code": "API_INTERNAL_ERROR" }`
|
||||
|
||||
type ErrorCode string
|
||||
|
||||
var (
|
||||
ErrUpdateRunning = errorResponse{
|
||||
Code: "UPDATE_RUNNING",
|
||||
Error: "Update already running",
|
||||
Status: http.StatusConflict,
|
||||
}
|
||||
ErrNotFound = errorResponse{
|
||||
Code: "NOT_FOUND",
|
||||
Error: "Endpoint is not registered to a handler",
|
||||
Status: http.StatusNotFound,
|
||||
}
|
||||
ErrInvalidToken = errorResponse{
|
||||
Code: "INVALID_TOKEN",
|
||||
Error: "The supplied token does not match the configured auth token",
|
||||
Status: http.StatusUnauthorized,
|
||||
}
|
||||
ErrMissingToken = errorResponse{
|
||||
Code: "MISSING_TOKEN",
|
||||
Error: "No authentication token was supplied",
|
||||
Status: http.StatusUnauthorized,
|
||||
}
|
||||
)
|
||||
39
pkg/api/prelude/handler_func.go
Normal file
39
pkg/api/prelude/handler_func.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package prelude
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type HandlerFunc func(c *Context) Response
|
||||
|
||||
func (hf HandlerFunc) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
w.Header().Set("Content-Type", DefaultContentType)
|
||||
context := newContext(w, req)
|
||||
|
||||
reqLog := context.Log.WithFields(log.Fields{
|
||||
"query": req.URL.RawQuery,
|
||||
})
|
||||
reqLog.Trace("Received API Request")
|
||||
|
||||
res := hf(context)
|
||||
|
||||
status := res.Status
|
||||
|
||||
bytes, err := res.Bytes()
|
||||
if err != nil {
|
||||
context.Log.WithError(err).Errorf("Failed to create JSON payload for response")
|
||||
bytes = []byte(internalErrorPayload)
|
||||
status = http.StatusInternalServerError
|
||||
// Reset the content-type in case the handler changed it
|
||||
w.Header().Set("Content-Type", DefaultContentType)
|
||||
}
|
||||
|
||||
reqLog.WithField("status", status).Trace("Handled API Request")
|
||||
|
||||
w.WriteHeader(status)
|
||||
if _, err = w.Write(bytes); err != nil {
|
||||
localLog.Errorf("Failed to write HTTP response: %v", err)
|
||||
}
|
||||
}
|
||||
41
pkg/api/prelude/response.go
Normal file
41
pkg/api/prelude/response.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package prelude
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Body any
|
||||
Status int
|
||||
Raw bool
|
||||
}
|
||||
|
||||
func (r *Response) Bytes() ([]byte, error) {
|
||||
if bytes, raw := r.Body.([]byte); raw {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
if str, raw := r.Body.(string); raw {
|
||||
return []byte(str), nil
|
||||
}
|
||||
|
||||
return json.MarshalIndent(r.Body, "", " ")
|
||||
}
|
||||
|
||||
var localLog = log.WithField("notify", "no")
|
||||
|
||||
func OK(body any) Response {
|
||||
return Response{
|
||||
Status: http.StatusOK,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
|
||||
func Error(err errorResponse) Response {
|
||||
return Response{
|
||||
Status: err.Status,
|
||||
Body: err,
|
||||
}
|
||||
}
|
||||
5
pkg/api/prelude/types.go
Normal file
5
pkg/api/prelude/types.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package prelude
|
||||
|
||||
type Middleware func(next HandlerFunc) HandlerFunc
|
||||
|
||||
const DefaultContentType = "application/json"
|
||||
34
pkg/api/router.go
Normal file
34
pkg/api/router.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
. "github.com/containrrr/watchtower/pkg/api/prelude"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type router map[string]methodHandlers
|
||||
|
||||
type methodHandlers map[string]HandlerFunc
|
||||
|
||||
func (mh methodHandlers) Handler(c *Context) Response {
|
||||
handler, found := mh[c.Request.Method]
|
||||
if !found {
|
||||
return Error(ErrNotFound)
|
||||
}
|
||||
return handler(c)
|
||||
}
|
||||
|
||||
func (mh methodHandlers) post(handlerFunc HandlerFunc) {
|
||||
mh[http.MethodPost] = handlerFunc
|
||||
}
|
||||
func (mh methodHandlers) get(handlerFunc HandlerFunc) {
|
||||
mh[http.MethodGet] = handlerFunc
|
||||
}
|
||||
|
||||
func (r router) route(route string) methodHandlers {
|
||||
routeMethods, found := r[route]
|
||||
if !found {
|
||||
routeMethods = methodHandlers{}
|
||||
r[route] = routeMethods
|
||||
}
|
||||
return routeMethods
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
package update
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
lock chan bool
|
||||
)
|
||||
|
||||
// New is a factory function creating a new Handler instance
|
||||
func New(updateFn func(images []string), updateLock chan bool) *Handler {
|
||||
if updateLock != nil {
|
||||
lock = updateLock
|
||||
} else {
|
||||
lock = make(chan bool, 1)
|
||||
lock <- true
|
||||
}
|
||||
|
||||
return &Handler{
|
||||
fn: updateFn,
|
||||
Path: "/v1/update",
|
||||
}
|
||||
}
|
||||
|
||||
// Handler is an API handler used for triggering container update scans
|
||||
type Handler struct {
|
||||
fn func(images []string)
|
||||
Path string
|
||||
}
|
||||
|
||||
// Handle is the actual http.Handle function doing all the heavy lifting
|
||||
func (handle *Handler) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
log.Info("Updates triggered by HTTP API request.")
|
||||
|
||||
_, err := io.Copy(os.Stdout, r.Body)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var images []string
|
||||
imageQueries, found := r.URL.Query()["image"]
|
||||
if found {
|
||||
for _, image := range imageQueries {
|
||||
images = append(images, strings.Split(image, ",")...)
|
||||
}
|
||||
|
||||
} else {
|
||||
images = nil
|
||||
}
|
||||
|
||||
if len(images) > 0 {
|
||||
chanValue := <-lock
|
||||
defer func() { lock <- chanValue }()
|
||||
handle.fn(images)
|
||||
} else {
|
||||
select {
|
||||
case chanValue := <-lock:
|
||||
defer func() { lock <- chanValue }()
|
||||
handle.fn(images)
|
||||
default:
|
||||
log.Debug("Skipped. Another update already running.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
pkg/api/updates/updates.go
Normal file
22
pkg/api/updates/updates.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package updates
|
||||
|
||||
import (
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ModifyParamsFunc func(up *types.UpdateParams)
|
||||
type InvokedFunc func(ModifyParamsFunc) types.Report
|
||||
|
||||
func parseImages(u *url.URL) []string {
|
||||
var images []string
|
||||
imageQueries, found := u.Query()["image"]
|
||||
if found {
|
||||
for _, image := range imageQueries {
|
||||
images = append(images, strings.Split(image, ",")...)
|
||||
}
|
||||
|
||||
}
|
||||
return images
|
||||
}
|
||||
37
pkg/api/updates/updates_v1.go
Normal file
37
pkg/api/updates/updates_v1.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package updates
|
||||
|
||||
import (
|
||||
. "github.com/containrrr/watchtower/pkg/api/prelude"
|
||||
"github.com/containrrr/watchtower/pkg/filters"
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PostV1 creates an API http.HandlerFunc for V1 of updates
|
||||
func PostV1(updateFn InvokedFunc, updateLock *sync.Mutex) HandlerFunc {
|
||||
return func(c *Context) Response {
|
||||
log.Info("Updates triggered by HTTP API request.")
|
||||
|
||||
images := parseImages(c.Request.URL)
|
||||
|
||||
if !updateLock.TryLock() {
|
||||
if len(images) > 0 {
|
||||
// If images have been passed, wait until the current updates are done
|
||||
updateLock.Lock()
|
||||
} else {
|
||||
// If a full update is running (no explicit image filter), skip this update
|
||||
log.Debug("Skipped. Another updates already running.")
|
||||
return OK(nil) // For backwards compatibility
|
||||
}
|
||||
}
|
||||
|
||||
defer updateLock.Unlock()
|
||||
_ = updateFn(func(up *types.UpdateParams) {
|
||||
up.Filter = filters.FilterByImage(images, up.Filter)
|
||||
})
|
||||
|
||||
return OK(nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -45,12 +45,11 @@ func (metrics *Metrics) Register(metric *Metric) {
|
|||
metrics.channel <- metric
|
||||
}
|
||||
|
||||
// Default creates a new metrics handler if none exists, otherwise returns the existing one
|
||||
func Default() *Metrics {
|
||||
// Init creates a new metrics handler if none exists
|
||||
func Init() {
|
||||
if metrics != nil {
|
||||
return metrics
|
||||
return
|
||||
}
|
||||
|
||||
metrics = &Metrics{
|
||||
scanned: promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "watchtower_containers_scanned",
|
||||
|
|
@ -76,6 +75,11 @@ func Default() *Metrics {
|
|||
}
|
||||
|
||||
go metrics.HandleUpdate(metrics.channel)
|
||||
}
|
||||
|
||||
// Default creates a new metrics handler if none exists, otherwise returns the existing one
|
||||
func Default() *Metrics {
|
||||
Init()
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue