feat(api): /update, /healthz, /version handlers
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/shcizo/package-updater/internal/discovery"
|
||||
"github.com/shcizo/package-updater/internal/logging"
|
||||
"github.com/shcizo/package-updater/internal/updater"
|
||||
)
|
||||
|
||||
// Finder is the discovery interface used by the update handler.
|
||||
type Finder interface {
|
||||
FindJobs(ctx context.Context, image string) ([]discovery.Job, error)
|
||||
}
|
||||
|
||||
// Submitter is the queue interface used by the update handler.
|
||||
type Submitter interface {
|
||||
Submit(ctx context.Context, jobs []discovery.Job) []updater.Result
|
||||
}
|
||||
|
||||
// Pinger pings the Docker daemon for the healthcheck.
|
||||
type Pinger interface {
|
||||
Ping(ctx context.Context) (types.Ping, error)
|
||||
}
|
||||
|
||||
// Handlers wires the HTTP endpoints to the rest of the service.
|
||||
type Handlers struct {
|
||||
finder Finder
|
||||
submitter Submitter
|
||||
pinger Pinger
|
||||
version string
|
||||
commit string
|
||||
buildTime string
|
||||
}
|
||||
|
||||
// NewHandlers constructs a Handlers value with all dependencies injected.
|
||||
func NewHandlers(f Finder, s Submitter, p Pinger, version, commit, buildTime string) *Handlers {
|
||||
return &Handlers{finder: f, submitter: s, pinger: p, version: version, commit: commit, buildTime: buildTime}
|
||||
}
|
||||
|
||||
// Update implements POST /update.
|
||||
func (h *Handlers) Update(w http.ResponseWriter, r *http.Request) {
|
||||
var req UpdateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSONError(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
if req.Image == "" {
|
||||
writeJSONError(w, http.StatusBadRequest, "image is required")
|
||||
return
|
||||
}
|
||||
|
||||
jobs, err := h.finder.FindJobs(r.Context(), req.Image)
|
||||
if err != nil {
|
||||
writeJSONError(w, http.StatusInternalServerError, "discovery failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := UpdateResponse{
|
||||
RequestID: logging.RequestIDFrom(r.Context()),
|
||||
Image: req.Image,
|
||||
Tag: req.Tag,
|
||||
Matched: len(jobs),
|
||||
Results: []ResultDTO{},
|
||||
}
|
||||
|
||||
if len(jobs) == 0 {
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
results := h.submitter.Submit(r.Context(), jobs)
|
||||
updated, failed := 0, 0
|
||||
for _, res := range results {
|
||||
composeFile := ""
|
||||
if len(res.Job.ConfigFiles) > 0 {
|
||||
composeFile = res.Job.ConfigFiles[0]
|
||||
}
|
||||
resp.Results = append(resp.Results, ResultDTO{
|
||||
Project: res.Job.Project,
|
||||
Service: res.Job.Service,
|
||||
ComposeFile: composeFile,
|
||||
Status: string(res.Status),
|
||||
Error: res.Error,
|
||||
DurationMs: res.DurationMs,
|
||||
})
|
||||
if res.Status == updater.StatusUpdated {
|
||||
updated++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case failed == 0:
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
case updated == 0:
|
||||
writeJSON(w, http.StatusInternalServerError, resp)
|
||||
default:
|
||||
writeJSON(w, http.StatusMultiStatus, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// Healthz implements GET /healthz.
|
||||
func (h *Handlers) Healthz(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := h.pinger.Ping(r.Context()); err != nil {
|
||||
writeJSON(w, http.StatusServiceUnavailable, HealthResponse{Status: "unhealthy", Docker: "unreachable"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, HealthResponse{Status: "ok", Docker: "ok"})
|
||||
}
|
||||
|
||||
// Version implements GET /version.
|
||||
func (h *Handlers) Version(w http.ResponseWriter, _ *http.Request) {
|
||||
writeJSON(w, http.StatusOK, VersionResponse{
|
||||
Version: h.version,
|
||||
Commit: h.commit,
|
||||
BuildTime: h.buildTime,
|
||||
})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, code int, body any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
_ = json.NewEncoder(w).Encode(body)
|
||||
}
|
||||
|
||||
func writeJSONError(w http.ResponseWriter, code int, msg string) {
|
||||
writeJSON(w, code, map[string]string{"error": msg})
|
||||
}
|
||||
Reference in New Issue
Block a user