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

difftreelog

refactor drop unnecessary async/await

vnlkvprsYaroslav Bolyukin2026-01-22parent: #33f3601.patch.diff
in: trunk

15 files changed

modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
3030
31async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<PathBuf> {31async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<PathBuf> {
32 info!("building");32 info!("building");
33 let host = config.host(&hostname).await?;33 let host = config.host(&hostname)?;
34 // let action = Action::from(self.subcommand.clone());34 // let action = Action::from(self.subcommand.clone());
35 let nixos = host.nixos_config().await?;35 let nixos = host.nixos_config()?;
36 let drv = nix_go!(nixos.system.build[{ build_attr }]);36 let drv = nix_go!(nixos.system.build[{ build_attr }]);
37 let out_output = spawn_blocking(move || drv.build("out"))37 let out_output = spawn_blocking(move || drv.build("out"))
38 .await38 .await
5959
60impl BuildSystems {60impl BuildSystems {
61 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {61 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {
62 let hosts = opts.filter_skipped(config.list_hosts().await?).await?;62 let hosts = opts.filter_skipped(config.list_hosts()?)?;
63 let set = LocalSet::new();63 let set = LocalSet::new();
64 let build_attr = self.build_attr.clone();64 let build_attr = self.build_attr.clone();
65 for host in hosts {65 for host in hosts {
9595
96impl Deploy {96impl Deploy {
97 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {97 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {
98 let hosts = opts.filter_skipped(config.list_hosts().await?).await?;98 let hosts = opts.filter_skipped(config.list_hosts()?)?;
99 let set = LocalSet::new();99 let set = LocalSet::new();
100 for host in hosts.into_iter() {100 for host in hosts.into_iter() {
101 let config = config.clone();101 let config = config.clone();
102 let span = info_span!("deploy", host = field::display(&host.name));102 let span = info_span!("deploy", host = field::display(&host.name));
103 let hostname = host.name.clone();103 let hostname = host.name.clone();
104 let opts = opts.clone();104 let opts = opts.clone();
105 if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind").await? {105 if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind")? {
106 host.set_deploy_kind(deploy_kind);106 host.set_deploy_kind(deploy_kind);
107 };107 };
108 if let Some(destination) = opts.action_attr::<String>(&host, "dest").await? {108 if let Some(destination) = opts.action_attr::<String>(&host, "dest")? {
109 host.set_session_destination(destination);109 host.set_session_destination(destination);
110 };110 };
111 if let Some(legacy) = opts.action_attr::<bool>(&host, "legacy_ssh_store").await? {111 if let Some(legacy) = opts.action_attr::<bool>(&host, "legacy_ssh_store")? {
112 host.set_legacy_ssh_store(legacy);112 host.set_legacy_ssh_store(legacy);
113 };113 };
114114
153 self.action,153 self.action,
154 &host,154 &host,
155 remote_path,155 remote_path,
156 match opts.action_attr(&host, "specialisation").await {156 match opts.action_attr(&host, "specialisation") {
157 Ok(v) => v,157 Ok(v) => v,
158 _ => {158 _ => {
159 error!("unreachable? failed to get specialization");159 error!("unreachable? failed to get specialization");
modifiedcmds/fleet/src/cmds/info.rsdiffbeforeafterboth
35 let mut data = Vec::new();35 let mut data = Vec::new();
36 match self.cmd {36 match self.cmd {
37 InfoCmd::ListHosts { ref tagged } => {37 InfoCmd::ListHosts { ref tagged } => {
38 'host: for host in config.list_hosts().await? {38 'host: for host in config.list_hosts()? {
39 if !tagged.is_empty() {39 if !tagged.is_empty() {
40 let config = &config.config_field;40 let config = &config.config_field;
41 let host_name = &host.name;41 let host_name = &host.name;
59 "at leas one of --external or --internal must be set"59 "at leas one of --external or --internal must be set"
60 );60 );
61 let mut out = <BTreeSet<String>>::new();61 let mut out = <BTreeSet<String>>::new();
62 let host = config.system_config(&host).await?;62 let host = config.system_config(&host)?;
63 if external {63 if external {
64 let data: Vec<String> = nix_go_json!(host.network.externalIps);64 let data: Vec<String> = nix_go_json!(host.network.externalIps);
65 out.extend(data);65 out.extend(data);
modifiedcmds/fleet/src/cmds/rollback.rsdiffbeforeafterboth
7575
76impl RollbackSingle {76impl RollbackSingle {
77 pub(crate) async fn run(&self, config: &Config, _opts: &FleetOpts) -> Result<()> {77 pub(crate) async fn run(&self, config: &Config, _opts: &FleetOpts) -> Result<()> {
78 let host = config.host(&self.machine).await?;78 let host = config.host(&self.machine)?;
79 match &self.action {79 match &self.action {
80 RollbackAction::ListTargets => {80 RollbackAction::ListTargets => {
81 let generations = list_all_generations(&host, config).await;81 let generations = list_all_generations(&host, config).await;
modifiedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
1use std::{1use std::{
2 collections::{BTreeMap, BTreeSet, HashSet},2 collections::{BTreeSet, HashSet},
3 io::{self, Read, Write, stdin, stdout},3 io::{Read, Write, stdin, stdout},
4 path::PathBuf,4 path::PathBuf,
5};5};
66
7use anyhow::{Context, Result, anyhow, bail, ensure};7use anyhow::{Context, Result, bail, ensure};
8use chrono::{DateTime, Utc};
9use clap::Parser;8use clap::Parser;
10use fleet_base::{9use fleet_base::{host::Config, opts::FleetOpts};
11 fleetdata::{FleetSecretData, FleetSecretDistribution, FleetSecretPart, encrypt_secret_data},
12 host::Config,
13 opts::FleetOpts,
14 secret::{Expectations, RegenerationReason, secret_needs_regeneration},
15};
16use fleet_shared::SecretData;10use fleet_shared::SecretData;
17use nix_eval::{NixType, Value, nix_go, nix_go_json};
18use serde::Deserialize;
19use tabled::{Table, Tabled};11use tabled::Tabled;
20use tokio::{fs::read, task::spawn_blocking};12use tokio::fs::read;
21use tracing::{Instrument, error, info, info_span, warn};13use tracing::{info, info_span, warn};
2214
23#[derive(Parser)]15#[derive(Parser)]
24pub enum Secret {16pub enum Secret {
145}137}
146*/138*/
147139
148#[derive(Deserialize)]140/*
149#[serde(rename_all = "camelCase")]141async fn generate_pure(
150enum GeneratorKind {142 _config: &Config,
151 Impure,143 _display_name: &str,
152 Pure,144 _secret: Value,
153}145 _default_generator: Value,
154146 _expectations: &Expectations,
155async fn generate_pure(147) -> Result<FleetSecretData> {
156 _config: &Config,148 bail!("pure generators are broken for now")
157 _display_name: &str,149}
158 _secret: Value,150async fn generate_impure(
159 _default_generator: Value,151 config: &Config,
160 _expectations: &Expectations,152 _display_name: &str,
161) -> Result<FleetSecretData> {153 secret: Value,
162 bail!("pure generators are broken for now")154 default_generator: Value,
163}155 expectations: &Expectations,
164async fn generate_impure(156) -> Result<FleetSecretData> {
165 config: &Config,157 let generator = nix_go!(secret.generator);
166 _display_name: &str,158 let on: Option<String> = nix_go_json!(default_generator.impureOn);
167 secret: Value,159
168 default_generator: Value,160 let nixpkgs = &config.nixpkgs;
169 expectations: &Expectations,161
170) -> Result<FleetSecretData> {162 let host = if let Some(on) = &on {
171 let generator = nix_go!(secret.generator);163 config.host(on).await?
172 let on: Option<String> = nix_go_json!(default_generator.impureOn);164 } else {
173165 config.local_host()
174 let nixpkgs = &config.nixpkgs;166 };
175167 let on_pkgs = host.pkgs().await?;
176 let host = if let Some(on) = &on {168 let mk_secret_generators = nix_go!(on_pkgs.mkSecretGenerators);
177 config.host(on).await?169
178 } else {170 let mut recipients = Vec::new();
179 config.local_host()171 for owner in &expectations.owners {
180 };172 let key = config.key(owner).await?;
181 let on_pkgs = host.pkgs().await?;173 recipients.push(key);
182 let mk_secret_generators = nix_go!(on_pkgs.mkSecretGenerators);174 }
183175 let generators = nix_go!(mk_secret_generators(Obj { recipients }));
184 let mut recipients = Vec::new();176 let pkgs_and_generators = on_pkgs.attrs_update(generators)?;
185 for owner in &expectations.owners {177
186 let key = config.key(owner).await?;178 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));
187 recipients.push(key);179
188 }180 let generator = nix_go!(call_package(generator)(Obj {}));
189 let generators = nix_go!(mk_secret_generators(Obj { recipients }));181
190 let pkgs_and_generators = on_pkgs.attrs_update(generators)?;182 let generator = spawn_blocking(move || generator.build("out"))
191183 .await
192 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));184 .expect("nix build shouldn't fail")?;
193185 let generator = host.remote_derivation(&generator).await?;
194 let generator = nix_go!(call_package(generator)(Obj {}));186
195187 let out_parent = host.mktemp_dir().await?;
196 let generator = spawn_blocking(move || generator.build("out"))188 let out = format!("{out_parent}/out");
197 .await189
198 .expect("nix build shouldn't fail")?;190 let mut r#gen = host.cmd(generator).await?;
199 let generator = host.remote_derivation(&generator).await?;191 r#gen.env("out", &out);
200192 if on.is_none() {
201 let out_parent = host.mktemp_dir().await?;193 // This path is local, thus we can feed `OsString` directly to env var... But I don't think that's necessary to handle.
202 let out = format!("{out_parent}/out");194 let project_path: String = config
203195 .directory
204 let mut r#gen = host.cmd(generator).await?;196 .clone()
205 r#gen.env("out", &out);197 .into_os_string()
206 if on.is_none() {198 .into_string()
207 // This path is local, thus we can feed `OsString` directly to env var... But I don't think that's necessary to handle.199 .map_err(|s| anyhow!("fleet project path is not utf-8: {s:?}"))?;
208 let project_path: String = config200 r#gen.env("FLEET_PROJECT", project_path);
209 .directory201 }
210 .clone()202 r#gen.run().await.context("impure generator")?;
211 .into_os_string()203
212 .into_string()204 {
213 .map_err(|s| anyhow!("fleet project path is not utf-8: {s:?}"))?;205 let marker = host.read_file_text(format!("{out}/marker")).await?;
214 r#gen.env("FLEET_PROJECT", project_path);206 ensure!(marker == "SUCCESS", "generation not succeeded");
215 }207 }
216 r#gen.run().await.context("impure generator")?;208
217209 let mut parts = BTreeMap::new();
218 {210 for part in host.read_dir(&out).await? {
219 let marker = host.read_file_text(format!("{out}/marker")).await?;211 if part == "created_at" || part == "expires_at" || part == "marker" {
220 ensure!(marker == "SUCCESS", "generation not succeeded");212 continue;
221 }213 }
222214 let contents: SecretData = host
223 let mut parts = BTreeMap::new();215 .read_file_text(format!("{out}/{part}"))
224 for part in host.read_dir(&out).await? {216 .await?
225 if part == "created_at" || part == "expires_at" || part == "marker" {217 .parse()
226 continue;218 .map_err(|e| anyhow!("failed to decode secret {out:?} part {part:?}: {e}"))?;
227 }219 parts.insert(part.to_owned(), FleetSecretPart { raw: contents });
228 let contents: SecretData = host220 }
229 .read_file_text(format!("{out}/{part}"))221
230 .await?222 let created_at = host.read_file_value(format!("{out}/created_at")).await?;
231 .parse()223 let expires_at = host.read_file_value(format!("{out}/expires_at")).await.ok();
232 .map_err(|e| anyhow!("failed to decode secret {out:?} part {part:?}: {e}"))?;224
233 parts.insert(part.to_owned(), FleetSecretPart { raw: contents });225 let new_data = FleetSecretData {
234 }226 created_at,
235227 expires_at,
236 let created_at = host.read_file_value(format!("{out}/created_at")).await?;228 parts,
237 let expires_at = host.read_file_value(format!("{out}/expires_at")).await.ok();229 generation_data: expectations.generation_data.clone(),
238230 };
239 let new_data = FleetSecretData {231
240 created_at,232 if let Some(reason) = secret_needs_regeneration(&new_data, &expectations.owners, expectations) {
241 expires_at,233 bail!("newly generated secret needs to be regenerated: {reason}")
242 parts,234 }
243 generation_data: expectations.generation_data.clone(),235
244 };236 Ok(new_data)
245237}
246 if let Some(reason) = secret_needs_regeneration(&new_data, &expectations.owners, expectations) {238
247 bail!("newly generated secret needs to be regenerated: {reason}")239async fn generate(
248 }240 config: &Config,
249241 display_name: &str,
250 Ok(new_data)242 secret: Value,
251}243 expectations: &Expectations,
252244) -> Result<FleetSecretData> {
253async fn generate(245 let generator = nix_go!(secret.generator);
254 config: &Config,246 // Can't properly check on nix module system level
255 display_name: &str,247 {
256 secret: Value,248 let gen_ty = generator.type_of();
257 expectations: &Expectations,249 if matches!(gen_ty, NixType::Null) {
258) -> Result<FleetSecretData> {250 bail!("secret has no generator defined, can't automatically generate it.");
259 let generator = nix_go!(secret.generator);251 }
260 // Can't properly check on nix module system level252 if matches!(gen_ty, NixType::Attrs) {
261 {253 if !generator.has_field("__functor")? {
262 let gen_ty = generator.type_of();254 bail!("generator should be functor, got {gen_ty:?}");
263 if matches!(gen_ty, NixType::Null) {255 }
264 bail!("secret has no generator defined, can't automatically generate it.");256 } else if matches!(gen_ty, NixType::Function) {
265 }257 bail!("generator should be functor, got {gen_ty:?}");
266 if matches!(gen_ty, NixType::Attrs) {258 }
267 if !generator.has_field("__functor")? {259 }
268 bail!("generator should be functor, got {gen_ty:?}");260 let nixpkgs = &config.nixpkgs;
269 }261 let default_pkgs = &config.default_pkgs;
270 } else if matches!(gen_ty, NixType::Function) {262 let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);
271 bail!("generator should be functor, got {gen_ty:?}");263 // Generators provide additional information in passthru, to access
272 }264 // passthru we should call generator, but information about where this generator is supposed to build
273 }265 // is located in passthru... Thus evaluating generator on host.
274 let nixpkgs = &config.nixpkgs;266 //
275 let default_pkgs = &config.default_pkgs;267 // Maybe it is also possible to do some magic with __functor?
276 let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);268 //
277 // Generators provide additional information in passthru, to access269 // I don't want to make modules always responsible for additional secret data anyway,
278 // passthru we should call generator, but information about where this generator is supposed to build270 // so it should be in derivation, and not in the secret data itself.
279 // is located in passthru... Thus evaluating generator on host.271 let generators = nix_go!(default_mk_secret_generators(Obj {
280 //272 recipients: <Vec<String>>::new(),
281 // Maybe it is also possible to do some magic with __functor?273 }));
282 //274 let pkgs_and_generators = default_pkgs.clone().attrs_update(generators)?;
283 // I don't want to make modules always responsible for additional secret data anyway,275
284 // so it should be in derivation, and not in the secret data itself.276 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));
285 let generators = nix_go!(default_mk_secret_generators(Obj {277 let default_generator = nix_go!(call_package(generator)(Obj {}));
286 recipients: <Vec<String>>::new(),278
287 }));279 let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);
288 let pkgs_and_generators = default_pkgs.clone().attrs_update(generators)?;280
289281 match kind {
290 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));282 GeneratorKind::Impure => {
291 let default_generator = nix_go!(call_package(generator)(Obj {}));283 generate_impure(
292284 config,
293 let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);285 display_name,
294286 secret,
295 match kind {287 default_generator,
296 GeneratorKind::Impure => {288 expectations,
297 generate_impure(289 )
298 config,290 .await
299 display_name,291 }
300 secret,292 GeneratorKind::Pure => {
301 default_generator,293 generate_pure(
302 expectations,294 config,
303 )295 display_name,
304 .await296 secret,
305 }297 default_generator,
306 GeneratorKind::Pure => {298 expectations,
307 generate_pure(299 )
308 config,300 .await
309 display_name,301 }
310 secret,302 }
311 default_generator,303}
312 expectations,304*/
313 )
314 .await
315 }
316 }
317}
318/*305/*
319async fn generate_shared(306async fn generate_shared(
320 config: &Config,307 config: &Config,
421 todo!("part of fleet-pusher")408 todo!("part of fleet-pusher")
422 }409 }
423 Secret::ForceKeys => {410 Secret::ForceKeys => {
424 for host in config.list_hosts().await? {411 for host in config.list_hosts()? {
425 if opts.should_skip(&host).await? {412 if opts.should_skip(&host)? {
426 continue;413 continue;
427 }414 }
428 config.key(&host.name).await?;415 config.key(&host.name).await?;
467 let Some(identity_holder) = identity_holder else {454 let Some(identity_holder) = identity_holder else {
468 bail!("no available holder found");455 bail!("no available holder found");
469 };456 };
470 let host = config.host(identity_holder).await?;457 let host = config.host(identity_holder)?;
471 host.decrypt(part.raw.clone()).await?458 host.decrypt(part.raw.clone()).await?
472 } else {459 } else {
473 part.raw.data.clone()460 part.raw.data.clone()
619 }606 }
620 Secret::List {} => {607 Secret::List {} => {
621 let _span = info_span!("loading secrets").entered();608 let _span = info_span!("loading secrets").entered();
622 let configured = config.list_configured_shared().await?;609 let configured = config.list_configured_shared()?;
623 #[derive(Tabled)]610 #[derive(Tabled)]
624 struct SecretDisplay {611 struct SecretDisplay {
625 #[tabled(rename = "Name")]612 #[tabled(rename = "Name")]
662 .host_secret(&machine, &name)649 .host_secret(&machine, &name)
663 .context("secret not found")?;650 .context("secret not found")?;
664 if let Some(data) = secret.secret.parts.get(&part) {651 if let Some(data) = secret.secret.parts.get(&part) {
665 let host = config.host(&machine).await?;652 let host = config.host(&machine)?;
666 let secret = host.decrypt(data.raw.clone()).await?;653 let secret = host.decrypt(data.raw.clone()).await?;
667 String::from_utf8(secret).context("secret is not utf8")?654 String::from_utf8(secret).context("secret is not utf8")?
668 } else if add {655 } else if add {
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
222 nix_args,220 nix_args,
223 matches!(opts.command, Opts::Deploy(_) | Opts::BuildSystems(_)),221 matches!(opts.command, Opts::Deploy(_) | Opts::BuildSystems(_)),
224 )222 )?;
225 .await?;
226223
227 match run_command(&config, opts.fleet_opts, opts.command).await {224 match run_command(&config, opts.fleet_opts, opts.command).await {
228 Ok(()) => {225 Ok(()) => {
modifiedcrates/fleet-base/src/fleetdata.rsdiffbeforeafterboth
422 }422 }
423}423}
424
425#[derive(Debug)]
426pub struct Expectations {
427 pub owners: BTreeSet<String>,
428 pub generation_data: serde_json::Value,
429 pub parts: BTreeMap<String, GeneratorPart>,
430}
431#[derive(Deserialize, Debug, Clone)]
432pub struct GeneratorPart {
433 pub encrypted: bool,
434}
424435
modifiedcrates/fleet-base/src/host.rsdiffbeforeafterboth
472 }472 }
473}473}
474
475struct HostSecretDefinition(Value);
476
474impl ConfigHost {477impl ConfigHost {
475 // TOCTOU is possible here in case if config is changed, but this case is not handled anywhere anyway,478 // TOCTOU is possible here in case if config is changed, but this case is not handled anywhere anyway,
476 // assuming getting tags always returns the same value.479 // assuming getting tags always returns the same value.
477 pub async fn tags(&self) -> Result<Vec<String>> {480 pub fn tags(&self) -> Result<Vec<String>> {
478 if let Some(v) = self.groups.get() {481 if let Some(v) = self.groups.get() {
479 return Ok(v.clone());482 return Ok(v.clone());
480 }483 }
487490
488 Ok(tags)491 Ok(tags)
489 }492 }
490 pub async fn nixos_config(&self) -> Result<Value> {493 pub fn nixos_config(&self) -> Result<Value> {
491 if let Some(v) = self.nixos_config.get() {494 if let Some(v) = self.nixos_config.get() {
492 return Ok(v.clone());495 return Ok(v.clone());
493 }496 }
494 let Some(host_config) = &self.host_config else {497 let Some(host_config) = &self.host_config else {
495 bail!("local host has no nixos_config");498 bail!("local host has no nixos_config");
496 };499 };
497 let nixos_config = nix_go!(host_config.nixos.config);500 let nixos_config = nix_go!(host_config.nixos.config);
498 assert_warn("nixos config evaluation", &nixos_config).await?;501 assert_warn("nixos config evaluation", &nixos_config)?;
499502
500 let _ = self.nixos_config.set(nixos_config.clone());503 let _ = self.nixos_config.set(nixos_config.clone());
501504
522 }525 }
523526
524 /// Packages for this host, resolved with nixpkgs overlays527 /// Packages for this host, resolved with nixpkgs overlays
525 pub async fn pkgs(&self) -> Result<Value> {528 pub fn pkgs(&self) -> Result<Value> {
526 if let Some(value) = &self.pkgs_override {529 if let Some(value) = &self.pkgs_override {
527 return Ok(value.clone());530 return Ok(value.clone());
528 }531 }
534 }537 }
535}538}
539
540pub struct SharedSecretDefinition(Value);
541impl SharedSecretDefinition {
542 pub fn expected_owners(&self) -> Result<BTreeSet<String>> {
543 let secret = &self.0;
544 Ok(nix_go_json!(secret.expectedOwners))
545 }
546 pub fn generator(&self) -> Result<Value> {
547 let secret = &self.0;
548 Ok(nix_go!(secret.generator))
549 }
550}
536551
537impl Config {552impl Config {
538 pub async fn tagged_hostnames(&self, tag: &str) -> Result<Vec<String>> {553 pub fn tagged_hostnames(&self, tag: &str) -> Result<Vec<String>> {
539 let config = &self.config_field;554 let config = &self.config_field;
540 let tagged: Vec<String> = nix_go_json!(config.taggedWith[{ tag }]);555 let tagged: Vec<String> = nix_go_json!(config.taggedWith[{ tag }]);
541 Ok(tagged)556 Ok(tagged)
542 }557 }
543 pub async fn expand_owner_set(&self, owners: Vec<String>) -> Result<BTreeSet<String>> {558 pub fn expand_owner_set(&self, owners: Vec<String>) -> Result<BTreeSet<String>> {
544 let mut out = BTreeSet::new();559 let mut out = BTreeSet::new();
545 for owner in owners {560 for owner in owners {
546 if let Some(tag) = owner.strip_prefix('@') {561 if let Some(tag) = owner.strip_prefix('@') {
547 let hosts = self.tagged_hostnames(tag).await?;562 let hosts = self.tagged_hostnames(tag)?;
548 out.extend(hosts);563 out.extend(hosts);
549 } else {564 } else {
550 out.insert(owner);565 out.insert(owner);
574 }589 }
575 }590 }
576591
577 pub async fn host(&self, name: &str) -> Result<ConfigHost> {592 pub fn host(&self, name: &str) -> Result<ConfigHost> {
578 let config = &self.config_field;593 let config = &self.config_field;
579 let host_config = nix_go!(config.hosts[{ name }]);594 let host_config = nix_go!(config.hosts[{ name }]);
580595
595 legacy_ssh_store: OnceCell::new(),610 legacy_ssh_store: OnceCell::new(),
596 })611 })
597 }612 }
598 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {613 pub fn list_hosts(&self) -> Result<Vec<ConfigHost>> {
599 let config = &self.config_field;614 let config = &self.config_field;
600 let names = nix_go!(config.hosts).list_fields()?;615 let names = nix_go!(config.hosts).list_fields()?;
601 let mut out = vec![];616 let mut out = vec![];
602 for name in names {617 for name in names {
603 out.push(self.host(&name).await?);618 out.push(self.host(&name)?);
604 }619 }
605 Ok(out)620 Ok(out)
606 }621 }
607 // TODO: Replace usages with .host().nixos_config622 // TODO: Replace usages with .host().nixos_config
608 pub async fn system_config(&self, host: &str) -> Result<Value> {623 pub fn system_config(&self, host: &str) -> Result<Value> {
609 let fleet_field = &self.config_field;624 let fleet_field = &self.config_field;
610 Ok(nix_go!(fleet_field.hosts[{ host }].nixos.config))625 Ok(nix_go!(fleet_field.hosts[{ host }].nixos.config))
611 }626 }
612627
613 /// Shared secrets configured in fleet.nix or in flake628 /// Shared secrets configured in fleet.nix or in flake
614 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {629 pub fn list_configured_shared(&self) -> Result<Vec<String>> {
615 let config_field = &self.config_field;630 let config_field = &self.config_field;
616 nix_go!(config_field.sharedSecrets).list_fields()631 nix_go!(config_field.sharedSecrets).list_fields()
617 }632 }
659 data.secrets.get(secret).cloned()674 data.secrets.get(secret).cloned()
660 }675 }
676
677 pub fn secret_definition(&self, secret: &str) -> Result<Option<SharedSecretDefinition>> {
678 let config = &self.config_field;
679 let shared_secrets = nix_go!(config.secrets);
680 if !shared_secrets.has_field(secret)? {
681 return Ok(None);
682 }
683 Ok(Some(SharedSecretDefinition(nix_go!(
684 shared_secrets[secret]
685 ))))
686 }
661687
662 // TODO: Should this be something modifiable from other processes?688 // TODO: Should this be something modifiable from other processes?
663 // E.g terraform provider might want to update FleetData (e.g secrets),689 // E.g terraform provider might want to update FleetData (e.g secrets),
modifiedcrates/fleet-base/src/keys.rsdiffbeforeafterboth
12 pub fn cached_key(&self, host: &str) -> Option<String> {12 pub fn cached_key(&self, host: &str) -> Option<String> {
13 let data = self.data();13 let data = self.data();
14 let key = data.hosts.get(host).map(|h| &h.encryption_key);14 let key = data.hosts.get(host).map(|h| &h.encryption_key);
15 if let Some(key) = key {15 if let Some(key) = key
16 if key.is_empty() {16 && key.is_empty()
17 {
17 return None;18 return None;
18 }19 }
19 }
20 key.cloned()20 key.cloned()
21 }21 }
22 pub fn update_key(&self, host: &str, key: String) {22 pub fn update_key(&self, host: &str, key: String) {
30 Ok(key)30 Ok(key)
31 } else {31 } else {
32 warn!("Loading key for {}", host);32 warn!("Loading key for {}", host);
33 let host = self.host(host).await?;33 let host = self.host(host)?;
34 let mut cmd = host.cmd("cat").await?;34 let mut cmd = host.cmd("cat").await?;
35 cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");35 cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");
36 let key = cmd.run_string().await?;36 let key = cmd.run_string().await?;
47 }47 }
4848
49 pub async fn recipients(&self, hosts: Vec<String>) -> Result<Vec<Box<dyn Recipient>>> {49 pub async fn recipients(&self, hosts: Vec<String>) -> Result<Vec<Box<dyn Recipient>>> {
50 let hosts = self.expand_owner_set(hosts).await?;50 let hosts = self.expand_owner_set(hosts)?;
51 futures::stream::iter(hosts.iter())51 futures::stream::iter(hosts.iter())
52 .then(|m| self.recipient(m.as_ref()))52 .then(|m| self.recipient(m.as_ref()))
53 .try_collect::<Vec<_>>()53 .try_collect::<Vec<_>>()
59 let mut out = Vec::new();59 let mut out = Vec::new();
60 let host_names = self60 let host_names = self.list_hosts()?.into_iter().map(|h| h.name).collect_vec();
61 .list_hosts()
62 .await?
63 .into_iter()
64 .map(|h| h.name)
65 .collect_vec();
modifiedcrates/fleet-base/src/opts.rsdiffbeforeafterboth
104}104}
105105
106impl FleetOpts {106impl FleetOpts {
107 pub async fn filter_skipped(107 pub fn filter_skipped(
108 &self,108 &self,
109 hosts: impl IntoIterator<Item = ConfigHost>,109 hosts: impl IntoIterator<Item = ConfigHost>,
110 ) -> Result<Vec<ConfigHost>> {110 ) -> Result<Vec<ConfigHost>> {
111 let mut out = Vec::new();111 let mut out = Vec::new();
112 for host in hosts {112 for host in hosts {
113 if self.should_skip(&host).await? {113 if self.should_skip(&host)? {
114 continue;114 continue;
115 }115 }
116 out.push(host);116 out.push(host);
117 }117 }
118 Ok(out)118 Ok(out)
119 }119 }
120 pub async fn should_skip(&self, host: &ConfigHost) -> Result<bool> {120 pub fn should_skip(&self, host: &ConfigHost) -> Result<bool> {
121 if self.skip.iter().any(|h| h as &str == host.name) {121 if self.skip.iter().any(|h| h as &str == host.name) {
122 return Ok(true);122 return Ok(true);
123 }123 }
137 }137 }
138 }138 }
139 if have_group_matches {139 if have_group_matches {
140 let host_tags = host.tags().await?;140 let host_tags = host.tags()?;
141 for item in self.only.iter() {141 for item in self.only.iter() {
142 match item {142 match item {
143 HostItem::Tag { name, .. } if host_tags.contains(name) => {143 HostItem::Tag { name, .. } if host_tags.contains(name) => {
149 }149 }
150 Ok(true)150 Ok(true)
151 }151 }
152 pub async fn action_attr<T: FromStr>(&self, host: &ConfigHost, attr: &str) -> Result<Option<T>>152 pub fn action_attr<T: FromStr>(&self, host: &ConfigHost, attr: &str) -> Result<Option<T>>
153 where153 where
154 T::Err: Sync,154 T::Err: Sync,
155 anyhow::Error: From<T::Err>,155 anyhow::Error: From<T::Err>,
156 {156 {
157 let str = self.action_attr_str(host, attr).await?;157 let str = self.action_attr_str(host, attr)?;
158 Ok(str.map(|v| T::from_str(&v)).transpose()?)158 Ok(str.map(|v| T::from_str(&v)).transpose()?)
159 }159 }
160 pub async fn action_attr_str(&self, host: &ConfigHost, attr: &str) -> Result<Option<String>> {160 pub fn action_attr_str(&self, host: &ConfigHost, attr: &str) -> Result<Option<String>> {
161 if self.only.is_empty() {161 if self.only.is_empty() {
162 return Ok(None);162 return Ok(None);
163 }163 }
176 }176 }
177 }177 }
178 if have_group_matches {178 if have_group_matches {
179 let host_tags = host.tags().await?;179 let host_tags = host.tags()?;
180 for item in self.only.iter() {180 for item in self.only.iter() {
181 match item {181 match item {
182 HostItem::Tag { name, attrs }182 HostItem::Tag { name, attrs }
195 }195 }
196196
197 // TODO: Config should be detached from opts.197 // TODO: Config should be detached from opts.
198 pub async fn build(&self, nix_args: Vec<OsString>, assert: bool) -> Result<Config> {198 pub fn build(&self, nix_args: Vec<OsString>, assert: bool) -> Result<Config> {
199 let cwd = current_dir()?;199 let cwd = current_dir()?;
200 let mut directory = cwd.clone();200 let mut directory = cwd.clone();
201 let mut fleet_data_path = directory.join("fleet.nix");201 let mut fleet_data_path = directory.join("fleet.nix");
248248
249 if assert {249 if assert {
250 assert_warn("fleet config evaluation", &config_field)250 assert_warn("fleet config evaluation", &config_field)
251 .await
252 .context("failed to verify assertions")?;251 .context("failed to verify assertions")?;
253 }252 }
254253
modifiedcrates/fleet-base/src/primops.rsdiffbeforeafterboth
1use std::cell::OnceCell;
2use std::collections::{BTreeMap, HashMap};1use std::collections::{BTreeMap, BTreeSet, HashMap};
3use std::sync::{Arc, Mutex, OnceLock};2use std::sync::OnceLock;
43
5use anyhow::{Context, bail};4use anyhow::{Context, bail, ensure};
5use fleet_shared::SecretData;
6use itertools::Itertools;6use itertools::Itertools;
7use nix_eval::{NativeFn, Value, nix_go, nix_go_json};7use nix_eval::{NativeFn, Value, nix_go, nix_go_json};
8use serde::Deserialize;8use serde::Deserialize;
9use tracing::{info, warn};9use tracing::{info, warn};
1010
11use crate::fleetdata::{FleetData, FleetSecrets};11use crate::fleetdata::{
12 Expectations, FleetSecretData, FleetSecretDistribution, FleetSecretPart, GeneratorPart,
13};
12use crate::host::Config;14use crate::host::{Config, ConfigHost};
15use crate::secret::{RegenerationReason, secret_needs_regeneration};
16use anyhow::{Result, anyhow};
1317
14#[derive(thiserror::Error, Debug)]18#[derive(thiserror::Error, Debug)]
15enum Error {}19enum Error {}
16
17struct Parts {
18 encrypted: Vec<String>,
19 public: Vec<String>,
20}
21
22trait SecretsBackend {
23 fn has_shared(&self, name: &str);
24 fn has_host(&self, host: &str, name: &str);
25 fn shared_parts(&self, name: &str) -> Parts;
26 fn host_parts(&self, host: &str, name: &str) -> Parts;
27}
28
29struct FsSecretsBackend {}
3020
31pub static PRIMOPS_DATA: OnceLock<Config> = OnceLock::new();21pub static PRIMOPS_DATA: OnceLock<Config> = OnceLock::new();
3222
33#[derive(Deserialize, Debug)]23#[derive(Deserialize)]
24#[serde(rename_all = "camelCase")]
34struct GeneratorPart {25enum GeneratorKind {
35 encrypted: bool,26 Impure,
27 Pure,
36}28}
29
30pub fn get_pkgs_and_generators(host_on: &ConfigHost, recipients: Vec<String>) -> Result<Value> {
31 info!("get pkgs");
32 let pkgs = host_on.pkgs()?;
33 let default_mk_secret_generators = nix_go!(pkgs.mkSecretGenerators);
34 let generators = nix_go!(default_mk_secret_generators(Obj { recipients }));
35 Ok(pkgs.clone().attrs_update(generators)?)
36}
37pub fn get_default_pkgs_and_generators(config: &Config) -> Result<Value> {
38 let host_on = config.local_host();
39 get_pkgs_and_generators(&host_on, vec![])
40}
41pub fn call_package(config: &Config, pkgs: &Value, package: &Value) -> Result<Value> {
42 ensure!(
43 package.is_function(),
44 "package should be a function to be called with callPackage"
45 );
46 // No need to use nixpkgs.buildUsing, as only nixpkgs-lib is used.
47 let nixpkgs = &config.nixpkgs;
48 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs));
49 Ok(nix_go!(call_package(package)(Obj {})))
50}
51
52pub fn get_default_generator_drv(config: &Config, generator: &Value) -> Result<Value> {
53 let default_pkgs_and_generators = get_default_pkgs_and_generators(config)?;
54 let default_generator_drv = call_package(config, &default_pkgs_and_generators, generator)
55 .context("failed to initialize generator to get metadata")?;
56
57 Ok(default_generator_drv)
58}
59
60pub async fn generate(
61 config: &Config,
62 expectations: Expectations,
63 generator: &Value,
64 default_generator_drv: &Value,
65) -> Result<FleetSecretDistribution> {
66 let kind: GeneratorKind = nix_go_json!(default_generator_drv.generatorKind);
67
68 match kind {
69 GeneratorKind::Impure => {
70 let impure_on: Option<String> = nix_go_json!(default_generator_drv.impureOn);
71
72 let host_on = if let Some(on) = &impure_on {
73 config
74 .host(on)
75 .context("failed to get secret generation target host")?
76 } else {
77 config.local_host()
78 };
79 let pkgs_and_generators =
80 get_pkgs_and_generators(&host_on, expectations.owners.iter().cloned().collect())
81 .context("failed to get pkgs for target host")?;
82 let generator = call_package(config, &pkgs_and_generators, generator)
83 .context("failed to evaluate generator for target host")?;
84
85 let generator = generator
86 .build("out")
87 .context("failed to build generator for target host")?;
88
89 let generator = host_on
90 .remote_derivation(&generator)
91 .await
92 .context("failed to copy generator to target host")?;
93
94 // TODO: Remove destdir after everything is done
95 let out_parent = host_on
96 .mktemp_dir()
97 .await
98 .context("failed to prepare generator output dir on target host")?;
99 let out = format!("{out_parent}/out");
100 let mut generator_cmd = host_on.cmd(generator).await?;
101 generator_cmd.env("out", &out);
102 if impure_on.is_none() {
103 let project_path: String = config
104 .directory
105 .clone()
106 .into_os_string()
107 .into_string()
108 .map_err(|e| anyhow!("fleet project path is not utf-8: {e:?}"))?;
109 generator_cmd.env("FLEET_PROJECT", project_path);
110 };
111 generator_cmd
112 .run()
113 .await
114 .context("failed to run impure generator")?;
115
116 {
117 let marker = host_on.read_file_text(format!("{out}/marker")).await?;
118 ensure!(
119 marker == "SUCCESS",
120 "impure generator ended prematurely, secret generation failed"
121 );
122 }
123
124 let mut parts = BTreeMap::new();
125 for part in host_on.read_dir(&out).await? {
126 if part == "created_at" || part == "expires_at" || part == "marker" {
127 continue;
128 }
129 let contents: SecretData = host_on
130 .read_file_text(format!("{out}/{part}"))
131 .await?
132 .parse()
133 .map_err(|e| anyhow!("failed to decode secret {out:?} part {part:?}: {e}"))?;
134 parts.insert(part.to_owned(), FleetSecretPart { raw: contents });
135 }
136
137 let created_at = host_on.read_file_value(format!("{out}/created_at")).await?;
138 let expires_at = host_on
139 .read_file_value(format!("{out}/expires_at"))
140 .await
141 .ok();
142
143 let new_data = FleetSecretData {
144 created_at,
145 expires_at,
146 parts,
147 generation_data: expectations.generation_data.clone(),
148 };
149
150 let new_data = FleetSecretDistribution {
151 secret: new_data,
152 owners: expectations.owners.clone(),
153 _deprecated_managed: true,
154 };
155
156 if let Some(reason) = secret_needs_regeneration(&new_data, &expectations) {
157 bail!("newly generated secret needs to be regenerated: {reason}")
158 }
159
160 Ok(new_data)
161 }
162 GeneratorKind::Pure => {
163 bail!("pure generators are disabled for now")
164 }
165 }
166}
37167
38pub fn init_primops() {168pub fn init_primops() {
39 info!("initializing primops");169 info!("initializing primops");
52 .get()182 .get()
53 .expect("primops data should be set on init");183 .expect("primops data should be set on init");
54184
55 info!("get pkgs");
56 let nixpkgs = &config.nixpkgs;185 let shared_def = config.secret_definition(&secret).context("failed to get shared secret definition")?;
186
57 let default_pkgs = &config.default_pkgs;187 let (shared, generator, expected_owners) = if generator.is_string() {
58 let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);188 assert_eq!(generator.to_string()?, "shared", "asserted by nixos type system");
59 let generators = nix_go!(default_mk_secret_generators(Obj {189 let Some(shared_def) = shared_def else {
60 recipients: <Vec<String>>::new(),
61 }));190 bail!("secret {secret} is defined on host {host} as shared, but there is no shared secret with same name defined at fleetConfiguration.secrets.{secret}.generator")
191 };
62 let pkgs_and_generators = default_pkgs.clone().attrs_update(generators)?;192 let expected_owners = shared_def.expected_owners()?;
63193
64 info!("call package");194 ensure!(expected_owners.contains(&host), "secret {secret} does not define {host} as expected owner");
195
65 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));196 (true, shared_def.generator()?, expected_owners)
66 let default_generator = call_package197 } else {
198 if shared_def.is_some() {
199 bail!("hosts can only have their own generators for non-shared secrets, either set host secret generator to \"shared\", or remove shared secret generator at fleetConfiguration.secrets.{secret}.generator")
200 }
201
67 .call(generator.clone())202 (false, generator.clone(), BTreeSet::from_iter([host.clone()]))
68 .context("calling callPackage with generator")?203 };
69 .call(Value::new_attrs(HashMap::new()))204
70 .context("providing extra callPackage args")?;205 let default_generator_drv = get_default_generator_drv(config, &generator).context("failed to evaluate default generator")?;
71
72 info!("get parts");
73 let mut parts: BTreeMap<String, GeneratorPart> = nix_go_json!(default_generator.parts);206 let expectations = Expectations {
74 info!("got parts: {parts:?}");207 parts: nix_go_json!(default_generator_drv.parts),
208 generation_data: nix_go_json!(default_generator_drv.generationData),
209 owners: expected_owners,
210 };
75211
76 let Some(existing) = config212 let reason: RegenerationReason = 'regenerate: {
77 .host_secret(&host, &secret) else {
78 bail!("missing secret {secret} for host {host}; secret needs regeneration")
79 };
80
81 info!("got existing: {existing:?}");213 let Some(existing) = config
214 .host_secret(&host, &secret) else {
215 break 'regenerate RegenerationReason::Missing;
216 };
217 if let Some(reason) = secret_needs_regeneration(&existing, &expectations) {
218 break 'regenerate reason;
219 }
220
221 let mut parts = expectations.parts.clone();
82222
83 let mut out = HashMap::new();223 let mut out = HashMap::new();
84
85 for (part_name, part) in &existing.secret.parts {224 for (part_name, part) in &existing.secret.parts {
86 let Some(definition) = parts.remove(part_name) else {225 let Some(definition) = parts.remove(part_name) else {
87 warn!("secret {secret} part {part_name} is stored, but not defined in nixos config, it will not be passed to nix");226 warn!("secret {secret} part {part_name} is stored, but not defined in nixos config, it will not be passed to nix");
88 continue;227 continue;
89 };228 };
90 if definition.encrypted != part.raw.encrypted {
91 bail!("secret {secret} part {part_name} is supposed to be {}, but it is {}; secret needs regeneration", if definition.encrypted {"encrypted"} else {"unencrypted"}, if part.raw.encrypted {"encrypted"} else {"unencrypted"});229 assert!(definition.encrypted != part.raw.encrypted, "encryption status is checked by secret_needs_regeneration");
92 }
93 out.insert(part_name.as_str(), Value::new_attrs(HashMap::from_iter([("raw", Value::new_str(&part.raw.to_string()))])));230 out.insert(part_name.as_str(), Value::new_attrs(HashMap::from_iter([("raw", Value::new_str(&part.raw.to_string()))])));
94 }231 }
95 if !parts.is_empty(){232 assert!(parts.is_empty(), "secret part is missing, secret_needs_regeneration should check that");
96 let defs = parts.keys().collect_vec();
97 bail!("secret parts are defined, but not stored: {defs:?}, secret needs regeneration")
98 }
99233
100 Ok(Value::new_attrs(out))234 return Ok(Value::new_attrs(out))
235 };
236
237 todo!()
238
239
101 },240 },
modifiedcrates/fleet-base/src/secret.rsdiffbeforeafterboth
1use std::collections::BTreeSet;1use std::collections::{BTreeMap, BTreeSet};
22
3use chrono::{DateTime, Utc};3use chrono::{DateTime, Utc};
44
5use crate::fleetdata::FleetSecretData;5use crate::fleetdata::{Expectations, FleetSecretData, FleetSecretDistribution, GeneratorPart};
6
7#[derive(Debug)]
8pub struct Expectations {
9 pub owners: BTreeSet<String>,
10 pub generation_data: serde_json::Value,
11 pub public_parts: BTreeSet<String>,
12 pub private_parts: BTreeSet<String>,
13}
146
15#[derive(thiserror::Error, Debug)]7#[derive(thiserror::Error, Debug)]
16pub enum RegenerationReason {8pub enum RegenerationReason {
35 #[error("secret is expired at {0}")]27 #[error("secret is expired at {0}")]
36 Expired(DateTime<Utc>),28 Expired(DateTime<Utc>),
29
30 #[error("secret is not generated for this host")]
31 Missing,
37}32}
3833
39pub fn secret_needs_regeneration(34pub fn secret_needs_regeneration(
40 secret: &FleetSecretData,35 secret: &FleetSecretDistribution,
41 owners: &BTreeSet<String>,
42 expectations: &Expectations,36 expectations: &Expectations,
43) -> Option<RegenerationReason> {37) -> Option<RegenerationReason> {
44 if !owners.is_empty() {
45 let added: BTreeSet<String> = expectations.owners.difference(owners).cloned().collect();38 let added: BTreeSet<String> = expectations
39 .owners
40 .difference(&secret.owners)
41 .cloned()
42 .collect();
46 if !added.is_empty() {43 if !added.is_empty() {
47 return Some(RegenerationReason::OwnersAdded(added));44 return Some(RegenerationReason::OwnersAdded(added));
48 }45 }
4946
50 let removed: BTreeSet<String> = owners.difference(&expectations.owners).cloned().collect();47 let removed: BTreeSet<String> = secret
48 .owners
49 .difference(&expectations.owners)
50 .cloned()
51 .collect();
51 if !removed.is_empty() {52 if !removed.is_empty() {
52 return Some(RegenerationReason::OwnersRemoved(removed));53 return Some(RegenerationReason::OwnersRemoved(removed));
53 }54 }
54 }
5555
56 if secret.generation_data != expectations.generation_data {56 if secret.secret.generation_data != expectations.generation_data {
57 return Some(RegenerationReason::GenerationData {57 return Some(RegenerationReason::GenerationData {
58 expected: expectations.generation_data.clone(),58 expected: expectations.generation_data.clone(),
59 found: secret.generation_data.clone(),59 found: secret.secret.generation_data.clone(),
60 });60 });
61 }61 }
6262
63 if !expectations.public_parts.is_empty() || !expectations.private_parts.is_empty() {
64 let expected: BTreeSet<String> = expectations63 let expected: BTreeSet<String> = expectations.parts.keys().cloned().collect();
65 .public_parts
66 .union(&expectations.private_parts)
67 .cloned()
68 .collect();
69 let found: BTreeSet<String> = secret.parts.keys().cloned().collect();64 let found: BTreeSet<String> = secret.secret.parts.keys().cloned().collect();
7065
71 if found != expected {66 if found != expected {
72 return Some(RegenerationReason::PartList { expected, found });67 return Some(RegenerationReason::PartList { expected, found });
73 }68 }
7469
75 for (name, value) in secret.parts.iter() {70 for (name, value) in secret.secret.parts.iter() {
71 let expectation = expectations
72 .parts
73 .get(name)
74 .expect("found == expected checked");
76 if value.raw.encrypted {75 if value.raw.encrypted {
77 if !expectations.private_parts.contains(name) {76 if !expectation.encrypted {
78 return Some(RegenerationReason::ExpectedPrivate(name.clone()));77 return Some(RegenerationReason::ExpectedPrivate(name.clone()));
79 }78 }
80 } else if !expectations.public_parts.contains(name) {79 } else if expectation.encrypted {
81 return Some(RegenerationReason::ExpectedPublic(name.clone()));80 return Some(RegenerationReason::ExpectedPublic(name.clone()));
82 }81 }
83 }82 }
84 }
8583
86 if let Some(expiration) = secret.expires_at {84 if let Some(expiration) = secret.secret.expires_at {
87 // TODO: Leeway?85 // TODO: Leeway?
88 if expiration < Utc::now() {86 if expiration < Utc::now() {
89 return Some(RegenerationReason::Expired(expiration));87 return Some(RegenerationReason::Expired(expiration));
modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
731 }731 }
732732
733 pub fn has_field(&self, field: &str) -> Result<bool> {733 pub fn has_field(&self, field: &str) -> Result<bool> {
734 if !matches!(self.type_of(), NixType::Attrs) {
735 bail!("invalid type: expected attrs");
736 }
737
734 let f = init_field_name(field);738 let f = init_field_name(field);
735 with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })739 with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })
881 pub fn is_null(&self) -> bool {885 pub fn is_null(&self) -> bool {
882 matches!(self.type_of(), NixType::Null)886 matches!(self.type_of(), NixType::Null)
883 }887 }
888 pub fn is_string(&self) -> bool {
889 matches!(self.type_of(), NixType::String)
890 }
891 pub fn is_attrs(&self) -> bool {
892 matches!(self.type_of(), NixType::Attrs)
893 }
884}894}
885895
886impl From<String> for Value {896impl From<String> for Value {
modifiedcrates/nix-eval/src/util.rsdiffbeforeafterboth
1use std::time::Instant;1use std::time::Instant;
22
3use anyhow::bail;3use anyhow::bail;
4use serde::Deserialize;
5use tracing::{debug, warn};4use tracing::{debug, warn};
65
7use crate::{Value, nix_go_json};6use crate::{Value, nix_go_json};
8
9#[derive(Deserialize, Debug)]
10struct Assertion {
11 assertion: bool,
12 message: String,
13}
147
15#[tracing::instrument(level = "info", skip(val))]8#[tracing::instrument(level = "info", skip(val))]
16pub async fn assert_warn(action: &str, val: &Value) -> anyhow::Result<()> {9pub fn assert_warn(action: &str, val: &Value) -> anyhow::Result<()> {
17 let before_errors = Instant::now();10 let before_errors = Instant::now();
18 let errors: Vec<String> = nix_go_json!(val.errors);11 let errors: Vec<String> = nix_go_json!(val.errors);
19 // let assertions: Vec<Assertion> = nix_go_json!(val.assertions);
20 debug!("errors evaluation took {:?} {errors:?} ", before_errors.elapsed());12 debug!("errors evaluation took {:?}", before_errors.elapsed());
21 if !errors.is_empty() {13 if !errors.is_empty() {
22 bail!(14 bail!(
23 "failed with error{}{}",15 "failed with error{}{}",
modifiedlib/default.nixdiffbeforeafterboth
160 mkImpureSecretGenerator,160 mkImpureSecretGenerator,
161 }:161 }:
162 mkImpureSecretGenerator {162 mkImpureSecretGenerator {
163 # TODO: Escape prompt?163 # TODO: Escape prompt/part (preferrably just use env) to prevent shell injection
164 script = ''164 script = ''
165 ${kdePackages.kdialog}/bin/kdialog --inputbox "${prompt}" | gh private -o $out/${part}165 ${kdePackages.kdialog}/bin/kdialog --inputbox "${prompt}" | gh private -o $out/${part}
166 '';166 '';
modifiedmodules/secrets.nixdiffbeforeafterboth
89 # If set - script will be run on remote machine, otherwise it will be run with fleet project in CWD89 # If set - script will be run on remote machine, otherwise it will be run with fleet project in CWD
90 # (Some secrets-encryption-in-git/managed PKI solution is expected)90 # (Some secrets-encryption-in-git/managed PKI solution is expected)
91 impureOn ? null,91 impureOn ? null,
92 generationData ? null,
92 parts,93 parts,
93 }:94 }:
94 (prev.writeShellScript "impureGenerator.sh" ''95 (prev.writeShellScript "impureGenerator.sh" ''
117 '').overrideAttrs118 '').overrideAttrs
118 (old: {119 (old: {
119 passthru = {120 passthru = {
120 inherit impureOn parts;121 inherit impureOn parts generationData;
121 generatorKind = "impure";122 generatorKind = "impure";
122 };123 };
123 });124 });