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}
64
65/// `faucet schedule` arguments.
66#[cfg(feature = "schedule")]
67#[derive(Debug, Parser)]
68pub struct ScheduleArgs {
69    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a `schedule:`
70    /// block. If omitted, auto-discover `faucet.yaml` / `.yml` / `.json` in cwd.
71    pub config: Option<PathBuf>,
72    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
73    /// Defaults to `.env` in cwd if present.
74    #[arg(long, conflicts_with = "no_env_file")]
75    pub env_file: Option<PathBuf>,
76    /// Skip auto-loading `.env` from cwd.
77    #[arg(long)]
78    pub no_env_file: bool,
79    /// Run exactly one pipeline run immediately, then exit (ignores cron timing).
80    /// Useful for platform-driven invocation (k8s CronJob / systemd OnCalendar).
81    #[arg(long)]
82    pub once: bool,
83}
84
85/// `faucet serve` arguments.
86#[cfg(feature = "serve")]
87#[derive(Debug, Clone, Parser)]
88pub struct ServeArgs {
89    /// Bind address. Defaults to loopback; set 0.0.0.0:PORT to expose externally.
90    #[arg(long, env = "FAUCET_SERVE_LISTEN", default_value = "127.0.0.1:8080")]
91    pub listen: String,
92    /// Bearer token required on /v1/* requests. Prefer the env var (avoids `ps` leakage).
93    #[arg(long, env = "FAUCET_SERVE_AUTH_TOKEN", conflicts_with = "no_auth")]
94    pub auth_token: Option<String>,
95    /// Explicitly disable authentication. Required if no token is set, so an
96    /// unauthenticated server is never accidental.
97    #[arg(long)]
98    pub no_auth: bool,
99    /// Max pipeline runs executing at once. Default: min(16, cpu count).
100    #[arg(long)]
101    pub max_concurrent_runs: Option<usize>,
102    /// Max queued (not-yet-running) runs before POST /v1/runs returns 429.
103    /// Default: 8 × max-concurrent-runs.
104    #[arg(long)]
105    pub max_queued_runs: Option<usize>,
106    /// Workspace-default config merged under every submitted run.
107    #[arg(long)]
108    pub default_config: Option<std::path::PathBuf>,
109    /// Run-history backend URL: omitted = in-memory; postgres://… ; sqlite:… .
110    #[arg(long)]
111    pub history: Option<String>,
112    /// CORS allow-list origin (repeatable). Omitted = CORS disabled.
113    #[arg(long)]
114    pub cors_origin: Vec<String>,
115    /// Max POST /v1/runs body size in bytes (413 on exceed).
116    #[arg(long, default_value_t = 1_048_576)]
117    pub body_limit_bytes: usize,
118    /// SIGTERM/SIGINT drain window in seconds.
119    #[arg(long, default_value_t = 60)]
120    pub shutdown_grace_secs: u64,
121    /// Retain terminal run records this long (seconds).
122    #[arg(long, default_value_t = 604_800)]
123    pub retain_terminal_runs_secs: u64,
124    /// Idempotency-key replay window (seconds).
125    #[arg(long, default_value_t = 86_400)]
126    pub idempotency_retention_secs: u64,
127    /// Run-ownership lease TTL in seconds (multi-instance orphan fencing). A run
128    /// is owned by the instance executing it and its lease is heartbeated at
129    /// ~⅓ of this interval; only a run whose lease has expired (owner presumed
130    /// dead) is recovered as failed. Make this comfortably larger than expected
131    /// GC/IO stalls so a healthy-but-slow instance is never falsely reclaimed.
132    /// Only relevant with a persistent (postgres/sqlite) history backend.
133    #[arg(long, default_value_t = 30)]
134    pub lease_ttl_secs: u64,
135    /// Per-probe timeout for `doctor_first` preflight (seconds).
136    #[arg(long, default_value_t = 10)]
137    pub probe_timeout_secs: u64,
138    /// Path to a `.env` file loaded for the server's own startup interpolation.
139    #[arg(long, conflicts_with = "no_env_file")]
140    pub env_file: Option<std::path::PathBuf>,
141    /// Skip auto-loading `.env` from cwd at startup.
142    #[arg(long)]
143    pub no_env_file: bool,
144}
145
146/// `faucet run` arguments.
147#[derive(Debug, Parser)]
148pub struct RunArgs {
149    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config.
150    /// If omitted (and `--from-env` is not set), auto-discover
151    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in the current directory.
152    /// Mutually exclusive with `--from-env`.
153    #[arg(conflicts_with = "from_env")]
154    pub config: Option<PathBuf>,
155    /// Build the pipeline entirely from `FAUCET_*` environment variables —
156    /// no YAML required. See `cli/README.md` for the variable schema.
157    #[arg(long)]
158    pub from_env: bool,
159    /// Path to a `.env` file to load before reading variables. Works in both
160    /// YAML mode (for `${env:VAR}` interpolation) and `--from-env` mode.
161    /// When omitted, `.env` in the current directory is auto-loaded if present.
162    /// Existing process-env values always win over file-supplied ones.
163    #[arg(long, conflicts_with = "no_env_file")]
164    pub env_file: Option<PathBuf>,
165    /// Skip auto-loading `.env` from the current directory.
166    #[arg(long)]
167    pub no_env_file: bool,
168    /// Stop after fetching from the source — write nothing to the sink.
169    #[arg(long)]
170    pub dry_run: bool,
171    /// Stop after writing this many records to the sink. Default: unlimited.
172    #[arg(long)]
173    pub limit: Option<usize>,
174    /// Override the state-store directory (file backend only).
175    #[arg(long)]
176    pub state_path: Option<PathBuf>,
177    /// Override the `${now.*}` interpolation clock (RFC3339 like
178    /// `2026-01-31T00:00:00Z`, or a date `2026-01-31`). Default: process start (UTC).
179    /// Use for backfills.
180    #[arg(long)]
181    pub clock: Option<String>,
182}
183
184/// `faucet validate` arguments.
185#[derive(Debug, Parser)]
186pub struct ValidateArgs {
187    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
188    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
189    pub config: Option<PathBuf>,
190    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
191    /// Defaults to `.env` in cwd if present.
192    #[arg(long, conflicts_with = "no_env_file")]
193    pub env_file: Option<PathBuf>,
194    /// Skip auto-loading `.env` from cwd.
195    #[arg(long)]
196    pub no_env_file: bool,
197    /// Validate grammar and structure only — skip fetching from secrets
198    /// managers (no network / credentials needed).
199    #[arg(long)]
200    pub no_secrets: bool,
201}
202
203/// `faucet schema` arguments.
204#[derive(Debug, Parser)]
205pub struct SchemaArgs {
206    #[command(subcommand)]
207    pub target: SchemaTarget,
208}
209
210/// Schema subcommand target — which connector or system component to describe.
211#[derive(Debug, Subcommand)]
212pub enum SchemaTarget {
213    /// JSON Schema for a source connector config.
214    Source {
215        /// Connector name (e.g. `rest`, `graphql`, `postgres`).
216        name: String,
217    },
218    /// JSON Schema for a sink connector config.
219    Sink {
220        /// Connector name (e.g. `jsonl`, `bigquery`, `postgres`).
221        name: String,
222    },
223    /// JSON Schema for a transform's inline config.
224    Transform {
225        /// Transform name (e.g. `flatten`, `keys_case`, `cast`).
226        /// Run `faucet list` to see what is compiled in.
227        name: String,
228    },
229    /// JSON Schema for the DLQ (Dead Letter Queue) specification.
230    Dlq,
231    /// JSON Schema for the `quality:` block.
232    #[cfg(feature = "quality")]
233    Quality,
234    /// Grammar reference for secrets-manager interpolation directives.
235    Secrets,
236    /// JSON Schema for the `schedule:` block.
237    #[cfg(feature = "schedule")]
238    Schedule,
239}
240
241/// `faucet preview` arguments.
242#[derive(Debug, Parser)]
243pub struct PreviewArgs {
244    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
245    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
246    pub config: Option<PathBuf>,
247    /// Stop after this many records. Default: 10.
248    #[arg(long, default_value_t = 10)]
249    pub limit: usize,
250    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
251    /// Defaults to `.env` in cwd if present.
252    #[arg(long, conflicts_with = "no_env_file")]
253    pub env_file: Option<PathBuf>,
254    /// Skip auto-loading `.env` from cwd.
255    #[arg(long)]
256    pub no_env_file: bool,
257}
258
259/// `faucet init` arguments.
260#[derive(Debug, Parser)]
261pub struct InitArgs {
262    /// Name written into the generated file's `name:` field. Defaults to
263    /// `my-pipeline` when omitted.
264    pub name: Option<String>,
265    /// Source connector kind to scaffold (e.g. `rest`, `postgres`, `s3`).
266    /// Defaults to `rest`. Run `faucet list` to see what is compiled in.
267    #[arg(long)]
268    pub source: Option<String>,
269    /// Sink connector kind to scaffold (e.g. `jsonl`, `bigquery`).
270    /// Defaults to `jsonl`. Run `faucet list` to see what is compiled in.
271    #[arg(long)]
272    pub sink: Option<String>,
273    /// Output file path. Defaults to `pipeline.yaml`.
274    #[arg(long, short = 'o', default_value = "pipeline.yaml")]
275    pub output: PathBuf,
276    /// Overwrite the output file if it already exists.
277    #[arg(long)]
278    pub force: bool,
279    /// Prompt for the source and sink kinds interactively instead of using
280    /// `--source` / `--sink`. Requires the `cli-interactive` build feature
281    /// and a TTY on stdin; falls back to the arg-driven path otherwise.
282    #[arg(long)]
283    pub interactive: bool,
284    /// Name of the template under which to register the scaffolded source
285    /// and sink. The generated config uses `pipeline.sources.<TEMPLATE>` and
286    /// `pipeline.sinks.<TEMPLATE>`. Defaults to `default` so a matrix row
287    /// without a `ref:` field still resolves through the new schema.
288    #[arg(long, default_value = "default")]
289    pub template: String,
290}