Skip to content

Commit 8b0f349

Browse files
author
Chris
committed
added local start commands
1 parent e9cd1aa commit 8b0f349

File tree

3 files changed

+90
-15
lines changed

3 files changed

+90
-15
lines changed

src/cli.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
use clap::{Args, Parser, Subcommand};
22

33
#[derive(Parser, Debug)]
4-
#[command(name = "k9", version, about = "K9 CLI")]
4+
#[command(name = "k9", version, about = "Canine CLI - Manage your Canine projects, clusters, and local development environment")]
55
pub struct Cli {
66
#[command(subcommand)]
77
pub namespace: Namespace,
88
}
99

1010
#[derive(Subcommand, Debug)]
1111
pub enum Namespace {
12-
/// Authentication commands
12+
/// Manage authentication (login, logout, status)
1313
Auth(AuthCmd),
1414

15-
/// Account commands
15+
/// Switch between Canine accounts
1616
Accounts(AccountCmd),
1717

18-
/// Project commands
18+
/// Manage projects (list, deploy, run commands)
1919
Projects(ProjectCmd),
2020

21-
/// Cluster commands
21+
/// Manage Kubernetes clusters (list, download kubeconfig, connect)
2222
Clusters(ClusterCmd),
2323

24-
// Build commands
24+
/// Manage project builds (list, kill)
2525
Builds(BuildCmd),
2626

27-
// Add on commands
27+
/// Manage add-ons (list, restart)
2828
AddOns(AddOnCmd),
2929

30-
/// Local environment commands
30+
/// Run Canine locally with Docker Compose
3131
Local(LocalCmd),
3232
}
3333

@@ -212,7 +212,11 @@ pub struct LocalCmd {
212212
#[derive(Subcommand, Debug)]
213213
pub enum LocalAction {
214214
/// Start local Canine environment
215-
Start,
215+
Start {
216+
/// Port to run the local environment on
217+
#[arg(long, short, default_value = "3000")]
218+
port: u16,
219+
},
216220

217221
/// Show status of local Canine environment
218222
Status,

src/commands/local.rs

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async fn download_docker_compose() -> Result<(), Box<dyn std::error::Error>> {
6969
Ok(())
7070
}
7171

72-
pub async fn handle_start() -> Result<(), Box<dyn std::error::Error>> {
72+
pub async fn handle_start(port: u16) -> Result<(), Box<dyn std::error::Error>> {
7373
if check_docker_compose().is_err() {
7474
println!(
7575
"{} Docker Compose not found. Install Docker Desktop: {}",
@@ -86,11 +86,13 @@ pub async fn handle_start() -> Result<(), Box<dyn std::error::Error>> {
8686

8787
let status = Command::new("docker")
8888
.args(["compose", "up", "-d"])
89+
.env("PORT", port.to_string())
8990
.current_dir(local_dir())
9091
.status()?;
9192

9293
if status.success() {
9394
println!("{} Local Canine environment started", "✓".green());
95+
println!("\n Open {} in your browser", format!("http://localhost:{}", port).cyan());
9496
} else {
9597
println!("{} Failed to start local Canine environment", "✗".red());
9698
std::process::exit(1);
@@ -106,11 +108,80 @@ pub async fn handle_status() -> Result<(), Box<dyn std::error::Error>> {
106108
std::process::exit(1);
107109
}
108110

109-
Command::new("docker")
110-
.args(["compose", "ps"])
111+
// Get structured JSON output from docker compose
112+
let output = Command::new("docker")
113+
.args(["compose", "ps", "--format", "json"])
111114
.current_dir(local_dir())
112-
.status()?;
115+
.output()?;
116+
117+
if !output.status.success() {
118+
println!("{} Failed to get container status", "✗".red());
119+
std::process::exit(1);
120+
}
121+
122+
let stdout = String::from_utf8_lossy(&output.stdout);
123+
124+
// Parse each line as a JSON object (docker outputs one JSON object per line)
125+
let services: Vec<serde_json::Value> = stdout
126+
.lines()
127+
.filter_map(|line| serde_json::from_str(line).ok())
128+
.collect();
129+
130+
if services.is_empty() {
131+
println!("{} No services running", "✗".yellow());
132+
println!(" Run {} to start", "canine local start".cyan());
133+
return Ok(());
134+
}
135+
136+
println!("\n{}", "Local Canine Services".bold());
137+
println!("{}", "─".repeat(50));
138+
139+
for svc in &services {
140+
let name = svc["Service"].as_str().unwrap_or("unknown");
141+
let state = svc["State"].as_str().unwrap_or("unknown");
142+
let health = svc["Health"].as_str().unwrap_or("");
143+
let ports = svc["Publishers"].as_array();
144+
145+
let status_icon = match state {
146+
"running" => "✓".green(),
147+
"exited" => "✗".red(),
148+
_ => "?".yellow(),
149+
};
150+
151+
let health_str = if !health.is_empty() {
152+
format!(" ({})", health)
153+
} else {
154+
String::new()
155+
};
156+
157+
// Extract published ports
158+
let port_str = ports
159+
.map(|p| {
160+
p.iter()
161+
.filter_map(|pub_info| {
162+
let published = pub_info["PublishedPort"].as_u64()?;
163+
if published > 0 {
164+
Some(format!(":{}", published))
165+
} else {
166+
None
167+
}
168+
})
169+
.collect::<Vec<_>>()
170+
.join(", ")
171+
})
172+
.unwrap_or_default();
173+
174+
println!(
175+
"{} {:<20} {:<10}{} {}",
176+
status_icon,
177+
name,
178+
state,
179+
health_str,
180+
port_str.cyan()
181+
);
182+
}
113183

184+
println!();
114185
Ok(())
115186
}
116187

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
4242
},
4343

4444
Namespace::Local(cmd) => match cmd.action {
45-
LocalAction::Start => {
46-
commands::local::handle_start().await?;
45+
LocalAction::Start { port } => {
46+
commands::local::handle_start(port).await?;
4747
}
4848
LocalAction::Status => {
4949
commands::local::handle_status().await?;

0 commit comments

Comments
 (0)