use crate::error::Result;
use crate::models::{EnvVar, Job, Pipeline, Provider, Step};
use serde_yaml::Value;
pub fn parse(content: &str) -> Result<Pipeline> {
let yaml: Value = serde_yaml::from_str(content)?;
let mapping = yaml.as_mapping().ok_or_else(|| {
crate::error::PipecheckError::InvalidPipeline("Expected YAML mapping".to_string())
})?;
let mut env = Vec::new();
if let Some(env_val) = mapping.get("variables") {
if let Some(env_map) = env_val.as_mapping() {
for (k, v) in env_map {
if let Some(key) = k.as_str() {
let value = match v {
Value::String(s) => s.clone(),
other => format!("{:?}", other),
};
env.push(EnvVar {
key: key.to_string(),
value,
is_secret: false,
});
}
}
}
}
let reserved = vec![
"stages",
"variables",
"image",
"before_script",
"after_script",
"cache",
"services",
"include",
"default",
];
let mut jobs = Vec::new();
for (key, value) in mapping {
let key_str = match key.as_str() {
Some(s) => s,
None => continue,
};
if reserved.contains(&key_str) || key_str == "workflow" {
continue;
}
if key_str == "stages" {
continue;
}
if let Some(job_map) = value.as_mapping() {
let job = parse_job(key_str, job_map)?;
jobs.push(job);
}
}
Ok(Pipeline {
provider: Provider::GitLabCI,
jobs,
env,
source: content.to_string(),
})
}
fn parse_job(id: &str, map: &serde_yaml::Mapping) -> Result<Job> {
let mut steps = Vec::new();
let mut depends_on = Vec::new();
let mut env = Vec::new();
let mut container_image: Option<String> = None;
let mut service_images: Vec<String> = Vec::new();
let mut timeout_minutes: Option<u64> = None;
if let Some(image_val) = map.get("image") {
if let Value::String(s) = image_val {
container_image = Some(s.clone());
} else if let Value::Mapping(m) = image_val {
if let Some(Value::String(s)) = m.get(Value::String("name".to_string())) {
container_image = Some(s.clone());
}
}
}
if let Some(timeout_val) = map.get("timeout") {
if let Some(n) = timeout_val.as_u64() {
timeout_minutes = Some(n);
}
}
if let Some(Value::Sequence(services)) = map.get("services") {
for svc in services {
if let Value::String(s) = svc {
service_images.push(s.clone());
} else if let Value::Mapping(m) = svc {
if let Some(Value::String(s)) = m.get(Value::String("name".to_string())) {
service_images.push(s.clone());
}
}
}
}
if let Some(vars_val) = map.get("variables") {
if let Some(vars_map) = vars_val.as_mapping() {
for (k, v) in vars_map {
if let Some(key) = k.as_str() {
let value = match v {
Value::String(s) => s.clone(),
other => format!("{:?}", other),
};
env.push(EnvVar {
key: key.to_string(),
value,
is_secret: false,
});
}
}
}
}
if let Some(needs_val) = map.get("needs") {
if let Value::Sequence(seq) = needs_val {
for item in seq {
match item {
Value::String(s) => depends_on.push(s.clone()),
Value::Mapping(m) => {
if let Some(Value::String(s)) = m.get(Value::String("job".to_string())) {
depends_on.push(s.clone());
}
}
_ => {}
}
}
}
} else if let Value::Sequence(seq) = map.get("dependencies").unwrap_or(&Value::Null) {
for item in seq {
if let Value::String(s) = item {
depends_on.push(s.clone());
}
}
}
for script_key in &["before_script", "script", "after_script"] {
if let Some(script_val) = map.get(*script_key) {
let run = match script_val {
Value::String(s) => s.clone(),
Value::Sequence(seq) => seq
.iter()
.map(|v| match v {
Value::String(s) => s.clone(),
other => format!("{:?}", other),
})
.collect::<Vec<_>>()
.join("\n"),
other => format!("{:?}", other),
};
steps.push(Step {
name: Some(script_key.to_string()),
uses: None,
run: Some(run),
env: Vec::new(),
with_inputs: None,
});
}
}
if let Some(trigger_val) = map.get("trigger") {
let trigger_str = match trigger_val {
Value::String(s) => s.clone(),
Value::Mapping(m) => {
if let Some(Value::String(s)) = m.get(Value::String("project".to_string())) {
format!("project: {}", s)
} else {
format!("{:?}", trigger_val)
}
}
other => format!("{:?}", other),
};
steps.push(Step {
name: Some("trigger".to_string()),
uses: None,
run: Some(format!("trigger: {}", trigger_str)),
env: Vec::new(),
with_inputs: None,
});
}
Ok(Job {
id: id.to_string(),
name: None,
depends_on,
steps,
env,
container_image,
service_images,
timeout_minutes,
})
}