1use std::{collections::BTreeMap, ffi::OsString, path::PathBuf};23use anyhow::{Context, Result};4use clap::Parser;5use fleet_base::host::Config;6use nix_eval::nix_go;7use serde::Deserialize;8use serde_json::Value;9use tempfile::NamedTempFile;10use tokio::{11 fs::{self, create_dir_all},12 process::Command,13};14use tracing::debug;1516#[derive(Deserialize, Debug)]17pub struct TfData {18 19 #[allow(dead_code)]20 managed: bool,21 22 #[serde(default)]23 #[serde(skip_serializing_if = "BTreeMap::is_empty")]24 pub hosts: BTreeMap<String, Value>,25}2627#[derive(Parser)]28pub struct Tf {29 args: Vec<OsString>,30}31impl Tf {32 pub async fn run(&self, config: &Config) -> Result<()> {33 let dir = config.directory.join(".fleet/tf/default");34 35 36 37 {38 debug!("generating terraform configs");39 let system = &config.local_system;40 let config = &config.config_field;41 let data: PathBuf = nix_go!(config.tf({ system })).build("out").await?;42 let data = fs::read(&data).await?;4344 create_dir_all(&dir).await?;4546 let tmp = NamedTempFile::new_in(&dir)?;47 fs::write(tmp.path(), data).await?;48 tmp.persist(dir.join("fleet.tf.json"))?;49 }5051 {52 debug!("running terraform command");53 Command::new("terraform")54 .current_dir(&dir)55 .args(&self.args)56 .status()57 .await?;58 }59 {60 debug!("syncing terraform data");61 let data = Command::new("terraform")62 .current_dir(dir)63 .arg("output")64 .arg("-json")65 .arg("fleet")66 .output()67 .await?;68 let tf_data: TfData = serde_json::from_slice(&data.stdout)69 .context("failed to parse terraform fleet output")?;7071 let mut data = config.data();72 debug!("synchronized done = {tf_data:?}");73 data.extra.insert(74 "terraformHosts".to_owned(),75 serde_json::to_value(tf_data.hosts).expect("should be valid extra"),76 );77 }7879 Ok(())80 }81}