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()))
}