- Add .gitignore: exclude compiled binaries, build artifacts, and Helm values files containing real secrets (authentik, prometheus) - Add all Kubernetes deployment manifests (deployment/) - Add services source code: ha-sync, device-inventory, games-console, paperclip, parts-inventory - Add Ansible orchestration: playbooks, roles, inventory, cloud-init - Add hardware specs, execution plans, scripts, HOMELAB.md - Add skills/homelab/SKILL.md + skills/install.sh to preserve Copilot skill - Remove previously-tracked inventory-cli binary from git index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
204 lines
5.8 KiB
Go
204 lines
5.8 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
k8sclient "github.com/vandachevici/games-console/internal/k8s"
|
|
"github.com/vandachevici/games-console/internal/health"
|
|
)
|
|
|
|
const nodeHost = "192.168.2.100"
|
|
|
|
// NewRouter returns a chi router with all API routes registered.
|
|
func NewRouter(client *k8sclient.Client) http.Handler {
|
|
r := chi.NewRouter()
|
|
r.Use(middleware.Logger)
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(corsMiddleware)
|
|
|
|
r.Get("/healthz", handleHealthz)
|
|
|
|
r.Route("/api/servers", func(r chi.Router) {
|
|
r.Get("/", makeListServers(client))
|
|
r.Post("/", makeCreateServer(client))
|
|
r.Get("/{name}", makeGetServer(client))
|
|
r.Put("/{name}", makeUpdateServer(client))
|
|
r.Delete("/{name}", makeDeleteServer(client))
|
|
r.Get("/{name}/logs", makeGetLogs(client))
|
|
r.Get("/{name}/health", makeHealthCheck(client))
|
|
r.Post("/{name}/start", makeStart(client))
|
|
r.Post("/{name}/stop", makeStop(client))
|
|
r.Post("/{name}/restart", makeRestart(client))
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
func corsMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func handleHealthz(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func makeListServers(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
servers, err := c.ListServers(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, servers)
|
|
}
|
|
}
|
|
|
|
func makeGetServer(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
server, err := c.GetServer(r.Context(), name)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, server)
|
|
}
|
|
}
|
|
|
|
func makeCreateServer(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req k8sclient.CreateServerRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
server, err := c.CreateServer(r.Context(), req)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, server)
|
|
}
|
|
}
|
|
|
|
func makeUpdateServer(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
var req k8sclient.UpdateServerRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
server, err := c.UpdateServer(r.Context(), name, req)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, server)
|
|
}
|
|
}
|
|
|
|
func makeDeleteServer(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
if err := c.DeleteServer(r.Context(), name); err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
func makeGetLogs(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
lines := int64(100)
|
|
if l := r.URL.Query().Get("lines"); l != "" {
|
|
if n, err := strconv.ParseInt(l, 10, 64); err == nil {
|
|
lines = n
|
|
}
|
|
}
|
|
logs, err := c.GetLogs(r.Context(), name, lines)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"logs": logs})
|
|
}
|
|
}
|
|
|
|
func makeHealthCheck(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
server, err := c.GetServer(r.Context(), name)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
tcpReachable := false
|
|
if server.NodePort > 0 {
|
|
tcpReachable = health.Probe(nodeHost, server.NodePort)
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"status": server.Status,
|
|
"tcpReachable": tcpReachable,
|
|
"nodePort": server.NodePort,
|
|
})
|
|
}
|
|
}
|
|
|
|
func makeStart(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
if err := c.ScaleServer(r.Context(), name, 1); err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "started"})
|
|
}
|
|
}
|
|
|
|
func makeStop(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
if err := c.ScaleServer(r.Context(), name, 0); err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "stopped"})
|
|
}
|
|
}
|
|
|
|
func makeRestart(c *k8sclient.Client) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
if err := c.RestartServer(r.Context(), name); err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "restarting"})
|
|
}
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(v)
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, status int, err error) {
|
|
writeJSON(w, status, map[string]string{"error": err.Error()})
|
|
}
|