Files
dsh-web/internal/api/reconcile.go
T
m1saka 9a71772771 Harden image admission, resource recovery and session renewal
Add exact-image compatibility policy, experimental admission, paginated build history and shared reauthentication checks. Preserve uncertain Docker creation records, bound frontend resource waits and renew runtime sessions proactively.

Lazy-load and split Monaco, add regression and opt-in live Docker fault tests, and record full race and real-browser acceptance results.
2026-10-14 13:41:54 +08:00

142 lines
4.5 KiB
Go

package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
)
// Reconcile checks persisted identities against Docker without deleting unknown
// containers. Orphans require explicit administrator review, never blind adoption.
// Runs before the HTTP server starts, so no request can race this pass.
func (a *API) Reconcile(ctx context.Context) error {
filters, _ := json.Marshal(map[string][]string{"label": {"dsh-mgr.instance"}})
var containers []struct {
ID string
State string
Labels map[string]string
}
if err := a.docker.Do(ctx, "GET", "/containers/json?all=1&filters="+url.QueryEscape(string(filters)), nil, &containers); err != nil {
return err
}
rows, err := a.s.DB.QueryContext(ctx, "SELECT id,uid,container_id,status FROM instances")
if err != nil {
return err
}
type record struct{ id, uid, container, status string }
var records []record
for rows.Next() {
var r record
if err = rows.Scan(&r.id, &r.uid, &r.container, &r.status); err != nil {
break
}
records = append(records, r)
}
if err == nil {
err = rows.Err()
}
rows.Close()
if err != nil {
return err
}
known := make(map[string]record)
for _, r := range records {
if r.container == "" && (r.status == "creating" || r.status == "error") {
i, loadErr := a.s.GetInstance(r.id)
if loadErr != nil {
return loadErr
}
if recoveryErr := a.recoverRuntimeContainer(ctx, &i); recoveryErr != nil {
a.s.AddAudit(r.uid, "reconcile.recovery_error", r.id, recoveryErr.Error())
}
r.container = i.ContainerID
r.status = "error"
if _, err = a.s.DB.ExecContext(ctx, "UPDATE instances SET status='error' WHERE id=?", r.id); err != nil {
return err
}
}
known[r.id] = r
}
matched := make(map[string]bool)
panel := os.Getenv("PANEL_CONTAINER_ID")
var networkErrors []error
for _, c := range containers {
id := c.Labels["dsh-mgr.instance"]
r, ok := known[id]
if !ok || r.container != c.ID || r.uid != c.Labels["dsh-mgr.owner"] {
a.s.AddAudit("", "reconcile.orphan", c.ID, "container identity requires administrator review")
continue
}
matched[id] = true
if panel != "" {
if networkErr := a.reconcilePanelNetwork(ctx, id, panel); networkErr != nil {
if _, err = a.s.DB.ExecContext(ctx, "UPDATE instances SET status='error' WHERE id=?", id); err != nil {
return err
}
a.s.AddAudit(r.uid, "reconcile.network_error", id, networkErr.Error())
networkErrors = append(networkErrors, fmt.Errorf("instance %s: %w", id, networkErr))
continue
}
}
if r.status != "error" {
if _, err = a.s.DB.ExecContext(ctx, "UPDATE instances SET status=? WHERE id=?", c.State, id); err != nil {
return err
}
}
}
for _, r := range records {
if r.container == "" || matched[r.id] || r.status == "error" {
continue
}
if _, err = a.s.DB.ExecContext(ctx, "UPDATE instances SET status='error' WHERE id=?", r.id); err != nil {
return err
}
a.s.AddAudit(r.uid, "reconcile.missing", r.id, "recorded container is absent or identity mismatch")
}
if ctx.Err() != nil {
return fmt.Errorf("reconcile: %w", ctx.Err())
}
return errors.Join(networkErrors...)
}
func (a *API) reconcilePanelNetwork(ctx context.Context, id, panel string) error {
name := runtimeNetwork(id)
var network struct {
ID, Name, Driver string
Labels map[string]string
}
if err := a.docker.Do(ctx, "GET", "/networks/"+url.PathEscape(name), nil, &network); err != nil {
return fmt.Errorf("inspect instance network: %w", err)
}
if network.ID == "" || network.Name != name || network.Driver != "bridge" || network.Labels["dsh-mgr.instance"] != id {
return fmt.Errorf("instance network identity mismatch")
}
container, err := a.docker.Inspect(ctx, panel)
if err != nil {
return fmt.Errorf("inspect panel: %w", err)
}
if container.ID == "" {
return fmt.Errorf("panel container identity missing")
}
for _, endpoint := range container.NetworkSettings.Networks {
if endpoint.NetworkID == network.ID {
return nil
}
}
if err := a.docker.Do(ctx, "POST", "/networks/"+url.PathEscape(network.ID)+"/connect", map[string]string{"Container": container.ID}, nil); err != nil {
// Another reconciliation may have connected the panel after inspection.
if current, inspectErr := a.docker.Inspect(ctx, container.ID); inspectErr == nil && current.ID == container.ID {
for _, endpoint := range current.NetworkSettings.Networks {
if endpoint.NetworkID == network.ID {
return nil
}
}
}
return fmt.Errorf("connect panel network: %w", err)
}
return nil
}