Files
mantrae/internal/api/handler.go
2024-08-23 18:39:26 +02:00

480 lines
12 KiB
Go

// Package api provides handlers for the API
package api
import (
"encoding/json"
"fmt"
"net/http"
"github.com/MizuchiLabs/mantrae/pkg/traefik"
"github.com/MizuchiLabs/mantrae/pkg/util"
)
// Helper function to write JSON response
func writeJSON(w http.ResponseWriter, data any) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func Login(w http.ResponseWriter, r *http.Request) {
var creds util.Credentials
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if creds.Username == "" || creds.Password == "" {
http.Error(w, "username and password cannot be empty", http.StatusBadRequest)
return
}
var valid util.Credentials
if err := valid.GetCreds(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if creds.Username != valid.Username || creds.Password != valid.Password {
http.Error(w, "invalid username or password", http.StatusUnauthorized)
return
}
token, err := GenerateJWT(creds.Username)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]string{"token": token})
}
func VerifyToken(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if len(tokenString) < 7 {
http.Error(w, "token cannot be empty", http.StatusBadRequest)
return
}
_, err := ValidateJWT(tokenString[7:])
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
}
// CreateProfile creates a new profile
func CreateProfile(w http.ResponseWriter, r *http.Request) {
var profile traefik.Profile
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := profile.Verify(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if len(p.Profiles) == 0 {
p.Profiles = make(map[string]traefik.Profile)
}
p.Profiles[profile.Name] = profile
if err := p.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
go traefik.GetTraefikConfig()
writeJSON(w, p.Profiles)
}
// GetProfiles returns all profiles
func GetProfiles(w http.ResponseWriter, r *http.Request) {
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, p.Profiles)
}
// UpdateProfile updates a single profile
func UpdateProfile(w http.ResponseWriter, r *http.Request) {
var p traefik.Profile
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := p.Verify(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
profileName := r.PathValue("name")
if profileName == "" {
http.Error(w, "profile name cannot be empty", http.StatusBadRequest)
return
}
var profiles traefik.Profiles
if err := profiles.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if len(profiles.Profiles) == 0 {
http.Error(w, "no profiles configured", http.StatusBadRequest)
return
}
if _, ok := profiles.Profiles[profileName]; !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
if p.Name != profileName {
delete(profiles.Profiles, profileName)
}
profiles.Profiles[p.Name] = p
if err := profiles.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
go traefik.GetTraefikConfig()
writeJSON(w, profiles.Profiles)
}
// DeleteProfile deletes a single profile
func DeleteProfile(w http.ResponseWriter, r *http.Request) {
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
profileName := r.PathValue("name")
if profileName == "" {
http.Error(w, "profile name cannot be empty", http.StatusBadRequest)
return
}
if _, ok := p.Profiles[profileName]; !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
delete(p.Profiles, profileName)
if err := p.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, p.Profiles)
}
// UpdateRouter updates or creates a router
func UpdateRouter(w http.ResponseWriter, r *http.Request) {
var router traefik.Router
if err := json.NewDecoder(r.Body).Decode(&router); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := router.Verify(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
profileName := r.PathValue("profile")
routerName := r.PathValue("router")
if profileName == "" || routerName == "" {
http.Error(w, "profile or router name cannot be empty", http.StatusBadRequest)
return
}
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
profile, ok := p.Profiles[profileName]
if !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
// Initialize the Routers map if it is nil
if profile.Dynamic.Routers == nil {
profile.Dynamic.Routers = make(map[string]traefik.Router)
}
// If the router name is being changed, delete the old entry
if routerName != router.Name {
delete(profile.Dynamic.Routers, routerName)
}
profile.Dynamic.Routers[router.Name] = router
p.Profiles[profileName] = profile // Update the profile in the map
if err := p.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, p.Profiles)
}
// DeleteRouter deletes a single router and it's services
func DeleteRouter(w http.ResponseWriter, r *http.Request) {
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
profileName := r.PathValue("profile")
routerName := r.PathValue("router")
if profileName == "" || routerName == "" {
http.Error(w, "profile or router name cannot be empty", http.StatusBadRequest)
return
}
if _, ok := p.Profiles[profileName]; !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
delete(p.Profiles[profileName].Dynamic.Routers, routerName)
delete(p.Profiles[profileName].Dynamic.Services, routerName)
if err := p.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, p.Profiles)
}
// UpdateService updates or creates a service
func UpdateService(w http.ResponseWriter, r *http.Request) {
var service traefik.Service
if err := json.NewDecoder(r.Body).Decode(&service); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := service.Verify(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
profileName := r.PathValue("profile")
serviceName := r.PathValue("service")
if profileName == "" || serviceName == "" {
http.Error(w, "profile or service name cannot be empty", http.StatusBadRequest)
return
}
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
profile, ok := p.Profiles[profileName]
if !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
if serviceName != service.Name {
delete(profile.Dynamic.Services, serviceName)
}
if profile.Dynamic.Services == nil {
profile.Dynamic.Services = make(map[string]traefik.Service)
}
profile.Dynamic.Services[service.Name] = service
p.Profiles[profileName] = profile // Update the profile in the map
if err := p.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, p.Profiles)
}
// DeleteService deletes a single service and its router
func DeleteService(w http.ResponseWriter, r *http.Request) {
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
profileName := r.PathValue("profile")
serviceName := r.PathValue("service")
if profileName == "" || serviceName == "" {
http.Error(w, "profile or service name cannot be empty", http.StatusBadRequest)
return
}
if _, ok := p.Profiles[r.PathValue("profile")]; !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
delete(p.Profiles[profileName].Dynamic.Services, serviceName)
delete(p.Profiles[profileName].Dynamic.Routers, serviceName)
if err := p.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, p.Profiles)
}
// UpdateMiddleware updates or creates a middleware
func UpdateMiddleware(w http.ResponseWriter, r *http.Request) {
var middleware traefik.Middleware
if err := json.NewDecoder(r.Body).Decode(&middleware); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
profileName := r.PathValue("profile")
middlewareName := r.PathValue("middleware")
if profileName == "" || middlewareName == "" {
http.Error(w, "profile or middleware name cannot be empty", http.StatusBadRequest)
return
}
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
profile, ok := p.Profiles[profileName]
if !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
if middlewareName != middleware.Name {
delete(p.Profiles[profileName].Dynamic.Middlewares, middlewareName)
}
if profile.Dynamic.Middlewares == nil {
profile.Dynamic.Middlewares = make(map[string]traefik.Middleware)
}
if middleware.BasicAuth != nil {
for i, u := range middleware.BasicAuth.Users {
hash, err := util.HashPassword(u)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
middleware.BasicAuth.Users[i] = hash
}
}
if middleware.DigestAuth != nil {
for i, u := range middleware.DigestAuth.Users {
hash, err := util.HashPassword(u)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
middleware.DigestAuth.Users[i] = hash
}
}
profile.Dynamic.Middlewares[middleware.Name] = middleware
p.Profiles[profileName] = profile // Update the profile in the map
if err := p.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, p.Profiles)
}
// DeleteMiddleware deletes a single middleware and it's services
func DeleteMiddleware(w http.ResponseWriter, r *http.Request) {
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
profileName := r.PathValue("profile")
middlewareName := r.PathValue("middleware")
if profileName == "" || middlewareName == "" {
http.Error(w, "profile or middleware name cannot be empty", http.StatusBadRequest)
return
}
if _, ok := p.Profiles[profileName]; !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
delete(p.Profiles[profileName].Dynamic.Middlewares, middlewareName)
if err := p.Save(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, p.Profiles)
}
// GetConfig returns the traefik config for a single profile
func GetConfig(w http.ResponseWriter, r *http.Request) {
var p traefik.Profiles
if err := p.Load(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
profileName := r.PathValue("name")
if profileName == "" {
http.Error(w, "profile name cannot be empty", http.StatusBadRequest)
return
}
profile, ok := p.Profiles[profileName]
if !ok {
http.Error(w, "profile not found", http.StatusNotFound)
return
}
yamlConfig, err := traefik.GenerateConfig(profile.Dynamic)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/yaml")
w.Header().
Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.yaml", profileName))
if _, err := w.Write(yamlConfig); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}