Files
topgrade/src/steps/remote/vagrant.rs

236 lines
6.4 KiB
Rust
Raw Normal View History

2020-06-10 11:51:52 +03:00
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{fmt::Display, rc::Rc, str::FromStr};
use anyhow::Result;
use log::{debug, error};
2022-04-23 15:09:54 +03:00
use regex::Regex;
2020-06-10 11:51:52 +03:00
use strum::EnumString;
use crate::execution_context::ExecutionContext;
use crate::executor::CommandExt;
use crate::terminal::print_separator;
use crate::{error::SkipStep, utils, Step};
2020-06-10 11:51:52 +03:00
#[derive(Debug, Copy, Clone, EnumString)]
#[strum(serialize_all = "lowercase")]
enum BoxStatus {
PowerOff,
Running,
2020-06-12 15:35:39 +03:00
Saved,
2020-06-13 07:40:03 +03:00
Aborted,
2020-06-10 11:51:52 +03:00
}
impl BoxStatus {
fn powered_on(self) -> bool {
2020-10-24 14:46:38 -05:00
matches!(self, BoxStatus::Running)
2020-06-10 11:51:52 +03:00
}
}
#[derive(Debug)]
pub struct VagrantBox {
2020-12-01 08:59:59 +02:00
path: Rc<Path>,
2020-06-10 11:51:52 +03:00
name: String,
initial_status: BoxStatus,
2020-06-10 11:51:52 +03:00
}
impl VagrantBox {
pub fn smart_name(&self) -> &str {
if self.name == "default" {
self.path.file_name().unwrap().to_str().unwrap()
} else {
&self.name
}
}
}
impl Display for VagrantBox {
2020-06-10 11:51:52 +03:00
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} @ {}", self.name, self.path.display())
2020-06-10 11:51:52 +03:00
}
}
struct Vagrant {
path: PathBuf,
}
impl Vagrant {
2021-01-07 10:03:20 +02:00
fn get_boxes(&self, directory: &str) -> Result<Vec<VagrantBox>> {
2020-12-01 08:59:59 +02:00
let path: Rc<Path> = Path::new(directory).into();
2020-06-10 11:51:52 +03:00
let output = Command::new(&self.path)
.arg("status")
.current_dir(directory)
.check_output()?;
debug!("Vagrant output in {}: {}", directory, output);
let boxes = output
.split('\n')
.skip(2)
.take_while(|line| !(line.is_empty() || line.starts_with('\r')))
.map(|line| {
debug!("Vagrant line: {:?}", line);
let mut elements = line.split_whitespace();
let name = elements.next().unwrap().to_string();
let initial_status = BoxStatus::from_str(elements.next().unwrap()).unwrap();
2020-06-10 11:51:52 +03:00
let vagrant_box = VagrantBox {
name,
path: path.clone(),
initial_status,
2020-06-10 11:51:52 +03:00
};
debug!("{:?}", vagrant_box);
vagrant_box
2020-06-10 11:51:52 +03:00
})
.collect();
Ok(boxes)
}
fn temporary_power_on<'a>(
&'a self,
vagrant_box: &'a VagrantBox,
ctx: &'a ExecutionContext,
) -> Result<TemporaryPowerOn<'a>> {
TemporaryPowerOn::create(&self.path, vagrant_box, ctx)
2020-06-10 11:51:52 +03:00
}
}
struct TemporaryPowerOn<'a> {
vagrant: &'a Path,
vagrant_box: &'a VagrantBox,
2020-06-10 11:51:52 +03:00
ctx: &'a ExecutionContext<'a>,
}
impl<'a> TemporaryPowerOn<'a> {
fn create(vagrant: &'a Path, vagrant_box: &'a VagrantBox, ctx: &'a ExecutionContext<'a>) -> Result<Self> {
let subcommand = match vagrant_box.initial_status {
2020-06-13 07:40:03 +03:00
BoxStatus::PowerOff | BoxStatus::Aborted => "up",
2020-06-12 15:35:39 +03:00
BoxStatus::Saved => "resume",
2020-06-13 07:40:03 +03:00
BoxStatus::Running => unreachable!(),
2020-06-12 15:35:39 +03:00
};
2020-06-10 11:51:52 +03:00
ctx.run_type()
.execute(vagrant)
.args([subcommand, &vagrant_box.name])
2020-12-01 08:59:59 +02:00
.current_dir(vagrant_box.path.clone())
2020-06-10 11:51:52 +03:00
.check_run()?;
Ok(TemporaryPowerOn {
vagrant,
vagrant_box,
ctx,
})
}
}
impl<'a> Drop for TemporaryPowerOn<'a> {
fn drop(&mut self) {
let subcommand = if self.ctx.config().vagrant_always_suspend().unwrap_or(false) {
"suspend"
} else {
match self.vagrant_box.initial_status {
BoxStatus::PowerOff | BoxStatus::Aborted => "halt",
BoxStatus::Saved => "suspend",
BoxStatus::Running => unreachable!(),
}
2020-06-12 15:35:39 +03:00
};
println!();
2020-06-10 11:51:52 +03:00
self.ctx
.run_type()
.execute(self.vagrant)
.args([subcommand, &self.vagrant_box.name])
2020-12-01 08:59:59 +02:00
.current_dir(self.vagrant_box.path.clone())
2020-06-10 11:51:52 +03:00
.check_run()
.ok();
}
}
pub fn collect_boxes(ctx: &ExecutionContext) -> Result<Vec<VagrantBox>> {
2020-08-21 23:04:36 +03:00
let directories = utils::require_option(
ctx.config().vagrant_directories(),
String::from("No Vagrant directories were specified in the configuration file"),
)?;
2020-06-10 11:51:52 +03:00
let vagrant = Vagrant {
path: utils::require("vagrant")?,
};
print_separator("Vagrant");
println!("Collecting Vagrant boxes");
let mut result = Vec::new();
2020-06-10 11:51:52 +03:00
for directory in directories {
2020-06-25 08:37:29 +03:00
match vagrant.get_boxes(directory) {
Ok(mut boxes) => {
result.append(&mut boxes);
}
Err(e) => error!("Error collecting vagrant boxes from {}: {}", directory, e),
};
}
2020-06-10 11:51:52 +03:00
Ok(result)
}
pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) -> Result<()> {
let vagrant = Vagrant {
path: utils::require("vagrant")?,
};
2020-06-10 11:51:52 +03:00
let seperator = format!("Vagrant ({})", vagrant_box.smart_name());
let mut _poweron = None;
if !vagrant_box.initial_status.powered_on() {
if !(ctx.config().vagrant_power_on().unwrap_or(true)) {
2020-08-21 23:04:36 +03:00
return Err(SkipStep(format!("Skipping powered off box {}", vagrant_box)).into());
} else {
print_separator(seperator);
2021-09-02 07:27:09 +03:00
_poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?);
2020-06-10 11:51:52 +03:00
}
} else {
print_separator(seperator);
2020-06-10 11:51:52 +03:00
}
let mut command = format!("env TOPGRADE_PREFIX={} topgrade", vagrant_box.smart_name());
if ctx.config().yes(Step::Vagrant) {
2020-07-01 20:54:31 +03:00
command.push_str(" -y");
}
ctx.run_type()
.execute(&vagrant.path)
2020-12-01 08:59:59 +02:00
.current_dir(&vagrant_box.path)
.args(["ssh", "-c", &command])
.check_run()
2020-06-10 11:51:52 +03:00
}
2022-04-23 15:09:54 +03:00
pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
let vagrant = utils::require("vagrant")?;
print_separator("Vagrant boxes");
let outdated = Command::new(&vagrant)
.args(["box", "outdated", "--global"])
2022-04-23 15:09:54 +03:00
.check_output()?;
let re = Regex::new(r"\* '(.*?)' for '(.*?)' is outdated").unwrap();
let mut found = false;
for ele in re.captures_iter(&outdated) {
found = true;
let _ = ctx
.run_type()
.execute(&vagrant)
.args(["box", "update", "--box"])
.arg(ele.get(1).unwrap().as_str())
2022-04-23 15:09:54 +03:00
.arg("--provider")
.arg(ele.get(2).unwrap().as_str())
.check_run();
}
if !found {
println!("No outdated boxes")
} else {
ctx.run_type().execute(&vagrant).args(["box", "prune"]).check_run()?;
2022-04-23 15:09:54 +03:00
}
Ok(())
}