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