difftreelog
feat fleet secret regenerate subcommand
in: trunk
1 file changed
cmds/fleet/src/cmds/secrets.rsdiffbeforeafterboth49 #[clap(short = 'm', long)]49 #[clap(short = 'm', long)]50 machine: Vec<String>,50 machine: Vec<String>,51 },51 },52 /// Regenerate secret (prune then ensure)53 Regenerate {54 /// Secret to regenerate55 name: String,5657 /// Machines to regenerate for - if specified, only those machines58 #[clap(short = 'm', long)]59 machine: Vec<String>,60 },52 List {},61 List {},53}62}5463170 machine,179 machine,171 whole_dist,180 whole_dist,172 } => {181 } => {173 let mut secrets = config.data.secrets.write().expect("not poisoned");174 let Some(dists) = secrets.get_mut(&name) else {175 bail!("secret {name} not found");176 };177 if machine.is_empty() && whole_dist {178 for dist in dists.distributions_mut() {179 dist.prune("manual prune".to_owned());180 }181 } else if machine.is_empty() {182 let dist = dists183 .distributions_mut()184 .exactly_one()185 .map_err(|e| anyhow!("{e}"))186 .context(187 "with no machine specified, there should be exactly one distribution",188 )?;189 dist.prune("manual prune".to_owned());182 Self::prune(config, &name, &machine, whole_dist)?;190 } else if whole_dist {191 for dist in dists.distributions_mut() {192 if machine193 .iter()194 .any(|m| dist.owners().any(|o| o.as_host() == Some(m.as_str())))195 {196 dist.prune(format!(197 "manual prune of distribution containing {}",198 machine.join(", ")199 ));200 }201 }202 } else {203 let owners: BTreeSet<SecretOwner> =204 machine.iter().map(SecretOwner::host).collect();205 for dist in dists.distributions_mut() {206 dist.prune_owners(&owners, "manual prune".to_owned());207 }208 }209 }183 }210 Secret::Ensure { name, machine } => {184 Secret::Ensure { name, machine } => {185 Self::ensure(config, opts, &name, &machine)?;186 }211 let hosts: Vec<String> = if machine.is_empty() {187 Secret::Regenerate { name, machine } => {212 config213 .list_hosts()?214 .into_iter()215 .filter(|h| opts.should_skip(h).ok() != Some(true))216 .map(|h| h.name)217 .collect()218 } else {219 machine220 };221222 for hostname in &hosts {223 let nixos_cfg = config.system_config(hostname)?;188 let pruned = Self::prune(config, &name, &machine, true)?;224 let secrets = nix_go!(nixos_cfg.secrets);189 // In general, this is not correct - already evaluated secret would still be cached after pruning190 // But as a dedicated CLI subcommand it is safe to assume it was not evaluated yet225 if secrets.has_field(&name)? {191 Self::ensure(config, opts, &name, &pruned)?;226 info!("ensuring secret {name} for {hostname}");227 // Force evaluation of secret parts, triggering __fleetEnsureHostSecret228 nix_go!(secrets[{ &name }].definition.parts);229 }230 }192 }231 }232 }193 }233 Ok(())194 Ok(())234 }195 }196197 fn prune(198 config: &Config,199 name: &str,200 machine: &[String],201 whole_dist: bool,202 ) -> Result<Vec<String>> {203 let mut secrets = config.data.secrets.write().expect("not poisoned");204 let Some(dists) = secrets.get_mut(name) else {205 bail!("secret {name} not found");206 };207 let owners_before: BTreeSet<String> = dists208 .owners()209 .filter_map(|o| o.as_host().map(str::to_owned))210 .collect();211212 if machine.is_empty() && whole_dist {213 for dist in dists.distributions_mut() {214 dist.prune("manual prune".to_owned());215 }216 } else if machine.is_empty() {217 let dist = dists218 .distributions_mut()219 .exactly_one()220 .map_err(|e| anyhow!("{e}"))221 .context("with no machine specified, there should be exactly one distribution")?;222 dist.prune("manual prune".to_owned());223 } else if whole_dist {224 for dist in dists.distributions_mut() {225 if machine226 .iter()227 .any(|m| dist.owners().any(|o| o.as_host() == Some(m.as_str())))228 {229 dist.prune(format!(230 "manual prune of distribution containing {}",231 machine.join(", ")232 ));233 }234 }235 } else {236 let owners: BTreeSet<SecretOwner> = machine.iter().map(SecretOwner::host).collect();237 for dist in dists.distributions_mut() {238 dist.prune_owners(&owners, "manual prune".to_owned());239 }240 }241242 let owners_after: BTreeSet<String> = dists243 .owners()244 .filter_map(|o| o.as_host().map(str::to_owned))245 .collect();246 Ok(owners_before.difference(&owners_after).cloned().collect())247 }248249 fn ensure(config: &Config, opts: &FleetOpts, name: &str, machine: &[String]) -> Result<()> {250 let hosts: Vec<String> = if machine.is_empty() {251 config252 .list_hosts()?253 .into_iter()254 .filter(|h| opts.should_skip(h).ok() != Some(true))255 .map(|h| h.name)256 .collect()257 } else {258 machine.to_vec()259 };260261 for hostname in &hosts {262 let nixos_cfg = config.system_config(hostname)?;263 let secrets = nix_go!(nixos_cfg.secrets);264 if secrets.has_field(name)? {265 info!("ensuring secret {name} for {hostname}");266 // Force evaluation of secret parts, triggering __fleetEnsureHostSecret267 nix_go!(secrets[{ name }].definition.parts);268 }269 }270 Ok(())271 }235}272}236273