This commit is contained in:
pavel 2026-05-14 23:16:46 +02:00
commit 53a5e2e45a
3 changed files with 67 additions and 92 deletions

View file

@ -72,70 +72,12 @@ func (a *App) ensureCaddyRoute(ctx context.Context, p Project) error {
if err != nil {
return err
}
indexes := findCaddyRouteIndexesByID(cfg, a.cfg.CaddyServerID, route["@id"].(string))
if len(indexes) > 0 {
// Update first matching route in place.
if err := a.putCaddyRouteAtIndex(ctx, indexes[0], route); err != nil {
return err
}
// Remove stale duplicates from highest index to lowest.
for i := len(indexes) - 1; i >= 1; i-- {
if err := a.deleteCaddyRouteAtIndex(ctx, indexes[i]); err != nil {
return err
}
}
return nil
}
if err := a.appendCaddyRoute(ctx, route); err != nil {
// If duplicate race happened, retry once through dedupe/update path.
if strings.Contains(err.Error(), "duplicate ID") {
cfg2, ferr := a.fetchCaddyConfig(ctx)
if ferr != nil {
return err
}
indexes2 := findCaddyRouteIndexesByID(cfg2, a.cfg.CaddyServerID, route["@id"].(string))
if len(indexes2) == 0 {
return err
}
if uerr := a.putCaddyRouteAtIndex(ctx, indexes2[0], route); uerr != nil {
return uerr
}
for i := len(indexes2) - 1; i >= 1; i-- {
if derr := a.deleteCaddyRouteAtIndex(ctx, indexes2[i]); derr != nil {
return derr
}
}
return nil
}
newRoutes, err := mergedDedupedRoutes(cfg, a.cfg.CaddyServerID, route)
if err != nil {
return err
}
return nil
}
func (a *App) appendCaddyRoute(ctx context.Context, route map[string]any) error {
body, _ := json.Marshal(route)
body, _ := json.Marshal(newRoutes)
url := fmt.Sprintf("%s/config/apps/http/servers/%s/routes", a.cfg.CaddyAdminURL, a.cfg.CaddyServerID)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(body)))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("caddy request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("caddy error: status=%d body=%s", resp.StatusCode, string(respBody))
}
return nil
}
func (a *App) putCaddyRouteAtIndex(ctx context.Context, idx int, route map[string]any) error {
body, _ := json.Marshal(route)
url := fmt.Sprintf("%s/config/apps/http/servers/%s/routes/%d", a.cfg.CaddyAdminURL, a.cfg.CaddyServerID, idx)
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, strings.NewReader(string(body)))
if err != nil {
return err
@ -143,66 +85,60 @@ func (a *App) putCaddyRouteAtIndex(ctx context.Context, idx int, route map[strin
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("caddy update request failed: %w", err)
return fmt.Errorf("caddy routes rewrite failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("caddy update error: status=%d body=%s", resp.StatusCode, string(respBody))
return fmt.Errorf("caddy routes rewrite error: status=%d body=%s", resp.StatusCode, string(respBody))
}
return nil
}
func (a *App) deleteCaddyRouteAtIndex(ctx context.Context, idx int) error {
url := fmt.Sprintf("%s/config/apps/http/servers/%s/routes/%d", a.cfg.CaddyAdminURL, a.cfg.CaddyServerID, idx)
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("caddy delete request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("caddy delete error: status=%d body=%s", resp.StatusCode, string(respBody))
}
return nil
}
func findCaddyRouteIndexesByID(cfg map[string]any, serverID, routeID string) []int {
var out []int
func mergedDedupedRoutes(cfg map[string]any, serverID string, desired map[string]any) ([]any, error) {
apps, ok := cfg["apps"].(map[string]any)
if !ok {
return out
return nil, errors.New("caddy config missing apps")
}
httpApp, ok := apps["http"].(map[string]any)
if !ok {
return out
return nil, errors.New("caddy config missing apps.http")
}
servers, ok := httpApp["servers"].(map[string]any)
if !ok {
return out
return nil, errors.New("caddy config missing apps.http.servers")
}
server, ok := servers[serverID].(map[string]any)
if !ok {
return out
return nil, fmt.Errorf("caddy config missing server %q", serverID)
}
routes, ok := server["routes"].([]any)
if !ok {
return out
routes = []any{}
}
desiredID, _ := desired["@id"].(string)
var out []any
seenDesired := false
for i, r := range routes {
_ = i
rm, ok := r.(map[string]any)
if !ok {
out = append(out, r)
continue
}
if rid, ok := rm["@id"].(string); ok && rid == routeID {
out = append(out, i)
if rid, ok := rm["@id"].(string); ok && rid == desiredID {
if !seenDesired {
out = append(out, desired)
seenDesired = true
}
continue
}
out = append(out, r)
}
return out
if !seenDesired {
out = append(out, desired)
}
return out, nil
}
func (a *App) fetchCaddyConfig(ctx context.Context) (map[string]any, error) {