Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
207 lines
5.6 KiB
Go
207 lines
5.6 KiB
Go
// SPDX-License-Identifier: LicenseRef-DDI-Proprietary
|
|
|
|
package httpapi
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/netip"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.beugsiud.de/beug.k/DDI/server/internal/service"
|
|
"git.beugsiud.de/beug.k/DDI/server/internal/store"
|
|
)
|
|
|
|
// intParam parses an optional integer query parameter within [lo, hi]
|
|
// (0 if absent).
|
|
func intParam(q url.Values, name string, lo, hi int64) (int64, error) {
|
|
v := q.Get(name)
|
|
if v == "" {
|
|
return 0, nil
|
|
}
|
|
n, err := strconv.ParseInt(v, 10, 64)
|
|
if err != nil || n < lo || n > hi {
|
|
return 0, errBadRequest("invalid_parameter", "invalid "+name)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (a *API) listAgents(w http.ResponseWriter, r *http.Request, _ *reqCtx) error {
|
|
agents, err := a.svc.ListAgents(r.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusOK, listResponse[service.AgentDTO]{Items: agents})
|
|
}
|
|
|
|
func (a *API) getAgent(w http.ResponseWriter, r *http.Request, _ *reqCtx) error {
|
|
ag, err := a.svc.GetAgent(r.Context(), r.PathValue("id"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusOK, ag)
|
|
}
|
|
|
|
func (a *API) revokeAgent(w http.ResponseWriter, r *http.Request, rc *reqCtx) error {
|
|
ag, err := a.svc.RevokeAgent(r.Context(), rc.Actor, r.PathValue("id"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusOK, ag)
|
|
}
|
|
|
|
func (a *API) deleteAgent(w http.ResponseWriter, r *http.Request, rc *reqCtx) error {
|
|
if err := a.svc.DeleteAgent(r.Context(), rc.Actor, r.PathValue("id")); err != nil {
|
|
return err
|
|
}
|
|
return noContent(w)
|
|
}
|
|
|
|
func (a *API) listEnrollmentTokens(w http.ResponseWriter, r *http.Request, _ *reqCtx) error {
|
|
tokens, err := a.svc.ListEnrollmentTokens(r.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusOK, listResponse[service.EnrollmentTokenDTO]{Items: tokens})
|
|
}
|
|
|
|
type enrollmentTokenRequest struct {
|
|
Role string `json:"role"`
|
|
Description string `json:"description"`
|
|
ValidHours *int `json:"valid_hours"`
|
|
}
|
|
|
|
func (a *API) createEnrollmentToken(w http.ResponseWriter, r *http.Request, rc *reqCtx) error {
|
|
var req enrollmentTokenRequest
|
|
if err := decode(w, r, &req); err != nil {
|
|
return err
|
|
}
|
|
out, err := a.svc.CreateEnrollmentToken(r.Context(), rc.Actor, service.TokenInput{
|
|
Role: req.Role, Description: req.Description, ValidHours: req.ValidHours,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusCreated, out)
|
|
}
|
|
|
|
func (a *API) deleteEnrollmentToken(w http.ResponseWriter, r *http.Request, rc *reqCtx) error {
|
|
if err := a.svc.DeleteEnrollmentToken(r.Context(), rc.Actor, r.PathValue("id")); err != nil {
|
|
return err
|
|
}
|
|
return noContent(w)
|
|
}
|
|
|
|
func (a *API) listRevisions(w http.ResponseWriter, r *http.Request, _ *reqCtx) error {
|
|
q := r.URL.Query()
|
|
limit, err := intParam(q, "limit", 1, service.MaxRevisionPage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
before, err := intParam(q, "before", 1, 1<<62)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
page, err := a.svc.ListRevisions(r.Context(), int(limit), before)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusOK, page)
|
|
}
|
|
|
|
type revisionRequest struct {
|
|
Comment string `json:"comment"`
|
|
}
|
|
|
|
func (a *API) createRevision(w http.ResponseWriter, r *http.Request, rc *reqCtx) error {
|
|
var req revisionRequest
|
|
// The body is optional.
|
|
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxBody))
|
|
if err != nil {
|
|
return errBadRequest("body_too_large", "request body too large")
|
|
}
|
|
if len(bytes.TrimSpace(body)) > 0 {
|
|
r.Body = io.NopCloser(bytes.NewReader(body))
|
|
if err := decode(w, r, &req); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
rev, created, err := a.svc.CreateRevision(r.Context(), rc.Actor, req.Comment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
status := http.StatusOK
|
|
if created {
|
|
status = http.StatusCreated
|
|
}
|
|
return writeJSON(w, status, rev)
|
|
}
|
|
|
|
func (a *API) getRevision(w http.ResponseWriter, r *http.Request, _ *reqCtx) error {
|
|
n, err := strconv.ParseInt(r.PathValue("number"), 10, 64)
|
|
if err != nil {
|
|
return service.ErrNotFound
|
|
}
|
|
rev, err := a.svc.GetRevision(r.Context(), n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusOK, rev)
|
|
}
|
|
|
|
func (a *API) listDeployments(w http.ResponseWriter, r *http.Request, _ *reqCtx) error {
|
|
q := r.URL.Query()
|
|
f := service.DeploymentFilter{AgentID: q.Get("agent_id")}
|
|
var err error
|
|
if f.Revision, err = intParam(q, "revision", 1, store.MaxRevision); err != nil {
|
|
return err
|
|
}
|
|
limit, err := intParam(q, "limit", 1, service.MaxDeploymentPage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.Limit = int(limit)
|
|
if f.BeforeID, err = intParam(q, "before_id", 1, 1<<62); err != nil {
|
|
return err
|
|
}
|
|
page, err := a.svc.ListDeployments(r.Context(), f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusOK, page)
|
|
}
|
|
|
|
var leaseStates = map[string]bool{"active": true, "expired": true, "released": true, "declined": true}
|
|
|
|
func (a *API) listLeases(w http.ResponseWriter, r *http.Request, _ *reqCtx) error {
|
|
q := r.URL.Query()
|
|
f := service.LeaseFilter{NetworkID: q.Get("network_id"), State: q.Get("state"), Query: strings.TrimSpace(q.Get("q"))}
|
|
if f.State != "" && !leaseStates[f.State] {
|
|
return errBadRequest("invalid_parameter", "unknown lease state "+strconv.Quote(f.State))
|
|
}
|
|
if len(f.Query) > 200 {
|
|
return errBadRequest("invalid_parameter", "q is too long")
|
|
}
|
|
limit, err := intParam(q, "limit", 1, store.MaxLeasePage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.Limit = int(limit)
|
|
if v := q.Get("after"); v != "" {
|
|
ip, err := netip.ParseAddr(v)
|
|
if err != nil {
|
|
return errBadRequest("invalid_parameter", "after must be an IP address")
|
|
}
|
|
ip = ip.Unmap()
|
|
f.After = &ip
|
|
}
|
|
page, err := a.svc.ListLeases(r.Context(), f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeJSON(w, http.StatusOK, page)
|
|
}
|