Skip to content

Commit 23aec7a

Browse files
committed
fix(bootstrap): preserve user shadowing on invalid metadata
Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>
1 parent ddd60fa commit 23aec7a

1 file changed

Lines changed: 91 additions & 13 deletions

File tree

crates/openshell-bootstrap/src/metadata.rs

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ fn system_gateway_metadata_path(name: &str) -> PathBuf {
107107

108108
fn resolve_gateway_metadata_path(name: &str) -> Result<Option<(PathBuf, GatewayMetadataSource)>> {
109109
let user = user_gateway_metadata_path(name)?;
110-
if user.exists() {
110+
if user_entry_shadows_system(&user) {
111111
return Ok(Some((user, GatewayMetadataSource::User)));
112112
}
113113

@@ -127,6 +127,10 @@ fn parse_gateway_metadata(path: &Path) -> Result<GatewayMetadata> {
127127
.into_diagnostic()
128128
.wrap_err("failed to parse gateway metadata")
129129
}
130+
fn user_entry_shadows_system(metadata_path: &Path) -> bool {
131+
metadata_path.try_exists().unwrap_or(true)
132+
}
133+
130134
/// Extract the hostname from an SSH destination string.
131135
///
132136
/// Handles formats like:
@@ -291,34 +295,63 @@ pub fn list_gateways_with_source() -> Result<Vec<ListedGateway>> {
291295
let mut gateways = Vec::new();
292296
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
293297

294-
let mut scan = |dir: PathBuf, source: GatewayMetadataSource| -> Result<()> {
295-
if !dir.exists() {
296-
return Ok(());
298+
let user_dir = user_gateways_dir()?;
299+
if user_dir.exists() {
300+
let entries = std::fs::read_dir(&user_dir)
301+
.into_diagnostic()
302+
.wrap_err_with(|| format!("failed to read directory {}", user_dir.display()))?;
303+
for entry in entries {
304+
let entry = entry.into_diagnostic()?;
305+
let path = entry.path();
306+
if !path.is_dir() {
307+
continue;
308+
}
309+
310+
let metadata_path = path.join("metadata.json");
311+
if !user_entry_shadows_system(&metadata_path) {
312+
continue;
313+
}
314+
315+
let name = entry.file_name().to_string_lossy().to_string();
316+
if !seen.insert(name) {
317+
continue;
318+
}
319+
320+
if let Ok(metadata) = parse_gateway_metadata(&metadata_path) {
321+
gateways.push(ListedGateway {
322+
metadata,
323+
source: GatewayMetadataSource::User,
324+
});
325+
}
297326
}
298-
let entries = std::fs::read_dir(&dir)
327+
}
328+
329+
let system_dir = system_gateways_dir();
330+
if system_dir.exists() {
331+
let entries = std::fs::read_dir(&system_dir)
299332
.into_diagnostic()
300-
.wrap_err_with(|| format!("failed to read directory {}", dir.display()))?;
333+
.wrap_err_with(|| format!("failed to read directory {}", system_dir.display()))?;
301334
for entry in entries {
302335
let entry = entry.into_diagnostic()?;
303336
let path = entry.path();
304337
if !path.is_dir() {
305338
continue;
306339
}
340+
307341
let name = entry.file_name().to_string_lossy().to_string();
308342
if seen.contains(&name) {
309343
continue;
310344
}
345+
311346
let metadata_path = path.join("metadata.json");
312347
if let Ok(metadata) = parse_gateway_metadata(&metadata_path) {
313-
seen.insert(name);
314-
gateways.push(ListedGateway { metadata, source });
348+
gateways.push(ListedGateway {
349+
metadata,
350+
source: GatewayMetadataSource::System,
351+
});
315352
}
316353
}
317-
Ok(())
318-
};
319-
320-
scan(user_gateways_dir()?, GatewayMetadataSource::User)?;
321-
scan(system_gateways_dir(), GatewayMetadataSource::System)?;
354+
}
322355

323356
gateways.sort_by(|a, b| a.metadata.name.cmp(&b.metadata.name));
324357
Ok(gateways)
@@ -730,4 +763,49 @@ mod tests {
730763
assert_eq!(gateways[0].source, GatewayMetadataSource::User);
731764
});
732765
}
766+
767+
#[test]
768+
fn list_gateways_invalid_user_entry_still_shadows_system() {
769+
let user = tempfile::tempdir().unwrap();
770+
let system = tempfile::tempdir().unwrap();
771+
with_tmp_xdg_and_system(user.path(), system.path(), || {
772+
let user_metadata_path = user_gateway_metadata_path("shared").unwrap();
773+
std::fs::create_dir_all(user_metadata_path.parent().unwrap()).unwrap();
774+
std::fs::write(&user_metadata_path, "{not-json").unwrap();
775+
776+
write_system_metadata(&system.path().join("gateways"), "shared", "https://system");
777+
778+
let gateways = list_gateways_with_source().unwrap();
779+
assert!(gateways.is_empty());
780+
});
781+
}
782+
#[test]
783+
fn list_gateways_empty_user_dir_does_not_hide_system() {
784+
let user = tempfile::tempdir().unwrap();
785+
let system = tempfile::tempdir().unwrap();
786+
with_tmp_xdg_and_system(user.path(), system.path(), || {
787+
let user_meta = GatewayMetadata {
788+
name: "shared".to_string(),
789+
gateway_endpoint: "https://user".to_string(),
790+
..Default::default()
791+
};
792+
store_gateway_metadata("shared", &user_meta).unwrap();
793+
remove_gateway_metadata("shared").unwrap();
794+
795+
let user_gateway_dir = user_gateway_metadata_path("shared")
796+
.unwrap()
797+
.parent()
798+
.unwrap()
799+
.to_path_buf();
800+
assert!(user_gateway_dir.is_dir());
801+
assert!(!user_gateway_dir.join("metadata.json").exists());
802+
803+
write_system_metadata(&system.path().join("gateways"), "shared", "https://system");
804+
805+
let gateways = list_gateways_with_source().unwrap();
806+
assert_eq!(gateways.len(), 1);
807+
assert_eq!(gateways[0].metadata.gateway_endpoint, "https://system");
808+
assert_eq!(gateways[0].source, GatewayMetadataSource::System);
809+
});
810+
}
733811
}

0 commit comments

Comments
 (0)