|
8 | 8 | import yaml
|
9 | 9 | import sys
|
10 | 10 | import argparse
|
| 11 | +import re |
11 | 12 |
|
12 | 13 | VERBOSE = False
|
13 | 14 |
|
@@ -171,6 +172,8 @@ def merge_files(dir="config"):
|
171 | 172 | with open(file, "r") as stream:
|
172 | 173 | try:
|
173 | 174 | data = yaml.safe_load(stream)
|
| 175 | + # Check for duplicate jobs in this file before merging |
| 176 | + validate_duplicate_jobs(data, file) |
174 | 177 | merged_data = recursive_merge(merged_data, data)
|
175 | 178 | except yaml.YAMLError as exc:
|
176 | 179 | print(f"Error in {file}: {exc}")
|
@@ -338,6 +341,55 @@ def walker(merged_data):
|
338 | 341 | return
|
339 | 342 |
|
340 | 343 |
|
| 344 | +def validate_duplicate_jobs(data, filename): |
| 345 | + """ |
| 346 | + Check for duplicate job keys in a single YAML file |
| 347 | + This catches cases where the same job name appears multiple times |
| 348 | + """ |
| 349 | + if 'jobs' not in data: |
| 350 | + return |
| 351 | + |
| 352 | + # Parse the file as raw text to detect duplicate keys |
| 353 | + with open(filename, 'r') as f: |
| 354 | + content = f.read() |
| 355 | + |
| 356 | + # Look for job definitions in the jobs section |
| 357 | + # Find the jobs section |
| 358 | + jobs_match = re.search(r'^jobs:\s*$', content, re.MULTILINE) |
| 359 | + if not jobs_match: |
| 360 | + return |
| 361 | + |
| 362 | + # Extract everything after the jobs: line until next top-level key or end of file |
| 363 | + jobs_start = jobs_match.end() |
| 364 | + jobs_section = content[jobs_start:] |
| 365 | + |
| 366 | + # Find the end of jobs section (next top-level key or end of file) |
| 367 | + next_section_match = re.search(r'^\S:', jobs_section, re.MULTILINE) |
| 368 | + if next_section_match: |
| 369 | + jobs_section = jobs_section[:next_section_match.start()] |
| 370 | + |
| 371 | + # Find all job names (lines that start with 2 spaces and contain a colon) |
| 372 | + job_pattern = r'^\s{2}([a-zA-Z0-9_\-]+):' |
| 373 | + job_matches = re.findall(job_pattern, jobs_section, re.MULTILINE) |
| 374 | + if VERBOSE: |
| 375 | + print(f"Found {len(job_matches)} job definitions in {filename}") |
| 376 | + |
| 377 | + # Count occurrences of each job name |
| 378 | + job_counts = {} |
| 379 | + for job_name in job_matches: |
| 380 | + job_counts[job_name] = job_counts.get(job_name, 0) + 1 |
| 381 | + |
| 382 | + # Report duplicates |
| 383 | + duplicates_found = False |
| 384 | + for job_name, count in job_counts.items(): |
| 385 | + if count > 1: |
| 386 | + print(f"ERROR: Duplicate job '{job_name}' found {count} times in {filename}") |
| 387 | + duplicates_found = True |
| 388 | + |
| 389 | + if duplicates_found: |
| 390 | + raise yaml.YAMLError(f"Duplicate job definitions found in {filename}") |
| 391 | + |
| 392 | + |
341 | 393 | def main():
|
342 | 394 | global VERBOSE
|
343 | 395 | parser = argparse.ArgumentParser(description="Validate and dump yaml files")
|
|
0 commit comments