1use std::{
2 ffi::OsString,
3 path::{Path, PathBuf},
4};
5
6use clap::{Parser, Subcommand};
7
8use crate::client::{load_balancing, worker::WorkerType};
9
10fn is_plumber(dir: &Path) -> bool {
11 let plumber = dir.join("plumber.R");
12 let plumber_entrypoint = dir.join("entrypoint.R");
13 plumber.exists() || plumber_entrypoint.exists()
14}
15
16fn is_shiny(dir: &Path) -> bool {
17 let shiny_app = dir.join("app.R");
18 let shiny_ui = dir.join("ui.R");
19 let shiny_server = dir.join("server.R");
20 shiny_app.exists() || (shiny_ui.exists() && shiny_server.exists())
21}
22
23#[derive(clap::ValueEnum, Debug, Clone, Copy)]
24enum ServerType {
25 Plumber,
26 Shiny,
27 QuartoShiny,
28 Auto,
29}
30
31#[derive(clap::ValueEnum, Debug, Clone, Copy)]
32pub enum Strategy {
33 RoundRobin,
35 IpHash,
37 CookieHash,
41 Rps,
43}
44
45impl From<Strategy> for load_balancing::Strategy {
46 fn from(value: Strategy) -> Self {
47 match value {
48 Strategy::RoundRobin => load_balancing::Strategy::RoundRobin,
49 Strategy::IpHash => load_balancing::Strategy::IpHash,
50 Strategy::CookieHash => load_balancing::Strategy::CookieHash,
51 Strategy::Rps => load_balancing::Strategy::Rps,
52 }
53 }
54}
55
56#[derive(clap::ValueEnum, Debug, Clone, Copy)]
57pub enum IpFrom {
58 Client,
59 XForwardedFor,
60 XRealIp,
61}
62
63impl From<IpFrom> for load_balancing::IpExtractor {
64 fn from(value: IpFrom) -> Self {
65 match value {
66 IpFrom::Client => load_balancing::IpExtractor::ClientAddr,
67 IpFrom::XForwardedFor => load_balancing::IpExtractor::XForwardedFor,
68 IpFrom::XRealIp => load_balancing::IpExtractor::XRealIp,
69 }
70 }
71}
72
73#[derive(clap::ValueEnum, Debug, Clone, Copy, Default)]
74pub enum Shutdown {
75 Graceful,
76 #[default]
77 Immediate,
78}
79
80#[derive(Parser, Debug)]
81pub struct StartArgs {
82 #[arg(short, long, env = "FAUCET_WORKERS", default_value_t = num_cpus::get())]
84 pub workers: usize,
85
86 #[arg(short, long, env = "FAUCET_STRATEGY", default_value = "round-robin")]
88 pub strategy: Strategy,
89
90 #[arg(short, long, env = "FAUCET_TYPE", default_value = "auto")]
92 type_: ServerType,
93
94 #[arg(short, long, env = "FAUCET_DIR", default_value = ".")]
97 pub dir: PathBuf,
98
99 #[arg(long, short, env = "FAUCET_APP_DIR", default_value = None)]
101 pub app_dir: Option<String>,
102
103 #[arg(long, short, env = "FAUCET_QMD", default_value = None)]
105 pub qmd: Option<PathBuf>,
106
107 #[arg(long, env = "FAUCET_MAX_RPS", default_value = None)]
109 pub max_rps: Option<f64>,
110}
111
112#[derive(Parser, Debug)]
113pub struct RouterArgs {
114 #[arg(
116 long,
117 short,
118 env = "FAUCET_ROUTER_CONF",
119 default_value = "./frouter.toml"
120 )]
121 pub conf: PathBuf,
122}
123
124#[derive(Subcommand, Debug)]
125pub enum Commands {
126 #[command(name = "start")]
128 Start(StartArgs),
129 #[command(name = "router")]
131 Router(RouterArgs),
132}
133
134#[derive(Debug, Clone, Copy, clap::ValueEnum)]
135pub enum PgSslMode {
136 Disable,
137 Prefer,
138 Require,
139 VerifyCa,
140 VerifyFull,
141}
142
143impl PgSslMode {
144 pub fn as_str(self) -> &'static str {
145 match self {
146 Self::Disable => "disable",
147 Self::Prefer => "prefer",
148 Self::Require => "require",
149 Self::VerifyCa => "verify-ca",
150 Self::VerifyFull => "verify-full",
151 }
152 }
153}
154
155#[derive(Parser)]
165#[command(author, version, verbatim_doc_comment)]
166pub struct Args {
167 #[command(subcommand)]
168 pub command: Commands,
169
170 #[arg(long, env = "FAUCET_HOST", default_value = "127.0.0.1:3838")]
172 pub host: String,
173
174 #[arg(short, long, env = "FAUCET_IP_FROM", default_value = "client")]
177 pub ip_from: IpFrom,
178
179 #[arg(long, short, env = "FAUCET_RSCRIPT", default_value = "Rscript")]
181 pub rscript: OsString,
182
183 #[arg(long, short, env = "FAUCET_QUARTO", default_value = "quarto")]
185 pub quarto: OsString,
186
187 #[arg(long, short, env = "FAUCET_LOG_FILE", default_value = None)]
189 pub log_file: Option<PathBuf>,
190
191 #[arg(long, short, env = "FAUCET_MAX_LOG_FILE_SIZE", default_value = None, value_parser = |s: &str| parse_size::parse_size(s))]
192 pub max_log_file_size: Option<u64>,
194
195 #[arg(long, env = "FAUCET_SHUTDOWN", default_value = "immediate")]
197 pub shutdown: Shutdown,
198
199 #[arg(long, env = "FAUCET_MAX_MESSAGE_SIZE", default_value = None, value_parser = |s: &str| parse_size::parse_size(s))]
201 pub max_message_size: Option<u64>,
202
203 #[arg(long, env = "FAUCET_TELEMETRY_POSTGRES_STRING", default_value = None)]
205 pub pg_con_string: Option<String>,
206
207 #[arg(long, env = "FAUCET_TELEMETRY_POSTGRES_SSLCERT", default_value = None)]
209 pub pg_sslcert: Option<PathBuf>,
210
211 #[arg(
213 long,
214 env = "FAUCET_TELEMETRY_POSTGRES_SSLMODE",
215 default_value = "prefer"
216 )]
217 pub pg_sslmode: PgSslMode,
218
219 #[arg(long, env = "FAUCET_TELEMETRY_NAMESPACE", default_value = "faucet")]
221 pub telemetry_namespace: String,
222
223 #[arg(long, env = "FAUCET_TELEMETRY_VERSION", default_value = None)]
225 pub telemetry_version: Option<String>,
226}
227
228impl StartArgs {
229 pub fn server_type(&self) -> WorkerType {
230 match self.type_ {
231 ServerType::Plumber => WorkerType::Plumber,
232 ServerType::Shiny => WorkerType::Shiny,
233 ServerType::QuartoShiny => WorkerType::QuartoShiny,
234 ServerType::Auto => {
235 if is_plumber(&self.dir) {
236 WorkerType::Plumber
237 } else if is_shiny(&self.dir) {
238 WorkerType::Shiny
239 } else {
240 log::error!(target: "faucet", "Could not determine worker type. Please specify with --type.");
241 std::process::exit(1);
242 }
243 }
244 }
245 }
246}