1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
use super::{ concourse::{self, ConcourseGen}, config::Config, repo::*, workspace::Workspace, }; use anyhow::*; use clap::{clap_app, crate_version, App, ArgMatches}; fn app() -> App<'static, 'static> { let app = clap_app!(cepler => (version: crate_version!()) (@setting VersionlessSubcommands) (@setting SubcommandRequiredElseHelp) (@arg CONFIG_FILE: -c --("config") env("CEPLER_CONF") default_value("cepler.yml") "Cepler config file") (@arg CLONE_DIR: --("clone") +takes_value requires_all(&["GIT_URL", "GIT_PRIVATE_KEY"]) "Clone the repository into <dir>") (@arg GIT_URL: --("git-url") +takes_value env("GIT_URL") "Remote url for --clone option") (@arg GIT_PRIVATE_KEY: --("git-private-key") +takes_value env("GIT_PRIVATE_KEY") "Private key for --clone option") (@arg GIT_BRANCH: --("git-branch") +takes_value default_value("main") env("GIT_BRANCH") "Branch for --clone option") (@subcommand check => (about: "Check wether the environment needs deploying. Exit codes: 0 - needs deploying; 1 - internal error; 2 - nothing to deploy") (@arg ENVIRONMENT: -e --("environment") env("CEPLER_ENVIRONMENT") +required +takes_value "The cepler environment") ) (@subcommand record => (about: "Record the state of an environment in the statefile") (@arg ENVIRONMENT: -e --("environment") env("CEPLER_ENVIRONMENT") +required +takes_value "The cepler environment") (@arg NO_COMMIT: --("no-commit") "Don't commit the new state") (@arg RESET_HEAD: --("reset-head") "Checkout files to head after committing the state") (@arg PUSH: --("push") requires_all(&["RESET_HEAD", "GIT_URL", "GIT_PRIVATE_KEY"]) "Push head to remote") (@arg GIT_URL: --("git-url") +takes_value env("GIT_URL") "Remote url for --clone option") (@arg GIT_PRIVATE_KEY: --("git-private-key") +takes_value env("GIT_PRIVATE_KEY") "Private key for --clone option") (@arg GIT_BRANCH: --("git-branch") +takes_value default_value("main") env("GIT_BRANCH") "Branch for --clone option") ) (@subcommand prepare => (about: "Prepare workspace for hook execution") (@arg ENVIRONMENT: -e --("environment") env("CEPLER_ENVIRONMENT") +required +takes_value "The cepler environment") (@arg FORCE_CLEAN: --("force-clean") "Delete all files not referenced in cepler.yml") ) (@subcommand concourse => (@setting SubcommandRequiredElseHelp) (about: "Subcommand for concourse integration") (@subcommand gen => (about: "Generate a concourse pipeline") ) (@subcommand check => (about: "The check command for the concourse resource") ) (@subcommand ci_in => (about: "The in command for the concourse resource") (@arg DESTINATION: * "The destination to put the resource") ) (@subcommand ci_out => (about: "The in command for the concourse resource") (@arg ORIGIN: * "The destination to put the resource") ) ) ); app } pub fn run() -> Result<()> { let matches = app().get_matches(); if let Some(dir) = matches.value_of("CLONE_DIR") { let conf = GitConfig { url: matches.value_of("GIT_URL").unwrap().to_string(), branch: matches.value_of("GIT_BRANCH").unwrap().to_string(), private_key: matches.value_of("GIT_PRIVATE_KEY").unwrap().to_string(), dir: dir.to_string(), }; let path = std::path::Path::new(&dir); if !path.exists() || path.read_dir()?.next().is_none() { Repo::clone(conf)?; std::env::set_current_dir(dir)?; } else { std::env::set_current_dir(dir)?; Repo::open()?.pull(conf)?; } } match matches.subcommand() { ("check", Some(sub_matches)) => check(sub_matches, &matches), ("prepare", Some(sub_matches)) => prepare(sub_matches, conf_from_matches(&matches)?), ("record", Some(sub_matches)) => record(sub_matches, conf_from_matches(&matches)?), ("concourse", Some(sub_matches)) => match sub_matches.subcommand() { ("gen", Some(_)) => concourse_gen(conf_from_matches(&matches)?), ("check", Some(_)) => concourse_check(), ("ci_in", Some(matches)) => concourse_in(&matches), ("ci_out", Some(matches)) => concourse_out(&matches), _ => unreachable!(), }, _ => unreachable!(), } } fn check(matches: &ArgMatches, main_matches: &ArgMatches) -> Result<()> { let env = matches.value_of("ENVIRONMENT").unwrap(); let config = conf_from_matches(main_matches)?; let ws = Workspace::new(config.1)?; let env = config .0 .environments .get(env) .context(format!("Environment '{}' not found in config", env))?; match ws.check(env)? { None => { println!("Nothing new to deploy"); std::process::exit(2); } Some(_) => { println!("Found new state to deploy"); } } Ok(()) } fn prepare(matches: &ArgMatches, config: (Config, String)) -> Result<()> { let env = matches.value_of("ENVIRONMENT").unwrap(); let force_clean: bool = matches.is_present("FORCE_CLEAN"); if force_clean { println!("WARNING removing all non-cepler specified files"); } let env = config .0 .environments .get(env) .context(format!("Environment '{}' not found in config", env))?; let ws = Workspace::new(config.1)?; ws.prepare(env, force_clean)?; Ok(()) } fn record(matches: &ArgMatches, config: (Config, String)) -> Result<()> { let env = matches.value_of("ENVIRONMENT").unwrap(); let commit = !matches.is_present("NO_COMMIT"); let reset = matches.is_present("RESET_HEAD"); let push = matches.is_present("PUSH"); let git_config = if push { Some(GitConfig { url: matches.value_of("GIT_URL").unwrap().to_string(), branch: matches.value_of("GIT_BRANCH").unwrap().to_string(), private_key: matches.value_of("GIT_PRIVATE_KEY").unwrap().to_string(), dir: String::new(), }) } else { None }; let env = config .0 .environments .get(env) .context(format!("Environment '{}' not found in config", env))?; let mut ws = Workspace::new(config.1)?; ws.record_env(env, commit, reset, git_config)?; Ok(()) } fn concourse_gen((conf, path_to_conf): (Config, String)) -> Result<()> { if conf.concourse.is_none() { return Err(anyhow!("concourse: key not specified")); } println!( "{}", ConcourseGen::new(conf, path_to_conf).render_pipeline() ); Ok(()) } fn concourse_check() -> Result<()> { concourse::check::exec() } fn concourse_in(matches: &ArgMatches) -> Result<()> { let destination = matches.value_of("DESTINATION").unwrap(); concourse::ci_in::exec(destination) } fn concourse_out(matches: &ArgMatches) -> Result<()> { let origin = matches.value_of("ORIGIN").unwrap(); concourse::ci_out::exec(origin) } fn conf_from_matches(matches: &ArgMatches) -> Result<(Config, String)> { let file_name = matches.value_of("CONFIG_FILE").unwrap(); Ok((Config::from_file(file_name)?, file_name.to_string())) }