package static import ( "context" "errors" "fmt" "log/slog" "github.com/alexei/tinyforge/internal/store" "github.com/alexei/tinyforge/internal/workload/plugin" ) // teardown drops every artifact deploy created: the running container, // the proxy route, the optional storage volume, and the container // index row. Idempotent — a workload that never deployed is a no-op. // // Mirrors the legacy Manager.Remove + Stop combination: stop is // implicit in RemoveContainer(force=true), and the volume removal // happens only when storage was opted into (the named volume is // otherwise nonexistent and best-effort delete would log a noisy // warning). func teardown(ctx context.Context, deps plugin.Deps, w plugin.Workload) error { cfg, err := plugin.SourceConfigOf[Config](w) if err != nil { return fmt.Errorf("static source: decode config: %w", err) } _, prevContainer, err := loadState(deps, w) if err != nil { return err } if prevContainer == nil { // Nothing was ever deployed — best-effort volume cleanup in // case storage was provisioned but the deploy crashed before // state landed, then return. if cfg.StorageEnabled { if err := deps.Docker.RemoveSiteVolume(ctx, siteVolumeKey(w)); err != nil { slog.Debug("static site: storage volume cleanup", "site", w.Name, "error", err) } } return nil } // Drop proxy route first so traffic stops landing on a container // that is about to disappear. if prevContainer.ProxyRouteID != "" { if err := deps.Proxy.DeleteRoute(ctx, prevContainer.ProxyRouteID); err != nil { slog.Warn("static site: failed to remove proxy route", "site", w.Name, "error", err) } } if prevContainer.ContainerID != "" { if err := deps.Docker.RemoveContainer(ctx, prevContainer.ContainerID, true); err != nil { slog.Warn("static site: failed to remove container", "site", w.Name, "error", err) } } if cfg.StorageEnabled { if err := deps.Docker.RemoveSiteVolume(ctx, siteVolumeKey(w)); err != nil { slog.Warn("static site: failed to remove storage volume", "site", w.Name, "error", err) } } // Delete the container row last so a partial failure leaves enough // state for a retry. ErrNotFound is fine. if err := deps.Store.DeleteContainer(prevContainer.ID); err != nil && !errors.Is(err, store.ErrNotFound) { slog.Warn("static site: failed to delete container row", "site", w.Name, "error", err) } // The per-workload save-mutex is reference-counted (see state.go) and // frees itself when the last holder releases, so teardown no longer // deletes it explicitly — doing so could race a concurrent saveState. return nil }