Skip to content

Commit ccc3873

Browse files
maltesanderclaude
andcommitted
refactor: Vendor Java properties writer into framework module
Add a vendored framework module (mirroring stackable_operator::v2) and move the Java .properties writer into framework/writer.rs, backed by the same java-properties crate as product_config::writer so the rendered output is byte-identical. Swap the zoo.cfg / security.properties rendering in zk_controller from product_config::writer to the local writer. This removes the first product-config touch point. Properties only; the Hadoop XML writer from the hdfs-operator source is not needed here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c53b6a2 commit ccc3873

7 files changed

Lines changed: 97 additions & 5 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const_format = "0.2"
2020
fnv = "1.0"
2121
futures = { version = "0.3", features = ["compat"] }
2222
indoc = "2.0"
23+
java-properties = "2.0"
2324
pin-project = "1.1"
2425
semver = "1.0"
2526
serde = { version = "1.0", features = ["derive"] }

rust/operator-binary/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const_format.workspace = true
1919
fnv.workspace = true
2020
futures.workspace = true
2121
indoc.workspace = true
22+
java-properties.workspace = true
2223
pin-project.workspace = true
2324
semver.workspace = true
2425
serde.workspace = true
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! Vendored framework code shared between Stackable operators.
2+
//!
3+
//! This module mirrors `stackable_operator::v2` (currently only available on the
4+
//! `smooth-operator` branch of operator-rs). It is vendored here so the
5+
//! zookeeper-operator can act as a second consumer that validates whether the
6+
//! abstractions generalize, before they are promoted into operator-rs proper.
7+
//! Once the upstream `v2` API has stabilized, these modules should be replaced
8+
//! by direct usage of `stackable_operator::v2`.
9+
10+
pub mod writer;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! Writer for Java `.properties` files (e.g. ZooKeeper's `zoo.cfg` and
2+
//! `security.properties`).
3+
//!
4+
//! Vendored from the `product-config` crate's `writer` module so the operator no
5+
//! longer depends on `product-config` for rendering. Backed by the same
6+
//! `java-properties` crate, so the rendered output is byte-identical to the
7+
//! previous `product_config::writer::to_java_properties_string`.
8+
9+
use std::io::Write;
10+
11+
use java_properties::{PropertiesError, PropertiesWriter};
12+
use snafu::{ResultExt, Snafu};
13+
14+
#[derive(Debug, Snafu)]
15+
pub enum PropertiesWriterError {
16+
#[snafu(display("failed to create properties file"))]
17+
Properties { source: PropertiesError },
18+
19+
#[snafu(display("failed to convert properties file byte array to UTF-8"))]
20+
FromUtf8 { source: std::string::FromUtf8Error },
21+
}
22+
23+
/// Creates a common Java properties file string in the format:
24+
/// `property_1=value_1\nproperty_2=value_2\n`.
25+
pub fn to_java_properties_string<'a, T>(properties: T) -> Result<String, PropertiesWriterError>
26+
where
27+
T: Iterator<Item = (&'a String, &'a Option<String>)>,
28+
{
29+
let mut output = Vec::new();
30+
write_java_properties(&mut output, properties)?;
31+
String::from_utf8(output).context(FromUtf8Snafu)
32+
}
33+
34+
/// Writes Java properties to the given writer. A `None` value is written as an
35+
/// empty value (`key=`).
36+
fn write_java_properties<'a, W, T>(writer: W, properties: T) -> Result<(), PropertiesWriterError>
37+
where
38+
W: Write,
39+
T: Iterator<Item = (&'a String, &'a Option<String>)>,
40+
{
41+
let mut writer = PropertiesWriter::new(writer);
42+
for (k, v) in properties {
43+
let property_value = v.as_deref().unwrap_or_default();
44+
writer.write(k, property_value).context(PropertiesSnafu)?;
45+
}
46+
writer.flush().context(PropertiesSnafu)?;
47+
Ok(())
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use std::collections::BTreeMap;
53+
54+
use super::*;
55+
56+
fn props(pairs: &[(&str, Option<&str>)]) -> String {
57+
let map: BTreeMap<String, Option<String>> = pairs
58+
.iter()
59+
.map(|(k, v)| (k.to_string(), v.map(str::to_string)))
60+
.collect();
61+
to_java_properties_string(map.iter()).unwrap()
62+
}
63+
64+
#[test]
65+
fn java_properties_renders_key_value() {
66+
assert_eq!(props(&[("a", Some("1")), ("b", Some("2"))]), "a=1\nb=2\n");
67+
}
68+
69+
#[test]
70+
fn java_properties_renders_none_as_empty() {
71+
assert_eq!(props(&[("none", None)]), "none=\n");
72+
}
73+
74+
#[test]
75+
fn java_properties_escapes_colon_in_value() {
76+
assert_eq!(
77+
props(&[("url", Some("file://this/location/file.abc"))]),
78+
"url=file\\://this/location/file.abc\n"
79+
);
80+
}
81+
}

rust/operator-binary/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ mod command;
4343
mod config;
4444
pub mod crd;
4545
mod discovery;
46+
mod framework;
4647
mod listener;
4748
mod operations;
4849
mod product_logging;

rust/operator-binary/src/zk_controller.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ use std::{
1010
use const_format::concatcp;
1111
use fnv::FnvHasher;
1212
use indoc::formatdoc;
13-
use product_config::{
14-
ProductConfigManager,
15-
types::PropertyNameKind,
16-
writer::{PropertiesWriterError, to_java_properties_string},
17-
};
13+
use product_config::{ProductConfigManager, types::PropertyNameKind};
1814
use snafu::{OptionExt, ResultExt, Snafu};
1915
use stackable_operator::{
2016
builder::{
@@ -87,6 +83,7 @@ use crate::{
8783
v1alpha1::{self, ZookeeperServerRoleConfig},
8884
},
8985
discovery::{self, build_discovery_configmap},
86+
framework::writer::{PropertiesWriterError, to_java_properties_string},
9087
listener::{build_role_listener, role_listener_name},
9188
operations::{graceful_shutdown::add_graceful_shutdown_config, pdb::add_pdbs},
9289
product_logging::extend_role_group_config_map,

0 commit comments

Comments
 (0)