diff --git a/internal/api/types.go b/internal/api/types.go index d87ebb9..9fb1865 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -1,5 +1,6 @@ package api +// BackupRequest is the request payload for a namespace or PVC backup. type BackupRequest struct { Namespace string `json:"namespace"` PVC string `json:"pvc"` @@ -10,6 +11,7 @@ type BackupRequest struct { KeepLast *int `json:"keep_last,omitempty"` } +// BackupResponse is the response payload returned after a backup request. type BackupResponse struct { Driver string `json:"driver,omitempty"` Volume string `json:"volume,omitempty"` @@ -23,6 +25,7 @@ type BackupResponse struct { KeepLast int `json:"keep_last"` } +// RestoreTestRequest is the request payload for a restore test. type RestoreTestRequest struct { Namespace string `json:"namespace"` PVC string `json:"pvc,omitempty"` @@ -34,6 +37,7 @@ type RestoreTestRequest struct { DryRun bool `json:"dry_run"` } +// RestoreTestResponse is the response payload returned after a restore test. type RestoreTestResponse struct { Driver string `json:"driver,omitempty"` Volume string `json:"volume,omitempty"` @@ -47,16 +51,19 @@ type RestoreTestResponse struct { DryRun bool `json:"dry_run"` } +// InventoryResponse wraps the full backup inventory view returned to the UI. type InventoryResponse struct { GeneratedAt string `json:"generated_at"` Namespaces []NamespaceInventory `json:"namespaces"` } +// NamespaceInventory groups PVC inventory for a single namespace. type NamespaceInventory struct { Name string `json:"name"` PVCs []PVCInventory `json:"pvcs"` } +// PVCInventory captures the backup health snapshot for one PVC. type PVCInventory struct { Namespace string `json:"namespace"` PVC string `json:"pvc"` @@ -82,6 +89,7 @@ type PVCInventory struct { Error string `json:"error,omitempty"` } +// BackupListResponse returns the backup history for a PVC. type BackupListResponse struct { Namespace string `json:"namespace"` PVC string `json:"pvc"` @@ -89,6 +97,7 @@ type BackupListResponse struct { Backups []BackupRecord `json:"backups"` } +// BackupRecord summarizes one backup entry in a history list. type BackupRecord struct { Name string `json:"name"` SnapshotName string `json:"snapshot_name,omitempty"` @@ -99,6 +108,7 @@ type BackupRecord struct { Latest bool `json:"latest,omitempty"` } +// AuthInfoResponse reports the authenticated user and the allowed groups. type AuthInfoResponse struct { Authenticated bool `json:"authenticated"` User string `json:"user,omitempty"` @@ -107,6 +117,7 @@ type AuthInfoResponse struct { AllowedGroups []string `json:"allowed_groups,omitempty"` } +// BackupPolicy stores the scheduling policy for a namespace or PVC. type BackupPolicy struct { ID string `json:"id"` Namespace string `json:"namespace"` @@ -119,6 +130,7 @@ type BackupPolicy struct { UpdatedAt string `json:"updated_at,omitempty"` } +// BackupPolicyUpsertRequest updates the stored backup policy for a scope. type BackupPolicyUpsertRequest struct { Namespace string `json:"namespace"` PVC string `json:"pvc,omitempty"` @@ -128,10 +140,12 @@ type BackupPolicyUpsertRequest struct { KeepLast *int `json:"keep_last,omitempty"` } +// BackupPolicyListResponse returns the configured backup policies. type BackupPolicyListResponse struct { Policies []BackupPolicy `json:"policies"` } +// NamespaceBackupRequest requests a backup sweep across a namespace. type NamespaceBackupRequest struct { Namespace string `json:"namespace"` DryRun bool `json:"dry_run"` @@ -139,6 +153,7 @@ type NamespaceBackupRequest struct { KeepLast *int `json:"keep_last,omitempty"` } +// NamespaceBackupResult reports the outcome for a single PVC backup. type NamespaceBackupResult struct { Namespace string `json:"namespace"` PVC string `json:"pvc"` @@ -148,6 +163,7 @@ type NamespaceBackupResult struct { Error string `json:"error,omitempty"` } +// NamespaceBackupResponse summarizes the namespace backup sweep results. type NamespaceBackupResponse struct { Namespace string `json:"namespace"` RequestedBy string `json:"requested_by,omitempty"` @@ -161,6 +177,7 @@ type NamespaceBackupResponse struct { Results []NamespaceBackupResult `json:"results"` } +// NamespaceRestoreRequest requests a restore sweep across a namespace. type NamespaceRestoreRequest struct { Namespace string `json:"namespace"` TargetNamespace string `json:"target_namespace,omitempty"` @@ -169,6 +186,7 @@ type NamespaceRestoreRequest struct { DryRun bool `json:"dry_run"` } +// NamespaceRestoreResult reports the outcome for a single PVC restore. type NamespaceRestoreResult struct { Namespace string `json:"namespace"` PVC string `json:"pvc"` @@ -180,6 +198,7 @@ type NamespaceRestoreResult struct { Error string `json:"error,omitempty"` } +// NamespaceRestoreResponse summarizes the namespace restore sweep results. type NamespaceRestoreResponse struct { Namespace string `json:"namespace"` TargetNamespace string `json:"target_namespace"` @@ -192,6 +211,7 @@ type NamespaceRestoreResponse struct { Results []NamespaceRestoreResult `json:"results"` } +// B2UsageResponse reports account and per-bucket Backblaze B2 usage. type B2UsageResponse struct { Enabled bool `json:"enabled"` Available bool `json:"available"` @@ -207,6 +227,7 @@ type B2UsageResponse struct { Error string `json:"error,omitempty"` } +// B2BucketUsage captures the usage metrics for one Backblaze B2 bucket. type B2BucketUsage struct { Name string `json:"name"` ObjectCount int64 `json:"object_count"` diff --git a/internal/config/config.go b/internal/config/config.go index c4c2c49..47fb74e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -27,6 +27,7 @@ const ( serviceNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" ) +// Config holds the runtime settings used to build and serve Soteria. type Config struct { Namespace string SecretNamespace string @@ -67,6 +68,7 @@ type Config struct { B2ScanTimeout time.Duration } +// Load builds the runtime configuration from environment variables and cluster state. func Load() (*Config, error) { cfg := &Config{} diff --git a/internal/k8s/client.go b/internal/k8s/client.go index 22b6292..60e5709 100644 --- a/internal/k8s/client.go +++ b/internal/k8s/client.go @@ -9,6 +9,7 @@ import ( "k8s.io/client-go/tools/clientcmd" ) +// Client wraps the Kubernetes interface used by Soteria. type Client struct { Clientset kubernetes.Interface } @@ -21,6 +22,7 @@ var ( } ) +// New returns a Kubernetes client, preferring in-cluster config and falling back to KUBECONFIG. func New() (*Client, error) { cfg, err := inClusterConfigFn() if err != nil { diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index f655e5b..5db4786 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -26,6 +26,7 @@ const ( annotationKeepLast = "soteria.bstein.dev/keep-last" ) +// BackupJobSummary captures the backup job details shown in the UI and metrics. type BackupJobSummary struct { Name string Namespace string @@ -38,6 +39,7 @@ type BackupJobSummary struct { State string } +// ListBackupJobs returns backup jobs in a namespace, newest first. func (c *Client) ListBackupJobs(ctx context.Context, namespace string) ([]BackupJobSummary, error) { selector := fmt.Sprintf("%s=soteria,%s=backup,%s=backup", labelAppName, labelComponent, labelAction) jobs, err := c.Clientset.BatchV1().Jobs(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) @@ -58,6 +60,7 @@ func (c *Client) ListBackupJobs(ctx context.Context, namespace string) ([]Backup return out, nil } +// ReadBackupJobLog fetches the log stream for the most recent pod of a backup job. func (c *Client) ReadBackupJobLog(ctx context.Context, namespace, jobName string) (string, error) { selector := fmt.Sprintf("job-name=%s", jobName) pods, err := c.Clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) @@ -97,6 +100,7 @@ func latestBackupJobPodName(pods []corev1.Pod) string { return pods[0].Name } +// ListBackupJobsForPVC returns backup jobs for a single PVC in newest-first order. func (c *Client) ListBackupJobsForPVC(ctx context.Context, namespace, pvc string) ([]BackupJobSummary, error) { selector := fmt.Sprintf("%s=soteria,%s=backup,%s=backup,%s=%s", labelAppName, labelComponent, labelAction, labelPVC, pvc) jobs, err := c.Clientset.BatchV1().Jobs(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) @@ -160,6 +164,7 @@ func sortBackupJobSummaries(items []BackupJobSummary) { }) } +// CreateBackupJob provisions the Kubernetes job and companion secret for a backup request. func (c *Client) CreateBackupJob(ctx context.Context, cfg *config.Config, req api.BackupRequest) (string, string, error) { if req.Namespace == "" { return "", "", errors.New("namespace is required") @@ -235,6 +240,7 @@ func (c *Client) resolvePVCMountedNode(ctx context.Context, namespace, pvc strin return "", nil } +// CreateRestoreJob provisions the Kubernetes job and companion secret for a restore request. func (c *Client) CreateRestoreJob(ctx context.Context, cfg *config.Config, req api.RestoreTestRequest) (string, string, error) { if req.Namespace == "" { return "", "", errors.New("namespace is required") diff --git a/internal/k8s/state.go b/internal/k8s/state.go index 316afbe..d2f49b1 100644 --- a/internal/k8s/state.go +++ b/internal/k8s/state.go @@ -9,6 +9,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// LoadSecretData returns a copy of the requested secret value when it exists. func (c *Client) LoadSecretData(ctx context.Context, namespace, secretName, key string) ([]byte, error) { secret, err := c.Clientset.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) if err != nil { @@ -29,6 +30,7 @@ func (c *Client) LoadSecretData(ctx context.Context, namespace, secretName, key return out, nil } +// SaveSecretData creates or updates the target secret while preserving labels. func (c *Client) SaveSecretData(ctx context.Context, namespace, secretName, key string, value []byte, labels map[string]string) error { secretClient := c.Clientset.CoreV1().Secrets(namespace) secret, err := secretClient.Get(ctx, secretName, metav1.GetOptions{}) diff --git a/internal/k8s/volumes.go b/internal/k8s/volumes.go index 959a393..36379c2 100644 --- a/internal/k8s/volumes.go +++ b/internal/k8s/volumes.go @@ -10,6 +10,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// PVCSummary describes a bound PVC and the volume metadata Soteria needs. type PVCSummary struct { Namespace string Name string @@ -20,6 +21,7 @@ type PVCSummary struct { AccessModes []string } +// ResolvePVCVolume loads a PVC and its bound PV so backup handlers can act on the real volume. func (c *Client) ResolvePVCVolume(ctx context.Context, namespace, pvcName string) (string, *corev1.PersistentVolumeClaim, *corev1.PersistentVolume, error) { pvc, err := c.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{}) if err != nil { @@ -37,6 +39,7 @@ func (c *Client) ResolvePVCVolume(ctx context.Context, namespace, pvcName string return pvc.Spec.VolumeName, pvc, pv, nil } +// ListBoundPVCs returns all bound PVCs in a stable namespace/name order. func (c *Client) ListBoundPVCs(ctx context.Context) ([]PVCSummary, error) { list, err := c.Clientset.CoreV1().PersistentVolumeClaims("").List(ctx, metav1.ListOptions{}) if err != nil { @@ -87,6 +90,7 @@ func (c *Client) ListBoundPVCs(ctx context.Context) ([]PVCSummary, error) { return items, nil } +// PersistentVolumeClaimExists reports whether a PVC can be read successfully. func (c *Client) PersistentVolumeClaimExists(ctx context.Context, namespace, pvcName string) (bool, error) { _, err := c.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{}) if err == nil { diff --git a/internal/longhorn/client.go b/internal/longhorn/client.go index caac960..ba3a629 100644 --- a/internal/longhorn/client.go +++ b/internal/longhorn/client.go @@ -14,11 +14,13 @@ import ( const defaultTimeout = 30 * time.Second +// Client wraps the Longhorn API base URL and HTTP client. type Client struct { baseURL string http *http.Client } +// New returns a Longhorn API client for the supplied base URL. func New(baseURL string) *Client { baseURL = strings.TrimSuffix(baseURL, "/") return &Client{ @@ -29,15 +31,18 @@ func New(baseURL string) *Client { } } +// APIError reports a non-2xx Longhorn API response. type APIError struct { Status int Message string } +// Error renders the Longhorn API failure in a human-friendly form. func (e *APIError) Error() string { return fmt.Sprintf("longhorn api error: status=%d message=%s", e.Status, e.Message) } +// Volume describes the Longhorn volume payload Soteria consumes. type Volume struct { Name string `json:"name"` Size string `json:"size"` @@ -48,6 +53,7 @@ type Volume struct { Actions map[string]any `json:"actions"` } +// BackupStatus describes the backup progress for a Longhorn snapshot. type BackupStatus struct { Snapshot string `json:"snapshot"` BackupURL string `json:"backupURL"` @@ -56,11 +62,13 @@ type BackupStatus struct { Progress int `json:"progress"` } +// BackupVolume describes a Longhorn backup volume and its action URLs. type BackupVolume struct { Name string `json:"name"` Actions map[string]string `json:"actions"` } +// Backup describes a Longhorn backup record. type Backup struct { Name string `json:"name"` SnapshotName string `json:"snapshotName"` @@ -95,6 +103,7 @@ type pvcCreateInput struct { PVCName string `json:"pvcName"` } +// CreateSnapshot requests a snapshot on the target Longhorn volume. func (c *Client) CreateSnapshot(ctx context.Context, volume, name string, labels map[string]string) error { path := fmt.Sprintf("%s/v1/volumes/%s?action=snapshotCreate", c.baseURL, url.PathEscape(volume)) input := snapshotCreateInput{ @@ -104,6 +113,7 @@ func (c *Client) CreateSnapshot(ctx context.Context, volume, name string, labels return c.doJSON(ctx, http.MethodPost, path, input, nil) } +// SnapshotBackup requests a backup snapshot and returns the updated volume payload. func (c *Client) SnapshotBackup(ctx context.Context, volume, name string, labels map[string]string, backupMode string) (*Volume, error) { path := fmt.Sprintf("%s/v1/volumes/%s?action=snapshotBackup", c.baseURL, url.PathEscape(volume)) input := snapshotInput{ @@ -120,6 +130,7 @@ func (c *Client) SnapshotBackup(ctx context.Context, volume, name string, labels return &out, nil } +// GetVolume fetches the named Longhorn volume. func (c *Client) GetVolume(ctx context.Context, volume string) (*Volume, error) { path := fmt.Sprintf("%s/v1/volumes/%s", c.baseURL, url.PathEscape(volume)) var out Volume @@ -129,6 +140,7 @@ func (c *Client) GetVolume(ctx context.Context, volume string) (*Volume, error) return &out, nil } +// CreateVolumeFromBackup restores a volume from a Longhorn backup URL. func (c *Client) CreateVolumeFromBackup(ctx context.Context, name, size string, replicas int, backupURL string) (*Volume, error) { path := fmt.Sprintf("%s/v1/volumes", c.baseURL) payload := map[string]any{ @@ -147,6 +159,7 @@ func (c *Client) CreateVolumeFromBackup(ctx context.Context, name, size string, return &out, nil } +// CreatePVC asks Longhorn to create a PVC that binds to the supplied volume. func (c *Client) CreatePVC(ctx context.Context, volumeName, namespace, pvcName string) error { path := fmt.Sprintf("%s/v1/volumes/%s?action=pvcCreate", c.baseURL, url.PathEscape(volumeName)) input := pvcCreateInput{ @@ -156,11 +169,13 @@ func (c *Client) CreatePVC(ctx context.Context, volumeName, namespace, pvcName s return c.doJSON(ctx, http.MethodPost, path, input, nil) } +// DeleteVolume removes the named Longhorn volume. func (c *Client) DeleteVolume(ctx context.Context, volumeName string) error { path := fmt.Sprintf("%s/v1/volumes/%s", c.baseURL, url.PathEscape(volumeName)) return c.doJSON(ctx, http.MethodDelete, path, nil, nil) } +// GetBackupVolume fetches the backup volume metadata for the named volume. func (c *Client) GetBackupVolume(ctx context.Context, volumeName string) (*BackupVolume, error) { path := fmt.Sprintf("%s/v1/backupvolumes/%s", c.baseURL, url.PathEscape(volumeName)) var out BackupVolume @@ -183,6 +198,7 @@ func (c *Client) GetBackupVolume(ctx context.Context, volumeName string) (*Backu } } +// ListBackups returns the backups available for a Longhorn volume. func (c *Client) ListBackups(ctx context.Context, volumeName string) ([]Backup, error) { backupVolume, err := c.GetBackupVolume(ctx, volumeName) if err != nil { @@ -199,6 +215,7 @@ func (c *Client) ListBackups(ctx context.Context, volumeName string) ([]Backup, return out.Data, nil } +// FindBackup locates either a specific backup or the most recent completed one. func (c *Client) FindBackup(ctx context.Context, volumeName, snapshot string) (*Backup, error) { backups, err := c.ListBackups(ctx, volumeName) if err != nil { diff --git a/internal/server/metrics.go b/internal/server/metrics.go index 6c3c795..5893b8e 100644 --- a/internal/server/metrics.go +++ b/internal/server/metrics.go @@ -72,6 +72,7 @@ func newTelemetry() *telemetry { } } +// Handler exposes the telemetry renderer as an HTTP endpoint. func (t *telemetry) Handler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8") @@ -79,48 +80,56 @@ func (t *telemetry) Handler() http.Handler { }) } +// RecordBackupRequest counts a backup request outcome by driver. func (t *telemetry) RecordBackupRequest(driver, result string) { t.mu.Lock() defer t.mu.Unlock() incMetric(t.backupRequests, map[string]string{"driver": driver, "result": result}) } +// RecordRestoreRequest counts a restore request outcome by driver. func (t *telemetry) RecordRestoreRequest(driver, result string) { t.mu.Lock() defer t.mu.Unlock() incMetric(t.restoreRequests, map[string]string{"driver": driver, "result": result}) } +// RecordPolicyBackup counts a policy-cycle backup outcome. func (t *telemetry) RecordPolicyBackup(result string) { t.mu.Lock() defer t.mu.Unlock() incMetric(t.policyBackups, map[string]string{"result": result}) } +// RecordNamespaceBackupRequest counts a namespace backup outcome by driver. func (t *telemetry) RecordNamespaceBackupRequest(driver, result string) { t.mu.Lock() defer t.mu.Unlock() incMetric(t.namespaceBackupRequests, map[string]string{"driver": driver, "result": result}) } +// RecordNamespaceRestoreRequest counts a namespace restore outcome by driver. func (t *telemetry) RecordNamespaceRestoreRequest(driver, result string) { t.mu.Lock() defer t.mu.Unlock() incMetric(t.namespaceRestoreReqs, map[string]string{"driver": driver, "result": result}) } +// RecordAuthzDenied counts authorization denials by reason. func (t *telemetry) RecordAuthzDenied(reason string) { t.mu.Lock() defer t.mu.Unlock() incMetric(t.authzDenials, map[string]string{"reason": reason}) } +// RecordInventoryFailure increments the inventory refresh failure counter. func (t *telemetry) RecordInventoryFailure() { t.mu.Lock() defer t.mu.Unlock() t.inventoryRefreshFailure++ } +// RecordInventory updates PVC backup metrics from the latest inventory snapshot. func (t *telemetry) RecordInventory(inv api.InventoryResponse) { t.mu.Lock() defer t.mu.Unlock() @@ -171,6 +180,7 @@ func (t *telemetry) RecordInventory(inv api.InventoryResponse) { t.inventoryRefreshTime = float64(time.Now().Unix()) } +// RecordB2Usage updates the B2 telemetry gauges from the latest scan result. func (t *telemetry) RecordB2Usage(usage api.B2UsageResponse) { t.mu.Lock() defer t.mu.Unlock() diff --git a/internal/server/server.go b/internal/server/server.go index b1899d4..3b1de4e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -40,6 +40,7 @@ type longhornClient interface { ListBackups(ctx context.Context, volumeName string) ([]longhorn.Backup, error) } +// Server owns HTTP routing, policy state, telemetry, and the UI renderer. type Server struct { cfg *config.Config client kubeClient @@ -99,6 +100,7 @@ type resticPersistedUsageDocument struct { } `json:"jobs"` } +// New constructs a server with fresh telemetry and in-memory policy state. func New(cfg *config.Config, client *k8s.Client, lh *longhorn.Client) *Server { s := &Server{ cfg: cfg, @@ -114,6 +116,7 @@ func New(cfg *config.Config, client *k8s.Client, lh *longhorn.Client) *Server { return s } +// Start launches telemetry and policy refresh loops for the active server. func (s *Server) Start(ctx context.Context) { if err := s.loadPolicies(ctx); err != nil { log.Printf("policy load failed: %v", err) @@ -155,6 +158,7 @@ func (s *Server) Start(ctx context.Context) { }() } +// Handler returns the HTTP handler used by the embedded server. func (s *Server) Handler() http.Handler { return s.handler } diff --git a/internal/server/ui_renderer.go b/internal/server/ui_renderer.go index 16cbf8a..dd5749b 100644 --- a/internal/server/ui_renderer.go +++ b/internal/server/ui_renderer.go @@ -16,10 +16,12 @@ type uiRenderer struct { fsys fs.FS } +// newUIRenderer loads the embedded UI bundle using the default dist directory. func newUIRenderer() *uiRenderer { return newUIRendererFromFS(uiDist, "ui-dist") } +// newUIRendererFromFS builds a renderer from an arbitrary filesystem root. func newUIRendererFromFS(root fs.FS, dir string) *uiRenderer { sub, err := fs.Sub(root, dir) if err != nil { @@ -28,6 +30,7 @@ func newUIRendererFromFS(root fs.FS, dir string) *uiRenderer { return &uiRenderer{fsys: sub} } +// ServeIndex writes the SPA entrypoint so deep links can hydrate client-side routes. func (u *uiRenderer) ServeIndex(w http.ResponseWriter, _ *http.Request) error { if u.fsys == nil { return fmt.Errorf("UI assets are not available") @@ -42,6 +45,7 @@ func (u *uiRenderer) ServeIndex(w http.ResponseWriter, _ *http.Request) error { return nil } +// ServeAsset serves hashed static assets when the request path matches a built file. func (u *uiRenderer) ServeAsset(w http.ResponseWriter, r *http.Request) bool { if u.fsys == nil { return false diff --git a/scripts/doc_hygiene_waivers.tsv b/scripts/doc_hygiene_waivers.tsv index ad63f85..ffe34a6 100644 --- a/scripts/doc_hygiene_waivers.tsv +++ b/scripts/doc_hygiene_waivers.tsv @@ -1,71 +1 @@ # relative_path kind name reason -internal/api/types.go type BackupRequest legacy-no-doc -internal/api/types.go type BackupResponse legacy-no-doc -internal/api/types.go type RestoreTestRequest legacy-no-doc -internal/api/types.go type RestoreTestResponse legacy-no-doc -internal/api/types.go type InventoryResponse legacy-no-doc -internal/api/types.go type NamespaceInventory legacy-no-doc -internal/api/types.go type PVCInventory legacy-no-doc -internal/api/types.go type BackupListResponse legacy-no-doc -internal/api/types.go type BackupRecord legacy-no-doc -internal/api/types.go type AuthInfoResponse legacy-no-doc -internal/api/types.go type BackupPolicy legacy-no-doc -internal/api/types.go type BackupPolicyUpsertRequest legacy-no-doc -internal/api/types.go type BackupPolicyListResponse legacy-no-doc -internal/api/types.go type NamespaceBackupRequest legacy-no-doc -internal/api/types.go type NamespaceBackupResult legacy-no-doc -internal/api/types.go type NamespaceBackupResponse legacy-no-doc -internal/api/types.go type NamespaceRestoreRequest legacy-no-doc -internal/api/types.go type NamespaceRestoreResult legacy-no-doc -internal/api/types.go type NamespaceRestoreResponse legacy-no-doc -internal/api/types.go type B2UsageResponse legacy-no-doc -internal/api/types.go type B2BucketUsage legacy-no-doc -internal/config/config.go type Config legacy-no-doc -internal/config/config.go func Load legacy-no-doc -internal/k8s/client.go type Client legacy-no-doc -internal/k8s/client.go func New legacy-no-doc -internal/k8s/jobs.go type BackupJobSummary legacy-no-doc -internal/k8s/jobs.go func ListBackupJobs legacy-no-doc -internal/k8s/jobs.go func ReadBackupJobLog legacy-no-doc -internal/k8s/jobs.go func ListBackupJobsForPVC legacy-no-doc -internal/k8s/jobs.go func CreateBackupJob legacy-no-doc -internal/k8s/jobs.go func CreateRestoreJob legacy-no-doc -internal/k8s/state.go func LoadSecretData legacy-no-doc -internal/k8s/state.go func SaveSecretData legacy-no-doc -internal/k8s/volumes.go type PVCSummary legacy-no-doc -internal/k8s/volumes.go func ResolvePVCVolume legacy-no-doc -internal/k8s/volumes.go func ListBoundPVCs legacy-no-doc -internal/k8s/volumes.go func PersistentVolumeClaimExists legacy-no-doc -internal/longhorn/client.go type Client legacy-no-doc -internal/longhorn/client.go func New legacy-no-doc -internal/longhorn/client.go type APIError legacy-no-doc -internal/longhorn/client.go func Error legacy-no-doc -internal/longhorn/client.go type Volume legacy-no-doc -internal/longhorn/client.go type BackupStatus legacy-no-doc -internal/longhorn/client.go type BackupVolume legacy-no-doc -internal/longhorn/client.go type Backup legacy-no-doc -internal/longhorn/client.go func CreateSnapshot legacy-no-doc -internal/longhorn/client.go func SnapshotBackup legacy-no-doc -internal/longhorn/client.go func GetVolume legacy-no-doc -internal/longhorn/client.go func CreateVolumeFromBackup legacy-no-doc -internal/longhorn/client.go func CreatePVC legacy-no-doc -internal/longhorn/client.go func DeleteVolume legacy-no-doc -internal/longhorn/client.go func GetBackupVolume legacy-no-doc -internal/longhorn/client.go func ListBackups legacy-no-doc -internal/longhorn/client.go func FindBackup legacy-no-doc -internal/server/metrics.go func Handler legacy-no-doc -internal/server/metrics.go func RecordBackupRequest legacy-no-doc -internal/server/metrics.go func RecordRestoreRequest legacy-no-doc -internal/server/metrics.go func RecordPolicyBackup legacy-no-doc -internal/server/metrics.go func RecordNamespaceBackupRequest legacy-no-doc -internal/server/metrics.go func RecordNamespaceRestoreRequest legacy-no-doc -internal/server/metrics.go func RecordAuthzDenied legacy-no-doc -internal/server/metrics.go func RecordInventoryFailure legacy-no-doc -internal/server/metrics.go func RecordInventory legacy-no-doc -internal/server/metrics.go func RecordB2Usage legacy-no-doc -internal/server/server.go type Server legacy-no-doc -internal/server/server.go func New legacy-no-doc -internal/server/server.go func Start legacy-no-doc -internal/server/server.go func Handler legacy-no-doc -internal/server/ui_renderer.go func ServeIndex legacy-no-doc -internal/server/ui_renderer.go func ServeAsset legacy-no-doc