1pub mod client;
22pub mod cmd;
23pub mod config;
24pub mod exit;
25pub mod model;
26pub mod output;
27
28use std::path::PathBuf;
29
30use clap::{Parser, Subcommand};
31
32use crate::client::CellosClient;
33use crate::exit::{CtlError, CtlResult};
34use crate::output::OutputFormat;
35
36#[derive(Parser, Debug)]
38#[command(
39 name = "cellctl",
40 version,
41 about = "kubectl-style CLI for CellOS",
42 long_about = None,
43)]
44struct Cli {
45 #[arg(long, global = true, env = "CELLCTL_SERVER")]
47 server: Option<String>,
48
49 #[arg(long, global = true, env = "CELLCTL_TOKEN", hide_env_values = true)]
51 token: Option<String>,
52
53 #[command(subcommand)]
54 cmd: Cmd,
55}
56
57#[derive(Subcommand, Debug)]
58enum Cmd {
59 Apply {
61 #[arg(short = 'f', long = "file")]
63 file: PathBuf,
64 },
65 Get {
67 #[command(subcommand)]
68 what: GetWhat,
69 },
70 Describe {
72 #[command(subcommand)]
73 what: DescribeWhat,
74 },
75 Delete {
77 #[command(subcommand)]
78 what: DeleteWhat,
79 },
80 Logs {
82 cell: String,
84 #[arg(long, short = 'f')]
86 follow: bool,
87 #[arg(long)]
89 tail: Option<usize>,
90 },
91 Events {
93 #[arg(long)]
95 formation: Option<String>,
96 #[arg(long, short = 'f')]
98 follow: bool,
99 },
100 Rollout {
102 #[command(subcommand)]
103 what: RolloutWhat,
104 },
105 Diff {
107 #[arg(short = 'f', long = "file")]
108 file: PathBuf,
109 },
110 Config {
112 #[command(subcommand)]
113 what: ConfigWhat,
114 },
115 Version,
117 Webui {
119 #[arg(long)]
121 open: bool,
122 #[arg(long, value_enum, default_value = "auto")]
125 bind: cmd::webui::BindMode,
126 },
127}
128
129#[derive(Subcommand, Debug)]
130enum GetWhat {
131 Formations {
133 #[arg(long, short = 'o', default_value = "table")]
134 output: String,
135 },
136 Cells {
138 #[arg(long)]
139 formation: Option<String>,
140 #[arg(long, short = 'o', default_value = "table")]
141 output: String,
142 },
143}
144
145#[derive(Subcommand, Debug)]
146enum DescribeWhat {
147 Formation { name: String },
149 Cell { name: String },
151}
152
153#[derive(Subcommand, Debug)]
154enum DeleteWhat {
155 Formation {
157 name: String,
158 #[arg(long, short = 'y')]
160 yes: bool,
161 },
162}
163
164#[derive(Subcommand, Debug)]
165enum RolloutWhat {
166 Status {
168 name: String,
169 #[arg(long)]
171 timeout: Option<u64>,
172 },
173}
174
175#[derive(Subcommand, Debug)]
176enum ConfigWhat {
177 SetServer { url: String },
179 SetToken { token: String },
181 Show,
183}
184
185pub fn run() {
190 if std::env::var_os("RUST_LOG").is_some() {
193 let _ = tracing_subscriber::fmt()
194 .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
195 .with_writer(std::io::stderr)
196 .try_init();
197 }
198
199 let cli = Cli::parse();
200
201 let rt = match tokio::runtime::Builder::new_current_thread()
204 .enable_all()
205 .build()
206 {
207 Ok(rt) => rt,
208 Err(e) => CtlError::usage(format!("init tokio runtime: {e}")).exit(),
209 };
210
211 match rt.block_on(dispatch(cli)) {
212 Ok(()) => {}
213 Err(e) => e.exit(),
214 }
215}
216
217async fn dispatch(cli: Cli) -> CtlResult<()> {
218 let mut cfg = config::load().unwrap_or_default();
220 if let Some(s) = cli.server {
221 cfg.server_url = Some(s);
222 }
223 if let Some(t) = cli.token {
224 cfg.api_token = Some(t);
225 }
226
227 match cli.cmd {
230 Cmd::Config { what } => return run_config(what),
231 Cmd::Version => {
232 let client = CellosClient::new(&cfg)?;
233 return cmd::version::run(&client).await;
234 }
235 Cmd::Webui { open, bind } => {
236 return cmd::webui::run(&cfg, open, bind).await;
237 }
238 _ => {}
239 }
240
241 let client = CellosClient::new(&cfg)?;
242
243 match cli.cmd {
244 Cmd::Apply { file } => cmd::apply::run(&client, &file).await,
245 Cmd::Get { what } => match what {
246 GetWhat::Formations { output } => {
247 let fmt: OutputFormat = output.parse()?;
248 cmd::get::formations(&client, fmt).await
249 }
250 GetWhat::Cells { formation, output } => {
251 let fmt: OutputFormat = output.parse()?;
252 cmd::get::cells(&client, formation.as_deref(), fmt).await
253 }
254 },
255 Cmd::Describe { what } => match what {
256 DescribeWhat::Formation { name } => cmd::describe::formation(&client, &name).await,
257 DescribeWhat::Cell { name } => cmd::describe::cell(&client, &name).await,
258 },
259 Cmd::Delete { what } => match what {
260 DeleteWhat::Formation { name, yes } => {
261 cmd::delete::formation(&client, &name, yes).await
262 }
263 },
264 Cmd::Logs { cell, follow, tail } => cmd::logs::run(&client, &cell, follow, tail).await,
265 Cmd::Events { formation, follow } => {
266 cmd::events::run(&client, formation.as_deref(), follow).await
267 }
268 Cmd::Rollout { what } => match what {
269 RolloutWhat::Status { name, timeout } => {
270 cmd::rollout::status(&client, &name, timeout).await
271 }
272 },
273 Cmd::Diff { file } => cmd::diff::run(&client, &file).await,
274 Cmd::Config { .. } | Cmd::Version | Cmd::Webui { .. } => {
275 unreachable!("handled above")
276 }
277 }
278}
279
280fn run_config(what: ConfigWhat) -> CtlResult<()> {
281 match what {
282 ConfigWhat::SetServer { url } => cmd::config_cmd::set_server(&url),
283 ConfigWhat::SetToken { token } => cmd::config_cmd::set_token(&token),
284 ConfigWhat::Show => cmd::config_cmd::show(),
285 }
286}