Skip to main content

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}