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}