faucet_cli/cli.rs
1//! Argument parser shared by `main.rs` and the integration tests.
2
3use clap::{Parser, Subcommand};
4use std::path::PathBuf;
5
6/// `faucet` — config-driven runner for faucet-stream pipelines.
7#[derive(Debug, Parser)]
8#[command(name = "faucet", version, about, long_about = None)]
9pub struct Cli {
10 /// Override the global log level (also honors `FAUCET_LOG`).
11 #[arg(long, global = true, env = "FAUCET_LOG", default_value = "info")]
12 pub log_level: String,
13
14 #[command(subcommand)]
15 pub command: Command,
16}
17
18/// Top-level subcommands.
19#[derive(Debug, Subcommand)]
20pub enum Command {
21 /// Execute a pipeline config end-to-end.
22 Run(RunArgs),
23 /// Parse + validate a pipeline config without running it.
24 Validate(ValidateArgs),
25 /// Print the JSON Schema for a specific connector.
26 Schema(SchemaArgs),
27 /// List every compiled-in source, sink, and transform with a one-line description.
28 List,
29 /// Run only the source side and print records to stdout (uses the stdout sink).
30 Preview(PreviewArgs),
31 /// Scaffold a starter `pipeline.yaml` to disk.
32 Init(InitArgs),
33 /// Probe every connector in a config (auth / network / permissions) and
34 /// print a green/red checklist. Exits non-zero if any probe fails.
35 Doctor(DoctorArgs),
36 /// Run a pipeline on a cron schedule (long-running; Ctrl-C / SIGTERM to stop).
37 #[cfg(feature = "schedule")]
38 Schedule(ScheduleArgs),
39 /// Run a long-running HTTP control plane (submit / poll / cancel pipeline runs).
40 #[cfg(feature = "serve")]
41 Serve(ServeArgs),
42}
43
44/// `faucet doctor` arguments.
45#[derive(Debug, Parser)]
46pub struct DoctorArgs {
47 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
48 /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
49 pub config: Option<PathBuf>,
50 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
51 /// Defaults to `.env` in cwd if present.
52 #[arg(long, conflicts_with = "no_env_file")]
53 pub env_file: Option<PathBuf>,
54 /// Skip auto-loading `.env` from cwd.
55 #[arg(long)]
56 pub no_env_file: bool,
57 /// Per-probe timeout in seconds.
58 #[arg(long, default_value_t = 10)]
59 pub timeout_secs: u64,
60 /// Emit machine-readable JSON instead of the human checklist.
61 #[arg(long)]
62 pub json: bool,
63 /// Select a named overlay from the config's `profiles:` block and deep-merge
64 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
65 #[arg(long, env = "FAUCET_PROFILE")]
66 pub profile: Option<String>,
67}
68
69/// `faucet schedule` arguments.
70#[cfg(feature = "schedule")]
71#[derive(Debug, Parser)]
72pub struct ScheduleArgs {
73 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a `schedule:`
74 /// block. If omitted, auto-discover `faucet.yaml` / `.yml` / `.json` in cwd.
75 pub config: Option<PathBuf>,
76 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
77 /// Defaults to `.env` in cwd if present.
78 #[arg(long, conflicts_with = "no_env_file")]
79 pub env_file: Option<PathBuf>,
80 /// Skip auto-loading `.env` from cwd.
81 #[arg(long)]
82 pub no_env_file: bool,
83 /// Run exactly one pipeline run immediately, then exit (ignores cron timing).
84 /// Useful for platform-driven invocation (k8s CronJob / systemd OnCalendar).
85 #[arg(long)]
86 pub once: bool,
87 /// Select a named overlay from the config's `profiles:` block and deep-merge
88 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
89 #[arg(long, env = "FAUCET_PROFILE")]
90 pub profile: Option<String>,
91}
92
93/// `faucet serve` arguments.
94#[cfg(feature = "serve")]
95#[derive(Debug, Clone, Parser)]
96pub struct ServeArgs {
97 /// Bind address. Defaults to loopback; set 0.0.0.0:PORT to expose externally.
98 #[arg(long, env = "FAUCET_SERVE_LISTEN", default_value = "127.0.0.1:8080")]
99 pub listen: String,
100 /// Bearer token required on /v1/* requests. Prefer the env var (avoids `ps` leakage).
101 #[arg(long, env = "FAUCET_SERVE_AUTH_TOKEN", conflicts_with = "no_auth")]
102 pub auth_token: Option<String>,
103 /// Explicitly disable authentication. Required if no token is set, so an
104 /// unauthenticated server is never accidental.
105 #[arg(long)]
106 pub no_auth: bool,
107 /// Max pipeline runs executing at once. Default: min(16, cpu count).
108 #[arg(long)]
109 pub max_concurrent_runs: Option<usize>,
110 /// Max queued (not-yet-running) runs before POST /v1/runs returns 429.
111 /// Default: 8 × max-concurrent-runs.
112 #[arg(long)]
113 pub max_queued_runs: Option<usize>,
114 /// Workspace-default config merged under every submitted run.
115 #[arg(long)]
116 pub default_config: Option<std::path::PathBuf>,
117 /// Run-history backend URL: omitted = in-memory; postgres://… ; sqlite:… .
118 #[arg(long)]
119 pub history: Option<String>,
120 /// CORS allow-list origin (repeatable). Omitted = CORS disabled.
121 #[arg(long)]
122 pub cors_origin: Vec<String>,
123 /// Max POST /v1/runs body size in bytes (413 on exceed).
124 #[arg(long, default_value_t = 1_048_576)]
125 pub body_limit_bytes: usize,
126 /// SIGTERM/SIGINT drain window in seconds.
127 #[arg(long, default_value_t = 60)]
128 pub shutdown_grace_secs: u64,
129 /// Retain terminal run records this long (seconds).
130 #[arg(long, default_value_t = 604_800)]
131 pub retain_terminal_runs_secs: u64,
132 /// Idempotency-key replay window (seconds).
133 #[arg(long, default_value_t = 86_400)]
134 pub idempotency_retention_secs: u64,
135 /// Run-ownership lease TTL in seconds (multi-instance orphan fencing). A run
136 /// is owned by the instance executing it and its lease is heartbeated at
137 /// ~⅓ of this interval; only a run whose lease has expired (owner presumed
138 /// dead) is recovered as failed. Make this comfortably larger than expected
139 /// GC/IO stalls so a healthy-but-slow instance is never falsely reclaimed.
140 /// Only relevant with a persistent (postgres/sqlite) history backend.
141 #[arg(long, default_value_t = 30)]
142 pub lease_ttl_secs: u64,
143 /// Per-probe timeout for `doctor_first` preflight (seconds).
144 #[arg(long, default_value_t = 10)]
145 pub probe_timeout_secs: u64,
146 /// Path to a `.env` file loaded for the server's own startup interpolation.
147 #[arg(long, conflicts_with = "no_env_file")]
148 pub env_file: Option<std::path::PathBuf>,
149 /// Skip auto-loading `.env` from cwd at startup.
150 #[arg(long)]
151 pub no_env_file: bool,
152 /// Disable serving the embedded web console (only meaningful in a build that
153 /// includes the `serve-ui` feature; the API is unaffected).
154 #[arg(long)]
155 pub no_ui: bool,
156 /// Enable clustered execution: run a claim loop that pulls Pending runs from
157 /// the shared history DB so N instances pull-balance and fail over. Requires
158 /// a postgres/sqlite --history backend.
159 #[arg(long)]
160 pub cluster: bool,
161 /// Claim-loop poll interval (seconds) in cluster mode. Also the
162 /// cross-instance cancel-propagation lag. Must be > 0.
163 #[arg(long, default_value_t = 2)]
164 pub cluster_poll_secs: u64,
165 /// Max failover re-runs of an orphaned run before it is marked Failed
166 /// (poison). Must be > 0.
167 #[arg(long, default_value_t = 3)]
168 pub cluster_max_attempts: u32,
169 /// Path to a triggers file (YAML/JSON) defining event-driven pipeline
170 /// triggers (object-arrival / webhook / queue-depth). Requires a build with
171 /// the `triggers` feature. See `faucet schema triggers`.
172 #[arg(long)]
173 pub triggers: Option<std::path::PathBuf>,
174}
175
176/// `faucet run` arguments.
177#[derive(Debug, Parser)]
178pub struct RunArgs {
179 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config.
180 /// If omitted (and `--from-env` is not set), auto-discover
181 /// `faucet.yaml` / `faucet.yml` / `faucet.json` in the current directory.
182 /// Mutually exclusive with `--from-env`.
183 #[arg(conflicts_with = "from_env")]
184 pub config: Option<PathBuf>,
185 /// Build the pipeline entirely from `FAUCET_*` environment variables —
186 /// no YAML required. See `cli/README.md` for the variable schema.
187 #[arg(long)]
188 pub from_env: bool,
189 /// Path to a `.env` file to load before reading variables. Works in both
190 /// YAML mode (for `${env:VAR}` interpolation) and `--from-env` mode.
191 /// When omitted, `.env` in the current directory is auto-loaded if present.
192 /// Existing process-env values always win over file-supplied ones.
193 #[arg(long, conflicts_with = "no_env_file")]
194 pub env_file: Option<PathBuf>,
195 /// Skip auto-loading `.env` from the current directory.
196 #[arg(long)]
197 pub no_env_file: bool,
198 /// Stop after fetching from the source — write nothing to the sink.
199 #[arg(long)]
200 pub dry_run: bool,
201 /// Stop after writing this many records to the sink. Default: unlimited.
202 #[arg(long)]
203 pub limit: Option<usize>,
204 /// Override the state-store directory (file backend only).
205 #[arg(long)]
206 pub state_path: Option<PathBuf>,
207 /// Override the `${now.*}` interpolation clock (RFC3339 like
208 /// `2026-01-31T00:00:00Z`, or a date `2026-01-31`). Default: process start (UTC).
209 /// Use for backfills.
210 #[arg(long)]
211 pub clock: Option<String>,
212 /// Select a named overlay from the config's `profiles:` block and deep-merge
213 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
214 /// Not applicable in `--from-env` mode (no config file to compose).
215 #[arg(long, env = "FAUCET_PROFILE")]
216 pub profile: Option<String>,
217}
218
219/// `faucet validate` arguments.
220#[derive(Debug, Parser)]
221pub struct ValidateArgs {
222 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
223 /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
224 pub config: Option<PathBuf>,
225 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
226 /// Defaults to `.env` in cwd if present.
227 #[arg(long, conflicts_with = "no_env_file")]
228 pub env_file: Option<PathBuf>,
229 /// Skip auto-loading `.env` from cwd.
230 #[arg(long)]
231 pub no_env_file: bool,
232 /// Validate grammar and structure only — skip fetching from secrets
233 /// managers (no network / credentials needed).
234 #[arg(long)]
235 pub no_secrets: bool,
236 /// Select a named overlay from the config's `profiles:` block and deep-merge
237 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
238 #[arg(long, env = "FAUCET_PROFILE")]
239 pub profile: Option<String>,
240 /// Print the fully-composed config (after extends/!include/profile, before
241 /// `${...}` interpolation) and exit. For debugging composition precedence.
242 /// `--no-secrets` is redundant here (no interpolation or secret fetch occurs).
243 #[arg(long)]
244 pub show_composed: bool,
245}
246
247/// `faucet schema` arguments.
248#[derive(Debug, Parser)]
249pub struct SchemaArgs {
250 #[command(subcommand)]
251 pub target: SchemaTarget,
252}
253
254/// Schema subcommand target — which connector or system component to describe.
255#[derive(Debug, Subcommand)]
256pub enum SchemaTarget {
257 /// JSON Schema for a source connector config.
258 Source {
259 /// Connector name (e.g. `rest`, `graphql`, `postgres`).
260 name: String,
261 },
262 /// JSON Schema for a sink connector config.
263 Sink {
264 /// Connector name (e.g. `jsonl`, `bigquery`, `postgres`).
265 name: String,
266 },
267 /// JSON Schema for a transform's inline config.
268 Transform {
269 /// Transform name (e.g. `flatten`, `keys_case`, `cast`).
270 /// Run `faucet list` to see what is compiled in.
271 name: String,
272 },
273 /// JSON Schema for the DLQ (Dead Letter Queue) specification.
274 Dlq,
275 /// JSON Schema for the `quality:` block.
276 #[cfg(feature = "quality")]
277 Quality,
278 /// Grammar reference for secrets-manager interpolation directives.
279 Secrets,
280 /// JSON Schema for the `schedule:` block.
281 #[cfg(feature = "schedule")]
282 Schedule,
283 /// JSON Schema for the `lineage:` (OpenLineage) block.
284 #[cfg(feature = "lineage")]
285 Lineage,
286 /// JSON Schema for the `--triggers` file (event-driven pipeline triggers).
287 #[cfg(feature = "triggers")]
288 Triggers,
289}
290
291/// `faucet preview` arguments.
292#[derive(Debug, Parser)]
293pub struct PreviewArgs {
294 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
295 /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
296 pub config: Option<PathBuf>,
297 /// Stop after this many records. Default: 10.
298 #[arg(long, default_value_t = 10)]
299 pub limit: usize,
300 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
301 /// Defaults to `.env` in cwd if present.
302 #[arg(long, conflicts_with = "no_env_file")]
303 pub env_file: Option<PathBuf>,
304 /// Skip auto-loading `.env` from cwd.
305 #[arg(long)]
306 pub no_env_file: bool,
307 /// Select a named overlay from the config's `profiles:` block and deep-merge
308 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
309 #[arg(long, env = "FAUCET_PROFILE")]
310 pub profile: Option<String>,
311}
312
313/// `faucet init` arguments.
314#[derive(Debug, Parser)]
315pub struct InitArgs {
316 /// Name written into the generated file's `name:` field. Defaults to
317 /// `my-pipeline` when omitted.
318 pub name: Option<String>,
319 /// Source connector kind to scaffold (e.g. `rest`, `postgres`, `s3`).
320 /// Defaults to `rest`. Run `faucet list` to see what is compiled in.
321 #[arg(long)]
322 pub source: Option<String>,
323 /// Sink connector kind to scaffold (e.g. `jsonl`, `bigquery`).
324 /// Defaults to `jsonl`. Run `faucet list` to see what is compiled in.
325 #[arg(long)]
326 pub sink: Option<String>,
327 /// Output file path. Defaults to `pipeline.yaml`.
328 #[arg(long, short = 'o', default_value = "pipeline.yaml")]
329 pub output: PathBuf,
330 /// Overwrite the output file if it already exists.
331 #[arg(long)]
332 pub force: bool,
333 /// Prompt for the source and sink kinds interactively instead of using
334 /// `--source` / `--sink`. Requires the `cli-interactive` build feature
335 /// and a TTY on stdin; falls back to the arg-driven path otherwise.
336 #[arg(long)]
337 pub interactive: bool,
338 /// Name of the template under which to register the scaffolded source
339 /// and sink. The generated config uses `pipeline.sources.<TEMPLATE>` and
340 /// `pipeline.sinks.<TEMPLATE>`. Defaults to `default` so a matrix row
341 /// without a `ref:` field still resolves through the new schema.
342 #[arg(long, default_value = "default")]
343 pub template: String,
344}