pipechecker 0.2.2

CI/CD Pipeline Auditor - Catch errors before you push
Documentation
use crate::error::Result;
use crate::models::{EnvVar, Job, Pipeline, Provider, Step};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Deserialize, Serialize)]
struct GitHubWorkflow {
    name: Option<String>,
    on: serde_yaml::Value,
    jobs: HashMap<String, GitHubJob>,
    env: Option<HashMap<String, serde_yaml::Value>>,
}

#[derive(Debug, Deserialize, Serialize)]
struct GitHubJob {
    name: Option<String>,
    #[serde(rename = "runs-on")]
    runs_on: serde_yaml::Value,
    needs: Option<serde_yaml::Value>,
    steps: Vec<GitHubStep>,
    env: Option<HashMap<String, serde_yaml::Value>>,
}

#[derive(Debug, Deserialize, Serialize)]
struct GitHubStep {
    name: Option<String>,
    uses: Option<String>,
    run: Option<String>,
    env: Option<HashMap<String, serde_yaml::Value>>,
}

pub fn parse(content: &str) -> Result<Pipeline> {
    let workflow: GitHubWorkflow = serde_yaml::from_str(content)?;

    let mut jobs = Vec::new();

    for (job_id, job_data) in workflow.jobs {
        let depends_on = match job_data.needs {
            Some(serde_yaml::Value::String(s)) => vec![s],
            Some(serde_yaml::Value::Sequence(seq)) => seq
                .into_iter()
                .filter_map(|v| v.as_str().map(String::from))
                .collect(),
            _ => Vec::new(),
        };

        let steps = job_data
            .steps
            .into_iter()
            .map(|s| Step {
                name: s.name,
                uses: s.uses,
                run: s.run,
                env: parse_env_vars(s.env),
            })
            .collect();

        jobs.push(Job {
            id: job_id,
            name: job_data.name,
            depends_on,
            steps,
            env: parse_env_vars(job_data.env),
        });
    }

    Ok(Pipeline {
        provider: Provider::GitHubActions,
        jobs,
        env: parse_env_vars(workflow.env),
    })
}

fn parse_env_vars(env: Option<HashMap<String, serde_yaml::Value>>) -> Vec<EnvVar> {
    env.unwrap_or_default()
        .into_iter()
        .map(|(key, value)| {
            let value_str = match value {
                serde_yaml::Value::String(s) => s,
                _ => format!("{:?}", value),
            };
            let is_secret = value_str.contains("secrets.");
            EnvVar {
                key,
                value: value_str,
                is_secret,
            }
        })
        .collect()
}