normalizeScheduleString handles the common schedule string parsing, warning emission, fuzzy scattering, and validation logic. It returns the normalized cron expression and the original friendly format, or an error if validation fails.
(scheduleStr string, itemIndex int)
| 16 | // fuzzy scattering, and validation logic. It returns the normalized cron expression |
| 17 | // and the original friendly format, or an error if validation fails. |
| 18 | func (c *Compiler) normalizeScheduleString(scheduleStr string, itemIndex int) (parsedCron string, friendlyFormat string, err error) { |
| 19 | // Try to parse as a schedule expression |
| 20 | parsedCron, original, err := parser.ParseSchedule(scheduleStr) |
| 21 | if err != nil { |
| 22 | // Return error for array items, but return nil error for top-level parsing |
| 23 | // (caller will handle differently based on context) |
| 24 | if itemIndex >= 0 { |
| 25 | return "", "", fmt.Errorf("invalid schedule expression in item %d: %w", itemIndex, err) |
| 26 | } |
| 27 | return "", "", err |
| 28 | } |
| 29 | |
| 30 | // Warn if using explicit daily cron pattern |
| 31 | if parser.IsDailyCron(parsedCron) && !parser.IsFuzzyCron(parsedCron) { |
| 32 | c.addDailyCronWarning(parsedCron) |
| 33 | } |
| 34 | |
| 35 | // Warn if using hourly interval with fixed minute |
| 36 | if parser.IsHourlyCron(parsedCron) && !parser.IsFuzzyCron(parsedCron) { |
| 37 | c.addHourlyCronWarning(parsedCron) |
| 38 | } |
| 39 | |
| 40 | // Warn if using explicit weekly cron pattern with fixed time |
| 41 | if parser.IsWeeklyCron(parsedCron) && !parser.IsFuzzyCron(parsedCron) { |
| 42 | c.addWeeklyCronWarning(parsedCron) |
| 43 | } |
| 44 | |
| 45 | // Scatter fuzzy schedules if workflow identifier is set |
| 46 | if parser.IsFuzzyCron(parsedCron) && c.workflowIdentifier != "" { |
| 47 | // Combine repo slug/dev prefix and workflow identifier for scattering seed |
| 48 | // This ensures workflows with the same name in different repositories |
| 49 | // get different execution times, distributing load across an organization. |
| 50 | // Format: |
| 51 | // - Dev mode: "dev/workflow-path" |
| 52 | // - Release mode: "owner/repo/workflow-path" or just "workflow-path" if no repo slug |
| 53 | seed := c.workflowIdentifier |
| 54 | if IsRelease() { |
| 55 | // Release mode: use repository slug if available |
| 56 | if c.repositorySlug != "" { |
| 57 | seed = c.repositorySlug + "/" + c.workflowIdentifier |
| 58 | } else { |
| 59 | // Warn if repository slug is not available - scattering will not be org-aware |
| 60 | schedulePreprocessingLog.Printf("Warning: repository slug not available for fuzzy schedule scattering") |
| 61 | c.IncrementWarningCount() |
| 62 | c.addScheduleWarning("Fuzzy schedule scattering without repository context. Workflows with the same name in different repositories may collide. Ensure you are in a git repository with a configured remote.") |
| 63 | } |
| 64 | } else { |
| 65 | // Dev mode: use "dev" prefix for consistent scattering across all workflows |
| 66 | seed = "dev/" + c.workflowIdentifier |
| 67 | schedulePreprocessingLog.Printf("Using dev mode seed for fuzzy schedule scattering: %s", seed) |
| 68 | } |
| 69 | scatteredCron, err := parser.ScatterSchedule(parsedCron, seed) |
| 70 | if err != nil { |
| 71 | schedulePreprocessingLog.Printf("Warning: failed to scatter fuzzy schedule: %v", err) |
| 72 | // Keep the original fuzzy schedule as fallback |
| 73 | } else { |
| 74 | schedulePreprocessingLog.Printf("Scattered fuzzy schedule %s to %s for workflow %s", parsedCron, scatteredCron, c.workflowIdentifier) |
| 75 | parsedCron = scatteredCron |
no test coverage detected