Skip to content

Commit 4217cf3

Browse files
committed
site update, compose volumes fix
1 parent 5803237 commit 4217cf3

20 files changed

Lines changed: 349 additions & 139 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@ The end-user tool. No server required for local deploys.
160160
| `stacker status` | Show running containers and health |
161161
| `stacker logs` | View container logs (`--follow`, `--service`, `--tail`) |
162162
| `stacker secrets` | Manage local `.env` secrets or remote Vault-backed `service` / `server` secrets |
163-
| `stacker list deployments` | List deployments on the Stacker server |
163+
| `stacker list deployments` / `stacker deployments` | List deployments on the Stacker server |
164+
| `stacker list servers` / `stacker servers` | List saved servers |
165+
| `stacker list clouds` / `stacker clouds` | List saved cloud credentials |
166+
| `stacker list ssh-keys` / `stacker ssh-keys` | List per-server SSH key status |
164167
| `stacker destroy` | Tear down the deployed stack |
165168
| `stacker config validate` | Validate `stacker.yml` syntax |
166169
| `stacker config show` | Show resolved configuration |

src/bin/stacker.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,43 @@ enum StackerCommands {
258258
#[command(subcommand)]
259259
command: ListCommands,
260260
},
261+
/// List all projects (alias for `stacker list projects`)
262+
Projects {
263+
/// Output in JSON format
264+
#[arg(long)]
265+
json: bool,
266+
},
267+
/// List deployments (alias for `stacker list deployments`)
268+
Deployments {
269+
/// Filter by project ID
270+
#[arg(long)]
271+
project: Option<i32>,
272+
/// Limit number of results
273+
#[arg(long)]
274+
limit: Option<i64>,
275+
/// Output in JSON format
276+
#[arg(long)]
277+
json: bool,
278+
},
279+
/// List all servers (alias for `stacker list servers`)
280+
Servers {
281+
/// Output in JSON format
282+
#[arg(long)]
283+
json: bool,
284+
},
285+
/// List SSH keys (alias for `stacker list ssh-keys`)
286+
#[command(name = "ssh-keys")]
287+
SshKeys {
288+
/// Output in JSON format
289+
#[arg(long)]
290+
json: bool,
291+
},
292+
/// List saved cloud credentials (alias for `stacker list clouds`)
293+
Clouds {
294+
/// Output in JSON format
295+
#[arg(long)]
296+
json: bool,
297+
},
261298
/// SSH key management (generate, show, upload, repair)
262299
#[command(long_about = "Manage Stacker server SSH keys.\n\n\
263300
Cloud deploys automatically create a local backup SSH key under the Stacker config directory and authorize it on the deployed server when possible. The `generate` command manages the server-side Vault key; `inject` repairs a server by using an already-working local private key to add the Vault public key.")]
@@ -1935,6 +1972,27 @@ fn get_command(
19351972
Box::new(stacker::console::commands::cli::list::ListCloudsCommand::new(json))
19361973
}
19371974
},
1975+
StackerCommands::Projects { json } => {
1976+
Box::new(stacker::console::commands::cli::list::ListProjectsCommand::new(json))
1977+
}
1978+
StackerCommands::Deployments {
1979+
json,
1980+
project,
1981+
limit,
1982+
} => Box::new(
1983+
stacker::console::commands::cli::list::ListDeploymentsCommand::new(
1984+
json, project, limit,
1985+
),
1986+
),
1987+
StackerCommands::Servers { json } => {
1988+
Box::new(stacker::console::commands::cli::list::ListServersCommand::new(json))
1989+
}
1990+
StackerCommands::SshKeys { json } => {
1991+
Box::new(stacker::console::commands::cli::list::ListSshKeysCommand::new(json))
1992+
}
1993+
StackerCommands::Clouds { json } => {
1994+
Box::new(stacker::console::commands::cli::list::ListCloudsCommand::new(json))
1995+
}
19381996
StackerCommands::SshKey { command: ssh_cmd } => match ssh_cmd {
19391997
SshKeyCommands::Generate { server_id, save_to } => Box::new(
19401998
stacker::console::commands::cli::ssh_key::SshKeyGenerateCommand::new(
@@ -2929,6 +2987,44 @@ mod tests {
29292987
assert!(sync.is_ok(), "secrets apps sync should parse successfully");
29302988
}
29312989

2990+
#[test]
2991+
fn test_top_level_servers_alias_parses() {
2992+
let parsed = Cli::try_parse_from(["stacker", "servers", "--json"]);
2993+
2994+
assert!(
2995+
parsed.is_ok(),
2996+
"top-level servers alias should parse successfully"
2997+
);
2998+
}
2999+
3000+
#[test]
3001+
fn test_top_level_deployments_alias_parses() {
3002+
let parsed = Cli::try_parse_from([
3003+
"stacker",
3004+
"deployments",
3005+
"--project",
3006+
"42",
3007+
"--limit",
3008+
"10",
3009+
"--json",
3010+
]);
3011+
3012+
assert!(
3013+
parsed.is_ok(),
3014+
"top-level deployments alias should parse successfully"
3015+
);
3016+
}
3017+
3018+
#[test]
3019+
fn test_top_level_ssh_keys_alias_parses() {
3020+
let parsed = Cli::try_parse_from(["stacker", "ssh-keys", "--json"]);
3021+
3022+
assert!(
3023+
parsed.is_ok(),
3024+
"top-level ssh-keys alias should parse successfully"
3025+
);
3026+
}
3027+
29323028
#[test]
29333029
fn test_secrets_help_mentions_remote_modes() {
29343030
let mut command = Cli::command();

src/cli/compose_service_sync.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,7 @@ fn inject_external_network(
6969
}
7070
}
7171
None => {
72-
svc.insert(
73-
networks_key,
74-
serde_yaml::Value::Sequence(vec![network_val]),
75-
);
72+
svc.insert(networks_key, serde_yaml::Value::Sequence(vec![network_val]));
7673
changed = true;
7774
}
7875
_ => {}
@@ -387,7 +384,9 @@ fn push_unique_network(networks: &mut Vec<String>, name: &str) {
387384
#[cfg(test)]
388385
mod tests {
389386
use super::*;
390-
use crate::cli::config_parser::{AppSource, DeployConfig, DomainConfig, ProjectConfig, SslMode};
387+
use crate::cli::config_parser::{
388+
AppSource, DeployConfig, DomainConfig, ProjectConfig, SslMode,
389+
};
391390
use std::collections::HashMap;
392391
use tempfile::TempDir;
393392

src/cli/generator/compose.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::convert::TryFrom;
33
use std::fmt;
44
use std::path::Path;
55

6-
use crate::cli::config_parser::{AppType, DomainConfig, ProxyType, ServiceDefinition, StackerConfig};
6+
use crate::cli::config_parser::{
7+
AppType, DomainConfig, ProxyType, ServiceDefinition, StackerConfig,
8+
};
79
use crate::cli::error::CliError;
810

911
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -160,7 +162,9 @@ impl TryFrom<&StackerConfig> for ComposeDefinition {
160162
}
161163
}
162164
if injected {
163-
compose.external_networks.push("default_network".to_string());
165+
compose
166+
.external_networks
167+
.push("default_network".to_string());
164168
}
165169
}
166170

@@ -773,9 +777,18 @@ mod tests {
773777
);
774778

775779
let yaml = compose.render();
776-
assert!(yaml.contains("volumes:"), "top-level volumes: block must exist");
777-
assert!(yaml.contains(" rustfs_data:"), "rustfs_data entry must appear");
778-
assert!(yaml.contains(" rustfs_logs:"), "rustfs_logs entry must appear");
780+
assert!(
781+
yaml.contains("volumes:"),
782+
"top-level volumes: block must exist"
783+
);
784+
assert!(
785+
yaml.contains(" rustfs_data:"),
786+
"rustfs_data entry must appear"
787+
);
788+
assert!(
789+
yaml.contains(" rustfs_logs:"),
790+
"rustfs_logs entry must appear"
791+
);
779792
}
780793

781794
#[test]
@@ -910,11 +923,16 @@ mod tests {
910923
api_svc.networks
911924
);
912925
assert!(
913-
compose.external_networks.contains(&"default_network".to_string()),
926+
compose
927+
.external_networks
928+
.contains(&"default_network".to_string()),
914929
"default_network should be declared as external"
915930
);
916931
let yaml = compose.render();
917-
assert!(yaml.contains("external: true"), "rendered YAML should declare default_network external:\n{yaml}");
932+
assert!(
933+
yaml.contains("external: true"),
934+
"rendered YAML should declare default_network external:\n{yaml}"
935+
);
918936
}
919937

920938
#[test]

src/cli/install_runner.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,14 @@ fn get_own_compose_running_ports(
278278
let compose_str = compose_path.to_string_lossy();
279279
let out = match executor.execute(
280280
"docker",
281-
&["compose", "-f", &compose_str, "ps", "--format", "{{.Ports}}"],
281+
&[
282+
"compose",
283+
"-f",
284+
&compose_str,
285+
"ps",
286+
"--format",
287+
"{{.Ports}}",
288+
],
282289
) {
283290
Ok(o) if o.success() => o,
284291
_ => return Default::default(),

src/connectors/hetzner.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,10 @@ impl HetznerCloudConnector for HetznerCloudClient {
168168

169169
let status = response.status();
170170
if !status.is_success() {
171-
return Err(status_to_error(status, "Hetzner server types lookup failed"));
171+
return Err(status_to_error(
172+
status,
173+
"Hetzner server types lookup failed",
174+
));
172175
}
173176

174177
let body: HetznerServerTypesResponse = response
@@ -374,10 +377,7 @@ mod tests {
374377
.await;
375378

376379
let client = HetznerCloudClient::new(api.uri()).unwrap();
377-
let types = client
378-
.list_server_types("test-token", None)
379-
.await
380-
.unwrap();
380+
let types = client.list_server_types("test-token", None).await.unwrap();
381381

382382
assert_eq!(types, vec!["cx22", "cx32", "cx42"]);
383383
}

src/console/commands/cli/agent.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,14 @@ fn print_health_result(info: &AgentCommandInfo) {
452452
if let Some(containers) = result.get("containers").and_then(|v| v.as_array()) {
453453
println!("{:<28} {:<10} {}", "CONTAINER", "STATE", "STATUS");
454454
for c in containers {
455-
let name = c.get("container_name").and_then(|v| v.as_str()).unwrap_or("-");
456-
let state = c.get("container_state").and_then(|v| v.as_str()).unwrap_or("-");
455+
let name = c
456+
.get("container_name")
457+
.and_then(|v| v.as_str())
458+
.unwrap_or("-");
459+
let state = c
460+
.get("container_state")
461+
.and_then(|v| v.as_str())
462+
.unwrap_or("-");
457463
let status = c.get("status").and_then(|v| v.as_str()).unwrap_or("-");
458464
println!(
459465
"{:<28} {} {:<8} {}",
@@ -469,9 +475,15 @@ fn print_health_result(info: &AgentCommandInfo) {
469475

470476
// Single-container health
471477
if result_type == "health" {
472-
let state = result.get("container_state").and_then(|v| v.as_str()).unwrap_or("-");
478+
let state = result
479+
.get("container_state")
480+
.and_then(|v| v.as_str())
481+
.unwrap_or("-");
473482
let status = result.get("status").and_then(|v| v.as_str()).unwrap_or("-");
474-
let app = result.get("app_code").and_then(|v| v.as_str()).unwrap_or("-");
483+
let app = result
484+
.get("app_code")
485+
.and_then(|v| v.as_str())
486+
.unwrap_or("-");
475487
println!(
476488
"{}: {} {} ({})",
477489
app,
@@ -508,7 +520,10 @@ fn print_all_container_health(containers: &[serde_json::Value]) {
508520
println!("Overall: {} {}", progress::status_icon(overall), overall);
509521
println!();
510522

511-
println!("{:<28} {:<12} {:<8} {:<8} {}", "CONTAINER", "STATE", "CPU%", "MEM%", "IMAGE");
523+
println!(
524+
"{:<28} {:<12} {:<8} {:<8} {}",
525+
"CONTAINER", "STATE", "CPU%", "MEM%", "IMAGE"
526+
);
512527
for c in containers {
513528
let name = c.get("name").and_then(|v| v.as_str()).unwrap_or("-");
514529
let state = c.get("status").and_then(|v| v.as_str()).unwrap_or("-");
@@ -656,8 +671,7 @@ impl CallableTrait for AgentHealthCommand {
656671
// No specific app requested → list all containers with health metrics.
657672
// This avoids sending app_code="all" to older agents that don't handle it.
658673
if self.app_code.is_none() && !self.include_system {
659-
let containers = fetch_live_containers(&ctx, &hash)?
660-
.unwrap_or_default();
674+
let containers = fetch_live_containers(&ctx, &hash)?.unwrap_or_default();
661675
if self.json {
662676
println!("{}", serde_json::to_string_pretty(&containers)?);
663677
} else {
@@ -1764,7 +1778,10 @@ fn print_containers_summary(containers: &[serde_json::Value]) {
17641778
return;
17651779
}
17661780

1767-
println!("{:<24} {:<12} {:<22} {:<30}", "CONTAINER", "STATE", "PORTS", "IMAGE");
1781+
println!(
1782+
"{:<24} {:<12} {:<22} {:<30}",
1783+
"CONTAINER", "STATE", "PORTS", "IMAGE"
1784+
);
17681785
for c in containers {
17691786
let name = c.get("name").and_then(|v| v.as_str()).unwrap_or("-");
17701787
let state = c

0 commit comments

Comments
 (0)