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 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}