git.delta.rocks / jrsonnet / refs/heads / trunk

difftreelog

source

crates/fleet-base/src/secret_storage.rs6.5 KiBsourcehistory
1use anyhow::{Result, bail, ensure};2use itertools::Itertools;3use std::fs::{File, metadata};4use std::io::{self, ErrorKind, Read, Write};5use std::path::PathBuf;6use std::str::FromStr;7use std::{env, fs};89use tempfile::{TempPath, tempfile_in};10use toml_edit::{Document, DocumentMut, Formatted, Item, Value};1112struct Name(String);1314fn encode_name(name: &str) -> Name {15	assert!(16		!name.starts_with(['_', '.']),17		"groups should not start with _ or ."18	);19	assert!(20		!name.chars().any(|c| c == '/'),21		"group name should not contain internal slash"22	);23	Name(name.to_owned())24}2526enum RewriteError {27	ConcurrentCreate,28	ConcurrentDelete,29	ConcurrentModify,30	ConcurrentWrite,31	Io(io::Error),32	Persist(tempfile::PersistError),33}3435fn safe_rewrite(36	path: &PathBuf,37	old_content: Option<Vec<u8>>,38	new_content: Option<Vec<u8>>,39) -> Result<(), RewriteError> {40	let mut f = match (old_content.is_some(), new_content.is_some()) {41		(false, true) => match File::create_new(path) {42			Ok(v) => v,43			Err(e) if e.kind() == ErrorKind::AlreadyExists => {44				return Err(RewriteError::ConcurrentCreate);45			}46			Err(e) => return Err(RewriteError::Io(e)),47		},48		(true, _) => match File::open(&path) {49			Ok(v) => v,50			Err(_e) => return Err(RewriteError::ConcurrentDelete),51		},52		(false, false) => match metadata(&path) {53			Err(e) if e.kind() == ErrorKind::NotFound => {54				return Ok(());55			}56			Ok(_) => return Err(RewriteError::ConcurrentCreate),57			Err(e) => return Err(RewriteError::Io(e)),58		},59	};60	f.lock().map_err(RewriteError::Io)?;61	let mut check_content = vec![];62	f.read_to_end(&mut check_content)63		.map_err(RewriteError::Io)?;64	match &old_content {65		Some(old) => {66			if old != &check_content {67				return Err(RewriteError::ConcurrentModify);68			}69		}70		None => {71			if !check_content.is_empty() {72				return Err(RewriteError::ConcurrentDelete);73			}74		}75	}76	if let Some(new_content) = new_content {77		if Some(&new_content) == old_content.as_ref() {78			return Ok(());79		}80		let dir = path.parent().expect("file is in directory, thus not root");81		let mut tempfile = tempfile::Builder::new()82			.prefix(".rewrite-")83			.tempfile_in(dir)84			.map_err(RewriteError::Io)?;85		tempfile.write_all(&new_content).map_err(RewriteError::Io)?;86		tempfile.flush().map_err(RewriteError::Io)?;87		tempfile.persist(path).map_err(RewriteError::Persist)?;88	} else {89		fs::remove_file(path).map_err(RewriteError::Io)?;90	}91	let _ = f.unlock();92	Ok(())93}94fn update_string(path: PathBuf, modify: impl Fn(&mut Option<String>) -> Result<()>) -> Result<()> {95	loop {96		let orig = match fs::read_to_string(&path) {97			Ok(v) => Some(v),98			Err(e) if e.kind() == ErrorKind::NotFound => None,99			Err(e) => return Err(e.into()),100		};101		let mut edit = orig.clone();102		modify(&mut edit);103104		match safe_rewrite(&path, orig.map(String::into), edit.map(String::into)) {105			Ok(()) => return Ok(()),106			Err(107				RewriteError::ConcurrentCreate108				| RewriteError::ConcurrentModify109				| RewriteError::ConcurrentWrite110				| RewriteError::ConcurrentDelete,111			) => {112				continue;113			}114			Err(RewriteError::Io(io)) => return Err(io.into()),115			Err(RewriteError::Persist(io)) => return Err(io.into()),116		}117	}118}119fn update_toml(path: PathBuf, modify: impl Fn(&mut DocumentMut) -> Result<()>) -> Result<()> {120	update_string(path, |str| {121		let mut doc = match str {122			None => DocumentMut::new(),123			Some(v) => DocumentMut::from_str(v)?,124		};125		modify(&mut doc)?;126		if doc.is_empty() {127			*str = None128		} else {129			*str = Some(doc.to_string())130		}131		Ok(())132	})133}134fn update_lines(path: PathBuf, modify: impl Fn(&mut Vec<String>) -> Result<()>) -> Result<()> {135	update_string(path, |str| {136		let mut list = if let Some(str) = str {137			str.split('\n').map(|s| s.to_owned()).collect_vec()138		} else {139			vec![]140		};141		let had_end_newline = if list.last().map(|v| v.as_str()) == Some("") {142			list.pop();143			true144		} else {145			false146		};147		modify(&mut list)?;148		if list.is_empty() {149			*str = None150		} else {151			if had_end_newline {152				list.push("".to_owned())153			}154			*str = Some(list.join("\n"));155		}156		Ok(())157	})158}159fn update_section(160	data: &mut Vec<String>,161	start: &str,162	end: &str,163	modify: impl Fn(&mut Vec<String>) -> Result<()>,164) -> Result<()> {165	let first = data166		.iter()167		.enumerate()168		.filter(|(_, v)| *v == start)169		.at_most_one()170		.map_err(|_| anyhow::anyhow!("there should be at most one section start"))?171		.map(|(v, _)| v);172	let last = data173		.iter()174		.enumerate()175		.filter(|(_, v)| *v == end)176		.at_most_one()177		.map_err(|_| anyhow::anyhow!("there should be at most one section end"))?178		.map(|(v, _)| v);179180	match (first, last) {181		(None, None) => {182			let mut out = Vec::new();183			modify(&mut out)?;184			if out.is_empty() {185				return Ok(());186			}187			data.push(start.to_owned());188			data.extend(out);189			data.push(end.to_owned());190			Ok(())191		}192		(None, Some(_)) | (Some(_), None) => {193			bail!("mismatched section start/end")194		}195		(Some(first), Some(last)) => {196			ensure!(first < last, "section end should come after start");197			let mut out = data[first + 1..last]198				.iter()199				.map(|v| v.to_owned())200				.collect_vec();201			modify(&mut out)?;202			if out.is_empty() {203				data.drain(first..=last);204			} else {205				data.splice(first + 1..last, out);206			}207			Ok(())208		}209	}210}211212struct Group {213	path: PathBuf,214}215impl Group {216	fn new(path: PathBuf) -> Self {217		Self { path }218	}219	fn manage(&self, manager: &str) {}220	fn ensure_managing(&self, manager: &str) {221		if !self.has_stored() {222			return;223		}224		let managed = match fs::read_to_string(self.path.join(".managed_by")) {225			Ok(found_manager) => found_manager.lines().any(|line| line == manager),226			Err(e) if e.kind() == ErrorKind::NotFound => true,227			Err(e) => panic!("{e}"),228		};229		assert!(managed);230	}231	fn has_stored(&self) -> bool {232		match fs::metadata(&self.path) {233			Ok(d) => d.is_dir(),234			Err(e) if e.kind() == ErrorKind::NotFound => false,235			Err(e) => panic!("{e}"),236		}237	}238}239240struct Root {241	path: PathBuf,242}243impl Root {244	fn new(path: PathBuf) -> Self {245		Self { path }246	}247	fn subgroup(&self, name: &str) -> Group {248		Group::new(self.path.join(name))249	}250}251252#[test]253fn test() {254	let mut data = vec![255		"a".to_owned(),256		"b".to_owned(),257		"start".to_owned(),258		"c".to_owned(),259		"d".to_owned(),260		"end".to_owned(),261		"e".to_owned(),262		"f".to_owned(),263	];264	update_section(&mut data, "start", "end", |a| {265		a.push("vv".to_owned());266		Ok(())267	})268	.unwrap();269	dbg!(&data);270	// for v in 0..1000 {271	// 	update_toml(PathBuf::from("./test.toml"), |e| {272	// 		e.as_table_mut()273	// 			.insert("hello", Item::Value(Value::Integer(Formatted::new(v))));274	// 	})275	// 	.expect("update")276	// }277	// v.subgroup(name)278}