difftreelog
fix post-refactor
in: trunk
3 files changed
cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/secrets/mod.rs
+++ b/cmds/fleet/src/cmds/secrets/mod.rs
@@ -167,13 +167,12 @@
let on: String = nix_go_json!(default_generator.impureOn);
let call_package = nix_go!(
- config_field.buildableSystems(Obj {
- localSystem: { config.local_system.clone() },
- })[{ on }]
- .config
- .nixpkgs
- .resolvedPkgs
- .callPackage
+ config_field.hosts[{ on }]
+ .nixosSystem
+ .config
+ .nixpkgs
+ .resolvedPkgs
+ .callPackage
);
let host = config.host(&on).await?;
@@ -486,7 +485,6 @@
}
let config_field = &config.config_unchecked_field;
- let config_field = nix_go!(config_field.configUnchecked);
let field = nix_go!(config_field.sharedSecrets[{ name }]);
let updated = update_owner_set(
@@ -512,7 +510,6 @@
let shared_set = config.list_shared().into_iter().collect::<HashSet<_>>();
for missing in expected_shared_set.difference(&shared_set) {
let config_field = &config.config_unchecked_field;
- let config_field = nix_go!(config_field.configUnchecked);
let secret = nix_go!(config_field.sharedSecrets[{ missing }]);
let expected_owners: Option<Vec<String>> =
nix_go_json!(secret.expectedOwners);
@@ -561,7 +558,6 @@
info!("updating secret: {name}");
let data = config.shared_secret(name)?;
let config_field = &config.config_unchecked_field;
- let config_field = nix_go!(config_field.configUnchecked);
let expected_owners: Vec<String> =
nix_go_json!(config_field.sharedSecrets[{ name }].expectedOwners);
if expected_owners.is_empty() {
cmds/fleet/src/host.rsdiffbeforeafterboth1use std::{2 env::current_dir,3 ffi::{OsStr, OsString},4 fmt::Display,5 io::Write,6 ops::Deref,7 path::PathBuf,8 str::FromStr,9 sync::{Arc, Mutex, MutexGuard, OnceLock},10};1112use anyhow::{anyhow, bail, Context, Result};13use clap::{ArgGroup, Parser};14use openssh::SessionBuilder;15use serde::de::DeserializeOwned;16use tempfile::NamedTempFile;17use tracing::instrument;1819use crate::{20 better_nix_eval::{Field, NixSessionPool},21 command::MyCommand,22 fleetdata::{FleetData, FleetSecret, FleetSharedSecret, SecretData},23 nix_go, nix_go_json,24};2526pub struct FleetConfigInternals {27 pub local_system: String,28 pub directory: PathBuf,29 pub opts: FleetOpts,30 pub data: Mutex<FleetData>,31 pub nix_args: Vec<OsString>,32 /// fleet_config.config33 pub config_field: Field,34 /// fleet_config.unchecked.config35 pub config_unchecked_field: Field,3637 /// import nixpkgs {system = local};38 pub default_pkgs: Field,39}4041#[derive(Clone)]42pub struct Config(Arc<FleetConfigInternals>);4344impl Deref for Config {45 type Target = FleetConfigInternals;4647 fn deref(&self) -> &Self::Target {48 &self.049 }50}5152pub struct ConfigHost {53 config: Config,54 pub name: String,55 pub local: bool,56 pub session: OnceLock<Arc<openssh::Session>>,5758 pub nixos_config: Field,59}60impl ConfigHost {61 async fn open_session(&self) -> Result<Arc<openssh::Session>> {62 assert!(!self.local, "do not open ssh connection to local session");63 // FIXME: TOCTOU64 if let Some(session) = &self.session.get() {65 return Ok((*session).clone());66 };67 let session = SessionBuilder::default();6869 let session = session70 .connect(&self.name)71 .await72 .map_err(|e| anyhow!("ssh error while connecting to {}: {e}", self.name))?;73 let session = Arc::new(session);74 self.session.set(session.clone()).expect("TOCTOU happened");75 Ok(session)76 }77 pub async fn mktemp_dir(&self) -> Result<String> {78 let mut cmd = self.cmd("mktemp").await?;79 cmd.arg("-d");80 let path = cmd.run_string().await?;81 Ok(path.trim_end().to_owned())82 }83 pub async fn read_file_bin(&self, path: impl AsRef<OsStr>) -> Result<Vec<u8>> {84 let mut cmd = self.cmd("cat").await?;85 cmd.arg(path);86 cmd.run_bytes().await87 }88 pub async fn read_file_text(&self, path: impl AsRef<OsStr>) -> Result<String> {89 let mut cmd = self.cmd("cat").await?;90 cmd.arg(path);91 cmd.run_string().await92 }93 pub async fn read_file_json<D: DeserializeOwned>(&self, path: impl AsRef<OsStr>) -> Result<D> {94 let text = self.read_file_text(path).await?;95 Ok(serde_json::from_str(&text)?)96 }97 pub async fn read_file_value<D: FromStr>(&self, path: impl AsRef<OsStr>) -> Result<D>98 where99 <D as FromStr>::Err: Display,100 {101 let text = self.read_file_text(path).await?;102 D::from_str(&text).map_err(|e| anyhow!("failed to parse value: {e}"))103 }104 pub async fn cmd(&self, cmd: impl AsRef<OsStr>) -> Result<MyCommand> {105 if self.local {106 Ok(MyCommand::new(cmd))107 } else {108 let session = self.open_session().await?;109 Ok(MyCommand::new_on(cmd, session))110 }111 }112113 pub async fn decrypt(&self, data: SecretData) -> Result<Vec<u8>> {114 let mut cmd = self.cmd("fleet-install-secrets").await?;115 cmd.arg("decrypt").eqarg("--secret", data.encode_z85());116 let encoded = cmd117 .sudo()118 .run_string()119 .await120 .context("failed to call remote host for decrypt")?;121 z85::decode(encoded.trim_end()).context("bad encoded data? outdated host?")122 }123 pub async fn reencrypt(&self, data: SecretData, targets: Vec<String>) -> Result<SecretData> {124 let mut cmd = self.cmd("fleet-install-secrets").await?;125 cmd.arg("reencrypt").eqarg("--secret", data.encode_z85());126 for target in targets {127 let key = self.config.key(&target).await?;128 cmd.eqarg("--targets", key);129 }130 let encoded = cmd131 .sudo()132 .run_string()133 .await134 .context("failed to call remote host for decrypt")?;135 SecretData::decode_z85(encoded.trim_end()).context("bad encoded data? outdated host?")136 }137 /// Returns path for futureproofing, as path might change i.e on conversion to CA138 pub async fn remote_derivation(&self, path: &PathBuf) -> Result<PathBuf> {139 if self.local {140 // Path is located locally, thus already trusted.141 return Ok(path.to_owned());142 }143 let mut nix = MyCommand::new("nix");144 nix.arg("copy")145 .arg("--substitute-on-destination")146 .comparg("--to", format!("ssh-ng://{}", self.name))147 .arg(path);148 nix.run_nix().await.context("nix copy")?;149 Ok(path.to_owned())150 }151 pub async fn systemctl_stop(&self, name: &str) -> Result<()> {152 let mut cmd = self.cmd("systemctl").await?;153 cmd.arg("stop").arg(name);154 cmd.sudo().run().await155 }156 pub async fn systemctl_start(&self, name: &str) -> Result<()> {157 let mut cmd = self.cmd("systemctl").await?;158 cmd.arg("start").arg(name);159 cmd.sudo().run().await160 }161162 pub async fn rm_file(&self, path: impl AsRef<OsStr>, sudo: bool) -> Result<()> {163 let mut cmd = self.cmd("rm").await?;164 cmd.arg("-f").arg(path);165 if sudo {166 cmd = cmd.sudo()167 }168 cmd.run().await169 }170171 pub async fn list_configured_secrets(&self) -> Result<Vec<String>> {172 let nixos = &self.nixos_config;173 let secrets = nix_go!(nixos.secrets);174 let mut out = Vec::new();175 for name in secrets.list_fields().await? {176 let secret = nix_go!(secrets[{ name }]);177 let is_shared: bool = nix_go_json!(secret.shared);178 if is_shared {179 continue;180 }181 out.push(name);182 }183 Ok(out)184 }185 pub async fn secret_field(&self, name: &str) -> Result<Field> {186 let nixos = &self.nixos_config;187 Ok(nix_go!(nixos.secrets[{ name }]))188 }189}190191impl Config {192 pub fn should_skip(&self, host: &str) -> bool {193 if !self.opts.skip.is_empty() {194 self.opts.skip.iter().any(|h| h as &str == host)195 } else if !self.opts.only.is_empty() {196 !self.opts.only.iter().any(|h| h as &str == host)197 } else {198 false199 }200 }201 pub fn is_local(&self, host: &str) -> bool {202 self.opts.localhost.as_ref().map(|s| s as &str) == Some(host)203 }204205 pub async fn host(&self, name: &str) -> Result<ConfigHost> {206 let config = &self.config_unchecked_field;207 let nixos_config = nix_go!(config.configuredSystems[{ name }].config);208 Ok(ConfigHost {209 config: self.clone(),210 name: name.to_owned(),211 local: self.is_local(name),212 session: OnceLock::new(),213 nixos_config,214 })215 }216 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {217 let config = &self.config_unchecked_field;218 let names = nix_go!(config.hosts).list_fields().await?;219 let mut out = vec![];220 for name in names {221 out.push(self.host(&name).await?);222 }223 Ok(out)224 }225 pub async fn system_config(&self, host: &str) -> Result<Field> {226 let fleet_field = &self.config_unchecked_field;227 Ok(nix_go!(fleet_field.hosts[{ host }].nixosSystem.config))228 }229230 pub(super) fn data(&self) -> MutexGuard<FleetData> {231 self.data.lock().unwrap()232 }233 pub(super) fn data_mut(&self) -> MutexGuard<FleetData> {234 self.data.lock().unwrap()235 }236 /// Shared secrets configured in fleet.nix or in flake237 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {238 let config_field = &self.config_unchecked_field;239 nix_go!(config_field.configUnchecked.sharedSecrets)240 .list_fields()241 .await242 }243 /// Shared secrets configured in fleet.nix244 pub fn list_shared(&self) -> Vec<String> {245 let data = self.data();246 data.shared_secrets.keys().cloned().collect()247 }248 pub fn has_shared(&self, name: &str) -> bool {249 let data = self.data();250 data.shared_secrets.contains_key(name)251 }252 pub fn replace_shared(&self, name: String, shared: FleetSharedSecret) {253 let mut data = self.data_mut();254 data.shared_secrets.insert(name.to_owned(), shared);255 }256 pub fn remove_shared(&self, secret: &str) {257 let mut data = self.data_mut();258 data.shared_secrets.remove(secret);259 }260261 pub fn list_secrets(&self, host: &str) -> Vec<String> {262 let data = self.data();263 let Some(secrets) = data.host_secrets.get(host) else {264 return Vec::new();265 };266 secrets.keys().cloned().collect()267 }268269 pub fn has_secret(&self, host: &str, secret: &str) -> bool {270 let data = self.data();271 let Some(host_secrets) = data.host_secrets.get(host) else {272 return false;273 };274 host_secrets.contains_key(secret)275 }276 pub fn insert_secret(&self, host: &str, secret: String, value: FleetSecret) {277 let mut data = self.data_mut();278 let host_secrets = data.host_secrets.entry(host.to_owned()).or_default();279 host_secrets.insert(secret, value);280 }281282 pub fn host_secret(&self, host: &str, secret: &str) -> Result<FleetSecret> {283 let data = self.data();284 let Some(host_secrets) = data.host_secrets.get(host) else {285 bail!("no secrets for machine {host}");286 };287 let Some(secret) = host_secrets.get(secret) else {288 bail!("machine {host} has no secret {secret}");289 };290 Ok(secret.clone())291 }292 pub fn shared_secret(&self, secret: &str) -> Result<FleetSharedSecret> {293 let data = self.data();294 let Some(secret) = data.shared_secrets.get(secret) else {295 bail!("no shared secret {secret}");296 };297 Ok(secret.clone())298 }299 pub async fn shared_secret_expected_owners(&self, secret: &str) -> Result<Vec<String>> {300 let config_field = &self.config_unchecked_field;301 Ok(nix_go_json!(302 config_field.configUnchecked.sharedSecrets[{ secret }].expectedOwners303 ))304 }305306 pub fn save(&self) -> Result<()> {307 let mut tempfile = NamedTempFile::new_in(self.directory.clone())?;308 let data = nixlike::serialize(&self.data() as &FleetData)?;309 tempfile.write_all(310 format!(311 "# This file contains fleet state and shouldn't be edited by hand\n\n{}\n\n# vim: ts=2 et nowrap\n",312 data313 )314 .as_bytes(),315 )?;316 let mut fleet_data_path = self.directory.clone();317 fleet_data_path.push("fleet.nix");318 tempfile.persist(fleet_data_path)?;319 Ok(())320 }321}322323#[derive(Parser, Clone)]324#[clap(group = ArgGroup::new("target_hosts"))]325pub struct FleetOpts {326 /// All hosts except those would be skipped327 #[clap(long, number_of_values = 1, group = "target_hosts")]328 only: Vec<String>,329330 /// Hosts to skip331 #[clap(long, number_of_values = 1, group = "target_hosts")]332 skip: Vec<String>,333334 /// Host, which should be threaten as current machine335 #[clap(long)]336 pub localhost: Option<String>,337338 /// Override detected system for host, to perform builds via339 /// binfmt-declared qemu instead of trying to crosscompile340 #[clap(long, default_value = "detect")]341 pub local_system: String,342}343344impl FleetOpts {345 pub async fn build(mut self, nix_args: Vec<OsString>) -> Result<Config> {346 if self.localhost.is_none() {347 self.localhost348 .replace(hostname::get().unwrap().to_str().unwrap().to_owned());349 }350 let directory = current_dir()?;351352 let pool = NixSessionPool::new(directory.as_os_str().to_owned(), nix_args.clone()).await?;353 let root_field = pool.get().await?;354355 let builtins_field = Field::field(root_field.clone(), "builtins").await?;356 if self.local_system == "detect" {357 self.local_system = nix_go_json!(builtins_field.currentSystem);358 }359 let local_system = self.local_system.clone();360361 let fleet_root = Field::field(root_field, "fleetConfigurations").await?;362 let fleet_field = nix_go!(fleet_root.default);363364 let config_field = nix_go!(fleet_field.config);365 let config_unchecked_field = nix_go!(fleet_field.unchecked.config);366367 let import = nix_go!(builtins_field.import);368 let overlays = nix_go!(fleet_field.overlays);369 let nixpkgs = nix_go!(fleet_field.nixpkgs | import);370371 let default_pkgs = nix_go!(nixpkgs(Obj {372 overlays,373 system: { self.local_system.clone() },374 }));375376 let mut fleet_data_path = directory.clone();377 fleet_data_path.push("fleet.nix");378 let bytes = std::fs::read_to_string(fleet_data_path)?;379 let data = nixlike::parse_str(&bytes)?;380381 Ok(Config(Arc::new(FleetConfigInternals {382 opts: self,383 directory,384 data,385 local_system,386 nix_args,387 config_field,388 config_unchecked_field,389 default_pkgs,390 })))391 }392}lib/default.nixdiffbeforeafterboth--- a/lib/default.nix
+++ b/lib/default.nix
@@ -22,7 +22,7 @@
++ [
data
({...}: {
- inherit globalModules hosts;
+ inherit globalModules hosts overlays;
})
]
++ modules;
@@ -39,7 +39,6 @@
root,
data,
}: {
- configuredHosts = root.config.hosts;
config = root.config;
};
defaultData = withData {
@@ -49,9 +48,9 @@
uncheckedData = withData {inherit data root;};
in {
inherit nixpkgs overlays;
- inherit (defaultData) configuredHosts configuredSystems config buildableSystems;
+ inherit (defaultData) config;
unchecked = {
- inherit (uncheckedData) configuredHosts configuredSystems config buildableSystems;
+ inherit (uncheckedData) config;
};
};
}