git.delta.rocks / jrsonnet / refs/commits / 43c14038052d

difftreelog

refactor pending new secret commands

nwrmmvsuYaroslav Bolyukin2026-02-03parent: #1e20ff1.patch.diff
in: trunk

5 files changed

modifiedCargo.lockdiffbeforeafterboth
before · Cargo.lock
461 packageslockfile v4
modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/build_systems.rs
+++ b/cmds/fleet/src/cmds/build_systems.rs
@@ -73,7 +73,7 @@
 					let built = match build_task(config, hostname.clone(), &build_attr).await {
 						Ok(path) => path,
 						Err(e) => {
-							error!("failed to deploy host: {}", e);
+							error!("failed to deploy host: {:?}", e);
 							return;
 						}
 					};
modifiedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/secrets/mod.rs
+++ b/cmds/fleet/src/cmds/secrets/mod.rs
@@ -1,20 +1,18 @@
 use std::{
 	collections::{BTreeSet, HashSet},
-	io::{Read, Write, stdin, stdout},
+	io::{Read, stdin},
 	path::PathBuf,
 };
 
-use anyhow::{Context, Result, bail, ensure};
+use anyhow::{Result, bail, ensure};
 use clap::Parser;
 use fleet_base::{host::Config, opts::FleetOpts};
 use fleet_shared::SecretData;
-use tabled::Tabled;
 use tokio::fs::read;
-use tracing::{info, info_span, warn};
+use tracing::{info, warn};
 
 #[derive(Parser)]
 pub enum Secret {
-	AddManager,
 	/// Force load host keys for all defined hosts
 	ForceKeys,
 	/// Read secret from remote host, requires sudo on one of the owning hosts
@@ -29,21 +27,30 @@
 
 		/// Which private secret part to read
 		#[clap(short = 'p', long, default_value = "secret")]
-		part: String,
+		part: Option<String>,
 
 		/// Which host should we use to decrypt, in case if reencryption is required, without
 		/// regeneration
 		#[clap(long)]
 		prefer_identities: Vec<String>,
 	},
