#![forbid(unsafe_code)]
use std::io::{stdin, stdout};
use clap::Parser;
use gl_env::{
cli::{Cli, Commands},
gitlab::{Gitlab, Target, Variable},
State,
};
const RED: &str = "\x1b[0;31m";
const GREEN: &str = "\x1b[0;32m";
const YELLOW: &str = "\x1b[0;33m";
const BLUE: &str = "\x1b[0;34m";
const GREY: &str = "\x1b[0;90m";
const RESET: &str = "\x1b[0m";
fn main() {
let args = Cli::parse();
match args.command {
Commands::List(args) => {
let gitlab = Gitlab::from(&args);
let t = Target::from(&args);
list(&gitlab, t);
}
Commands::Diff(args) => {
let gitlab = Gitlab::from(&args);
let t = Target::from(&args);
diff(&gitlab, t);
}
Commands::Dump(args) => {
let gitlab = Gitlab::from(&args);
let t = Target::from(&args);
dump(&gitlab, t);
}
Commands::Apply {
args,
dry_run,
prune,
} => {
let gitlab = Gitlab::from(&args);
let t = Target::from(&args);
apply(&gitlab, t, prune, dry_run);
}
}
}
fn list(gitlab: &Gitlab, target: Target) {
let mut variables = gitlab.list_variables(&target).unwrap();
if variables.len() < 1 {
println!("{GREY}no variables defined{RESET}");
return;
}
variables.sort();
let key_len = variables.iter().map(|v| v.key.len()).max().unwrap().max(3);
let env_len = variables
.iter()
.map(|v| v.environment_scope.len())
.max()
.unwrap()
.max(3);
let divider = format!(
"+-{}-+-{}-+---+---+---+",
"-".repeat(key_len),
"-".repeat(env_len)
);
println!(
"{}\n| {:key_len$} | {:env_len$} | m | p | r |\n{}",
divider, "KEY", "ENV", divider
);
for v in variables {
println!(
"| {BLUE}{:key_len$}{RESET} | {:env_len$} | {GREEN}{}{RESET} | {GREEN}{}{RESET} | {GREEN}{}{RESET} |",
v.key,
v.environment_scope,
v.masked.then(|| 'x').unwrap_or_else(|| ' '),
v.protected.then(|| 'x').unwrap_or_else(|| ' '),
v.raw.then(|| 'x').unwrap_or_else(|| ' '),
);
}
println!("{divider}");
}
fn apply(gitlab: &Gitlab, target: Target, prune: bool, dry_run: bool) {
if dry_run {
println!("Running in DRY RUN MODE");
}
let desired: State = serde_yml::from_reader(stdin()).expect("Read desired state");
let desired: Vec<Variable> = desired.into();
let mut actual = gitlab.list_variables(&target).unwrap();
for variable in desired {
let existing = actual
.iter()
.position(|a| a.is_same(&variable))
.map(|i| actual.remove(i));
match existing {
Some(v) if v == variable => println!("{variable} no change needed"),
Some(_) => {
if !dry_run {
gitlab.update_variable(&target, &variable).unwrap();
}
println!("{YELLOW}{variable} updated{RESET}");
}
None => {
if !dry_run {
gitlab.create_variable(&target, &variable).unwrap();
}
println!("{GREEN}{variable} created{RESET}");
}
}
}
if prune {
for variable in actual {
gitlab.delete_variable(&target, &variable).unwrap();
println!("{RED}{variable} deleted{RESET}");
}
} else {
for variable in actual {
println!("{RED}{variable} exists in GitLab, but not in desired state.{RESET}");
}
}
}
fn diff(gitlab: &Gitlab, target: Target) {
let desired: State = serde_yml::from_reader(stdin()).expect("Read desired state");
let desired: Vec<Variable> = desired.into();
let desired = serde_yml::to_string(&desired).unwrap();
let actual: State = gitlab.list_variables(&target).unwrap().into();
let actual: Vec<Variable> = actual.into();
let actual = serde_yml::to_string(&actual).unwrap();
for diff in diff::lines(&actual, &desired) {
match diff {
diff::Result::Left(l) => println!("{RED}- {l}{RESET}"),
diff::Result::Both(l, _) => println!(" {l}"),
diff::Result::Right(r) => println!("{GREEN}+ {r}{RESET}"),
}
}
}
fn dump(gitlab: &Gitlab, target: Target) {
let vars = gitlab.list_variables(&target).unwrap();
let state = State::from(vars);
serde_yml::to_writer(stdout(), &state).expect("Serialize data");
}