package ops import ( "encoding/json" "errors" "path/filepath" "strings" "ops-assistant/internal/core/runbook" "ops-assistant/models" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func decodeInputJSON(raw string, out *map[string]string) error { if strings.TrimSpace(raw) == "" { return nil } return json.Unmarshal([]byte(raw), out) } func RetryJobWithDB(db *gorm.DB, baseDir string, jobID uint) (uint, error) { if db == nil { return 0, errors.New("db is nil") } var old models.OpsJob if err := db.First(&old, jobID).Error; err != nil { return 0, err } if strings.TrimSpace(old.Status) != "failed" { return 0, errors.New("only failed jobs can retry") } inputs := map[string]string{} if strings.TrimSpace(old.InputJSON) != "" { _ = decodeInputJSON(old.InputJSON, &inputs) } meta := runbook.NewMeta() meta.Target = old.Target meta.RiskLevel = old.RiskLevel meta.RequestID = old.RequestID + "-retry" meta.ConfirmHash = old.ConfirmHash exec := runbook.NewExecutor(db, filepath.Join(baseDir, "runbooks")) newID, _, err := exec.RunWithInputsAndMeta(old.Command, old.Runbook, old.Operator, inputs, meta) if err != nil { return newID, err } return newID, nil } func RetryJob(dbPath, baseDir string, jobID uint) (uint, error) { db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { return 0, err } return RetryJobWithDB(db, baseDir, jobID) }