-	Regenerate {
-		/// Which host should we use to decrypt, in case if reencryption is required, without
-		/// regeneration
-		#[clap(long)]
-		prefer_identities: Vec<String>,
-		/// Only regenerate shared secrets
-		#[clap(long)]
-		skip_hosts: bool,
+	/// Prune (remove, mark for regeneration) secrets
+	Prune {
+		/// Secret to prune
+		name: String,
+
+		/// Machines to prune - if specified, only the choosen machines will be pruned
+		#[clap(short = 'm', long)]
+		machine: Vec<String>,
+	},
+	/// Ensure secret is generated and not expired
+	Ensure {
+		/// Secret to ensure generated
+		name: String,
+
+		/// Machines to force secret for
+		#[clap(short = 'm', long)]
+		machine: Vec<String>,
 	},
 	List {},
 	Edit {
@@ -58,270 +65,7 @@
 		#[clap(short = 'p', long, default_value = "secret")]
 		part: String,
 	},
-}
-
-/*
-#[allow(clippy::too_many_arguments)]
-#[tracing::instrument(skip(config, secret, definition, prefer_identities))]
-async fn maybe_regenerate_shared_secret(
-	secret_name: &str,
-	config: &Config,
-	mut secret: FleetSecretDistribution,
-	definition: SharedSecretDefinition,
-	prefer_identities: &[String],
-	expectations: &Expectations,
-) -> Result<FleetSecretDistribution> {
-	let reason = secret_needs_regeneration(&secret.secret, &secret.owners, expectations);
-	let value = definition.definition_value();
-
-	let (should_reencrypt, reason) = match reason {
-		Some(RegenerationReason::OwnersAdded(_)) => {
-			// Secret always needs to be reencrypted for new owners to be able to read it
-			(
-				true,
-				if nix_go_json!(value.regenerateOnOwnerAdded) {
-					reason
-				} else {
-					None
-				},
-			)
-		}
-		Some(RegenerationReason::OwnersRemoved(_)) => {
-			// No need to reencrypt, we can just leave stanzas in place.
-			if nix_go_json!(value.regenerateOnOwnerRemoved) {
-				(true, reason)
-			} else {
-				(false, None)
-			}
-		}
-		Some(_) => (true, reason),
-		None => (false, None),
-	};
-
-	if let Some(reason) = reason {
-		info!("secret needs to be regenerated: {reason}");
-		let generated = generate_shared(config, secret_name, definition, expectations).await?;
-		Ok(generated)
-	} else if should_reencrypt {
-		info!("secret needs to be reencrypted");
-		let identity_holder = if !prefer_identities.is_empty() {
-			prefer_identities
-				.iter()
-				.find(|i| secret.owners.iter().any(|s| s == *i))
-		} else {
-			secret.owners.first()
-		};
-		let Some(identity_holder) = identity_holder else {
-			bail!("no available holder found");
-		};
-
-		for (part_name, part) in secret.secret.parts.iter_mut() {
-			let _span = info_span!("part reencryption", part_name);
-			if !part.raw.encrypted {
-				continue;
-			}
-			let host = config.host(identity_holder).await?;
-			let encrypted = host
-				.reencrypt(
-					part.raw.clone(),
-					expectations.owners.iter().cloned().collect(),
-				)
-				.await?;
-			part.raw = encrypted;
-		}
-		secret.owners = expectations.owners.clone();
-		Ok(secret)
-	} else {
-		Ok(secret)
-	}
-}
-*/
-
-/*
-async fn generate_pure(
-	_config: &Config,
-	_display_name: &str,
-	_secret: Value,
-	_default_generator: Value,
-	_expectations: &Expectations,
-) -> Result<FleetSecretData> {
-	bail!("pure generators are broken for now")
-}
-async fn generate_impure(
-	config: &Config,
-	_display_name: &str,
-	secret: Value,
-	default_generator: Value,
-	expectations: &Expectations,
-) -> Result<FleetSecretData> {
-	let generator = nix_go!(secret.generator);
-	let on: Option<String> = nix_go_json!(default_generator.impureOn);
-
-	let nixpkgs = &config.nixpkgs;
-
-	let host = if let Some(on) = &on {
-		config.host(on).await?
-	} else {
-		config.local_host()
-	};
-	let on_pkgs = host.pkgs().await?;
-	let mk_secret_generators = nix_go!(on_pkgs.mkSecretGenerators);
-
-	let mut recipients = Vec::new();
-	for owner in &expectations.owners {
-		let key = config.key(owner).await?;
-		recipients.push(key);
-	}
-	let generators = nix_go!(mk_secret_generators(Obj { recipients }));
-	let pkgs_and_generators = on_pkgs.attrs_update(generators)?;
-
-	let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));
-
-	let generator = nix_go!(call_package(generator)(Obj {}));
-
-	let generator = spawn_blocking(move || generator.build("out"))
-		.await
-		.expect("nix build shouldn't fail")?;
-	let generator = host.remote_derivation(&generator).await?;
-
-	let out_parent = host.mktemp_dir().await?;
-	let out = format!("{out_parent}/out");
-
-	let mut r#gen = host.cmd(generator).await?;
-	r#gen.env("out", &out);
-	if on.is_none() {
-		// This path is local, thus we can feed `OsString` directly to env var... But I don't think that's necessary to handle.
-		let project_path: String = config
-			.directory
-			.clone()
-			.into_os_string()
-			.into_string()
-			.map_err(|s| anyhow!("fleet project path is not utf-8: {s:?}"))?;
-		r#gen.env("FLEET_PROJECT", project_path);
-	}
-	r#gen.run().await.context("impure generator")?;
-
-	{
-		let marker = host.read_file_text(format!("{out}/marker")).await?;
-		ensure!(marker == "SUCCESS", "generation not succeeded");
-	}
-
-	let mut parts = BTreeMap::new();
-	for part in host.read_dir(&out).await? {
-		if part == "created_at" || part == "expires_at" || part == "marker" {
-			continue;
-		}
-		let contents: SecretData = host
-			.read_file_text(format!("{out}/{part}"))
-			.await?
-			.parse()
-			.map_err(|e| anyhow!("failed to decode secret {out:?} part {part:?}: {e}"))?;
-		parts.insert(part.to_owned(), FleetSecretPart { raw: contents });
-	}
-
-	let created_at = host.read_file_value(format!("{out}/created_at")).await?;
-	let expires_at = host.read_file_value(format!("{out}/expires_at")).await.ok();
-
-	let new_data = FleetSecretData {
-		created_at,
-		expires_at,
-		parts,
-		generation_data: expectations.generation_data.clone(),
-	};
-
-	if let Some(reason) = secret_needs_regeneration(&new_data, &expectations.owners, expectations) {
-		bail!("newly generated secret needs to be regenerated: {reason}")
-	}
-
-	Ok(new_data)
-}
-
-async fn generate(
-	config: &Config,
-	display_name: &str,
-	secret: Value,
-	expectations: &Expectations,
-) -> Result<FleetSecretData> {
-	let generator = nix_go!(secret.generator);
-	// Can't properly check on nix module system level
-	{
-		let gen_ty = generator.type_of();
-		if matches!(gen_ty, NixType::Null) {
-			bail!("secret has no generator defined, can't automatically generate it.");
-		}
-		if matches!(gen_ty, NixType::Attrs) {
-			if !generator.has_field("__functor")? {
-				bail!("generator should be functor, got {gen_ty:?}");
-			}
-		} else if matches!(gen_ty, NixType::Function) {
-			bail!("generator should be functor, got {gen_ty:?}");
-		}
-	}
-	let nixpkgs = &config.nixpkgs;
-	let default_pkgs = &config.default_pkgs;
-	let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);
-	// Generators provide additional information in passthru, to access
-	// passthru we should call generator, but information about where this generator is supposed to build
-	// is located in passthru... Thus evaluating generator on host.
-	//
-	// Maybe it is also possible to do some magic with __functor?
-	//
-	// I don't want to make modules always responsible for additional secret data anyway,
-	// so it should be in derivation, and not in the secret data itself.
-	let generators = nix_go!(default_mk_secret_generators(Obj {
-		recipients: <Vec<String>>::new(),
-	}));
-	let pkgs_and_generators = default_pkgs.clone().attrs_update(generators)?;
-
-	let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));
-	let default_generator = nix_go!(call_package(generator)(Obj {}));
-
-	let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);
-
-	match kind {
-		GeneratorKind::Impure => {
-			generate_impure(
-				config,
-				display_name,
-				secret,
-				default_generator,
-				expectations,
-			)
-			.await
-		}
-		GeneratorKind::Pure => {
-			generate_pure(
-				config,
-				display_name,
-				secret,
-				default_generator,
-				expectations,
-			)
-			.await
-		}
-	}
 }
-*/
-/*
-async fn generate_shared(
-	config: &Config,
-	display_name: &str,
-	secret: SharedSecretDefinition,
-	expectations: &Expectations,
-) -> Result<FleetSecretDistribution> {
-	// let owners: Vec<String> = nix_go_json!(secret.expectedOwners);
-	Ok(FleetSecretDistribution {
-		managed: Some(true),
-		secret: generate(
-			config,
-			display_name,
-			secret.definition_value(),
-			expectations,
-		)
-		.await?,
-		owners: expectations.owners.clone(),
-	})
-}*/
 
 async fn parse_public(
 	public: Option<String>,
@@ -404,9 +148,6 @@
 impl Secret {
 	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {
 		match self {
-			Secret::AddManager => {
-				todo!("part of fleet-pusher")
-			}
 			Secret::ForceKeys => {
 				for host in config.list_hosts()? {
 					if opts.should_skip(&host)? {
@@ -464,149 +205,6 @@
 				*/
 				todo!()
 			}
-			Secret::Regenerate {
-				prefer_identities,
-				skip_hosts,
-			} => {
-				/*
-								info!("checking for secrets to regenerate");
-								let expected_shared_set = config
-									.list_configured_shared()
-									.await?
-									.into_iter()
-									.collect::<HashSet<_>>();
-								let stored_shared_set = config.list_secrets().into_iter().collect::<HashSet<_>>();
-								{
-									// Generate missing shared
-									let _span = info_span!("shared").entered();
-									for missing in expected_shared_set.difference(&stored_shared_set) {
-										let definition = config.shared_secret_definition(missing)?;
-										if !definition.is_managed()? {
-											info!("skipping unmanaged secret: {missing}");
-											continue;
-										}
-										let expectations = definition
-											.expectations()
-											.with_context(|| format!("expectations for shared {missing:?}"))?;
-										info!("generating secret: {missing}");
-										let shared = generate_shared(config, missing, definition, &expectations)
-											.in_current_span()
-											.await?;
-										config.replace_shared(missing.to_string(), shared)
-									}
-								}
-								if !skip_hosts {
-									for host in config.list_hosts().await? {
-										if opts.should_skip(&host).await? {
-											continue;
-										}
-
-										let _span = info_span!("host", host = host.name).entered();
-										let expected_set = host
-											.list_defined_secrets()?
-											.into_iter()
-											.collect::<HashSet<_>>();
-										let stored_set = config
-											.list_secrets_for_owner(&host.name)
-											.into_iter()
-											.collect::<HashSet<_>>();
-										for missing_secret in expected_set.difference(&stored_set) {
-											let secret = host.secret_definition(missing_secret)?;
-											if secret.is_shared()? {
-												continue;
-											}
-											info!("generating missing secret: {missing_secret}");
-											let expectations = secret.expectations().with_context(|| {
-												format!("expectations for {missing_secret:?} of {:?}", host.name)
-											})?;
-											let generated = match generate(
-												config,
-												missing_secret,
-												secret.definition_value()?,
-												&expectations,
-											)
-											.in_current_span()
-											.await
-											{
-												Ok(v) => v,
-												Err(e) => {
-													error!("{e:?}");
-													continue;
-												}
-											};
-											config.insert_secret(host.name, missing_secret.to_string(), generated)
-										}
-										for known_secret in stored_set.intersection(&expected_set) {
-											let secret = host.secret_definition(known_secret)?;
-											if secret.is_shared()? {
-												continue;
-											}
-											info!("updating secret: {known_secret}");
-											let data = config.host_secret(&host.name, known_secret)?;
-											let expectations = secret.expectations()?;
-											if let Some(regen_reason) = data.needs_regeneration(&expectations) {
-												info!("needs regeneration: {regen_reason}");
-												let generated = match generate(
-													config,
-													known_secret,
-													secret.definition_value()?,
-													&expectations,
-												)
-												.in_current_span()
-												.await
-												{
-													Ok(v) => v,
-													Err(e) => {
-														error!("{e:?}");
-														continue;
-													}
-												};
-												config.insert_secret(
-													&host.name,
-													known_secret.to_string(),
-													FleetLegacyHostSecret {
-														managed: Some(true),
-														secret: generated,
-													},
-												)
-											}
-										}
-										for removed_secret in stored_set.difference(&expected_set) {
-											let definition = host.secret_definition(removed_secret)?;
-											if definition.is_shared()? {
-												continue;
-											}
-											info!("removing secret: {removed_secret}");
-											config.remove_secret(&host.name, removed_secret);
-										}
-									}
-								}
-								for known_secret in stored_shared_set.intersection(&expected_shared_set) {
-									info!("updating shared secret: {known_secret}");
-									let data = config.shared_secret(known_secret)?.expect("exists");
-
-									let definition = config.shared_secret_definition(known_secret)?;
-									let expectations = definition.expectations()?;
-									config.replace_shared(
-										known_secret.to_owned(),
-										maybe_regenerate_shared_secret(
-											known_secret,
-											config,
-											data,
-											definition,
-											&prefer_identities,
-											&expectations,
-										)
-										.await?,
-									);
-								}
-								for removed_secret in stored_shared_set.difference(&expected_shared_set) {
-									info!("removing shared secret: {removed_secret}");
-									config.remove_shared(removed_secret);
-								}
-				*/
-				todo!()
-			}
 			Secret::List {} => {
 				/*
 				let _span = info_span!("loading secrets").entered();
@@ -665,6 +263,8 @@
 				};*/
 				todo!()
 			}
+			Secret::Prune { name, machine } => todo!(),
+			Secret::Ensure { name, machine } => todo!(),
 		}
 		Ok(())
 	}
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -17,8 +17,7 @@
 	tf::Tf,
 };
 use fleet_base::{host::Config, opts::FleetOpts};
-use futures::{TryStreamExt, future::LocalBoxFuture, stream::FuturesUnordered};
-// use host::Config;
+use futures::{TryStreamExt, stream::FuturesUnordered};
 #[cfg(feature = "indicatif")]
 use human_repr::HumanCount;
 #[cfg(feature = "indicatif")]
modifiedcrates/nixlike/Cargo.tomldiffbeforeafterboth
--- a/crates/nixlike/Cargo.toml
+++ b/crates/nixlike/Cargo.toml
@@ -10,7 +10,6 @@
 itertools = "0.14.0"
 linked-hash-map = "0.5.6"
 peg = "0.8.5"
-ron = "0.11.0"
 serde = { version = "1.0.219", features = ["derive"] }
 serde-transcode = "1.1.1"
 serde_json = "1.0.140"