aboutsummaryrefslogtreecommitdiff
path: root/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/data.rs73
-rw-r--r--src/utils/deploy.rs179
-rw-r--r--src/utils/mod.rs304
-rw-r--r--src/utils/push.rs174
4 files changed, 0 insertions, 730 deletions
diff --git a/src/utils/data.rs b/src/utils/data.rs
deleted file mode 100644
index f557e41..0000000
--- a/src/utils/data.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-// SPDX-FileCopyrightText: 2020 Serokell <https://serokell.io/>
-//
-// SPDX-License-Identifier: MPL-2.0
-
-use merge::Merge;
-
-use std::collections::HashMap;
-
-#[derive(Deserialize, Debug, Clone, Merge)]
-pub struct GenericSettings {
- #[serde(rename(deserialize = "sshUser"))]
- pub ssh_user: Option<String>,
- pub user: Option<String>,
- #[serde(
- skip_serializing_if = "Vec::is_empty",
- default,
- rename(deserialize = "sshOpts")
- )]
- #[merge(strategy = merge::vec::append)]
- pub ssh_opts: Vec<String>,
- #[serde(rename(deserialize = "fastConnection"))]
- pub fast_connection: Option<bool>,
- #[serde(rename(deserialize = "autoRollback"))]
- pub auto_rollback: Option<bool>,
- #[serde(rename(deserialize = "confirmTimeout"))]
- pub confirm_timeout: Option<u16>,
- #[serde(rename(deserialize = "tempPath"))]
- pub temp_path: Option<String>,
- #[serde(rename(deserialize = "magicRollback"))]
- pub magic_rollback: Option<bool>,
-}
-
-#[derive(Deserialize, Debug, Clone)]
-pub struct NodeSettings {
- pub hostname: String,
- pub profiles: HashMap<String, Profile>,
- #[serde(
- skip_serializing_if = "Vec::is_empty",
- default,
- rename(deserialize = "profilesOrder")
- )]
- pub profiles_order: Vec<String>,
-}
-
-#[derive(Deserialize, Debug, Clone)]
-pub struct ProfileSettings {
- pub path: String,
- #[serde(rename(deserialize = "profilePath"))]
- pub profile_path: Option<String>,
-}
-
-#[derive(Deserialize, Debug, Clone)]
-pub struct Profile {
- #[serde(flatten)]
- pub profile_settings: ProfileSettings,
- #[serde(flatten)]
- pub generic_settings: GenericSettings,
-}
-
-#[derive(Deserialize, Debug, Clone)]
-pub struct Node {
- #[serde(flatten)]
- pub generic_settings: GenericSettings,
- #[serde(flatten)]
- pub node_settings: NodeSettings,
-}
-
-#[derive(Deserialize, Debug, Clone)]
-pub struct Data {
- #[serde(flatten)]
- pub generic_settings: GenericSettings,
- pub nodes: HashMap<String, Node>,
-}
diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs
deleted file mode 100644
index 14a44a0..0000000
--- a/src/utils/deploy.rs
+++ /dev/null
@@ -1,179 +0,0 @@
-// SPDX-FileCopyrightText: 2020 Serokell <https://serokell.io/>
-// SPDX-FileCopyrightText: 2020 Andreas Fuchs <asf@boinkor.net>
-//
-// SPDX-License-Identifier: MPL-2.0
-
-use std::borrow::Cow;
-use tokio::process::Command;
-
-use thiserror::Error;
-
-fn build_activate_command(
- sudo: &Option<String>,
- profile_path: &str,
- closure: &str,
- auto_rollback: bool,
- temp_path: &Cow<str>,
- confirm_timeout: u16,
- magic_rollback: bool,
-) -> String {
- let mut self_activate_command = format!(
- "{}/activate-rs '{}' '{}' --temp-path {} --confirm-timeout {}",
- closure, profile_path, closure, temp_path, confirm_timeout
- );
-
- if magic_rollback {
- self_activate_command = format!("{} --magic-rollback", self_activate_command);
- }
-
- if auto_rollback {
- self_activate_command = format!("{} --auto-rollback", self_activate_command);
- }
-
- if let Some(sudo_cmd) = &sudo {
- self_activate_command = format!("{} {}", sudo_cmd, self_activate_command);
- }
-
- self_activate_command
-}
-
-#[test]
-fn test_activation_command_builder() {
- let activate_path_str = "/blah/bin/activate".to_string();
- let sudo = Some("sudo -u test".to_string());
- let profile_path = "/blah/profiles/test";
- let closure = "/nix/store/blah/etc";
- let auto_rollback = true;
- let temp_path = &"/tmp".into();
- let confirm_timeout = 30;
- let magic_rollback = true;
-
- assert_eq!(
- build_activate_command(
- &sudo,
- profile_path,
- closure,
- auto_rollback,
- temp_path,
- confirm_timeout,
- magic_rollback
- ),
- "sudo -u test /nix/store/blah/etc/activate-rs '/blah/profiles/test' '/nix/store/blah/etc' --temp-path /tmp --confirm-timeout 30 --magic-rollback --auto-rollback"
- .to_string(),
- );
-}
-
-#[derive(Error, Debug)]
-pub enum DeployProfileError {
- #[error("Failed to calculate activate bin path from deploy bin path: {0}")]
- DeployPathToActivatePathError(#[from] super::DeployPathToActivatePathError),
- #[error("Failed to run activation command over SSH: {0}")]
- SSHActivateError(std::io::Error),
- #[error("Activation over SSH resulted in a bad exit code: {0:?}")]
- SSHActivateExitError(Option<i32>),
- #[error("Failed to run confirmation command over SSH (the server should roll back): {0}")]
- SSHConfirmError(std::io::Error),
- #[error(
- "Confirming activation over SSH resulted in a bad exit code (the server should roll back): {0:?}"
- )]
- SSHConfirmExitError(Option<i32>),
-}
-
-pub async fn deploy_profile(
- deploy_data: &super::DeployData<'_>,
- deploy_defs: &super::DeployDefs,
-) -> Result<(), DeployProfileError> {
- info!(
- "Activating profile `{}` for node `{}`",
- deploy_data.profile_name, deploy_data.node_name
- );
-
- let temp_path: Cow<str> = match &deploy_data.merged_settings.temp_path {
- Some(x) => x.into(),
- None => "/tmp".into(),
- };
-
- let confirm_timeout = deploy_data.merged_settings.confirm_timeout.unwrap_or(30);
-
- let magic_rollback = deploy_data.merged_settings.magic_rollback.unwrap_or(true);
-
- let auto_rollback = deploy_data.merged_settings.auto_rollback.unwrap_or(true);
-
- let self_activate_command = build_activate_command(
- &deploy_defs.sudo,
- &deploy_defs.profile_path,
- &deploy_data.profile.profile_settings.path,
- auto_rollback,
- &temp_path,
- confirm_timeout,
- magic_rollback,
- );
-
- debug!("Constructed activation command: {}", self_activate_command);
-
- let hostname = match deploy_data.cmd_overrides.hostname {
- Some(ref x) => x,
- None => &deploy_data.node.node_settings.hostname,
- };
-
- let mut c = Command::new("ssh");
- let mut ssh_command = c
- .arg("-t")
- .arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname));
-
- for ssh_opt in &deploy_data.merged_settings.ssh_opts {
- ssh_command = ssh_command.arg(ssh_opt);
- }
-
- let ssh_exit_status = ssh_command
- .arg(self_activate_command)
- .status()
- .await
- .map_err(DeployProfileError::SSHActivateError)?;
-
- match ssh_exit_status.code() {
- Some(0) => (),
- a => return Err(DeployProfileError::SSHActivateExitError(a)),
- };
-
- info!("Success activating!");
-
- if magic_rollback {
- info!("Attempting to confirm activation");
-
- let mut c = Command::new("ssh");
- let mut ssh_confirm_command = c.arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname));
-
- for ssh_opt in &deploy_data.merged_settings.ssh_opts {
- ssh_confirm_command = ssh_confirm_command.arg(ssh_opt);
- }
-
- let lock_hash = &deploy_data.profile.profile_settings.path["/nix/store/".len()..];
- let lock_path = format!("{}/deploy-rs-canary-{}", temp_path, lock_hash);
-
- let mut confirm_command = format!("rm {}", lock_path);
- if let Some(sudo_cmd) = &deploy_defs.sudo {
- confirm_command = format!("{} {}", sudo_cmd, confirm_command);
- }
-
- debug!(
- "Attempting to run command to confirm deployment: {}",
- confirm_command
- );
-
- let ssh_exit_status = ssh_confirm_command
- .arg(confirm_command)
- .status()
- .await
- .map_err(DeployProfileError::SSHConfirmError)?;
-
- match ssh_exit_status.code() {
- Some(0) => (),
- a => return Err(DeployProfileError::SSHConfirmExitError(a)),
- };
-
- info!("Deployment confirmed.");
- }
-
- Ok(())
-}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
deleted file mode 100644
index a891261..0000000
--- a/src/utils/mod.rs
+++ /dev/null
@@ -1,304 +0,0 @@
-// SPDX-FileCopyrightText: 2020 Serokell <https://serokell.io/>
-// SPDX-FileCopyrightText: 2020 Andreas Fuchs <asf@boinkor.net>
-//
-// SPDX-License-Identifier: MPL-2.0
-
-use rnix::{types::*, NodeOrToken, SyntaxKind::*, SyntaxNode};
-
-use std::path::PathBuf;
-
-use merge::Merge;
-
-use thiserror::Error;
-
-#[macro_export]
-macro_rules! good_panic {
- ($($tts:tt)*) => {{
- error!($($tts)*);
- std::process::exit(1);
- }}
-}
-
-pub mod data;
-pub mod deploy;
-pub mod push;
-
-#[derive(Debug)]
-pub struct CmdOverrides {
- pub ssh_user: Option<String>,
- pub profile_user: Option<String>,
- pub ssh_opts: Option<String>,
- pub fast_connection: Option<bool>,
- pub auto_rollback: Option<bool>,
- pub hostname: Option<String>,
- pub magic_rollback: Option<bool>,
- pub temp_path: Option<String>,
- pub confirm_timeout: Option<u16>,
-}
-
-#[derive(PartialEq, Debug)]
-pub struct DeployFlake<'a> {
- pub repo: &'a str,
- pub node: Option<String>,
- pub profile: Option<String>,
-}
-
-#[derive(Error, Debug)]
-pub enum ParseFlakeError {
- #[error("The given path was too long, did you mean to put something in quotes?")]
- PathTooLong,
- #[error("Unrecognized node or token encountered")]
- Unrecognized,
-}
-pub fn parse_flake(flake: &str) -> Result<DeployFlake, ParseFlakeError> {
- let flake_fragment_start = flake.find('#');
- let (repo, maybe_fragment) = match flake_fragment_start {
- Some(s) => (&flake[..s], Some(&flake[s + 1..])),
- None => (flake, None),
- };
-
- let mut node: Option<String> = None;
- let mut profile: Option<String> = None;
-
- if let Some(fragment) = maybe_fragment {
- let ast = rnix::parse(fragment);
-
- let first_child = match ast.root().node().first_child() {
- Some(x) => x,
- None => {
- return Ok(DeployFlake {
- repo,
- node: None,
- profile: None,
- })
- }
- };
-
- let mut node_over = false;
-
- for entry in first_child.children_with_tokens() {
- let x: Option<String> = match (entry.kind(), node_over) {
- (TOKEN_DOT, false) => {
- node_over = true;
- None
- }
- (TOKEN_DOT, true) => {
- return Err(ParseFlakeError::PathTooLong);
- }
- (NODE_IDENT, _) => Some(entry.into_node().unwrap().text().to_string()),
- (TOKEN_IDENT, _) => Some(entry.into_token().unwrap().text().to_string()),
- (NODE_STRING, _) => {
- let c = entry
- .into_node()
- .unwrap()
- .children_with_tokens()
- .nth(1)
- .unwrap();
-
- Some(c.into_token().unwrap().text().to_string())
- }
- _ => return Err(ParseFlakeError::Unrecognized),
- };
-
- if !node_over {
- node = x;
- } else {
- profile = x;
- }
- }
- }
-
- Ok(DeployFlake {
- repo,
- node,
- profile,
- })
-}
-
-#[test]
-fn test_parse_flake() {
- assert_eq!(
- parse_flake("../deploy/examples/system").unwrap(),
- DeployFlake {
- repo: "../deploy/examples/system",
- node: None,
- profile: None,
- }
- );
-
- assert_eq!(
- parse_flake("../deploy/examples/system#").unwrap(),
- DeployFlake {
- repo: "../deploy/examples/system",
- node: None,
- profile: None,
- }
- );
-
- assert_eq!(
- parse_flake("../deploy/examples/system#computer.\"something.nix\"").unwrap(),
- DeployFlake {
- repo: "../deploy/examples/system",
- node: Some("computer".to_string()),
- profile: Some("something.nix".to_string()),
- }
- );
-
- assert_eq!(
- parse_flake("../deploy/examples/system#\"example.com\".system").unwrap(),
- DeployFlake {
- repo: "../deploy/examples/system",
- node: Some("example.com".to_string()),
- profile: Some("system".to_string()),
- }
- );
-
- assert_eq!(
- parse_flake("../deploy/examples/system#example").unwrap(),
- DeployFlake {
- repo: "../deploy/examples/system",
- node: Some("example".to_string()),
- profile: None
- }
- );
-
- assert_eq!(
- parse_flake("../deploy/examples/system#example.system").unwrap(),
- DeployFlake {
- repo: "../deploy/examples/system",
- node: Some("example".to_string()),
- profile: Some("system".to_string())
- }
- );
-
- assert_eq!(
- parse_flake("../deploy/examples/system").unwrap(),
- DeployFlake {
- repo: "../deploy/examples/system",
- node: None,
- profile: None,
- }
- );
-}
-
-#[derive(Debug, Clone)]
-pub struct DeployData<'a> {
- pub node_name: &'a str,
- pub node: &'a data::Node,
- pub profile_name: &'a str,
- pub profile: &'a data::Profile,
-
- pub cmd_overrides: &'a CmdOverrides,
-
- pub merged_settings: data::GenericSettings,
-}
-
-#[derive(Debug)]
-pub struct DeployDefs {
- pub ssh_user: String,
- pub profile_user: String,
- pub profile_path: String,
- pub sudo: Option<String>,
-}
-
-#[derive(Error, Debug)]
-pub enum DeployDataDefsError {
- #[error("Neither `user` nor `sshUser` are set for profile {0} of node {1}")]
- NoProfileUser(String, String),
-}
-
-impl<'a> DeployData<'a> {
- pub fn defs(&'a self) -> Result<DeployDefs, DeployDataDefsError> {
- let ssh_user = match self.merged_settings.ssh_user {
- Some(ref u) => u.clone(),
- None => whoami::username(),
- };
-
- let profile_user = match self.merged_settings.user {
- Some(ref x) => x.clone(),
- None => match self.merged_settings.ssh_user {
- Some(ref x) => x.clone(),
- None => {
- return Err(DeployDataDefsError::NoProfileUser(
- self.profile_name.to_owned(),
- self.node_name.to_owned(),
- ))
- }
- },
- };
-
- let profile_path = match self.profile.profile_settings.profile_path {
- None => match &profile_user[..] {
- "root" => format!("/nix/var/nix/profiles/{}", self.profile_name),
- _ => format!(
- "/nix/var/nix/profiles/per-user/{}/{}",
- profile_user, self.profile_name
- ),
- },
- Some(ref x) => x.clone(),
- };
-
- let sudo: Option<String> = match self.merged_settings.user {
- Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)),
- _ => None,
- };
-
- Ok(DeployDefs {
- ssh_user,
- profile_user,
- profile_path,
- sudo,
- })
- }
-}
-
-pub fn make_deploy_data<'a, 's>(
- top_settings: &'s data::GenericSettings,
- node: &'a data::Node,
- node_name: &'a str,
- profile: &'a data::Profile,
- profile_name: &'a str,
- cmd_overrides: &'a CmdOverrides,
-) -> DeployData<'a> {
- let mut merged_settings = profile.generic_settings.clone();
- merged_settings.merge(node.generic_settings.clone());
- merged_settings.merge(top_settings.clone());
-
- if cmd_overrides.ssh_user.is_some() {
- merged_settings.ssh_user = cmd_overrides.ssh_user.clone();
- }
- if cmd_overrides.profile_user.is_some() {
- merged_settings.user = cmd_overrides.profile_user.clone();
- }
- if let Some(ref ssh_opts) = cmd_overrides.ssh_opts {
- merged_settings.ssh_opts = ssh_opts.split(' ').map(|x| x.to_owned()).collect();
- }
- if let Some(fast_connection) = cmd_overrides.fast_connection {
- merged_settings.fast_connection = Some(fast_connection);
- }
- if let Some(auto_rollback) = cmd_overrides.auto_rollback {
- merged_settings.auto_rollback = Some(auto_rollback);
- }
- if let Some(magic_rollback) = cmd_overrides.magic_rollback {
- merged_settings.magic_rollback = Some(magic_rollback);
- }
-
- DeployData {
- profile,
- profile_name,
- node,
- node_name,
-
- cmd_overrides,
-
- merged_settings,
- }
-}
-
-#[derive(Error, Debug)]
-pub enum DeployPathToActivatePathError {
- #[error("Deploy path did not have a parent directory")]
- PathTooShort,
- #[error("Deploy path was not valid utf8")]
- InvalidUtf8,
-}
diff --git a/src/utils/push.rs b/src/utils/push.rs
deleted file mode 100644
index 503e062..0000000
--- a/src/utils/push.rs
+++ /dev/null
@@ -1,174 +0,0 @@
-// SPDX-FileCopyrightText: 2020 Serokell <https://serokell.io/>
-//
-// SPDX-License-Identifier: MPL-2.0
-
-use std::process::Stdio;
-use tokio::process::Command;
-use std::path::Path;
-
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub enum PushProfileError {
- #[error("Failed to calculate activate bin path from deploy bin path: {0}")]
- DeployPathToActivatePathError(#[from] super::DeployPathToActivatePathError),
- #[error("Failed to run Nix build command: {0}")]
- BuildError(std::io::Error),
- #[error("Nix build command resulted in a bad exit code: {0:?}")]
- BuildExitError(Option<i32>),
- #[error("Activation script deploy-rs-activate does not exist in profile.\n\
- Did you forget to use deploy-rs#lib.<...>.activate.<...> on your profile path?")]
- DeployRsActivateDoesntExist,
- #[error("Activation script activate-rs does not exist in profile.\n\
- Is there a mismatch in deploy-rs used in the flake you're deploying and deploy-rs command you're running?")]
- ActivateRsDoesntExist,
- #[error("Failed to run Nix sign command: {0}")]
- SignError(std::io::Error),
- #[error("Nix sign command resulted in a bad exit code: {0:?}")]
- SignExitError(Option<i32>),
- #[error("Failed to run Nix copy command: {0}")]
- CopyError(std::io::Error),
- #[error("Nix copy command resulted in a bad exit code: {0:?}")]
- CopyExitError(Option<i32>),
-}
-
-pub async fn push_profile(
- supports_flakes: bool,
- check_sigs: bool,
- repo: &str,
- deploy_data: &super::DeployData<'_>,
- deploy_defs: &super::DeployDefs,
- keep_result: bool,
- result_path: Option<&str>,
- extra_build_args: &[String],
-) -> Result<(), PushProfileError> {
- info!(
- "Building profile `{}` for node `{}`",
- deploy_data.profile_name, deploy_data.node_name
- );
-
- let mut build_c = if supports_flakes {
- Command::new("nix")
- } else {
- Command::new("nix-build")
- };
-
- let mut build_command = if supports_flakes {
- build_c.arg("build").arg(format!(
- "{}#deploy.nodes.\"{}\".profiles.\"{}\".path",
- repo, deploy_data.node_name, deploy_data.profile_name
- ))
- } else {
- build_c.arg(&repo).arg("-A").arg(format!(
- "deploy.nodes.\"{}\".profiles.\"{}\".path",
- deploy_data.node_name, deploy_data.profile_name
- ))
- };
-
- build_command = match (keep_result, supports_flakes) {
- (true, _) => {
- let result_path = result_path.unwrap_or("./.deploy-gc");
-
- build_command.arg("--out-link").arg(format!(
- "{}/{}/{}",
- result_path, deploy_data.node_name, deploy_data.profile_name
- ))
- }
- (false, false) => build_command.arg("--no-out-link"),
- (false, true) => build_command.arg("--no-link"),
- };
-
- for extra_arg in extra_build_args {
- build_command = build_command.arg(extra_arg);
- }
-
- let build_exit_status = build_command
- // Logging should be in stderr, this just stops the store path from printing for no reason
- .stdout(Stdio::null())
- .status()
- .await
- .map_err(PushProfileError::BuildError)?;
-
- match build_exit_status.code() {
- Some(0) => (),
- a => return Err(PushProfileError::BuildExitError(a)),
- };
-
- if ! Path::new(format!("{}/deploy-rs-activate", deploy_data.profile.profile_settings.path).as_str()).exists() {
- return Err(PushProfileError::DeployRsActivateDoesntExist);
- }
-
- if ! Path::new(format!("{}/activate-rs", deploy_data.profile.profile_settings.path).as_str()).exists() {
- return Err(PushProfileError::ActivateRsDoesntExist);
- }
-
-
-
- if let Ok(local_key) = std::env::var("LOCAL_KEY") {
- info!(
- "Signing key present! Signing profile `{}` for node `{}`",
- deploy_data.profile_name, deploy_data.node_name
- );
-
- let sign_exit_status = Command::new("nix")
- .arg("sign-paths")
- .arg("-r")
- .arg("-k")
- .arg(local_key)
- .arg(&deploy_data.profile.profile_settings.path)
- .status()
- .await
- .map_err(PushProfileError::SignError)?;
-
- match sign_exit_status.code() {
- Some(0) => (),
- a => return Err(PushProfileError::SignExitError(a)),
- };
- }
-
- debug!(
- "Copying profile `{}` to node `{}`",
- deploy_data.profile_name, deploy_data.node_name
- );
-
- let mut copy_command_ = Command::new("nix");
- let mut copy_command = copy_command_.arg("copy");
-
- if deploy_data.merged_settings.fast_connection != Some(true) {
- copy_command = copy_command.arg("--substitute-on-destination");
- }
-
- if !check_sigs {
- copy_command = copy_command.arg("--no-check-sigs");
- }
-
- let ssh_opts_str = deploy_data
- .merged_settings
- .ssh_opts
- // This should provide some extra safety, but it also breaks for some reason, oh well
- // .iter()
- // .map(|x| format!("'{}'", x))
- // .collect::<Vec<String>>()
- .join(" ");
-
- let hostname = match deploy_data.cmd_overrides.hostname {
- Some(ref x) => x,
- None => &deploy_data.node.node_settings.hostname,
- };
-
- let copy_exit_status = copy_command
- .arg("--to")
- .arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname))
- .arg(&deploy_data.profile.profile_settings.path)
- .env("NIX_SSHOPTS", ssh_opts_str)
- .status()
- .await
- .map_err(PushProfileError::CopyError)?;
-
- match copy_exit_status.code() {
- Some(0) => (),
- a => return Err(PushProfileError::CopyExitError(a)),
- };
-
- Ok(())
-}