difftreelog
refactor drop unnecessary async/await
in: trunk
15 files changed
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth303031async 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 .await595960impl 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 {959596impl 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 };114114153 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");cmds/fleet/src/cmds/info.rsdiffbeforeafterboth35 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);cmds/fleet/src/cmds/rollback.rsdiffbeforeafterboth757576impl 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;cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth1use 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};667use 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};221423#[derive(Parser)]15#[derive(Parser)]24pub enum Secret {16pub enum Secret {145}137}146*/138*/147139148#[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,159168 default_generator: Value,160 let nixpkgs = &config.nixpkgs;169 expectations: &Expectations,161170) -> 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?169178 } 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 {177186 let key = config.key(owner).await?;178 let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));187 recipients.push(key);179188 }180 let generator = nix_go!(call_package(generator)(Obj {}));189 let generators = nix_go!(mk_secret_generators(Obj { recipients }));181190 let pkgs_and_generators = on_pkgs.attrs_update(generators)?;182 let generator = spawn_blocking(move || generator.build("out"))191183 .await192 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 {}));186195187 let out_parent = host.mktemp_dir().await?;196 let generator = spawn_blocking(move || generator.build("out"))188 let out = format!("{out_parent}/out");197 .await189198 .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 = config203195 .directory204 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()203212 .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")?;208217209 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 = host223 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}"))221230 .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}"))?;224233 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 {231240 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(),235244 };236 Ok(new_data)245237}246 if let Some(reason) = secret_needs_regeneration(&new_data, &expectations.owners, expectations) {238247 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 level255 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 access272 }264 // passthru we should call generator, but information about where this generator is supposed to build273 }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,275284 // 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(),278287 }));279 let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);288 let pkgs_and_generators = default_pkgs.clone().attrs_update(generators)?;280289281 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 .await299 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 .await309 display_name,301 }310 secret,302 }311 default_generator,303}312 expectations,304*/313 )314 .await315 }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 {cmds/fleet/src/main.rsdiffbeforeafterboth222 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?;226223227 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(()) => {crates/fleet-base/src/fleetdata.rsdiffbeforeafterboth422 }422 }423}423}424425#[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}424435crates/fleet-base/src/host.rsdiffbeforeafterboth472 }472 }473}473}474475struct HostSecretDefinition(Value);476474impl 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 }487490488 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)?;499502500 let _ = self.nixos_config.set(nixos_config.clone());503 let _ = self.nixos_config.set(nixos_config.clone());501504522 }525 }523526524 /// Packages for this host, resolved with nixpkgs overlays527 /// Packages for this host, resolved with nixpkgs overlays525 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}539540pub 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}536551537impl 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 }576591577 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 }]);580595595 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_config608 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 }612627613 /// Shared secrets configured in fleet.nix or in flake628 /// Shared secrets configured in fleet.nix or in flake614 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 }676677 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 }661687662 // 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),crates/fleet-base/src/keys.rsdiffbeforeafterboth12 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) = key16 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 }484849 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();crates/fleet-base/src/opts.rsdiffbeforeafterboth104}104}105105106impl 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 where154 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 }196196197 // 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");248248249 if assert {249 if assert {250 assert_warn("fleet config evaluation", &config_field)250 assert_warn("fleet config evaluation", &config_field)251 .await252 .context("failed to verify assertions")?;251 .context("failed to verify assertions")?;253 }252 }254253crates/fleet-base/src/primops.rsdiffbeforeafterboth1use std::cell::OnceCell;2use std::collections::{BTreeMap, HashMap};1use std::collections::{BTreeMap, BTreeSet, HashMap};3use std::sync::{Arc, Mutex, OnceLock};2use std::sync::OnceLock;435use 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};101011use 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};131714#[derive(thiserror::Error, Debug)]18#[derive(thiserror::Error, Debug)]15enum Error {}19enum Error {}1617struct Parts {18 encrypted: Vec<String>,19 public: Vec<String>,20}2122trait 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}2829struct FsSecretsBackend {}302031pub static PRIMOPS_DATA: OnceLock<Config> = OnceLock::new();21pub static PRIMOPS_DATA: OnceLock<Config> = OnceLock::new();322233#[derive(Deserialize, Debug)]23#[derive(Deserialize)]24#[serde(rename_all = "camelCase")]34struct GeneratorPart {25enum GeneratorKind {35 encrypted: bool,26 Impure,27 Pure,36}28}2930pub 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}5152pub 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")?;5657 Ok(default_generator_drv)58}5960pub 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);6768 match kind {69 GeneratorKind::Impure => {70 let impure_on: Option<String> = nix_go_json!(default_generator_drv.impureOn);7172 let host_on = if let Some(on) = &impure_on {73 config74 .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")?;8485 let generator = generator86 .build("out")87 .context("failed to build generator for target host")?;8889 let generator = host_on90 .remote_derivation(&generator)91 .await92 .context("failed to copy generator to target host")?;9394 // TODO: Remove destdir after everything is done95 let out_parent = host_on96 .mktemp_dir()97 .await98 .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 = config104 .directory105 .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_cmd112 .run()113 .await114 .context("failed to run impure generator")?;115116 {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 }123124 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_on130 .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 }136137 let created_at = host_on.read_file_value(format!("{out}/created_at")).await?;138 let expires_at = host_on139 .read_file_value(format!("{out}/expires_at"))140 .await141 .ok();142143 let new_data = FleetSecretData {144 created_at,145 expires_at,146 parts,147 generation_data: expectations.generation_data.clone(),148 };149150 let new_data = FleetSecretDistribution {151 secret: new_data,152 owners: expectations.owners.clone(),153 _deprecated_managed: true,154 };155156 if let Some(reason) = secret_needs_regeneration(&new_data, &expectations) {157 bail!("newly generated secret needs to be regenerated: {reason}")158 }159160 Ok(new_data)161 }162 GeneratorKind::Pure => {163 bail!("pure generators are disabled for now")164 }165 }166}3716738pub 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");5418455 info!("get pkgs");56 let nixpkgs = &config.nixpkgs;185 let shared_def = config.secret_definition(&secret).context("failed to get shared secret definition")?;18657 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()?;6319364 info!("call package");194 ensure!(expected_owners.contains(&host), "secret {secret} does not define {host} as expected owner");19565 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 }20167 .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()))20470 .context("providing extra callPackage args")?;205 let default_generator_drv = get_default_generator_drv(config, &generator).context("failed to evaluate default generator")?;7172 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 };7521176 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 };8081 info!("got existing: {existing:?}");213 let Some(existing) = config214 .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 }220221 let mut parts = expectations.parts.clone();8222283 let mut out = HashMap::new();223 let mut out = HashMap::new();8485 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 }99233100 Ok(Value::new_attrs(out))234 return Ok(Value::new_attrs(out))235 };236237 todo!()238239101 },240 },crates/fleet-base/src/secret.rsdiffbeforeafterboth1use std::collections::BTreeSet;1use std::collections::{BTreeMap, BTreeSet};223use chrono::{DateTime, Utc};3use chrono::{DateTime, Utc};445use crate::fleetdata::FleetSecretData;5use crate::fleetdata::{Expectations, FleetSecretData, FleetSecretDistribution, GeneratorPart};67#[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}14615#[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>),2930 #[error("secret is not generated for this host")]31 Missing,37}32}383339pub 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> = expectations39 .owners40 .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 }494650 let removed: BTreeSet<String> = owners.difference(&expectations.owners).cloned().collect();47 let removed: BTreeSet<String> = secret48 .owners49 .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 }555556 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 }626263 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_parts66 .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();706571 if found != expected {66 if found != expected {72 return Some(RegenerationReason::PartList { expected, found });67 return Some(RegenerationReason::PartList { expected, found });73 }68 }746975 for (name, value) in secret.parts.iter() {70 for (name, value) in secret.secret.parts.iter() {71 let expectation = expectations72 .parts73 .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 }858386 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));crates/nix-eval/src/lib.rsdiffbeforeafterboth731 }731 }732732733 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 }737734 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}885895886impl From<String> for Value {896impl From<String> for Value {crates/nix-eval/src/util.rsdiffbeforeafterboth1use std::time::Instant;1use std::time::Instant;223use anyhow::bail;3use anyhow::bail;4use serde::Deserialize;5use tracing::{debug, warn};4use tracing::{debug, warn};657use crate::{Value, nix_go_json};6use crate::{Value, nix_go_json};89#[derive(Deserialize, Debug)]10struct Assertion {11 assertion: bool,12 message: String,13}14715#[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{}{}",lib/default.nixdiffbeforeafterboth160 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 injection164 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 '';modules/secrets.nixdiffbeforeafterboth89 # 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 CWD90 # (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 '').overrideAttrs118 (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 });