git.delta.rocks / jrsonnet / refs/commits / ca5174b8c4a4

difftreelog

feat prefetch command

Yaroslav Bolyukin2023-07-09parent: #eee08d5.patch.diff
in: trunk

3 files changed

modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
before · cmds/fleet/src/cmds/build_systems.rs
1use std::{env::current_dir, time::Duration};23use crate::command::MyCommand;4use crate::host::Config;5use anyhow::Result;6use clap::Parser;7use tokio::{task::LocalSet, time::sleep};8use tracing::{error, field, info, info_span, warn, Instrument};910#[derive(Parser, Clone)]11pub struct BuildSystems {12	/// Do not continue on error13	#[clap(long)]14	fail_fast: bool,15	/// Run builds as sudo16	#[clap(long)]17	privileged_build: bool,18	#[clap(subcommand)]19	subcommand: Subcommand,20}2122enum UploadAction {23	Test,24	Boot,25	Switch,26}27impl UploadAction {28	fn name(&self) -> &'static str {29		match self {30			UploadAction::Test => "test",31			UploadAction::Boot => "boot",32			UploadAction::Switch => "switch",33		}34	}3536	pub(crate) fn should_switch_profile(&self) -> bool {37		matches!(self, Self::Switch | Self::Boot)38	}39	pub(crate) fn should_activate(&self) -> bool {40		matches!(self, Self::Switch | Self::Test)41	}42}4344enum PackageAction {45	SdImage,46	InstallationCd,47}48impl PackageAction {49	fn build_attr(&self) -> String {50		match self {51			PackageAction::SdImage => "sdImage".to_owned(),52			PackageAction::InstallationCd => "installationCd".to_owned(),53		}54	}55}5657enum Action {58	Upload { action: Option<UploadAction> },59	Package(PackageAction),60}61impl Action {62	fn build_attr(&self) -> String {63		match self {64			Action::Upload { .. } => "toplevel".to_owned(),65			Action::Package(p) => p.build_attr(),66		}67	}68}6970impl From<Subcommand> for Action {71	fn from(s: Subcommand) -> Self {72		match s {73			Subcommand::Upload => Self::Upload { action: None },74			Subcommand::Test => Self::Upload {75				action: Some(UploadAction::Test),76			},77			Subcommand::Boot => Self::Upload {78				action: Some(UploadAction::Boot),79			},80			Subcommand::Switch => Self::Upload {81				action: Some(UploadAction::Switch),82			},83			Subcommand::SdImage => Self::Package(PackageAction::SdImage),84			Subcommand::InstallationCd => Self::Package(PackageAction::InstallationCd),85		}86	}87}8889#[derive(Parser, Clone)]90enum Subcommand {91	/// Upload, but do not switch92	Upload,93	/// Upload + switch to built system until reboot94	Test,95	/// Upload + switch to built system after reboot96	Boot,97	/// Upload + test + boot98	Switch,99100	/// Build SD .img image101	SdImage,102	/// Build an installation cd ISO image103	InstallationCd,104}105106impl BuildSystems {107	async fn build_task(self, config: Config, host: String) -> Result<()> {108		info!("building");109		let action = Action::from(self.subcommand.clone());110		let built = {111			let dir = tempfile::tempdir()?;112			dir.path().to_owned()113		};114115		let mut nix_build = MyCommand::new("nix");116		nix_build117			.args([118				"build",119				"--impure",120				"--json",121				// "--show-trace",122				"--no-link",123			])124			.comparg("--out-link", &built)125			.arg(126				config.configuration_attr_name(&format!(127					"buildSystems.{}.{host}",128					action.build_attr()129				)),130			)131			.args(&config.nix_args);132133		if self.privileged_build {134			nix_build = nix_build.sudo();135		}136137		nix_build.run_nix().await.map_err(|e| {138			if action.build_attr() == "sdImage" {139				info!("sd-image build failed");140				info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");141				info!("This module was automatically imported before, but was removed for better customization")142			}143			e144		})?;145		let built = std::fs::canonicalize(built)?;146147		match action {148			Action::Upload { action } => {149				if !config.is_local(&host) {150					info!("uploading system closure");151					let mut tries = 0;152					loop {153						let mut nix = MyCommand::new("nix");154						nix.arg("copy")155							.comparg("--to", format!("ssh://root@{host}"))156							.arg(&built);157						match nix.run_nix().await {158							Ok(()) => break,159							Err(e) if tries < 3 => {160								tries += 1;161								warn!("Copy failure ({}/3): {}", tries, e);162								sleep(Duration::from_millis(5000)).await;163							}164							Err(e) => return Err(e),165						}166					}167				}168				if let Some(action) = action {169					if action.should_switch_profile() {170						info!("switching generation");171						let mut cmd = MyCommand::new("nix-env");172						cmd.comparg("--profile", "/nix/var/nix/profiles/system")173							.comparg("--set", &built);174						config.run_on(&host, cmd, true).await?;175					}176					if action.should_activate() {177						info!("executing activation script");178						let mut switch_script = built.clone();179						switch_script.push("bin");180						switch_script.push("switch-to-configuration");181						let mut cmd = MyCommand::new(switch_script);182						cmd.arg(action.name());183						config.run_on(&host, cmd, true).await?;184					}185				}186			}187			Action::Package(PackageAction::SdImage) => {188				let mut out = current_dir()?;189				out.push(format!("sd-image-{}", host));190191				info!("building sd image to {:?}", out);192				let mut nix_build = MyCommand::new("nix");193				nix_build194					.args(["build", "--impure", "--no-link"])195					.comparg("--out-link", &out)196					.arg(config.configuration_attr_name(&format!("buildSystems.sdImage.{}", host,)))197					.args(&config.nix_args);198				if !self.fail_fast {199					nix_build.arg("--keep-going");200				}201				if self.privileged_build {202					nix_build = nix_build.sudo();203				}204205				nix_build.run_nix().await?;206			}207			Action::Package(PackageAction::InstallationCd) => {208				let mut out = current_dir()?;209				out.push(format!("installation-cd-{}", host));210211				info!("building sd image to {:?}", out);212				let mut nix_build = MyCommand::new("nix");213				nix_build214					.args(["build", "--impure", "--no-link"])215					.comparg("--out-link", &out)216					.arg(217						config.configuration_attr_name(&format!(218							"buildSystems.installationCd.{}",219							host,220						)),221					)222					.args(&config.nix_args);223				if !self.fail_fast {224					nix_build.arg("--keep-going");225				}226				if self.privileged_build {227					nix_build = nix_build.sudo();228				}229230				nix_build.run_nix().await?;231			}232		};233		Ok(())234	}235236	pub async fn run(self, config: &Config) -> Result<()> {237		let hosts = config.list_hosts().await?;238		let set = LocalSet::new();239		let this = &self;240		for host in hosts.iter() {241			if config.should_skip(host) {242				continue;243			}244			let config = config.clone();245			let host = host.clone();246			let this = this.clone();247			let span = info_span!("deployment", host = field::display(&host));248			set.spawn_local(249				(async move {250					match this.build_task(config, host).await {251						Ok(_) => {}252						Err(e) => {253							error!("failed to deploy host: {}", e)254						}255					}256				})257				.instrument(span),258			);259		}260		set.await;261		Ok(())262	}263}
after · cmds/fleet/src/cmds/build_systems.rs
1use std::{env::current_dir, time::Duration};23use crate::command::MyCommand;4use crate::host::Config;5use anyhow::Result;6use clap::Parser;7use tokio::{task::LocalSet, time::sleep};8use tracing::{error, field, info, info_span, warn, Instrument};910#[derive(Parser, Clone)]11pub struct BuildSystems {12	/// Do not continue on error13	#[clap(long)]14	fail_fast: bool,15	/// Run builds as sudo16	#[clap(long)]17	privileged_build: bool,18	#[clap(subcommand)]19	subcommand: Subcommand,20}2122enum UploadAction {23	Test,24	Boot,25	Switch,26}27impl UploadAction {28	fn name(&self) -> &'static str {29		match self {30			UploadAction::Test => "test",31			UploadAction::Boot => "boot",32			UploadAction::Switch => "switch",33		}34	}3536	pub(crate) fn should_switch_profile(&self) -> bool {37		matches!(self, Self::Switch | Self::Boot)38	}39	pub(crate) fn should_activate(&self) -> bool {40		matches!(self, Self::Switch | Self::Test)41	}42}4344enum PackageAction {45	SdImage,46	InstallationCd,47}48impl PackageAction {49	fn build_attr(&self) -> String {50		match self {51			PackageAction::SdImage => "sdImage".to_owned(),52			PackageAction::InstallationCd => "installationCd".to_owned(),53		}54	}55}5657enum Action {58	Upload { action: Option<UploadAction> },59	Package(PackageAction),60}61impl Action {62	fn build_attr(&self) -> String {63		match self {64			Action::Upload { .. } => "toplevel".to_owned(),65			Action::Package(p) => p.build_attr(),66		}67	}68}6970impl From<Subcommand> for Action {71	fn from(s: Subcommand) -> Self {72		match s {73			Subcommand::Upload => Self::Upload { action: None },74			Subcommand::Test => Self::Upload {75				action: Some(UploadAction::Test),76			},77			Subcommand::Boot => Self::Upload {78				action: Some(UploadAction::Boot),79			},80			Subcommand::Switch => Self::Upload {81				action: Some(UploadAction::Switch),82			},83			Subcommand::SdImage => Self::Package(PackageAction::SdImage),84			Subcommand::InstallationCd => Self::Package(PackageAction::InstallationCd),85		}86	}87}8889#[derive(Parser, Clone)]90enum Subcommand {91	/// Upload, but do not switch92	Upload,93	/// Upload + switch to built system until reboot94	Test,95	/// Upload + switch to built system after reboot96	Boot,97	/// Upload + test + boot98	Switch,99100	/// Build SD .img image101	SdImage,102	/// Build an installation cd ISO image103	InstallationCd,104}105106impl BuildSystems {107	async fn build_task(self, config: Config, host: String) -> Result<()> {108		info!("building");109		let action = Action::from(self.subcommand.clone());110		let built = {111			let dir = tempfile::tempdir()?;112			dir.path().to_owned()113		};114115		let mut nix_build = MyCommand::new("nix");116		nix_build117			.args([118				"build",119				"--impure",120				"--json",121				// "--show-trace",122				"--no-link",123				"--option",124				"log-lines",125				"200",126			])127			.comparg("--out-link", &built)128			.arg(129				config.configuration_attr_name(&format!(130					"buildSystems.{}.{host}",131					action.build_attr()132				)),133			)134			.args(&config.nix_args);135136		if self.privileged_build {137			nix_build = nix_build.sudo();138		}139140		nix_build.run_nix().await.map_err(|e| {141			if action.build_attr() == "sdImage" {142				info!("sd-image build failed");143				info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");144				info!("This module was automatically imported before, but was removed for better customization")145			}146			e147		})?;148		let built = std::fs::canonicalize(built)?;149150		match action {151			Action::Upload { action } => {152				if !config.is_local(&host) {153					info!("uploading system closure");154					let mut tries = 0;155					loop {156						let mut nix = MyCommand::new("nix");157						nix.arg("copy")158							.comparg("--to", format!("ssh://root@{host}"))159							.arg(&built);160						match nix.run_nix().await {161							Ok(()) => break,162							Err(e) if tries < 3 => {163								tries += 1;164								warn!("Copy failure ({}/3): {}", tries, e);165								sleep(Duration::from_millis(5000)).await;166							}167							Err(e) => return Err(e),168						}169					}170				}171				if let Some(action) = action {172					if action.should_switch_profile() {173						info!("switching generation");174						let mut cmd = MyCommand::new("nix-env");175						cmd.comparg("--profile", "/nix/var/nix/profiles/system")176							.comparg("--set", &built);177						config.run_on(&host, cmd, true).await?;178					}179					if action.should_activate() {180						info!("executing activation script");181						let mut switch_script = built.clone();182						switch_script.push("bin");183						switch_script.push("switch-to-configuration");184						let mut cmd = MyCommand::new(switch_script);185						cmd.arg(action.name());186						config.run_on(&host, cmd, true).await?;187					}188				}189			}190			Action::Package(PackageAction::SdImage) => {191				let mut out = current_dir()?;192				out.push(format!("sd-image-{}", host));193194				info!("building sd image to {:?}", out);195				let mut nix_build = MyCommand::new("nix");196				nix_build197					.args(["build", "--impure", "--no-link"])198					.comparg("--out-link", &out)199					.arg(config.configuration_attr_name(&format!("buildSystems.sdImage.{}", host,)))200					.args(&config.nix_args);201				if !self.fail_fast {202					nix_build.arg("--keep-going");203				}204				if self.privileged_build {205					nix_build = nix_build.sudo();206				}207208				nix_build.run_nix().await?;209			}210			Action::Package(PackageAction::InstallationCd) => {211				let mut out = current_dir()?;212				out.push(format!("installation-cd-{}", host));213214				info!("building sd image to {:?}", out);215				let mut nix_build = MyCommand::new("nix");216				nix_build217					.args(["build", "--impure", "--no-link"])218					.comparg("--out-link", &out)219					.arg(220						config.configuration_attr_name(&format!(221							"buildSystems.installationCd.{}",222							host,223						)),224					)225					.args(&config.nix_args);226				if !self.fail_fast {227					nix_build.arg("--keep-going");228				}229				if self.privileged_build {230					nix_build = nix_build.sudo();231				}232233				nix_build.run_nix().await?;234			}235		};236		Ok(())237	}238239	pub async fn run(self, config: &Config) -> Result<()> {240		let hosts = config.list_hosts().await?;241		let set = LocalSet::new();242		let this = &self;243		for host in hosts.iter() {244			if config.should_skip(host) {245				continue;246			}247			let config = config.clone();248			let host = host.clone();249			let this = this.clone();250			let span = info_span!("deployment", host = field::display(&host));251			set.spawn_local(252				(async move {253					match this.build_task(config, host).await {254						Ok(_) => {}255						Err(e) => {256							error!("failed to deploy host: {}", e)257						}258					}259				})260				.instrument(span),261			);262		}263		set.await;264		Ok(())265	}266}
modifiedcmds/fleet/src/command.rsdiffbeforeafterboth
--- a/cmds/fleet/src/command.rs
+++ b/cmds/fleet/src/command.rs
@@ -254,9 +254,12 @@
 							NixLog::Start { text, level: 1, typ: 111, .. } if text.starts_with("waiting for a machine to build ") => {
 								// Useless repeating notification about build
 							}
