Skip to main content

opal/model/
lowering.rs

1use crate::GitLabRemoteConfig;
2use crate::gitlab::PipelineGraph;
3use crate::model::{
4    JobSpec, PipelineDefaultsSpec, PipelineFilterSpec, PipelineSpec, StageSpec, WorkflowSpec,
5};
6use anyhow::{Result, anyhow};
7use std::collections::HashMap;
8use std::path::Path;
9
10impl PipelineSpec {
11    pub fn from_path(path: &Path) -> Result<Self> {
12        Self::from_path_with_gitlab(path, None)
13    }
14
15    pub fn from_path_with_gitlab(path: &Path, gitlab: Option<&GitLabRemoteConfig>) -> Result<Self> {
16        let graph = PipelineGraph::from_path_with_gitlab(path, gitlab)?;
17        Self::try_from(&graph)
18    }
19}
20
21impl TryFrom<&PipelineGraph> for PipelineSpec {
22    type Error = anyhow::Error;
23
24    fn try_from(graph: &PipelineGraph) -> Result<Self> {
25        let mut jobs = HashMap::new();
26        let mut stages = Vec::with_capacity(graph.stages.len());
27
28        // TODO: again some bullshit for for for for - refactor
29
30        for stage in &graph.stages {
31            let mut stage_jobs = Vec::with_capacity(stage.jobs.len());
32            for node_idx in &stage.jobs {
33                let job = graph
34                    .graph
35                    .node_weight(*node_idx)
36                    .ok_or_else(|| anyhow!("missing job for stage '{}'", stage.name))?;
37                stage_jobs.push(job.name.clone());
38                jobs.insert(job.name.clone(), JobSpec::from(job));
39            }
40            stages.push(StageSpec {
41                name: stage.name.clone(),
42                jobs: stage_jobs,
43            });
44        }
45
46        Ok(PipelineSpec {
47            stages,
48            jobs,
49            defaults: PipelineDefaultsSpec::from(&graph.defaults),
50            workflow: graph.workflow.as_ref().map(WorkflowSpec::from),
51            filters: PipelineFilterSpec::from(&graph.filters),
52        })
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use crate::gitlab::PipelineGraph;
59    use crate::model::{ParallelConfigSpec, PipelineSpec};
60    use std::path::Path;
61
62    #[test]
63    fn lowers_pipeline_graph_to_pipeline_spec() {
64        let graph = PipelineGraph::from_path("pipelines/tests/needs-and-artifacts.gitlab-ci.yml")
65            .expect("pipeline parses");
66        let spec = PipelineSpec::from_path(Path::new(
67            "pipelines/tests/needs-and-artifacts.gitlab-ci.yml",
68        ))
69        .expect("pipeline lowers");
70
71        assert_eq!(spec.stages.len(), graph.stages.len());
72        assert!(spec.jobs.contains_key("build-matrix"));
73        assert!(matches!(
74            spec.jobs
75                .get("build-matrix")
76                .expect("job exists")
77                .parallel
78                .as_ref(),
79            Some(ParallelConfigSpec::Matrix(_))
80        ));
81    }
82
83    #[test]
84    fn lowers_default_cache_fallback_keys_into_pipeline_spec() {
85        let spec =
86            PipelineSpec::from_path(Path::new("pipelines/tests/cache-fallback.gitlab-ci.yml"))
87                .expect("pipeline lowers");
88
89        assert_eq!(
90            spec.defaults.cache[0].fallback_keys,
91            vec![
92                "$CACHE_NAMESPACE-$CI_DEFAULT_BRANCH".to_string(),
93                "$CACHE_NAMESPACE-default".to_string()
94            ]
95        );
96        assert_eq!(
97            spec.jobs["verify-fallback-cache"].cache[0].fallback_keys,
98            vec![
99                "$CACHE_NAMESPACE-$CI_DEFAULT_BRANCH".to_string(),
100                "$CACHE_NAMESPACE-default".to_string()
101            ]
102        );
103    }
104}