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    /// 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}