plan.rs
1 use std::path::PathBuf; 2 3 use clap::Parser; 4 use serde::Serialize; 5 6 use ambient_ci::{ 7 plan::{construct_all_plans, construct_runnable_plan, RunnablePlan}, 8 project::{ProjectError, Projects, State}, 9 runlog::RunLog, 10 }; 11 12 use super::{AmbientError, Config, Leaf}; 13 14 /// Prepare image. 15 #[derive(Debug, Parser)] 16 pub struct Plan { 17 /// Project specification file. May contain any number of projects. 18 #[clap(long)] 19 projects: Option<PathBuf>, 20 21 /// Which project to show plan for? 22 project: String, 23 24 /// rsync target for publishing artifacts with rsync program. 25 #[clap(long, aliases = ["rsync", "target"])] 26 rsync_target: Option<String>, 27 28 /// dput target for publishing .deb package. 29 #[clap(long, alias = "dput")] 30 dput_target: Option<String>, 31 32 /// Output as YAML. Default is JSON. 33 #[clap(long)] 34 yaml: bool, 35 36 /// Output runnable plan for `ambient-exeucte-plan`. 37 #[clap(long)] 38 runnable: bool, 39 } 40 41 impl Leaf for Plan { 42 fn run(&self, config: &Config, _runlog: &mut RunLog) -> Result<(), AmbientError> { 43 let projects = if let Some(filename) = &self.projects { 44 filename.to_path_buf() 45 } else { 46 config.projects().into() 47 }; 48 49 let projects = 50 Projects::from_file(&projects).map_err(|err| PlanError::FromFile(projects, err))?; 51 let project = projects 52 .get(&self.project) 53 .ok_or(PlanError::NoSuchProject(self.project.clone()))?; 54 55 config.lint(&projects)?; 56 57 if self.runnable { 58 let runnable = construct_runnable_plan(project.plan()).map_err(PlanError::Runnable)?; 59 let text = if self.yaml { 60 serde_norway::to_string(&runnable).map_err(PlanError::ToYaml)? 61 } else { 62 serde_json::to_string_pretty(&runnable).map_err(PlanError::ToJson)? 63 }; 64 println!("{text}"); 65 } else { 66 let mut config = config.clone(); 67 if let Some(s) = &self.dput_target { 68 config.set_dput_target(s); 69 }; 70 if let Some(s) = &self.rsync_target { 71 config.set_rsync_target(s); 72 }; 73 74 let state = 75 State::from_file(config.state(), &self.project).map_err(PlanError::State)?; 76 77 let (pre_plan, plan, post_plan) = 78 construct_all_plans(&config, &self.project, project, &state)?; 79 let plans = Plans { 80 pre_plan, 81 plan, 82 post_plan, 83 }; 84 85 let text = if self.yaml { 86 serde_norway::to_string(&plans).map_err(PlanError::ToYaml)? 87 } else { 88 serde_json::to_string_pretty(&plans).map_err(PlanError::ToJson)? 89 }; 90 println!("{text}"); 91 } 92 93 Ok(()) 94 } 95 } 96 97 #[derive(Serialize)] 98 struct Plans { 99 pre_plan: RunnablePlan, 100 plan: RunnablePlan, 101 post_plan: RunnablePlan, 102 } 103 104 #[derive(Debug, thiserror::Error)] 105 pub enum PlanError { 106 #[error("failed to load project list from file {0}")] 107 FromFile(PathBuf, #[source] ProjectError), 108 109 #[error("failed to serialize plans to JSON")] 110 ToJson(#[source] serde_json::Error), 111 112 #[error("failed to serialize plans to YAML")] 113 ToYaml(#[source] serde_norway::Error), 114 115 #[error("can't find project {0}")] 116 NoSuchProject(String), 117 118 #[error("can't construct runnable plan")] 119 Runnable(#[source] ambient_ci::plan::PlanError), 120 121 #[error("can't load project state")] 122 State(#[source] ambient_ci::project::ProjectError), 123 }