mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
fix: e2e QA hardening — honest runtime URLs, health, and read-only anon UI
Backend: - managed DB provisioning: attach DB containers to the platform network and probe the container IP, fixing the 127.0.0.1 reachability failure inside the backend container; log provisioning failures - stop fabricating connection URLs when provisioning produced none - template deploys now carry port + health_check into service runtime fields - service health probed over the project network (backend joins it) instead of an in-container wget that fails on shell-less images - runtime/public/connection URLs rewrite loopback to the request Host so links are usable for LAN/remote clients Frontend: - service Networking panel shows real runtime URLs/ports/health instead of hardcoded containr.dev + 8080->443 - anonymous views hide all mutation controls (new project, add service, group, context menu) - project cards: remove decorative svcN labels; badge says DOWN only when zero services run, IDLE when empty, DEGRADED for partial - mobile projects header no longer clips (fluid search width) - unknown routes render a not-found page instead of silently redirecting Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
d968eb2315
commit
9f5dcc7296
12 files changed
+404
-137
No files matched your search
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -58,7 +59,9 @@ type RuntimeState struct {
|
||||
Desired int `json:"desired"`
|
||||
Containers []RuntimeContainer `json:"containers"`
|
||||
URLs []string `json:"urls"`
|
||||
Status string `json:"status"` // running, degraded, stopped
|
||||
Ports []uint16 `json:"ports"`
|
||||
Health string `json:"health,omitempty"` // healthy, unhealthy, unreachable
|
||||
Status string `json:"status"` // running, degraded, stopped
|
||||
}
|
||||
|
||||
func projectNetworkName(projectID string) string {
|
||||
@@ -243,15 +246,6 @@ func (de *DeploymentEngine) createReplica(ctx context.Context, spec RuntimeSpec,
|
||||
// Always publish an ephemeral host port for direct access; a configured
|
||||
// domain is additionally routed via Traefik when the edge network exists.
|
||||
cfg.PortBindings = nat.PortMap{port: []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: ""}}}
|
||||
if spec.HealthPath != "" {
|
||||
cfg.Healthcheck = &container.HealthConfig{
|
||||
Test: []string{"CMD-SHELL", fmt.Sprintf("wget -q -O /dev/null http://127.0.0.1:%d%s || exit 1", spec.Port, spec.HealthPath)},
|
||||
Interval: 30 * time.Second,
|
||||
Timeout: 5 * time.Second,
|
||||
Retries: 3,
|
||||
StartPeriod: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id, err := de.dockerClient.CreateContainer(ctx, cfg)
|
||||
@@ -282,6 +276,7 @@ func (de *DeploymentEngine) RuntimeState(ctx context.Context, serviceID string,
|
||||
for _, p := range c.Ports {
|
||||
if p.PublicPort != 0 {
|
||||
state.URLs = append(state.URLs, fmt.Sprintf("http://localhost:%d", p.PublicPort))
|
||||
state.Ports = append(state.Ports, p.PublicPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,6 +294,57 @@ func (de *DeploymentEngine) RuntimeState(ctx context.Context, serviceID string,
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// ProbeServiceHealth performs an HTTP GET on healthPath and records the
|
||||
// outcome on state.Health. When the backend runs containerized it first
|
||||
// attaches to the service's project network and probes the service by DNS
|
||||
// name — this works for every image (no shell or wget required inside the
|
||||
// container) and checks the service itself, not the published edge. A
|
||||
// host-run backend falls back to probing the published host port.
|
||||
func (de *DeploymentEngine) ProbeServiceHealth(ctx context.Context, state *RuntimeState, projectID, serviceName string, port int, healthPath string) {
|
||||
if state == nil {
|
||||
return
|
||||
}
|
||||
state.Health = ""
|
||||
if healthPath == "" || port <= 0 {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(healthPath, "/") {
|
||||
healthPath = "/" + healthPath
|
||||
}
|
||||
|
||||
probeURL := ""
|
||||
if networkName := projectNetworkName(projectID); networkName != "" {
|
||||
if err := de.dockerClient.ConnectSelfToNetwork(ctx, networkName); err == nil {
|
||||
probeURL = fmt.Sprintf("http://%s:%d%s", serviceName, port, healthPath)
|
||||
}
|
||||
}
|
||||
if probeURL == "" && len(state.Ports) > 0 {
|
||||
probeURL = fmt.Sprintf("http://%s:%d%s", de.dockerClient.HostProbeHost(ctx), state.Ports[0], healthPath)
|
||||
}
|
||||
if probeURL == "" {
|
||||
return
|
||||
}
|
||||
|
||||
reqCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, probeURL, nil)
|
||||
if err != nil {
|
||||
state.Health = "unreachable"
|
||||
return
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
state.Health = "unreachable"
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 400 {
|
||||
state.Health = "healthy"
|
||||
return
|
||||
}
|
||||
state.Health = "unhealthy"
|
||||
}
|
||||
|
||||
// StopService stops (but keeps) all replicas of a service.
|
||||
func (de *DeploymentEngine) StopService(ctx context.Context, serviceID string) error {
|
||||
containers, err := de.ListServiceContainers(ctx, serviceID)
|
||||
|
||||
Reference in new issue
Block a user