a7df9ffc6c
- store: add tables and CRUD for sandboxes (with services), templates (with services, clone-into-sandbox), environments (named key/value sets), and routes (per-sandbox <service>_url overrides). - api: split into one file per resource. handleSandboxes/handleSandboxByID covers CRUD + 'clone from template' + 'deploy one service in a sandbox' (which merges the sandbox's env into the request, picks the port, and dispatches the deploy frame to the right node). handleTemplates/handleTemplateByID, handleEnvironments/handleEnvironmentByID, handlePushRoutes cover the rest. The control plane's repo->node resolution still lives in resolveNode (api-gateway -> gateway, everything else -> micro).
106 lines
2.7 KiB
Go
106 lines
2.7 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/sdp/control-plane/internal/store"
|
|
)
|
|
|
|
type templateReq struct {
|
|
Name string `json:"name"`
|
|
GatewayBranch string `json:"gatewayBranch"`
|
|
Services []store.TemplateService `json:"services"`
|
|
}
|
|
|
|
func (s *Server) handleTemplates(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
ts, err := s.st.ListTemplates()
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if ts == nil {
|
|
ts = []store.Template{}
|
|
}
|
|
writeJSON(w, http.StatusOK, ts)
|
|
case http.MethodPost:
|
|
var body templateReq
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
http.Error(w, "bad json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if body.Name == "" {
|
|
writeErr(w, http.StatusBadRequest, "name required")
|
|
return
|
|
}
|
|
t := store.Template{
|
|
ID: newID(),
|
|
Name: body.Name,
|
|
GatewayBranch: body.GatewayBranch,
|
|
Services: body.Services,
|
|
}
|
|
if err := s.st.CreateTemplate(t); err != nil {
|
|
writeErr(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, t)
|
|
default:
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleTemplateByID(w http.ResponseWriter, r *http.Request) {
|
|
id := strings.TrimPrefix(r.URL.Path, "/api/templates/")
|
|
if id == "" || strings.Contains(id, "/") {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
t, err := s.st.GetTemplate(id)
|
|
if err == store.ErrNotFound {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, t)
|
|
case http.MethodPut:
|
|
var body templateReq
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
http.Error(w, "bad json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
t := store.Template{
|
|
ID: id,
|
|
Name: body.Name,
|
|
GatewayBranch: body.GatewayBranch,
|
|
Services: body.Services,
|
|
}
|
|
if err := s.st.UpdateTemplate(t); err == store.ErrNotFound {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
} else if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, t)
|
|
case http.MethodDelete:
|
|
if err := s.st.DeleteTemplate(id); err == store.ErrNotFound {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
} else if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
|
|
default:
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|