http report wip

This commit is contained in:
nils måsén 2021-06-27 15:30:23 +02:00
parent e3dd8d688a
commit efaf7190ee
25 changed files with 350 additions and 284 deletions

View file

@ -2,6 +2,7 @@ package api
import (
"fmt"
"github.com/containrrr/watchtower/pkg/session"
log "github.com/sirupsen/logrus"
"net/http"
)
@ -10,8 +11,9 @@ 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
latestReport session.Report
}
// New is a factory function creating a new API instance
@ -74,3 +76,7 @@ func runHTTPServer() {
log.Info("Serving HTTP")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func (api *API) UpdateReport(report session.Report) {
api.latestReport = report
}

13
pkg/api/api_suite_test.go Normal file
View file

@ -0,0 +1,13 @@
package api_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestAPI(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "API Suite")
}

23
pkg/api/json.go Normal file
View file

@ -0,0 +1,23 @@
package api
import (
"encoding/json"
"github.com/sirupsen/logrus"
"net/http"
)
// WriteJsonOrError writes the supplied response to the http.ResponseWriter, handling any errors by logging and
// returning an Internal Server Error response (status 500)
func WriteJsonOrError(writer http.ResponseWriter, response interface{}) {
data, err := json.MarshalIndent(response, "", " ")
if err != nil {
logrus.WithError(err).Error("failed to create json payload")
writer.WriteHeader(500)
return
}
writer.Header().Set("Content-Type", "application/json")
_, err = writer.Write(data)
if err != nil {
logrus.WithError(err).Error("failed to write response")
}
}

32
pkg/api/metrics.go Normal file
View file

@ -0,0 +1,32 @@
package api
import (
"github.com/containrrr/watchtower/pkg/metrics"
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// MetricsHandler is a HTTP handler for serving metric data
type MetricsHandler struct {
Path string
Handle http.HandlerFunc
Metrics *metrics.Metrics
}
// NewMetricsHandler is a factory function creating a new Metrics instance
func NewMetricsHandler() *MetricsHandler {
m := metrics.Default()
handler := promhttp.Handler()
return &MetricsHandler{
Path: "/v1/metrics",
Handle: handler.ServeHTTP,
Metrics: m,
}
}
func MetricsEndpoint() (path string, handler http.HandlerFunc) {
mh := NewMetricsHandler()
return mh.Path, mh.Handle
}

View file

@ -1,27 +0,0 @@
package metrics
import (
"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,
}
}

View file

@ -1,4 +1,4 @@
package metrics_test
package api_test
import (
"fmt"
@ -6,10 +6,8 @@ import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/containrrr/watchtower/pkg/api"
metricsAPI "github.com/containrrr/watchtower/pkg/api/metrics"
"github.com/containrrr/watchtower/pkg/metrics"
. "github.com/onsi/ginkgo"
@ -21,11 +19,6 @@ const (
getURL = "http://localhost:8080/v1/metrics"
)
func TestMetrics(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Metrics Suite")
}
func getWithToken(handler http.Handler) map[string]string {
metricMap := map[string]string{}
respWriter := httptest.NewRecorder()
@ -51,7 +44,7 @@ func getWithToken(handler http.Handler) map[string]string {
var _ = Describe("the metrics API", func() {
httpAPI := api.New(token)
m := metricsAPI.New()
m := api.NewMetricsHandler()
handleReq := httpAPI.RequireToken(m.Handle)
tryGetMetrics := func() map[string]string { return getWithToken(handleReq) }

14
pkg/api/report.go Normal file
View file

@ -0,0 +1,14 @@
package api
import (
"github.com/containrrr/watchtower/pkg/session"
"net/http"
)
func ReportEndpoint(reportPtr **session.Report) (path string, handler http.HandlerFunc) {
path = "/v1/report"
handler = func(writer http.ResponseWriter, request *http.Request) {
WriteJsonOrError(writer, *reportPtr)
}
return path, handler
}

62
pkg/api/update.go Normal file
View file

@ -0,0 +1,62 @@
package api
import (
"github.com/containrrr/watchtower/pkg/metrics"
log "github.com/sirupsen/logrus"
"net/http"
)
var (
lock chan bool
)
// NewUpdateHandler is a factory function creating a new Handler instance
func NewUpdateHandler(updateFn func() *metrics.Metric, updateLock chan bool) *UpdateHandler {
if updateLock != nil {
lock = updateLock
} else {
lock = make(chan bool, 1)
lock <- true
}
return &UpdateHandler{
fn: updateFn,
Path: "/v1/update",
}
}
func UpdateEndpoint(updateFn func() *metrics.Metric, updateLock chan bool) (path string, handler http.HandlerFunc) {
uh := NewUpdateHandler(updateFn, updateLock)
return uh.Path, uh.Handle
}
// UpdateHandler is an API handler used for triggering container update scans
type UpdateHandler struct {
fn func() *metrics.Metric
Path string
}
// Handle is the actual http.Handle function doing all the heavy lifting
func (handler *UpdateHandler) Handle(w http.ResponseWriter, _ *http.Request) {
log.Info("Updates triggered by HTTP API request.")
result := updateResult{}
select {
case chanValue := <-lock:
defer func() { lock <- chanValue }()
metric := handler.fn()
metrics.RegisterScan(metric)
result.Result = metric
result.Skipped = false
default:
log.Debug("Skipped. Another update already running.")
result.Skipped = true
}
WriteJsonOrError(w, result)
}
type updateResult struct {
Skipped bool
Result *metrics.Metric
}

View file

@ -1,54 +0,0 @@
package update
import (
"io"
"net/http"
"os"
log "github.com/sirupsen/logrus"
)
var (
lock chan bool
)
// New is a factory function creating a new Handler instance
func New(updateFn func(), 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()
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
}
select {
case chanValue := <-lock:
defer func() { lock <- chanValue }()
handle.fn()
default:
log.Debug("Skipped. Another update already running.")
}
}