|  | 
|  | 1 | +use std::path::Path; | 
|  | 2 | + | 
|  | 3 | +use super::Job; | 
| 1 | 4 | use crate::jobs::{JobDatabase, load_job_db}; | 
|  | 5 | +use crate::{DOCKER_DIRECTORY, JOBS_YML_PATH, utils}; | 
| 2 | 6 | 
 | 
| 3 | 7 | #[test] | 
| 4 | 8 | fn lookup_job_pattern() { | 
| @@ -62,3 +66,65 @@ fn check_pattern(db: &JobDatabase, pattern: &str, expected: &[&str]) { | 
| 62 | 66 | 
 | 
| 63 | 67 |     assert_eq!(jobs, expected); | 
| 64 | 68 | } | 
|  | 69 | + | 
|  | 70 | +/// Validate that CodeBuild jobs use Docker images from ghcr.io registry. | 
|  | 71 | +/// This is needed because otherwise from CodeBuild we get rate limited by Docker Hub. | 
|  | 72 | +fn validate_codebuild_image(job: &Job) -> anyhow::Result<()> { | 
|  | 73 | +    let is_job_on_codebuild = job.codebuild.unwrap_or(false); | 
|  | 74 | +    if !is_job_on_codebuild { | 
|  | 75 | +        // Jobs in GitHub Actions don't get rate limited by Docker Hub. | 
|  | 76 | +        return Ok(()); | 
|  | 77 | +    } | 
|  | 78 | + | 
|  | 79 | +    let image_name = job.image(); | 
|  | 80 | +    // we hardcode host-x86_64 here, because in codebuild we only run jobs for this architecture. | 
|  | 81 | +    let dockerfile_path = | 
|  | 82 | +        Path::new(DOCKER_DIRECTORY).join("host-x86_64").join(&image_name).join("Dockerfile"); | 
|  | 83 | + | 
|  | 84 | +    if !dockerfile_path.exists() { | 
|  | 85 | +        return Err(anyhow::anyhow!( | 
|  | 86 | +            "Dockerfile not found for CodeBuild job '{}' at path: {}", | 
|  | 87 | +            job.name, | 
|  | 88 | +            dockerfile_path.display() | 
|  | 89 | +        )); | 
|  | 90 | +    } | 
|  | 91 | + | 
|  | 92 | +    let dockerfile_content = utils::read_to_string(&dockerfile_path)?; | 
|  | 93 | + | 
|  | 94 | +    // Check if all FROM statement uses ghcr.io registry | 
|  | 95 | +    let has_ghcr_from = dockerfile_content | 
|  | 96 | +        .lines() | 
|  | 97 | +        .filter(|line| line.trim_start().to_lowercase().starts_with("from ")) | 
|  | 98 | +        .all(|line| line.contains("ghcr.io")); | 
|  | 99 | + | 
|  | 100 | +    if !has_ghcr_from { | 
|  | 101 | +        return Err(anyhow::anyhow!( | 
|  | 102 | +            "CodeBuild job '{}' must use ghcr.io registry in its Dockerfile FROM statement. \ | 
|  | 103 | +                Dockerfile path: {dockerfile_path:?}", | 
|  | 104 | +            job.name, | 
|  | 105 | +        )); | 
|  | 106 | +    } | 
|  | 107 | + | 
|  | 108 | +    Ok(()) | 
|  | 109 | +} | 
|  | 110 | + | 
|  | 111 | +#[test] | 
|  | 112 | +fn validate_jobs() { | 
|  | 113 | +    let db = { | 
|  | 114 | +        let default_jobs_file = Path::new(JOBS_YML_PATH); | 
|  | 115 | +        let db_str = utils::read_to_string(default_jobs_file).unwrap(); | 
|  | 116 | +        load_job_db(&db_str).expect("Failed to load job database") | 
|  | 117 | +    }; | 
|  | 118 | + | 
|  | 119 | +    let all_jobs = | 
|  | 120 | +        db.pr_jobs.iter().chain(db.try_jobs.iter()).chain(db.auto_jobs.iter()).collect::<Vec<_>>(); | 
|  | 121 | + | 
|  | 122 | +    let errors: Vec<anyhow::Error> = | 
|  | 123 | +        all_jobs.into_iter().filter_map(|job| validate_codebuild_image(job).err()).collect(); | 
|  | 124 | + | 
|  | 125 | +    if !errors.is_empty() { | 
|  | 126 | +        let error_messages = | 
|  | 127 | +            errors.into_iter().map(|e| format!("- {e}")).collect::<Vec<_>>().join("\n"); | 
|  | 128 | +        panic!("Job validation failed:\n{error_messages}"); | 
|  | 129 | +    } | 
|  | 130 | +} | 
0 commit comments