Added ability to include directories as an extension of the config file (#421)

This commit is contained in:
PolpOnline
2023-05-25 12:22:11 +02:00
committed by GitHub
parent 7c3ba80270
commit cb7adc8ced
5 changed files with 573 additions and 130 deletions

View File

@@ -1,17 +1,20 @@
#![allow(dead_code)]
use std::collections::BTreeMap;
use std::fs::write;
use std::path::PathBuf;
use std::fs::{write, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, fs};
use clap::{ArgEnum, Parser};
use clap_complete::Shell;
use color_eyre::eyre;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy;
use merge::Merge;
use regex::Regex;
use regex_split::RegexSplit;
use serde::Deserialize;
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
use tracing::debug;
@@ -19,6 +22,7 @@ use which_crate::which;
use crate::command::CommandExt;
use crate::sudo::SudoKind;
use crate::utils::string_prepend_str;
use super::utils::{editor, hostname};
@@ -51,21 +55,43 @@ macro_rules! check_deprecated {
}
};
}
macro_rules! get_deprecated {
($config:expr, $old:ident, $section:ident, $new:ident) => {
if $config.$old.is_some() {
&$config.$old
} else {
if let Some(section) = &$config.$section {
&section.$new
} else {
&None
/// Get a deprecated option moved from a section to another
macro_rules! get_deprecated_moved_opt {
($old_section:expr, $old:ident, $new_section:expr, $new:ident) => {{
if let Some(old_section) = &$old_section {
if old_section.$old.is_some() {
return &old_section.$old;
}
}
};
if let Some(new_section) = &$new_section {
return &new_section.$new;
}
return &None;
}};
}
type Commands = BTreeMap<String, String>;
macro_rules! get_deprecated_moved_or_default_to {
($old_section:expr, $old:ident, $new_section:expr, $new:ident, $default_ret:ident) => {{
if let Some(old_section) = &$old_section {
if let Some(old) = old_section.$old {
return old;
}
}
if let Some(new_section) = &$new_section {
if let Some(new) = new_section.$new {
return new;
}
}
return $default_ret;
}};
}
pub type Commands = BTreeMap<String, String>;
#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
#[clap(rename_all = "snake_case")]
@@ -169,24 +195,38 @@ pub enum Step {
Yarn,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Include {
#[merge(strategy = merge::vec::append)]
paths: Vec<String>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Git {
max_concurrency: Option<usize>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
repos: Option<Vec<String>>,
pull_predefined: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Vagrant {
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
directories: Option<Vec<String>>,
power_on: Option<bool>,
always_suspend: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Windows {
accept_all_updates: Option<bool>,
@@ -197,7 +237,7 @@ pub struct Windows {
wsl_update_use_web_download: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Python {
enable_pip_review: Option<bool>,
@@ -205,43 +245,45 @@ pub struct Python {
enable_pipupgrade: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct Distrobox {
use_root: Option<bool>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
containers: Option<Vec<String>>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct Yarn {
use_sudo: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct NPM {
use_sudo: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct Firmware {
upgrade: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct Flatpak {
use_sudo: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Brew {
greedy_cask: Option<bool>,
@@ -262,88 +304,191 @@ pub enum ArchPackageManager {
Yay,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Linux {
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
yay_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
aura_aur_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
aura_pacman_arguments: Option<String>,
arch_package_manager: Option<ArchPackageManager>,
show_arch_news: Option<bool>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
garuda_update_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
trizen_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
pikaur_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
pamac_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
dnf_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
nix_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
apt_arguments: Option<String>,
enable_tlmgr: Option<bool>,
redhat_distro_sync: Option<bool>,
suse_dup: Option<bool>,
rpm_ostree: Option<bool>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
emerge_sync_flags: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
emerge_update_flags: Option<String>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Composer {
self_update: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Vim {
force_plug_update: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Misc {
pre_sudo: Option<bool>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
git_repos: Option<Vec<String>>,
predefined_git_repos: Option<bool>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
disable: Option<Vec<Step>>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
ignore_failures: Option<Vec<Step>>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
remote_topgrades: Option<Vec<String>>,
remote_topgrade_path: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
ssh_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
git_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
tmux_arguments: Option<String>,
set_title: Option<bool>,
display_time: Option<bool>,
display_preamble: Option<bool>,
assume_yes: Option<bool>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
yay_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
aura_aur_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
aura_pacman_arguments: Option<String>,
no_retry: Option<bool>,
run_in_tmux: Option<bool>,
cleanup: Option<bool>,
notify_each_step: Option<bool>,
accept_all_windows_updates: Option<bool>,
skip_notify: Option<bool>,
bashit_branch: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
only: Option<Vec<Step>>,
no_self_update: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
/// Configuration file
pub struct ConfigFile {
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
include: Option<Include>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
misc: Option<Misc>,
sudo_command: Option<SudoKind>,
pre_sudo: Option<bool>,
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
pre_commands: Option<Commands>,
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
post_commands: Option<Commands>,
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
commands: Option<Commands>,
git_repos: Option<Vec<String>>,
predefined_git_repos: Option<bool>,
disable: Option<Vec<Step>>,
ignore_failures: Option<Vec<Step>>,
remote_topgrades: Option<Vec<String>>,
remote_topgrade_path: Option<String>,
ssh_arguments: Option<String>,
git_arguments: Option<String>,
tmux_arguments: Option<String>,
set_title: Option<bool>,
display_time: Option<bool>,
display_preamble: Option<bool>,
assume_yes: Option<bool>,
yay_arguments: Option<String>,
aura_aur_arguments: Option<String>,
aura_pacman_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
python: Option<Python>,
no_retry: Option<bool>,
run_in_tmux: Option<bool>,
cleanup: Option<bool>,
notify_each_step: Option<bool>,
accept_all_windows_updates: Option<bool>,
skip_notify: Option<bool>,
bashit_branch: Option<String>,
only: Option<Vec<Step>>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
composer: Option<Composer>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
brew: Option<Brew>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
linux: Option<Linux>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
git: Option<Git>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
windows: Option<Windows>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
npm: Option<NPM>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
yarn: Option<Yarn>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
vim: Option<Vim>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
firmware: Option<Firmware>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
vagrant: Option<Vagrant>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
flatpak: Option<Flatpak>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
distrobox: Option<Distrobox>,
no_self_update: Option<bool>,
}
fn config_directory() -> PathBuf {
@@ -354,58 +499,164 @@ fn config_directory() -> PathBuf {
return crate::WINDOWS_DIRS.config_dir();
}
/// The only purpose of this struct is to deserialize only the `include` field of the config file.
#[derive(Deserialize, Default, Debug)]
struct ConfigFileIncludeOnly {
include: Option<Include>,
}
impl ConfigFile {
fn ensure() -> Result<PathBuf> {
/// Returns the main config file and any additional config files
/// 0 = main config file
/// 1 = additional config files coming from topgrade.d
fn ensure() -> Result<(PathBuf, Vec<PathBuf>)> {
let mut res = (PathBuf::new(), Vec::new());
let config_directory = config_directory();
let config_path = config_directory.join("topgrade.toml");
let alt_config_path = config_directory.join("topgrade/topgrade.toml");
let possible_config_paths = vec![
config_directory.join("topgrade.toml"),
config_directory.join("topgrade/topgrade.toml"),
];
if config_path.exists() {
debug!("Configuration at {}", config_path.display());
Ok(config_path)
} else if alt_config_path.exists() {
debug!("Configuration at {}", alt_config_path.display());
Ok(alt_config_path)
} else {
// Search for the main config file
for path in possible_config_paths.iter() {
if path.exists() {
debug!("Configuration at {}", path.display());
res.0 = path.clone();
break;
}
}
res.1 = Self::ensure_topgrade_d(&config_directory)?;
// If no config file exists, create a default one in the config directory
if !res.0.exists() && res.1.is_empty() {
debug!("No configuration exists");
write(&config_path, EXAMPLE_CONFIG).map_err(|e| {
write(&res.0, EXAMPLE_CONFIG).map_err(|e| {
debug!(
"Unable to write the example configuration file to {}: {}. Using blank config.",
config_path.display(),
&res.0.display(),
e
);
e
})?;
Ok(config_path)
}
Ok(res)
}
/// Searches topgrade.d for additional config files
fn ensure_topgrade_d(config_directory: &Path) -> Result<Vec<PathBuf>> {
let mut res = Vec::new();
let dir_to_search = config_directory.join("topgrade.d");
if dir_to_search.exists() {
for entry in fs::read_dir(dir_to_search)? {
let entry = entry?;
if entry.file_type()?.is_file() {
debug!(
"Found additional (directory) configuration file at {}",
entry.path().display()
);
res.push(entry.path());
}
}
res.sort();
} else {
debug!("No additional configuration directory exists, creating one");
fs::create_dir_all(&dir_to_search)?;
}
Ok(res)
}
/// Read the configuration file.
///
/// If the configuration file does not exist the function returns the default ConfigFile.
/// If the configuration file does not exist, the function returns the default ConfigFile.
fn read(config_path: Option<PathBuf>) -> Result<ConfigFile> {
let mut result = Self::default();
let config_path = if let Some(path) = config_path {
path
} else {
Self::ensure()?
let (path, dir_include) = Self::ensure()?;
/*
The Function was called without a config_path, we need
to read the include directory before returning the main config path
*/
for include in dir_include {
let include_contents = fs::read_to_string(&include).map_err(|e| {
tracing::error!("Unable to read {}", include.display());
e
})?;
let include_contents_parsed = toml::from_str(include_contents.as_str()).map_err(|e| {
tracing::error!("Failed to deserialize {}", include.display());
e
})?;
result.merge(include_contents_parsed);
}
path
};
let contents = fs::read_to_string(&config_path).map_err(|e| {
let mut contents_non_split = fs::read_to_string(&config_path).map_err(|e| {
tracing::error!("Unable to read {}", config_path.display());
e
})?;
let mut result: Self = toml::from_str(&contents).map_err(|e| {
tracing::error!("Failed to deserialize {}", config_path.display());
e
})?;
Self::ensure_misc_is_present(&mut contents_non_split, &config_path);
if let Some(ref mut paths) = &mut result.git_repos {
for path in paths.iter_mut() {
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
debug!("Path {} expanded to {}", path, expanded);
*path = expanded;
// To parse [include] sections in the order as they are written,
// we split the file and parse each part as a separate file
let regex_match_include = Regex::new(r"\[include]").expect("Failed to compile regex");
let contents_split = regex_match_include.split_inclusive_left(contents_non_split.as_str());
for contents in contents_split {
let config_file_include_only: ConfigFileIncludeOnly = toml::from_str(contents).map_err(|e| {
tracing::error!("Failed to deserialize an include section of {}", config_path.display());
e
})?;
if let Some(includes) = &config_file_include_only.include {
// Parses the [include] section present in the slice
for include in includes.paths.iter().rev() {
let include_path = shellexpand::tilde::<&str>(&include.as_ref()).into_owned();
let include_path = PathBuf::from(include_path);
let include_contents = match fs::read_to_string(&include_path) {
Ok(c) => c,
Err(e) => {
tracing::error!("Unable to read {}: {}", include_path.display(), e);
continue;
}
};
match toml::from_str::<Self>(&include_contents) {
Ok(include_parsed) => result.merge(include_parsed),
Err(e) => {
tracing::error!("Failed to deserialize {}: {}", include_path.display(), e);
continue;
}
};
debug!("Configuration include found: {}", include_path.display());
}
}
match toml::from_str::<Self>(contents) {
Ok(contents) => result.merge(contents),
Err(e) => tracing::error!("Failed to deserialize {}: {}", config_path.display(), e),
}
}
if let Some(misc) = &mut result.misc {
if let Some(ref mut paths) = &mut misc.git_repos {
for path in paths.iter_mut() {
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
debug!("Path {} expanded to {}", path, expanded);
*path = expanded;
}
}
}
@@ -423,7 +674,7 @@ impl ConfigFile {
}
fn edit() -> Result<()> {
let config_path = Self::ensure()?;
let config_path = Self::ensure()?.0;
let editor = editor();
debug!("Editor: {:?}", editor);
@@ -436,6 +687,18 @@ impl ConfigFile {
.status_checked()
.context("Failed to open configuration file editor")
}
/// [Misc] was added later, here we check if it is present in the config file and add it if not
fn ensure_misc_is_present(contents: &mut String, path: &PathBuf) {
if !contents.contains("[misc]") {
debug!("Adding [misc] section to {}", path.display());
string_prepend_str(contents, "[misc]\n");
File::create(path)
.and_then(|mut f| f.write_all(contents.as_bytes()))
.expect("Tried to auto-migrate the config file, unable to write to config file.\nPlease add \"[misc]\" section manually to the first line of the file.\nError");
}
}
}
// Command line arguments
@@ -565,7 +828,7 @@ impl CommandLineArgs {
/// Represents the application configuration
///
/// The struct holds the loaded configuration file, as well as the arguments parsed from the command line.
/// Its provided methods decide the appropriate options based on combining the configuraiton file and the
/// Its provided methods decide the appropriate options based on combining the configuration file and the
/// command line arguments.
pub struct Config {
opt: CommandLineArgs,
@@ -576,7 +839,7 @@ pub struct Config {
impl Config {
/// Load the configuration.
///
/// The function parses the command line arguments and reading the configuration file.
/// The function parses the command line arguments and reads the configuration file.
pub fn load(opt: CommandLineArgs) -> Result<Self> {
let config_directory = config_directory();
let config_file = if config_directory.is_dir() {
@@ -587,15 +850,17 @@ impl Config {
ConfigFile::default()
})
} else {
tracing::debug!("Configuration directory {} does not exist", config_directory.display());
debug!("Configuration directory {} does not exist", config_directory.display());
ConfigFile::default()
};
check_deprecated!(config_file, git_arguments, git, arguments);
check_deprecated!(config_file, git_repos, git, repos);
check_deprecated!(config_file, predefined_git_repos, git, pull_predefined);
check_deprecated!(config_file, yay_arguments, linux, yay_arguments);
check_deprecated!(config_file, accept_all_windows_updates, windows, accept_all_updates);
if let Some(misc) = &config_file.misc {
check_deprecated!(misc, git_arguments, git, arguments);
check_deprecated!(misc, git_repos, git, repos);
check_deprecated!(misc, predefined_git_repos, git, pull_predefined);
check_deprecated!(misc, yay_arguments, linux, yay_arguments);
check_deprecated!(misc, accept_all_windows_updates, windows, accept_all_updates);
}
let allowed_steps = Self::allowed_steps(&opt, &config_file);
@@ -628,7 +893,7 @@ impl Config {
/// The list of additional git repositories to pull.
pub fn git_repos(&self) -> &Option<Vec<String>> {
get_deprecated!(self.config_file, git_repos, git, repos)
get_deprecated_moved_opt!(&self.config_file.misc, git_repos, &self.config_file.git, repos)
}
/// Tell whether the specified step should run.
@@ -643,8 +908,10 @@ impl Config {
let mut enabled_steps: Vec<Step> = Vec::new();
enabled_steps.extend(&opt.only);
if let Some(only) = config_file.only.as_ref() {
enabled_steps.extend(only)
if let Some(misc) = config_file.misc.as_ref() {
if let Some(only) = misc.only.as_ref() {
enabled_steps.extend(only);
}
}
if enabled_steps.is_empty() {
@@ -653,27 +920,47 @@ impl Config {
let mut disabled_steps: Vec<Step> = Vec::new();
disabled_steps.extend(&opt.disable);
if let Some(disabled) = config_file.disable.as_ref() {
disabled_steps.extend(disabled);
if let Some(misc) = config_file.misc.as_ref() {
if let Some(disabled) = misc.disable.as_ref() {
disabled_steps.extend(disabled);
}
}
enabled_steps.retain(|e| !disabled_steps.contains(e) || opt.only.contains(e));
enabled_steps
}
/// Tell whether we should run a self update.
/// Tell whether we should run a self-update.
pub fn no_self_update(&self) -> bool {
self.opt.no_self_update || self.config_file.no_self_update.unwrap_or(false)
self.opt.no_self_update
|| self
.config_file
.misc
.as_ref()
.and_then(|misc| misc.no_self_update)
.unwrap_or(false)
}
/// Tell whether we should run in tmux.
pub fn run_in_tmux(&self) -> bool {
self.opt.run_in_tmux || self.config_file.run_in_tmux.unwrap_or(false)
self.opt.run_in_tmux
|| self
.config_file
.misc
.as_ref()
.and_then(|misc| misc.run_in_tmux)
.unwrap_or(false)
}
/// Tell whether we should perform cleanup steps.
pub fn cleanup(&self) -> bool {
self.opt.cleanup || self.config_file.cleanup.unwrap_or(false)
self.opt.cleanup
|| self
.config_file
.misc
.as_ref()
.and_then(|misc| misc.cleanup)
.unwrap_or(false)
}
/// Tell whether we are dry-running.
@@ -683,32 +970,54 @@ impl Config {
/// Tell whether we should not attempt to retry anything.
pub fn no_retry(&self) -> bool {
self.opt.no_retry || self.config_file.no_retry.unwrap_or(false)
self.opt.no_retry
|| self
.config_file
.misc
.as_ref()
.and_then(|misc| misc.no_retry)
.unwrap_or(false)
}
/// List of remote hosts to run Topgrade in
pub fn remote_topgrades(&self) -> &Option<Vec<String>> {
&self.config_file.remote_topgrades
pub fn remote_topgrades(&self) -> Option<&Vec<String>> {
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.remote_topgrades.as_ref())
}
/// Path to Topgrade executable used for all remote hosts
pub fn remote_topgrade_path(&self) -> &str {
self.config_file.remote_topgrade_path.as_deref().unwrap_or("topgrade")
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.remote_topgrade_path.as_deref())
.unwrap_or("topgrade")
}
/// Extra SSH arguments
pub fn ssh_arguments(&self) -> &Option<String> {
&self.config_file.ssh_arguments
pub fn ssh_arguments(&self) -> Option<&String> {
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.ssh_arguments.as_ref())
}
/// Extra Git arguments
pub fn git_arguments(&self) -> &Option<String> {
get_deprecated!(self.config_file, git_arguments, git, arguments)
get_deprecated_moved_opt!(&self.config_file.misc, git_arguments, &self.config_file.git, arguments)
}
/// Extra Tmux arguments
pub fn tmux_arguments(&self) -> eyre::Result<Vec<String>> {
let args = &self.config_file.tmux_arguments.as_deref().unwrap_or_default();
pub fn tmux_arguments(&self) -> Result<Vec<String>> {
let args = &self
.config_file
.misc
.as_ref()
.and_then(|misc| misc.tmux_arguments.as_ref())
.map(String::to_owned)
.unwrap_or_default();
shell_words::split(args)
// The only time the parse failed is in case of a missing close quote.
// The error message looks like this:
@@ -726,7 +1035,7 @@ impl Config {
/// Skip sending a notification at the end of a run
pub fn skip_notify(&self) -> bool {
if let Some(yes) = self.config_file.skip_notify {
if let Some(yes) = self.config_file.misc.as_ref().and_then(|misc| misc.skip_notify) {
return yes;
}
@@ -735,12 +1044,16 @@ impl Config {
/// Whether to set the terminal title
pub fn set_title(&self) -> bool {
self.config_file.set_title.unwrap_or(true)
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.set_title)
.unwrap_or(true)
}
/// Whether to say yes to package managers
pub fn yes(&self, step: Step) -> bool {
if let Some(yes) = self.config_file.assume_yes {
if let Some(yes) = self.config_file.misc.as_ref().and_then(|misc| misc.assume_yes) {
return yes;
}
@@ -757,18 +1070,22 @@ impl Config {
/// Bash-it branch
pub fn bashit_branch(&self) -> &str {
self.config_file.bashit_branch.as_deref().unwrap_or("stable")
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.bashit_branch.as_deref())
.unwrap_or("stable")
}
/// Whether to accept all Windows updates
pub fn accept_all_windows_updates(&self) -> bool {
get_deprecated!(
self.config_file,
get_deprecated_moved_or_default_to!(
&self.config_file.misc,
accept_all_windows_updates,
windows,
accept_all_updates
&self.config_file.windows,
accept_all_updates,
true
)
.unwrap_or(true)
}
/// Whether to self rename the Topgrade executable during the run
@@ -836,7 +1153,11 @@ impl Config {
/// Whether to send a desktop notification at the beginning of every step
pub fn notify_each_step(&self) -> bool {
self.config_file.notify_each_step.unwrap_or(false)
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.notify_each_step)
.unwrap_or(false)
}
/// Extra garuda-update arguments
@@ -962,7 +1283,7 @@ impl Config {
self.config_file.git.as_ref().and_then(|git| git.max_concurrency)
}
/// Should we power on vagrant boxes if needed
/// Determine whether we should power on vagrant boxes
pub fn vagrant_power_on(&self) -> Option<bool> {
self.config_file.vagrant.as_ref().and_then(|vagrant| vagrant.power_on)
}
@@ -992,7 +1313,7 @@ impl Config {
.unwrap_or(false)
}
/// Use distro-sync in Red Hat based distrbutions
/// Use distro-sync in Red Hat based distributions
pub fn redhat_distro_sync(&self) -> bool {
self.config_file
.linux
@@ -1019,18 +1340,25 @@ impl Config {
.unwrap_or(false)
}
/// Should we ignore failures for this step
/// Determine if we should ignore failures for this step
pub fn ignore_failure(&self, step: Step) -> bool {
self.config_file
.ignore_failures
.misc
.as_ref()
.and_then(|misc| misc.ignore_failures.as_ref())
.map(|v| v.contains(&step))
.unwrap_or(false)
}
pub fn use_predefined_git_repos(&self) -> bool {
!self.opt.disable_predefined_git_repos
&& get_deprecated!(self.config_file, predefined_git_repos, git, pull_predefined).unwrap_or(true)
&& get_deprecated_moved_or_default_to!(
&self.config_file.misc,
predefined_git_repos,
&self.config_file.git,
pull_predefined,
true
)
}
pub fn verbose(&self) -> bool {
@@ -1056,7 +1384,11 @@ impl Config {
/// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the
/// start of the session (and not in the middle).
pub fn pre_sudo(&self) -> bool {
self.config_file.pre_sudo.unwrap_or(false)
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.pre_sudo)
.unwrap_or(false)
}
#[cfg(target_os = "linux")]
@@ -1150,11 +1482,19 @@ impl Config {
}
pub fn display_time(&self) -> bool {
self.config_file.display_time.unwrap_or(true)
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.display_time)
.unwrap_or(true)
}
pub fn display_preamble(&self) -> bool {
self.config_file.display_preamble.unwrap_or(true)
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.display_preamble)
.unwrap_or(true)
}
pub fn should_run_custom_command(&self, name: &str) -> bool {