1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use clap::clap_derive::Parser;
4use cronback_client::{
5 ClientBuilder,
6 Response,
7 BASE_URL_ENV,
8 DEFAULT_BASE_URL,
9};
10use once_cell::sync::OnceCell;
11use url::Url;
12
13#[cfg(feature = "admin")]
14use crate::admin;
15#[cfg(feature = "admin")]
16use crate::admin::RunAdminCommand;
17use crate::client::WrappedClient;
18use crate::ui::FancyToString;
19use crate::{runs, triggers, whoami, Command};
20
21const CRONBACK_SECRET_TOKEN_VAR: &str = "CRONBACK_SECRET_TOKEN";
22
23#[derive(Parser, Debug, Clone)]
24pub struct Cli {
26 #[clap(flatten)]
27 pub common: CommonOptions,
28 #[clap(flatten)]
29 pub verbose: clap_verbosity_flag::Verbosity,
30
31 #[command(subcommand)]
32 pub command: CliCommand,
33}
34
35#[derive(Parser, Debug, Clone)]
36pub struct CommonOptions {
37 #[arg(long, global = true)]
38 pub localhost: bool,
40 #[arg(long, global = true, value_name = "URL", env(BASE_URL_ENV))]
41 pub base_url: Option<Url>,
42 #[arg(
45 long,
46 value_name = "TOKEN",
47 env(CRONBACK_SECRET_TOKEN_VAR),
48 hide_env_values = true
49 )]
50 pub secret_token: String,
53
54 #[arg(long, global = true)]
55 pub show_meta: bool,
57 #[arg(long, short, global = true)]
59 pub yes: bool,
60}
61
62#[derive(Parser, Debug, Clone)]
63pub enum CliCommand {
64 Triggers {
66 #[command(subcommand)]
67 command: TriggerCommand,
68 },
69 Runs {
71 #[command(subcommand)]
72 command: RunsCommand,
73 },
74 #[command(name = "whoami")]
75 WhoAmI(whoami::WhoAmI),
77
78 #[cfg(feature = "admin")]
80 Admin {
81 #[clap(flatten)]
82 admin_options: admin::AdminOptions,
83 #[command(subcommand)]
84 command: admin::AdminCommand,
85 },
86}
87
88#[derive(Parser, Debug, Clone)]
89pub enum TriggerCommand {
90 #[command(visible_alias = "ls")]
92 List(triggers::List),
93 #[command(visible_alias = "lr")]
95 ListRuns(triggers::ListRuns),
96 Create(triggers::Create),
98 #[command(visible_alias = "v")]
100 View(triggers::View),
101 Cancel(triggers::Cancel),
103 Run(triggers::Run),
105 Pause(triggers::Pause),
107 Resume(triggers::Resume),
109 Delete(triggers::Delete),
111}
112
113#[derive(Parser, Debug, Clone)]
114pub enum RunsCommand {
115 View(runs::View),
117}
118
119impl CommonOptions {
120 pub fn base_url(&self) -> &Url {
121 if self.localhost {
122 static LOCALHOST_URL: OnceCell<Url> = OnceCell::new();
123 LOCALHOST_URL
124 .get_or_init(|| Url::parse("http://localhost:8888").unwrap())
125 } else {
126 self.base_url.as_ref().unwrap_or(&DEFAULT_BASE_URL)
127 }
128 }
129
130 pub fn new_client(&self) -> Result<WrappedClient> {
131 let base_url = self.base_url();
132 let inner = ClientBuilder::new()
133 .base_url(base_url.clone())
134 .context("Error while parsing base url")?
135 .secret_token(self.secret_token.clone())
136 .build()?;
137 Ok(WrappedClient {
138 common_options: self.clone(),
139 inner,
140 })
141 }
142
143 pub fn show_response_meta<T>(&self, response: &Response<T>) {
144 use colored::Colorize;
145 if self.show_meta {
147 eprintln!();
148 eprintln!(
149 "{}",
150 "<<-------------------------------------------------".green()
151 );
152 eprintln!("Path: {}", response.url());
153 eprintln!("Status Code: {}", response.status_code().fancy());
154 eprintln!(
155 "Request Id: {}",
156 response.request_id().clone().unwrap_or_default().green()
157 );
158 eprintln!(
159 "Project Id: {}",
160 response.project_id().clone().unwrap_or_default().green()
161 );
162 eprintln!(
163 "{}",
164 "-------------------------------------------------".green()
165 );
166 eprintln!();
167 }
168 }
169}
170
171#[async_trait]
173impl Command for CliCommand {
174 async fn run<
175 A: tokio::io::AsyncWrite + Send + Sync + Unpin,
176 B: tokio::io::AsyncWrite + Send + Sync + Unpin,
177 >(
178 &self,
179 out: &mut tokio::io::BufWriter<A>,
180 err: &mut tokio::io::BufWriter<B>,
181 common_options: &CommonOptions,
182 ) -> Result<()> {
183 match self {
184 | CliCommand::Triggers { command } => {
185 command.run(out, err, common_options).await
186 }
187 | CliCommand::Runs { command } => {
188 command.run(out, err, common_options).await
189 }
190 #[cfg(feature = "admin")]
191 | CliCommand::Admin {
192 admin_options,
193 command,
194 } => command.run(out, err, common_options, admin_options).await,
195 | CliCommand::WhoAmI(c) => c.run(out, err, common_options).await,
196 }
197 }
198}