chore(release): stage v1.7.52-alpha

This commit is contained in:
archipelago
2026-05-05 11:29:18 -04:00
parent 10fbb8f87c
commit 745cb1c626
86 changed files with 4084 additions and 966 deletions

View File

@@ -109,7 +109,7 @@ impl PodmanClient {
pub fn lan_address_for(name: &str) -> Option<String> {
let url = match name {
"bitcoin-knots" | "bitcoin-ui" => "http://localhost:8334",
"lnd" | "archy-lnd-ui" => "http://localhost:8081",
"lnd" | "archy-lnd-ui" => "http://localhost:18083",
"homeassistant" => "http://localhost:8123",
"archy-mempool-web" | "mempool" => "http://localhost:4080",
"btcpay-server" => "http://localhost:23000",
@@ -374,7 +374,10 @@ impl PodmanClient {
"env": env_map,
"entrypoint": manifest.app.container.entrypoint.clone(),
"command": manifest.app.container.custom_args.clone(),
"hostadd": ["host.containers.internal:host-gateway"],
"hostadd": [
"host.containers.internal:host-gateway",
"host.archipelago:10.89.0.1",
],
"devices": manifest.app.devices.iter().map(|d| {
serde_json::json!({"path": d})
}).collect::<Vec<_>>(),
@@ -392,7 +395,10 @@ impl PodmanClient {
if let Some(network) = custom_network {
body.as_object_mut()
.expect("container create body is a JSON object")
.insert("networks".to_string(), serde_json::json!({ network: {} }));
.insert(
"networks".to_string(),
serde_json::json!({ network: { "aliases": [name] } }),
);
}
let result = self

View File

@@ -104,7 +104,20 @@ impl ContainerRuntime for PodmanRuntime {
}
async fn list_containers(&self) -> Result<Vec<ContainerStatus>> {
self.client.list_containers().await
match self.client.list_containers().await {
Ok(containers) => Ok(containers),
Err(api_err) => {
let output = self.podman_cli(&["ps", "-a", "--format", "json"]).await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(
api_err.context(format!("podman ps fallback failed: {}", stderr.trim()))
);
}
parse_podman_ps_json(&output.stdout)
.with_context(|| format!("podman API list failed: {api_err}"))
}
}
}
async fn image_exists(&self, image_ref: &str) -> Result<bool> {
@@ -147,6 +160,83 @@ impl ContainerRuntime for PodmanRuntime {
}
}
fn parse_podman_ps_json(stdout: &[u8]) -> Result<Vec<ContainerStatus>> {
let text = String::from_utf8_lossy(stdout);
if text.trim().is_empty() {
return Ok(Vec::new());
}
let containers: Vec<serde_json::Value> = serde_json::from_str(&text)?;
Ok(containers
.into_iter()
.map(|c| {
let name = c
.get("Names")
.and_then(|v| v.as_array())
.and_then(|a| a.first())
.and_then(|v| v.as_str())
.or_else(|| c.get("Names").and_then(|v| v.as_str()))
.unwrap_or("")
.to_string();
let status = c.get("Status").and_then(|v| v.as_str()).unwrap_or("");
let state = c.get("State").and_then(|v| v.as_str()).unwrap_or("unknown");
ContainerStatus {
id: c
.get("Id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
name: name.clone(),
state: ContainerState::from(state),
health: parse_health_from_status(status),
exit_code: c.get("ExitCode").and_then(|v| v.as_i64()).map(|c| c as i32),
started_at: c
.get("StartedAt")
.and_then(|v| v.as_str())
.map(|s| s.to_string()),
image: c
.get("Image")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
created: c
.get("Created")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
ports: parse_podman_ps_ports(c.get("Ports")),
lan_address: PodmanClient::lan_address_for(&name),
}
})
.collect())
}
fn parse_podman_ps_ports(ports: Option<&serde_json::Value>) -> Vec<String> {
ports
.and_then(|v| v.as_array())
.map(|ports| {
ports
.iter()
.filter_map(|port| {
let host = port.get("host_port").and_then(|v| v.as_u64())?;
let container = port.get("container_port").and_then(|v| v.as_u64())?;
let proto = port
.get("protocol")
.and_then(|v| v.as_str())
.unwrap_or("tcp");
Some(format!("0.0.0.0:{host}->{container}/{proto}"))
})
.collect()
})
.unwrap_or_default()
}
fn parse_health_from_status(status: &str) -> Option<String> {
let start = status.rfind('(')?;
let end = status.rfind(')')?;
(start < end).then(|| status[start + 1..end].to_string())
}
/// Build the argv for `podman build` from a BuildConfig.
///
/// Extracted so it can be unit-tested without actually invoking podman.
@@ -646,4 +736,36 @@ mod tests {
let args = build_args_for_podman(&c);
assert_eq!(args.last().unwrap(), "/final/context");
}
#[test]
fn parse_podman_ps_json_handles_cli_output() {
let stdout = br#"[
{
"Id": "abc123",
"Names": ["mempool"],
"Image": "docker.io/mempool/frontend:latest",
"State": "running",
"Status": "Up 2 minutes (healthy)",
"Created": "2026-05-03T00:00:00Z",
"StartedAt": "2026-05-03T00:01:00Z",
"ExitCode": 0,
"Ports": [
{
"host_port": 4080,
"container_port": 8080,
"protocol": "tcp"
}
]
}
]"#;
let containers = parse_podman_ps_json(stdout).unwrap();
assert_eq!(containers.len(), 1);
assert_eq!(containers[0].id, "abc123");
assert_eq!(containers[0].name, "mempool");
assert_eq!(containers[0].state, ContainerState::Running);
assert_eq!(containers[0].health.as_deref(), Some("healthy"));
assert_eq!(containers[0].exit_code, Some(0));
assert_eq!(containers[0].ports, vec!["0.0.0.0:4080->8080/tcp"]);
}
}