krabka_format/lib.rs
1//! Formats the log directories of a krabka broker node.
2//!
3//! A `KRaft` node will not boot against an unformatted directory: the broker
4//! treats one as operator error and aborts startup. Formatting seeds Kafka's
5//! `meta.properties`, the bootstrap records, and the singleton `VotersRecord`,
6//! and can provision seed SCRAM credentials at the same time. One run formats
7//! every directory of the node, as `kafka-storage format` does, and writes the
8//! cluster and directory ids in Kafka's 22-character base64 form.
9//! `docs/format-divergences.md` in the repository lists where the command
10//! differs from `kafka-storage format`, and why.
11//!
12//! [`MetaProperties`] reads and writes `meta.properties` byte for byte as
13//! Kafka does. The broker reads the file through it, and so can any tool that
14//! has to find the identity of a formatted directory.
15//!
16//! [`run_with_records`] takes further [`MetadataRecord`]s from the caller and
17//! seeds them into the same stream. A restore tool that rebuilds a cluster from
18//! tiered-storage archives hands over the topic and partition records it
19//! recovered that way, and the broker then boots with those topics present.
20//!
21//! This is the `krabka format` command from the monorepo's `krabka-cli`. That
22//! crate also drives the gres layer, which is why it could not follow the broker
23//! into this repository; the command itself needs only [`krabka_metadata`] and
24//! [`krabka_security`]. It is a library as well as a binary so `krabka-cli` can
25//! call it rather than carry a second copy.
26
27use {
28 clap::Parser,
29 std::{io, io::Write},
30};
31
32mod format;
33mod ids;
34mod meta_properties;
35
36pub use self::{
37 format::{
38 FAIL_AFTER_ENV, FormatArgs, LATEST_PRODUCTION_METADATA_VERSION, ScramSpec, parse_node_id,
39 run, run_with_records, run_with_records_to,
40 },
41 ids::{ClusterId, DirectoryId, KafkaUuidError, random_uuid},
42 meta_properties::{
43 META_PROPERTIES, META_PROPERTIES_TMP, META_PROPERTIES_VERSION, MetaProperties,
44 MetaPropertiesError, verify_ensemble,
45 },
46};
47/// The seed record type [`run_with_records`] accepts, re-exported so a caller
48/// building a bootstrap stream does not have to name [`krabka_metadata`]
49/// itself.
50pub use krabka_metadata::MetadataRecord;
51
52/// The formatter's command line.
53///
54/// Shared by the binary and by [`run_from_args`], so both accept exactly the
55/// same flags.
56#[derive(Parser)]
57#[command(
58 name = "krabka-format",
59 version,
60 about = "Format the log directories of a node, with optional seed SCRAM credentials",
61 long_about = "Format the log directories of a node, with optional seed SCRAM credentials.\n\n\
62 This is the counterpart of `kafka-storage format`. It takes no --config \
63 file: krabka's broker does not read server.properties, so the directories \
64 come from --log-dir and --metadata-log-dir. Without --metadata-log-dir, \
65 the first --log-dir is the metadata log directory.\n\n\
66 Exit codes: 0 success; 2 a SCRAM iteration count below 4096 or an invalid \
67 command line; 3 a directory that is already formatted, holds foreign files, \
68 or names another cluster; 4 a write failure or an invalid voter set; 5 an \
69 invalid feature or quorum mode."
70)]
71pub struct Cli {
72 /// The formatter's arguments, flattened so they are top-level flags.
73 #[command(flatten)]
74 pub args: FormatArgs,
75}
76
77/// Run the formatter from an argv-style iterator, returning its exit code.
78///
79/// Every broker test that boots a node needs a formatted log directory first.
80/// Calling this beats spawning the binary: a subprocess needs a Cargo working
81/// tree to build from, which a Bazel test sandbox does not have, and the
82/// formatting is setup rather than the thing under test. The binary itself is
83/// covered end to end by `tests/format_smoke.rs`.
84///
85/// Malformed arguments print a diagnostic and return 2. Help and version
86/// requests print to stdout and return 0, without terminating the caller.
87#[krabka_macros::argv_entrypoint]
88pub async fn run_from_args() -> i32 { run_from_args_with_records(argv, Vec::new()).await }
89
90/// Run the formatter from an argv-style iterator, seeding `extra` alongside the
91/// records the flags produce, and return its exit code.
92///
93/// This is the entry point for a tool that materializes a cluster and then has
94/// to hand the formatter the metadata it recovered, such as the topic and
95/// partition records behind a point-in-time restore. [`FormatArgs`] holds
96/// private fields, so an argv is how such a caller states the rest of the
97/// format; [`run_with_records`] documents where `extra` lands in the seed
98/// stream.
99///
100/// Malformed arguments print a diagnostic and return 2. Help and version
101/// requests print to stdout and return 0, without terminating the caller.
102#[krabka_macros::argv_entrypoint]
103pub async fn run_from_args_with_records(extra: Vec<MetadataRecord>) -> i32 {
104 run_from_args_with_records_to(argv, extra, &mut io::stdout()).await
105}
106
107/// [`run_from_args_with_records`], with the progress lines of a successful
108/// format written to `progress` instead of stdout; [`run_with_records_to`]
109/// says why a caller wants that.
110///
111/// Malformed arguments print a diagnostic and return 2. Help and version
112/// requests print to stdout and return 0, without terminating the caller.
113#[krabka_macros::argv_entrypoint]
114pub async fn run_from_args_with_records_to(
115 extra: Vec<MetadataRecord>,
116 progress: &mut (dyn Write + Send),
117) -> i32 {
118 match Cli::try_parse_from(argv) {
119 Ok(cli) => run_with_records_to(cli.args, extra, progress).await,
120 Err(error) => {
121 let _ = error.print();
122 if error.use_stderr() { 2 } else { 0 }
123 }
124 }
125}