diff --git a/README.md b/README.md index 5695b1e..527dc00 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Environment variables: - `SOTERIA_S3_ENDPOINT` (optional) Example: `s3.us-west-004.backblazeb2.com` - `SOTERIA_S3_REGION` (optional) Example: `us-west-004` - `SOTERIA_JOB_TTL_SECONDS` (default: 86400) +- `SOTERIA_JOB_NODE_SELECTOR` (optional) Comma-separated node selector, e.g. `kubernetes.io/arch=arm64,node-role.kubernetes.io/worker=true` - `SOTERIA_JOB_SERVICE_ACCOUNT` (optional) ServiceAccount for backup Jobs - `SOTERIA_LISTEN_ADDR` (default: `:8080`) diff --git a/internal/config/config.go b/internal/config/config.go index 3e4debc..af4980e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,6 +26,7 @@ type Config struct { S3Endpoint string S3Region string JobTTLSeconds int32 + JobNodeSelector map[string]string WorkerServiceAccount string ListenAddr string } @@ -56,6 +57,7 @@ func Load() (*Config, error) { cfg.S3Region = getenv("SOTERIA_S3_REGION") cfg.WorkerServiceAccount = getenv("SOTERIA_JOB_SERVICE_ACCOUNT") cfg.ListenAddr = getenvDefault("SOTERIA_LISTEN_ADDR", defaultListenAddr) + cfg.JobNodeSelector = parseNodeSelector(getenv("SOTERIA_JOB_NODE_SELECTOR")) if ttl, ok := getenvInt("SOTERIA_JOB_TTL_SECONDS"); ok { cfg.JobTTLSeconds = int32(ttl) @@ -73,6 +75,9 @@ func Load() (*Config, error) { if strings.Contains(cfg.ResticRepository, "..") { return nil, errors.New("SOTERIA_RESTIC_REPOSITORY contains invalid path segments") } + if cfg.JobNodeSelector == nil { + return nil, errors.New("SOTERIA_JOB_NODE_SELECTOR is invalid; expected key=value pairs") + } return cfg, nil } @@ -99,3 +104,31 @@ func getenvInt(key string) (int, bool) { } return parsed, true } + +func parseNodeSelector(raw string) map[string]string { + raw = strings.TrimSpace(raw) + if raw == "" { + return map[string]string{} + } + + selector := map[string]string{} + parts := strings.Split(raw, ",") + for _, part := range parts { + part = strings.TrimSpace(part) + if part == "" { + continue + } + key, value, found := strings.Cut(part, "=") + if !found { + return nil + } + key = strings.TrimSpace(key) + value = strings.TrimSpace(value) + if key == "" || value == "" { + return nil + } + selector[key] = value + } + + return selector +} diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 38002c2..9ff1cf5 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -149,6 +149,9 @@ func buildBackupJob(cfg *config.Config, req api.BackupRequest, jobName, secretNa }, } + if len(cfg.JobNodeSelector) > 0 { + pod.NodeSelector = cfg.JobNodeSelector + } if cfg.WorkerServiceAccount != "" { pod.ServiceAccountName = cfg.WorkerServiceAccount } @@ -209,6 +212,9 @@ func buildRestoreJob(cfg *config.Config, req api.RestoreTestRequest, jobName, se }, } + if len(cfg.JobNodeSelector) > 0 { + pod.NodeSelector = cfg.JobNodeSelector + } if req.TargetPVC != "" { pod.Volumes[0] = corev1.Volume{ Name: "restore",