pub mod executor;
pub mod types;
pub use executor::{PipelineExecutor, PipelineResult};
pub use types::{PipelineCacheConfig, PipelineDefaults, PipelineImage, PushConfig, ZPipeline};
use crate::error::{BuildError, Result};
pub fn parse_pipeline(content: &str) -> Result<ZPipeline> {
serde_yaml::from_str(content)
.map_err(|e| BuildError::zimagefile_parse(format!("Pipeline parse error: {e}")))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_minimal_pipeline() {
let yaml = r"
images:
app:
file: Dockerfile
";
let pipeline = parse_pipeline(yaml).unwrap();
assert_eq!(pipeline.images.len(), 1);
assert!(pipeline.images.contains_key("app"));
}
#[test]
fn test_parse_full_pipeline() {
let yaml = r#"
version: "1"
vars:
VERSION: "1.0.0"
defaults:
format: oci
images:
base:
file: images/Dockerfile.base
tags:
- "myapp/base:${VERSION}"
app:
file: images/Dockerfile.app
context: "."
depends_on: [base]
tags:
- "myapp/app:${VERSION}"
push:
after_all: true
"#;
let pipeline = parse_pipeline(yaml).unwrap();
assert_eq!(pipeline.version, Some("1".to_string()));
assert_eq!(pipeline.vars.get("VERSION"), Some(&"1.0.0".to_string()));
assert_eq!(pipeline.defaults.format, Some("oci".to_string()));
assert_eq!(pipeline.images.len(), 2);
assert!(pipeline.push.after_all);
let app = &pipeline.images["app"];
assert_eq!(app.depends_on, vec!["base"]);
}
#[test]
fn test_rejects_unknown_fields() {
let yaml = r#"
images:
app:
file: Dockerfile
unknown_field: "should fail"
"#;
assert!(parse_pipeline(yaml).is_err());
}
#[test]
fn test_image_order_preserved() {
let yaml = r"
images:
third:
file: Dockerfile.third
first:
file: Dockerfile.first
second:
file: Dockerfile.second
";
let pipeline = parse_pipeline(yaml).unwrap();
let keys: Vec<&String> = pipeline.images.keys().collect();
assert_eq!(keys, vec!["third", "first", "second"]);
}
#[test]
fn test_vars_and_defaults() {
let yaml = r#"
vars:
REGISTRY: "ghcr.io/myorg"
VERSION: "v1.2.3"
defaults:
format: docker
build_args:
RUST_VERSION: "1.90"
no_cache: true
images:
app:
file: Dockerfile
"#;
let pipeline = parse_pipeline(yaml).unwrap();
assert_eq!(
pipeline.vars.get("REGISTRY"),
Some(&"ghcr.io/myorg".to_string())
);
assert_eq!(pipeline.vars.get("VERSION"), Some(&"v1.2.3".to_string()));
assert_eq!(pipeline.defaults.format, Some("docker".to_string()));
assert_eq!(
pipeline.defaults.build_args.get("RUST_VERSION"),
Some(&"1.90".to_string())
);
assert!(pipeline.defaults.no_cache);
}
#[test]
fn test_image_with_all_fields() {
let yaml = r#"
images:
app:
file: images/Dockerfile.app
context: "./app"
tags:
- "myapp:latest"
- "myapp:v1.0.0"
build_args:
NODE_ENV: production
DEBUG: "false"
depends_on:
- base
- utils
no_cache: true
format: oci
"#;
let pipeline = parse_pipeline(yaml).unwrap();
let app = &pipeline.images["app"];
assert_eq!(app.file.to_string_lossy(), "images/Dockerfile.app");
assert_eq!(app.context.to_string_lossy(), "./app");
assert_eq!(app.tags.len(), 2);
assert_eq!(
app.build_args.get("NODE_ENV"),
Some(&"production".to_string())
);
assert_eq!(app.depends_on, vec!["base", "utils"]);
assert_eq!(app.no_cache, Some(true));
assert_eq!(app.format, Some("oci".to_string()));
}
#[test]
fn test_empty_vars_and_defaults() {
let yaml = r"
images:
app:
file: Dockerfile
";
let pipeline = parse_pipeline(yaml).unwrap();
assert!(pipeline.vars.is_empty());
assert!(pipeline.defaults.format.is_none());
assert!(pipeline.defaults.build_args.is_empty());
assert!(!pipeline.defaults.no_cache);
assert!(!pipeline.push.after_all);
}
#[test]
fn test_roundtrip_serialization() {
let yaml = r#"
version: "1"
vars:
VERSION: "1.0.0"
images:
app:
file: Dockerfile
tags:
- "myapp:latest"
"#;
let pipeline = parse_pipeline(yaml).unwrap();
let serialized = serde_yaml::to_string(&pipeline).unwrap();
let pipeline2 = parse_pipeline(&serialized).unwrap();
assert_eq!(pipeline.version, pipeline2.version);
assert_eq!(pipeline.vars, pipeline2.vars);
assert_eq!(pipeline.images.len(), pipeline2.images.len());
}
#[test]
fn test_parse_error_message() {
let yaml = r"
images:
- this is invalid yaml structure
";
let result = parse_pipeline(yaml);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("Pipeline parse error"));
}
}