hubcio commented on code in PR #4350:
URL: https://github.com/apache/iggy/pull/4350#discussion_r4143323676
##########
core/configs_derive/src/config_env.rs:
##########
@@ -359,6 +389,48 @@ fn generate_struct_impl(
}
};
+ let own_template_entries = mappings.iter().map(|mapping| {
+ let env_suffix = &mapping.env_suffix;
+ quote! {
+ configs::EnvVarTemplate { env_name: #env_suffix, max_elements: &[]
}
+ }
+ });
+ let nested_template_extends = nested_fields.iter().map(|info| {
+ let ty = &info.element_type;
+ let segment = &info.field_env_segment;
+ if info.is_vec {
+ let max_elements = info.max_elements;
+ quote! {
+ for template in <#ty as
configs::ConfigEnvMappings>::env_templates() {
+ let env_name = Box::leak(format!("{}_<N>_{}", #segment,
template.env_name).into_boxed_str());
+ let limits = Box::leak(std::iter::once(#max_elements)
+ .chain(template.max_elements.iter().copied())
+ .collect::<Vec<_>>()
+ .into_boxed_slice());
+ all_templates.push(configs::EnvVarTemplate { env_name,
max_elements: limits });
+ }
+ }
+ } else {
+ quote! {
+ for template in <#ty as
configs::ConfigEnvMappings>::env_templates() {
+ let env_name = Box::leak(format!("{}_{}", #segment,
template.env_name).into_boxed_str());
+ all_templates.push(configs::EnvVarTemplate {
Review Comment:
critical: this literal still builds `EnvVarTemplate` without `max_elements`,
so `configs` and every crate that depends on it fail with E0063. add
`max_elements: template.max_elements,` after line 418, like in 7e5172adf.
##########
core/connectors/runtime/src/main.rs:
##########
@@ -61,13 +64,28 @@ mod stream;
mod transform;
const VERSION: &str = env!("CARGO_PKG_VERSION");
+const CONNECTOR_PREFIX_PATTERN: &str = "IGGY_CONNECTORS";
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
#[derive(Parser, Debug)]
#[command(author = "Apache Iggy", version)]
-struct Args {}
+ /// Print supported configuration environment variables and exit.
Review Comment:
warning: this doc block is on `struct Args`, so clap uses it as the program
description, and rustfmt, clippy and rustdoc reject it. move the text into a
`long_help` string on `list_config_env_vars` - a doc block still fails rustdoc
on `<N>`.
##########
core/server/src/main.rs:
##########
@@ -103,3 +107,24 @@ fn main() -> Result<(), ServerError> {
info!("server shutdown complete");
Ok(())
}
+
+fn print_config_env_vars() {
+ let mut names: Vec<_> = ServerConfig::env_templates()
+ .iter()
+ .map(|template| template.env_name)
+ .chain([
+ "IGGY_CONFIG_PATH",
+ "IGGY_DISPLAY_CONFIG",
+ "IGGY_ENV_PATH",
+ "IGGY_ROOT_PASSWORD",
+ "IGGY_ROOT_USERNAME",
+ "IGGY_SHARD_EVENT_INTERVAL",
+ "IGGY_SHARD_RUNTIME_CAPACITY",
+ ])
+ .collect();
+ names.sort_unstable();
+ names.dedup();
+ for name in names {
+ println!("{name}");
Review Comment:
warning: `println!` panics when stdout closes early, so `iggy-server
--list-config-env-vars | head -1` exits 101 with a panic message. lock stdout
once, write with `writeln!`, and treat `BrokenPipe` as a clean exit 0.
also at `core/ai/mcp/src/main.rs:84`.
##########
core/server/src/main.rs:
##########
@@ -103,3 +107,24 @@ fn main() -> Result<(), ServerError> {
info!("server shutdown complete");
Ok(())
}
+
+fn print_config_env_vars() {
+ let mut names: Vec<_> = ServerConfig::env_templates()
+ .iter()
+ .map(|template| template.env_name)
+ .chain([
Review Comment:
warning: these names are hand-copied from `SERVER_PROCESS_ENV_VARS`, so the
listing drifts on the next edit. split that constant into runtime and scan-only
lists as #4322 asks, and give each binary an exported runtime list to chain.
also at `core/ai/mcp/src/main.rs:75` and
`core/connectors/runtime/src/main.rs:156`, which copy `IGNORED_ENV_VARS` and
`DISPLAY_CONFIG_ENV`.
##########
core/server/src/args.rs:
##########
@@ -87,6 +87,23 @@ For more information, visit:
https://iggy.apache.org/docs/introduction/getting-s
// variable names and paths must stay unquoted rather than wear rustdoc
backticks.
#[allow(clippy::doc_markdown)]
pub struct Args {
+ /// Print supported configuration environment variables and exit.
+ ///
+ /// Lists all supported IGGY_* environment variable names and templates,
+ /// sorted and deduplicated. Template syntax:
+ /// - `<N>` represents vector indices (0-255 for most fields)
Review Comment:
warning: "0-255 for most fields" hides that `ADVERTISED_ADDRESSES_<N>` only
takes 0-15 (`cluster.rs:466`), and the listing prints no limits. name that cap
in the help and document the flag in the configuration docs, as #4322 asks.
##########
core/integration/tests/config_env_listing.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use assert_cmd::Command;
+use std::collections::HashSet;
+
+#[test]
+fn config_env_listing_exits_before_startup_for_each_binary() {
Review Comment:
warning: seven of 17 #4322 acceptance items have no test, including
`--fresh`, no side effects, and identical output across conditions. run
`--fresh --list-config-env-vars` over a data dir with a sentinel file, and
assert the dir and the output stay the same.
##########
core/server/src/args.rs:
##########
@@ -87,6 +87,23 @@ For more information, visit:
https://iggy.apache.org/docs/introduction/getting-s
// variable names and paths must stay unquoted rather than wear rustdoc
backticks.
#[allow(clippy::doc_markdown)]
pub struct Args {
+ /// Print supported configuration environment variables and exit.
+ ///
+ /// Lists all supported IGGY_* environment variable names and templates,
+ /// sorted and deduplicated. Template syntax:
+ /// - `<N>` represents vector indices (0-255 for most fields)
+ /// - `<KEY>` represents connector keys (uppercased)
Review Comment:
nit: `iggy-server` never prints `<KEY>` or `<FIELD>`, and these backticks
show up raw in `--help`, against the note at line 86. drop those two lines and
move the text into a `long_help` string without backticks.
##########
core/integration/tests/config_env_listing.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use assert_cmd::Command;
+use std::collections::HashSet;
+
+#[test]
+fn config_env_listing_exits_before_startup_for_each_binary() {
+ for (binary, config_env, dotenv_env) in [
+ ("iggy-server", "IGGY_CONFIG_PATH", "IGGY_ENV_PATH"),
+ (
+ "iggy-connectors",
+ "IGGY_CONNECTORS_CONFIG_PATH",
+ "IGGY_CONNECTORS_ENV_PATH",
+ ),
+ ("iggy-mcp", "IGGY_MCP_CONFIG_PATH", "IGGY_MCP_ENV_PATH"),
+ ] {
+ let directory = tempfile::tempdir().expect("temporary directory");
+ let output = Command::cargo_bin(binary)
Review Comment:
warning: no spawn here sets a timeout, so if the early exit regresses, the
test hangs on a real server and writes `local_data` into `core/integration`.
set a short `.timeout()` and a temp `current_dir` on every spawn.
##########
core/server/src/main.rs:
##########
@@ -103,3 +107,24 @@ fn main() -> Result<(), ServerError> {
info!("server shutdown complete");
Ok(())
}
+
+fn print_config_env_vars() {
Review Comment:
simplification: the server, MCP and connectors each carry their own collect,
sort, dedup and print, with two different write-error behaviors. one `configs`
helper that sorts, dedups and writes through one stdout lock removes about 20
lines and leaves one error policy.
also at `core/ai/mcp/src/main.rs:71` and
`core/connectors/runtime/src/main.rs:151`.
##########
core/integration/tests/config_env_listing.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use assert_cmd::Command;
+use std::collections::HashSet;
+
+#[test]
+fn config_env_listing_exits_before_startup_for_each_binary() {
+ for (binary, config_env, dotenv_env) in [
+ ("iggy-server", "IGGY_CONFIG_PATH", "IGGY_ENV_PATH"),
+ (
+ "iggy-connectors",
+ "IGGY_CONNECTORS_CONFIG_PATH",
+ "IGGY_CONNECTORS_ENV_PATH",
+ ),
+ ("iggy-mcp", "IGGY_MCP_CONFIG_PATH", "IGGY_MCP_ENV_PATH"),
+ ] {
+ let directory = tempfile::tempdir().expect("temporary directory");
+ let output = Command::cargo_bin(binary)
+ .expect("binary should be built")
+ .current_dir(directory.path())
+ .arg("--list-config-env-vars")
+ .env(config_env, directory.path().join("missing-config.toml"))
+ .env(dotenv_env, directory.path().join("missing.env"))
+ .output()
+ .expect("listing command should run");
+
+ assert!(output.status.success(), "{binary} failed");
+ assert!(output.stderr.is_empty(), "{binary} wrote to stderr");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: Vec<_> = stdout.lines().collect();
+ assert!(!names.is_empty(), "{binary} returned no variables");
+ assert!(
+ names.windows(2).all(|pair| pair[0] < pair[1]),
+ "{binary} output must be sorted and deduplicated"
+ );
+ assert!(names.iter().all(|name| name.starts_with("IGGY_")));
+ }
+}
+
+#[test]
+fn config_env_listing_includes_vector_index_templates() {
+ // Verify that vector fields are correctly represented with <N> placeholder
+ let output = Command::cargo_bin("iggy-server")
+ .expect("binary should be built")
+ .arg("--list-config-env-vars")
+ .output()
+ .expect("listing command should run");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: HashSet<_> = stdout.lines().collect();
+
+ // Cluster nodes should have indexed templates
+ assert!(
+ names
+ .iter()
+ .any(|name| name.contains("IGGY_CLUSTER_NODES_<N>_")),
+ "server should list cluster node index templates"
+ );
+
+ // Verify nested vector expansion (nested <N> placeholders)
+ assert!(
+ names
+ .iter()
+ .any(|name|
name.contains("IGGY_CLUSTER_NODES_<N>_ADVERTISED_ADDRESSES_<N>_")),
+ "server should list nested vector templates"
+ );
+}
+
+#[test]
+fn config_env_listing_includes_connector_templates() {
+ // Verify that connector SINK/SOURCE templates are correctly formatted
+ let output = Command::cargo_bin("iggy-connectors")
+ .expect("binary should be built")
+ .arg("--list-config-env-vars")
+ .output()
+ .expect("listing command should run");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: HashSet<_> = stdout.lines().collect();
+
+ // Both SINK and SOURCE templates should be present
+ assert!(
+ names
+ .iter()
+ .any(|name| name.contains("IGGY_CONNECTORS_SINK_<KEY>_")),
+ "connectors should list SINK templates with <KEY> placeholder"
+ );
+ assert!(
+ names
+ .iter()
+ .any(|name| name.contains("IGGY_CONNECTORS_SOURCE_<KEY>_")),
+ "connectors should list SOURCE templates with <KEY> placeholder"
+ );
+
+ // Plugin config templates should be present
+ assert!(
+ names
+ .iter()
+ .any(|name|
name.contains("IGGY_CONNECTORS_SINK_<KEY>_PLUGIN_CONFIG_<FIELD>")),
+ "connectors should list SINK plugin config templates"
+ );
+ assert!(
+ names
+ .iter()
+ .any(|name|
name.contains("IGGY_CONNECTORS_SOURCE_<KEY>_PLUGIN_CONFIG_<FIELD>")),
+ "connectors should list SOURCE plugin config templates"
+ );
+}
+
+#[test]
+fn config_env_listing_deduplicates_output() {
Review Comment:
simplification: the strict `pair[0] < pair[1]` assertion at line 49 already
proves that no name repeats, for the same three binaries. delete this test.
##########
core/connectors/runtime/src/main.rs:
##########
@@ -61,13 +64,28 @@ mod stream;
mod transform;
const VERSION: &str = env!("CARGO_PKG_VERSION");
+const CONNECTOR_PREFIX_PATTERN: &str = "IGGY_CONNECTORS";
Review Comment:
simplification: this is one more spelling of the prefix that
`ConnectorsRuntimeConfig::ENV_PREFIX` holds, next to line 174 and
`local_provider.rs:351` and `:825`. one prefix function built on `ENV_PREFIX`,
called from here and `local_provider.rs`, keeps the listing in step with what
the runtime reads.
##########
core/configs_derive/src/config_env.rs:
##########
@@ -359,6 +389,48 @@ fn generate_struct_impl(
}
};
+ let own_template_entries = mappings.iter().map(|mapping| {
Review Comment:
warning: this walk rebuilds the names of the mapping walk above by hand, and
no test ties them, so one edit can desync the listing. add a `configs` test
that expands every template by `max_elements` and compares the set with
`env_mappings()`.
##########
core/integration/tests/mod.rs:
##########
@@ -37,6 +37,7 @@ mod cli;
// Raw-wire spec tests for VSR session continuity across a node restart
// (IGGY-137).
mod cluster;
+mod config_env_listing;
Review Comment:
warning: cargo also builds `tests/config_env_listing.rs` as its own test
target (this crate has no `autotests = false`), so every test in it compiles
and runs twice. move it to `tests/config_env_listing/mod.rs` and keep this
`mod` line.
##########
core/connectors/runtime/src/main.rs:
##########
@@ -117,10 +135,62 @@ fn print_ascii_art(text: &str) {
println!("{}", figure.unwrap());
}
-#[tokio::main]
-async fn main() -> Result<(), RuntimeError> {
+fn main() -> Result<(), RuntimeError> {
+ let args = Args::parse();
+ if args.list_config_env_vars {
+ print_config_env_vars();
+ return Ok(());
+ }
capture_allowed_cpus();
- Args::parse();
+ Builder::new_multi_thread()
+ .enable_all()
+ .build()?
+ .block_on(run())
+}
+
+fn print_config_env_vars() {
+ // Collect static template names from ConfigEnv derived configs
+ let names: Vec<&'static str> = ConnectorsRuntimeConfig::env_templates()
+ .iter()
+ .map(|template| template.env_name)
+ .chain([
+ "IGGY_CONNECTORS_CONFIG_PATH",
+ "IGGY_CONNECTORS_ENV_PATH",
+ "IGGY_DISPLAY_CONFIG",
+ ])
+ .collect();
+
+ // Collect sink and source connector templates (reusable across all
connectors)
+ let sink_source_templates: Vec<String> = [
Review Comment:
warning: these `<KEY>` templates print unconditionally, but only the local
connectors provider reads them (`local_provider.rs:328`), and nothing tells the
operator. say in the flag help that `<KEY>` overrides need the local provider
and that `<FIELD>` cannot be `FORMAT`, which `plugin_config_format` owns.
##########
core/connectors/runtime/src/main.rs:
##########
@@ -117,10 +135,62 @@ fn print_ascii_art(text: &str) {
println!("{}", figure.unwrap());
}
-#[tokio::main]
-async fn main() -> Result<(), RuntimeError> {
+fn main() -> Result<(), RuntimeError> {
+ let args = Args::parse();
+ if args.list_config_env_vars {
+ print_config_env_vars();
+ return Ok(());
+ }
capture_allowed_cpus();
- Args::parse();
+ Builder::new_multi_thread()
+ .enable_all()
+ .build()?
+ .block_on(run())
+}
+
+fn print_config_env_vars() {
+ // Collect static template names from ConfigEnv derived configs
+ let names: Vec<&'static str> = ConnectorsRuntimeConfig::env_templates()
+ .iter()
+ .map(|template| template.env_name)
+ .chain([
+ "IGGY_CONNECTORS_CONFIG_PATH",
+ "IGGY_CONNECTORS_ENV_PATH",
+ "IGGY_DISPLAY_CONFIG",
+ ])
+ .collect();
+
+ // Collect sink and source connector templates (reusable across all
connectors)
+ let sink_source_templates: Vec<String> = [
+ ("SINK", SinkConfig::env_templates()),
+ ("SOURCE", SourceConfig::env_templates()),
+ ]
+ .iter()
+ .flat_map(|(kind, templates)| {
+ templates
+ .iter()
+ .map(move |template|
format!("{CONNECTOR_PREFIX_PATTERN}_{kind}_<KEY>_{}", template.env_name))
Review Comment:
nit: rustfmt rewrites this 106-char line and the doc block at line 74, so
`rust/fmt` stays red after the build fix. run `cargo fmt --all` and `prek run`
before the next push.
##########
core/configs_derive/src/config_env.rs:
##########
@@ -180,7 +180,7 @@ fn generate_enum_impl(
})
.collect();
- let tag_mapping = tag.map(|tag_name| {
+ let tag_mapping = tag.clone().map(|tag_name| {
Review Comment:
nit: this clone exists only because line 224 consumes `tag` again, and both
closures repeat `to_uppercase()`. use `tag.as_deref()` here, or build both
token streams from one uppercased segment.
##########
core/connectors/runtime/src/main.rs:
##########
@@ -117,10 +135,62 @@ fn print_ascii_art(text: &str) {
println!("{}", figure.unwrap());
}
-#[tokio::main]
-async fn main() -> Result<(), RuntimeError> {
+fn main() -> Result<(), RuntimeError> {
+ let args = Args::parse();
+ if args.list_config_env_vars {
+ print_config_env_vars();
+ return Ok(());
+ }
capture_allowed_cpus();
- Args::parse();
+ Builder::new_multi_thread()
+ .enable_all()
+ .build()?
+ .block_on(run())
+}
+
+fn print_config_env_vars() {
+ // Collect static template names from ConfigEnv derived configs
+ let names: Vec<&'static str> = ConnectorsRuntimeConfig::env_templates()
+ .iter()
+ .map(|template| template.env_name)
+ .chain([
+ "IGGY_CONNECTORS_CONFIG_PATH",
+ "IGGY_CONNECTORS_ENV_PATH",
+ "IGGY_DISPLAY_CONFIG",
+ ])
+ .collect();
+
+ // Collect sink and source connector templates (reusable across all
connectors)
+ let sink_source_templates: Vec<String> = [
+ ("SINK", SinkConfig::env_templates()),
+ ("SOURCE", SourceConfig::env_templates()),
+ ]
+ .iter()
+ .flat_map(|(kind, templates)| {
+ templates
+ .iter()
+ .map(move |template|
format!("{CONNECTOR_PREFIX_PATTERN}_{kind}_<KEY>_{}", template.env_name))
+ .chain(std::iter::once(format!(
+ "IGGY_CONNECTORS_{kind}_<KEY>_PLUGIN_CONFIG_<FIELD>"
+ )))
+ })
+ .collect();
+
+ // Combine static and dynamic templates, sort, deduplicate
+ let mut all_names: Vec<String> = names
+ .into_iter()
+ .map(|s| s.to_string())
+ .chain(sink_source_templates)
+ .collect();
+ all_names.sort_unstable();
+ all_names.dedup();
+
+ for name in all_names {
+ let _ = writeln!(std::io::stdout(), "{name}");
Review Comment:
warning: `let _ =` drops every write error and keeps looping, so
`--list-config-env-vars > /dev/full` exits 0 with nothing written. stop at the
first write error and exit non-zero, except on `BrokenPipe`.
##########
core/integration/tests/config_env_listing.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use assert_cmd::Command;
+use std::collections::HashSet;
+
+#[test]
+fn config_env_listing_exits_before_startup_for_each_binary() {
+ for (binary, config_env, dotenv_env) in [
+ ("iggy-server", "IGGY_CONFIG_PATH", "IGGY_ENV_PATH"),
+ (
+ "iggy-connectors",
+ "IGGY_CONNECTORS_CONFIG_PATH",
+ "IGGY_CONNECTORS_ENV_PATH",
+ ),
+ ("iggy-mcp", "IGGY_MCP_CONFIG_PATH", "IGGY_MCP_ENV_PATH"),
+ ] {
+ let directory = tempfile::tempdir().expect("temporary directory");
+ let output = Command::cargo_bin(binary)
+ .expect("binary should be built")
+ .current_dir(directory.path())
+ .arg("--list-config-env-vars")
+ .env(config_env, directory.path().join("missing-config.toml"))
+ .env(dotenv_env, directory.path().join("missing.env"))
+ .output()
+ .expect("listing command should run");
+
+ assert!(output.status.success(), "{binary} failed");
+ assert!(output.stderr.is_empty(), "{binary} wrote to stderr");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: Vec<_> = stdout.lines().collect();
+ assert!(!names.is_empty(), "{binary} returned no variables");
+ assert!(
+ names.windows(2).all(|pair| pair[0] < pair[1]),
+ "{binary} output must be sorted and deduplicated"
+ );
+ assert!(names.iter().all(|name| name.starts_with("IGGY_")));
+ }
+}
+
+#[test]
+fn config_env_listing_includes_vector_index_templates() {
+ // Verify that vector fields are correctly represented with <N> placeholder
+ let output = Command::cargo_bin("iggy-server")
+ .expect("binary should be built")
+ .arg("--list-config-env-vars")
+ .output()
+ .expect("listing command should run");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: HashSet<_> = stdout.lines().collect();
+
+ // Cluster nodes should have indexed templates
+ assert!(
+ names
+ .iter()
+ .any(|name| name.contains("IGGY_CLUSTER_NODES_<N>_")),
+ "server should list cluster node index templates"
+ );
+
+ // Verify nested vector expansion (nested <N> placeholders)
+ assert!(
+ names
+ .iter()
+ .any(|name|
name.contains("IGGY_CLUSTER_NODES_<N>_ADVERTISED_ADDRESSES_<N>_")),
+ "server should list nested vector templates"
+ );
+}
+
+#[test]
+fn config_env_listing_includes_connector_templates() {
+ // Verify that connector SINK/SOURCE templates are correctly formatted
+ let output = Command::cargo_bin("iggy-connectors")
+ .expect("binary should be built")
+ .arg("--list-config-env-vars")
+ .output()
+ .expect("listing command should run");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: HashSet<_> = stdout.lines().collect();
+
+ // Both SINK and SOURCE templates should be present
+ assert!(
Review Comment:
warning: these checks also match the two hardcoded
`..._PLUGIN_CONFIG_<FIELD>` names in the connectors `main.rs`, so they pass
even when the sink and source templates are empty. assert names that come from
the mappings, like `IGGY_CONNECTORS_SINK_<KEY>_ENABLED`.
##########
core/integration/tests/config_env_listing.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use assert_cmd::Command;
+use std::collections::HashSet;
+
+#[test]
+fn config_env_listing_exits_before_startup_for_each_binary() {
+ for (binary, config_env, dotenv_env) in [
+ ("iggy-server", "IGGY_CONFIG_PATH", "IGGY_ENV_PATH"),
+ (
+ "iggy-connectors",
+ "IGGY_CONNECTORS_CONFIG_PATH",
+ "IGGY_CONNECTORS_ENV_PATH",
+ ),
+ ("iggy-mcp", "IGGY_MCP_CONFIG_PATH", "IGGY_MCP_ENV_PATH"),
+ ] {
+ let directory = tempfile::tempdir().expect("temporary directory");
+ let output = Command::cargo_bin(binary)
+ .expect("binary should be built")
+ .current_dir(directory.path())
+ .arg("--list-config-env-vars")
+ .env(config_env, directory.path().join("missing-config.toml"))
+ .env(dotenv_env, directory.path().join("missing.env"))
+ .output()
+ .expect("listing command should run");
+
+ assert!(output.status.success(), "{binary} failed");
+ assert!(output.stderr.is_empty(), "{binary} wrote to stderr");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: Vec<_> = stdout.lines().collect();
+ assert!(!names.is_empty(), "{binary} returned no variables");
+ assert!(
+ names.windows(2).all(|pair| pair[0] < pair[1]),
+ "{binary} output must be sorted and deduplicated"
+ );
+ assert!(names.iter().all(|name| name.starts_with("IGGY_")));
+ }
+}
+
+#[test]
+fn config_env_listing_includes_vector_index_templates() {
+ // Verify that vector fields are correctly represented with <N> placeholder
+ let output = Command::cargo_bin("iggy-server")
+ .expect("binary should be built")
+ .arg("--list-config-env-vars")
+ .output()
+ .expect("listing command should run");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: HashSet<_> = stdout.lines().collect();
+
+ // Cluster nodes should have indexed templates
+ assert!(
+ names
+ .iter()
+ .any(|name| name.contains("IGGY_CLUSTER_NODES_<N>_")),
+ "server should list cluster node index templates"
+ );
+
+ // Verify nested vector expansion (nested <N> placeholders)
+ assert!(
+ names
+ .iter()
+ .any(|name|
name.contains("IGGY_CLUSTER_NODES_<N>_ADVERTISED_ADDRESSES_<N>_")),
+ "server should list nested vector templates"
+ );
+}
+
+#[test]
+fn config_env_listing_includes_connector_templates() {
+ // Verify that connector SINK/SOURCE templates are correctly formatted
+ let output = Command::cargo_bin("iggy-connectors")
+ .expect("binary should be built")
+ .arg("--list-config-env-vars")
+ .output()
+ .expect("listing command should run");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: HashSet<_> = stdout.lines().collect();
+
+ // Both SINK and SOURCE templates should be present
+ assert!(
+ names
+ .iter()
+ .any(|name| name.contains("IGGY_CONNECTORS_SINK_<KEY>_")),
+ "connectors should list SINK templates with <KEY> placeholder"
+ );
+ assert!(
+ names
+ .iter()
+ .any(|name| name.contains("IGGY_CONNECTORS_SOURCE_<KEY>_")),
+ "connectors should list SOURCE templates with <KEY> placeholder"
+ );
+
+ // Plugin config templates should be present
+ assert!(
+ names
+ .iter()
+ .any(|name|
name.contains("IGGY_CONNECTORS_SINK_<KEY>_PLUGIN_CONFIG_<FIELD>")),
+ "connectors should list SINK plugin config templates"
+ );
+ assert!(
+ names
+ .iter()
+ .any(|name|
name.contains("IGGY_CONNECTORS_SOURCE_<KEY>_PLUGIN_CONFIG_<FIELD>")),
+ "connectors should list SOURCE plugin config templates"
+ );
+}
+
+#[test]
+fn config_env_listing_deduplicates_output() {
+ // Verify no duplicates in output across multiple runs
+ for binary in ["iggy-server", "iggy-connectors", "iggy-mcp"] {
+ let output = Command::cargo_bin(binary)
+ .expect("binary should be built")
+ .arg("--list-config-env-vars")
+ .output()
+ .expect("listing command should run");
+
+ let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+ let names: Vec<_> = stdout.lines().collect();
+ let unique_names: HashSet<_> = names.iter().collect();
+
+ assert_eq!(
+ names.len(),
+ unique_names.len(),
+ "{binary} output contains duplicates"
+ );
+ }
+}
+
+#[test]
+fn config_env_listing_exits_before_runtime_with_invalid_env_values() {
+ // Verify that invalid environment values don't cause failures
+ let output = Command::cargo_bin("iggy-server")
+ .expect("binary should be built")
+ .arg("--list-config-env-vars")
+ .env("IGGY_TCP_PORT", "not_a_number") // Invalid port value
Review Comment:
nit: `TcpConfig` has no port field, so `IGGY_TCP_PORT` names no setting and
the `// Invalid port value` comment is wrong. drop the line - the invalid
`IGGY_HTTP_ADDRESS` already covers the case.
##########
core/configs_derive/src/config_env.rs:
##########
@@ -359,6 +389,48 @@ fn generate_struct_impl(
}
};
+ let own_template_entries = mappings.iter().map(|mapping| {
Review Comment:
simplification: two generated walks build the same names, this one with
`<N>` and the expanded one at line 294. give templates `config_path` and
`is_secret`, and build `env_mappings()` by expanding them in one `configs`
function, so they cannot drift.
##########
core/connectors/runtime/src/main.rs:
##########
@@ -117,10 +135,62 @@ fn print_ascii_art(text: &str) {
println!("{}", figure.unwrap());
}
-#[tokio::main]
-async fn main() -> Result<(), RuntimeError> {
+fn main() -> Result<(), RuntimeError> {
+ let args = Args::parse();
+ if args.list_config_env_vars {
+ print_config_env_vars();
+ return Ok(());
+ }
capture_allowed_cpus();
- Args::parse();
+ Builder::new_multi_thread()
+ .enable_all()
+ .build()?
+ .block_on(run())
+}
+
+fn print_config_env_vars() {
+ // Collect static template names from ConfigEnv derived configs
+ let names: Vec<&'static str> = ConnectorsRuntimeConfig::env_templates()
Review Comment:
simplification: `names`, `sink_source_templates` and `all_names` are three
collections for one sorted list. collect everything into one `Vec<String>` with
a single iterator chain, then sort and dedup it - about 10 lines less.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]