-							NixLog::Start { text, level: 3, typ: 111, .. } if text.starts_with("resolved derivation:  ") => {
+							NixLog::Start { text, level: 3, typ: 111, .. } if text.starts_with("resolved derivation: ") => {
 								// CA resolved
 							}
+							NixLog::Start { text, level: 1, typ: 111, .. } if text.starts_with("waiting for lock on ") => {
+								// Concurrent build of the same message
+							}
 							NixLog::Stop { .. } => {},
 							NixLog::Result { .. } => {},
 							_ => warn!("unknown log: {:?}", log)
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -5,23 +5,56 @@
 
 mod fleetdata;
 
+use std::ffi::OsString;
 use std::io;
 
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, Result};
 use clap::Parser;
 
 use cmds::{build_systems::BuildSystems, info::Info, secrets::Secrets};
 use host::{Config, FleetOpts};
+use tokio::fs;
+use tokio::process::Command;
 use tracing::{info, metadata::LevelFilter};
 use tracing_subscriber::EnvFilter;
 
 #[derive(Parser)]
+struct Prefetch {}
+impl Prefetch {
+	async fn run(&self, config: &Config) -> Result<()> {
+		let mut prefetch_dir = config.directory.to_path_buf();
+		prefetch_dir.push("prefetch");
+		if !prefetch_dir.is_dir() {
+			info!("nothing to prefetch: no prefetch directory");
+			return Ok(());
+		}
+		for entry in std::fs::read_dir(&prefetch_dir)? {
+			let entry = entry?;
+			if !entry.metadata()?.is_file() {
+				bail!("only files should exist in prefetch directory");
+			}
+			info!("prefetching {:?}", entry.file_name());
+			let mut path = OsString::new();
+			path.push("file://");
+			path.push(entry.path());
+			let status = Command::new("nix-prefetch-url").arg(path).status().await?;
+			if !status.success() {
+				bail!("failed with {status}");
+			}
+		}
+		Ok(())
+	}
+}
+
+#[derive(Parser)]
 enum Opts {
 	/// Prepare systems for deployments
 	BuildSystems(BuildSystems),
 	/// Secret management
 	#[clap(subcommand)]
 	Secrets(Secrets),
+	/// Upload prefetch directory to the nix store
+	Prefetch(Prefetch),
 	/// Config parsing
 	Info(Info),
 }
@@ -40,6 +73,7 @@
 		Opts::BuildSystems(c) => c.run(config).await?,
 		Opts::Secrets(s) => s.run(config).await?,
 		Opts::Info(i) => i.run(config).await?,
+		Opts::Prefetch(p) => p.run(config).await?,
 	};
 	Ok(())
 }