@redpanda-data/docs-extensions-and-macros 4.7.4 → 4.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/doc-tools.js +90 -20
- package/package.json +1 -1
- package/tools/property-extractor/Makefile +64 -24
- package/tools/property-extractor/generate-handlebars-docs.js +344 -0
- package/tools/property-extractor/helpers/and.js +10 -0
- package/tools/property-extractor/helpers/eq.js +9 -0
- package/tools/property-extractor/helpers/formatPropertyValue.js +128 -0
- package/tools/property-extractor/helpers/formatUnits.js +26 -0
- package/tools/property-extractor/helpers/index.js +13 -0
- package/tools/property-extractor/helpers/join.js +18 -0
- package/tools/property-extractor/helpers/ne.js +9 -0
- package/tools/property-extractor/helpers/not.js +8 -0
- package/tools/property-extractor/helpers/or.js +10 -0
- package/tools/property-extractor/helpers/renderPropertyExample.js +42 -0
- package/tools/property-extractor/package-lock.json +77 -0
- package/tools/property-extractor/package.json +6 -0
- package/tools/property-extractor/property_extractor.py +1163 -20
- package/tools/property-extractor/requirements.txt +1 -0
- package/tools/property-extractor/templates/deprecated-properties.hbs +25 -0
- package/tools/property-extractor/templates/deprecated-property.hbs +7 -0
- package/tools/property-extractor/templates/property-page.hbs +22 -0
- package/tools/property-extractor/templates/property.hbs +70 -0
- package/tools/property-extractor/templates/topic-property.hbs +59 -0
- package/tools/property-extractor/topic_property_extractor.py +630 -0
- package/tools/property-extractor/transformers.py +80 -4
- package/tools/property-extractor/json-to-asciidoc/generate_docs.py +0 -466
|
@@ -36,13 +36,52 @@ class IsNullableTransformer:
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class IsArrayTransformer:
|
|
39
|
+
"""
|
|
40
|
+
Detects properties that should be treated as arrays based on their C++ type declarations.
|
|
41
|
+
|
|
42
|
+
This transformer identifies two types of array properties:
|
|
43
|
+
1. std::vector<T> - Standard C++ vectors
|
|
44
|
+
2. one_or_many_property<T> - Redpanda's custom type that accepts either a single value or an array
|
|
45
|
+
|
|
46
|
+
The one_or_many_property type is used in Redpanda configuration for properties like 'admin'
|
|
47
|
+
and 'admin_api_tls' where users can specify either:
|
|
48
|
+
- A single object: admin: {address: "127.0.0.1", port: 9644}
|
|
49
|
+
- An array of objects: admin: [{address: "127.0.0.1", port: 9644}, {address: "0.0.0.0", port: 9645}]
|
|
50
|
+
|
|
51
|
+
When detected, these properties are marked with:
|
|
52
|
+
- type: "array"
|
|
53
|
+
- items: {type: <inner_type>} where <inner_type> is extracted from T
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Class-level constants for array type patterns
|
|
57
|
+
ARRAY_PATTERN_STD_VECTOR = "std::vector"
|
|
58
|
+
ARRAY_PATTERN_ONE_OR_MANY = "one_or_many_property"
|
|
59
|
+
|
|
39
60
|
def __init__(self, type_transformer):
|
|
40
61
|
self.type_transformer = type_transformer
|
|
41
62
|
|
|
42
63
|
def accepts(self, info, file_pair):
|
|
43
|
-
|
|
64
|
+
"""
|
|
65
|
+
Check if this property declaration represents an array type.
|
|
66
|
+
|
|
67
|
+
Returns True for:
|
|
68
|
+
- std::vector<T> declarations (standard C++ vectors)
|
|
69
|
+
- one_or_many_property<T> declarations (Redpanda's flexible array type)
|
|
70
|
+
"""
|
|
71
|
+
return (self.ARRAY_PATTERN_STD_VECTOR in info["declaration"] or
|
|
72
|
+
self.ARRAY_PATTERN_ONE_OR_MANY in info["declaration"])
|
|
44
73
|
|
|
45
74
|
def parse(self, property, info, file_pair):
|
|
75
|
+
"""
|
|
76
|
+
Transform the property to indicate it's an array type.
|
|
77
|
+
|
|
78
|
+
Sets:
|
|
79
|
+
- property["type"] = "array"
|
|
80
|
+
- property["items"]["type"] = <extracted_inner_type>
|
|
81
|
+
|
|
82
|
+
The inner type is extracted by the type_transformer, which handles
|
|
83
|
+
removing the wrapper (std::vector<> or one_or_many_property<>) to get T.
|
|
84
|
+
"""
|
|
46
85
|
property["type"] = "array"
|
|
47
86
|
property["items"] = PropertyBag()
|
|
48
87
|
property["items"]["type"] = self.type_transformer.get_type_from_declaration(
|
|
@@ -94,10 +133,35 @@ class VisibilityTransformer:
|
|
|
94
133
|
|
|
95
134
|
|
|
96
135
|
class TypeTransformer:
|
|
136
|
+
|
|
137
|
+
# Class-level constants for type pattern matching
|
|
138
|
+
# Shared with IsArrayTransformer for consistency
|
|
139
|
+
ARRAY_PATTERN_STD_VECTOR = "std::vector"
|
|
140
|
+
ARRAY_PATTERN_ONE_OR_MANY = "one_or_many_property"
|
|
141
|
+
OPTIONAL_PATTERN = "std::optional"
|
|
142
|
+
|
|
97
143
|
def accepts(self, info, file_pair):
|
|
98
144
|
return True
|
|
99
145
|
|
|
100
146
|
def get_cpp_type_from_declaration(self, declaration):
|
|
147
|
+
"""
|
|
148
|
+
Extract the inner type from C++ property declarations.
|
|
149
|
+
|
|
150
|
+
This method handles various C++ template types and extracts the core type T from:
|
|
151
|
+
- property<T> -> T
|
|
152
|
+
- std::optional<T> -> T
|
|
153
|
+
- std::vector<T> -> T
|
|
154
|
+
- one_or_many_property<T> -> T (Redpanda's flexible array type)
|
|
155
|
+
|
|
156
|
+
For one_or_many_property, this is crucial because it allows the same property
|
|
157
|
+
to accept either a single value or an array of values in the configuration.
|
|
158
|
+
Examples:
|
|
159
|
+
- one_or_many_property<model::broker_endpoint> -> model::broker_endpoint
|
|
160
|
+
- one_or_many_property<endpoint_tls_config> -> endpoint_tls_config
|
|
161
|
+
|
|
162
|
+
The extracted type is then used to determine the JSON schema type and
|
|
163
|
+
for resolving default values from the definitions.
|
|
164
|
+
"""
|
|
101
165
|
one_line_declaration = declaration.replace("\n", "").strip()
|
|
102
166
|
raw_type = (
|
|
103
167
|
re.sub(r"^.*property<(.+)>.*", "\\1", one_line_declaration)
|
|
@@ -105,11 +169,19 @@ class TypeTransformer:
|
|
|
105
169
|
.replace(",", "")
|
|
106
170
|
)
|
|
107
171
|
|
|
108
|
-
if
|
|
172
|
+
if self.OPTIONAL_PATTERN in raw_type:
|
|
109
173
|
raw_type = re.sub(".*std::optional<(.+)>.*", "\\1", raw_type)
|
|
110
174
|
|
|
111
|
-
if
|
|
175
|
+
if self.ARRAY_PATTERN_STD_VECTOR in raw_type:
|
|
112
176
|
raw_type = re.sub(".*std::vector<(.+)>.*", "\\1", raw_type)
|
|
177
|
+
|
|
178
|
+
# Handle one_or_many_property<T> - extract the inner type T
|
|
179
|
+
# This is essential for Redpanda's flexible configuration properties
|
|
180
|
+
# that can accept either single values or arrays
|
|
181
|
+
# Check and extract from raw_type for consistency with other type extractors
|
|
182
|
+
if self.ARRAY_PATTERN_ONE_OR_MANY in raw_type:
|
|
183
|
+
raw_type = re.sub(".*one_or_many_property<(.+)>.*", "\\1", raw_type)
|
|
184
|
+
raw_type = raw_type.split()[0].replace(",", "")
|
|
113
185
|
|
|
114
186
|
return raw_type
|
|
115
187
|
|
|
@@ -282,6 +354,10 @@ class FriendlyDefaultTransformer:
|
|
|
282
354
|
- std::chrono::milliseconds(10)
|
|
283
355
|
- std::nullopt
|
|
284
356
|
"""
|
|
357
|
+
|
|
358
|
+
# Class-level constants for pattern matching in default values
|
|
359
|
+
ARRAY_PATTERN_STD_VECTOR = "std::vector"
|
|
360
|
+
|
|
285
361
|
def accepts(self, info, file_pair):
|
|
286
362
|
return info.get("params") and len(info["params"]) > 3
|
|
287
363
|
|
|
@@ -308,7 +384,7 @@ class FriendlyDefaultTransformer:
|
|
|
308
384
|
return property
|
|
309
385
|
|
|
310
386
|
# Transform std::vector defaults.
|
|
311
|
-
if
|
|
387
|
+
if self.ARRAY_PATTERN_STD_VECTOR in default:
|
|
312
388
|
m = re.search(r'\{([^}]+)\}', default)
|
|
313
389
|
if m:
|
|
314
390
|
contents = m.group(1).strip()
|
|
@@ -1,466 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
import re
|
|
4
|
-
import argparse
|
|
5
|
-
|
|
6
|
-
# --- Constants for Paths and Filenames ---
|
|
7
|
-
INPUT_JSON_PATH = "gen/"
|
|
8
|
-
INPUT_JSON_FILE = "properties-output.json"
|
|
9
|
-
|
|
10
|
-
OUTPUT_DIR_DEFAULT = "output"
|
|
11
|
-
PAGE_FOLDER_NAME = "pages"
|
|
12
|
-
ERROR_FOLDER_NAME = "error"
|
|
13
|
-
|
|
14
|
-
OUTPUT_FILE_BROKER = "broker-properties.adoc"
|
|
15
|
-
OUTPUT_FILE_CLUSTER = "cluster-properties.adoc"
|
|
16
|
-
OUTPUT_FILE_CLOUD = "object-storage-properties.adoc"
|
|
17
|
-
OUTPUT_FILE_DEPRECATED = os.path.join("deprecated", "partials", "deprecated-properties.adoc")
|
|
18
|
-
ALL_PROPERTIES_FILE = "all_properties.txt"
|
|
19
|
-
|
|
20
|
-
ERROR_FILE_DESCRIPTION = "empty_description.txt"
|
|
21
|
-
ERROR_FILE_TYPE = "empty_type.txt"
|
|
22
|
-
ERROR_FILE_MAX_WITHOUT_MIN = "max_without_min.txt"
|
|
23
|
-
ERROR_FILE_MIN_WITHOUT_MAX = "min_without_max.txt"
|
|
24
|
-
|
|
25
|
-
# --- Static Documentation Strings ---
|
|
26
|
-
BROKER_PAGE_TITLE = (
|
|
27
|
-
"= Broker Configuration Properties\n"
|
|
28
|
-
":page-aliases: reference:node-properties.adoc, reference:node-configuration-sample.adoc\n"
|
|
29
|
-
":description: Reference of broker configuration properties.\n\n"
|
|
30
|
-
)
|
|
31
|
-
BROKER_INTRO = (
|
|
32
|
-
"Broker configuration properties are applied individually to each broker in a cluster. "
|
|
33
|
-
"You can find and modify these properties in the `redpanda.yaml` configuration file.\n\n"
|
|
34
|
-
"For information on how to edit broker properties, see xref:manage:cluster-maintenance/node-property-configuration.adoc[].\n\n"
|
|
35
|
-
"NOTE: All broker properties require that you restart Redpanda for any update to take effect.\n\n"
|
|
36
|
-
)
|
|
37
|
-
BROKER_TITLE = "== Broker configuration\n\n"
|
|
38
|
-
|
|
39
|
-
SCHEMA_REGISTRY_TITLE = "== Schema Registry\n\n"
|
|
40
|
-
PANDAPROXY_TITLE = "== HTTP Proxy\n\n"
|
|
41
|
-
KAFKA_CLIENT_TITLE = "== HTTP Proxy Client\n\n"
|
|
42
|
-
|
|
43
|
-
SCHEMA_REGISTRY_INTRO = (
|
|
44
|
-
"The Schema Registry provides configuration properties to help you enable producers and consumers "
|
|
45
|
-
"to share information needed to serialize and deserialize producer and consumer messages.\n\n"
|
|
46
|
-
"For information on how to edit broker properties for the Schema Registry, see xref:manage:cluster-maintenance/node-property-configuration.adoc[].\n\n"
|
|
47
|
-
)
|
|
48
|
-
PANDAPROXY_INTRO = (
|
|
49
|
-
"Redpanda HTTP Proxy allows access to your data through a REST API. For example, you can list topics or brokers, "
|
|
50
|
-
"get events, produce events, subscribe to events from topics using consumer groups, and commit offsets for a consumer.\n\n"
|
|
51
|
-
"See xref:develop:http-proxy.adoc[]\n\n"
|
|
52
|
-
)
|
|
53
|
-
KAFKA_CLIENT_INTRO = "Configuration options for HTTP Proxy Client.\n\n"
|
|
54
|
-
|
|
55
|
-
CLUSTER_PAGE_TITLE = (
|
|
56
|
-
"= Cluster Configuration Properties\n"
|
|
57
|
-
":page-aliases: reference:tunable-properties.adoc, reference:cluster-properties.adoc\n"
|
|
58
|
-
":description: Cluster configuration properties list.\n\n"
|
|
59
|
-
)
|
|
60
|
-
CLUSTER_CONFIG_INTRO = (
|
|
61
|
-
"Cluster configuration properties are the same for all brokers in a cluster, and are set at the cluster level.\n\n"
|
|
62
|
-
"For information on how to edit cluster properties, see xref:manage:cluster-maintenance/cluster-property-configuration.adoc[] "
|
|
63
|
-
"or xref:manage:kubernetes/k-cluster-property-configuration.adoc[].\n\n"
|
|
64
|
-
"NOTE: Some cluster properties require that you restart the cluster for any updates to take effect. "
|
|
65
|
-
"See the specific property details to identify whether or not a restart is required.\n\n"
|
|
66
|
-
)
|
|
67
|
-
CLUSTER_CONFIG_TITLE = "== Cluster configuration\n\n"
|
|
68
|
-
|
|
69
|
-
CLOUD_PAGE_TITLE = (
|
|
70
|
-
"= Object Storage Properties\n"
|
|
71
|
-
":description: Reference of object storage properties.\n\n"
|
|
72
|
-
)
|
|
73
|
-
CLOUD_CONFIG_INTRO = (
|
|
74
|
-
"Object storage properties are a type of cluster property. For information on how to edit cluster properties, "
|
|
75
|
-
"see xref:manage:cluster-maintenance/cluster-property-configuration.adoc[].\n\n"
|
|
76
|
-
"NOTE: Some object storage properties require that you restart the cluster for any updates to take effect. "
|
|
77
|
-
"See the specific property details to identify whether or not a restart is required.\n\n"
|
|
78
|
-
)
|
|
79
|
-
CLOUD_CONFIG_TITLE = (
|
|
80
|
-
"== Object storage configuration\n\n"
|
|
81
|
-
"Object storage properties should only be set if you enable xref:manage:tiered-storage.adoc[Tiered Storage].\n\n"
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
DEPRECATED_PROPERTIES_TITLE = "\n== Configuration properties\n\n"
|
|
85
|
-
DEPRECATED_PROPERTIES_INTRO = "This is an exhaustive list of all the deprecated properties.\n\n"
|
|
86
|
-
DEPRECATED_BROKER_TITLE = "=== Broker properties\n\n"
|
|
87
|
-
DEPRECATED_CLUSTER_TITLE = "=== Cluster properties\n\n"
|
|
88
|
-
|
|
89
|
-
# --- Mapping Constants ---
|
|
90
|
-
DEFINED_IN_MAPPING = {
|
|
91
|
-
"src/v/config/node_config.cc": "broker",
|
|
92
|
-
"src/v/pandaproxy/schema_registry/configuration.cc": "schema reg",
|
|
93
|
-
"src/v/pandaproxy/rest/configuration.cc": "http proxy",
|
|
94
|
-
"src/v/kafka/client/configuration.cc": "http client",
|
|
95
|
-
"src/v/config/configuration.cc": "cluster"
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
SUFFIX_TO_UNIT = {
|
|
99
|
-
"ms": "milliseconds",
|
|
100
|
-
"sec": "seconds", # Code is not always consistent when using seconds.
|
|
101
|
-
"seconds": "seconds",
|
|
102
|
-
"bytes": "bytes",
|
|
103
|
-
"buf": "bytes",
|
|
104
|
-
"partitions": "number of partitions per topic",
|
|
105
|
-
"percent": "percent",
|
|
106
|
-
"bps": "bytes per second",
|
|
107
|
-
"fraction": "fraction"
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# --- Utility Functions ---
|
|
111
|
-
def parse_arguments():
|
|
112
|
-
parser = argparse.ArgumentParser(
|
|
113
|
-
description="Generate documentation from properties JSON"
|
|
114
|
-
)
|
|
115
|
-
parser.add_argument(
|
|
116
|
-
"--output-dir",
|
|
117
|
-
type=str,
|
|
118
|
-
required=True,
|
|
119
|
-
help="Directory to save the generated documentation",
|
|
120
|
-
)
|
|
121
|
-
return parser.parse_args()
|
|
122
|
-
|
|
123
|
-
def ensure_directory_exists(directory):
|
|
124
|
-
os.makedirs(directory, exist_ok=True)
|
|
125
|
-
|
|
126
|
-
def load_json(input_path, input_file):
|
|
127
|
-
try:
|
|
128
|
-
with open(os.path.join(input_path, input_file), "r", encoding="utf-8") as json_file:
|
|
129
|
-
return json.load(json_file)
|
|
130
|
-
except FileNotFoundError:
|
|
131
|
-
print(f"Error: The file '{input_file}' does not exist.")
|
|
132
|
-
return {}
|
|
133
|
-
except json.JSONDecodeError as e:
|
|
134
|
-
print(f"Error: Failed to parse JSON in '{input_file}': {str(e)}")
|
|
135
|
-
return {}
|
|
136
|
-
|
|
137
|
-
def process_defaults(input_string, suffix):
|
|
138
|
-
# Test for ip:port in vector
|
|
139
|
-
vector_match = re.search(
|
|
140
|
-
r'std::vector<net::unresolved_address>\(\{\{("([\d.]+)",\s*(\d+))\}\}\)', input_string
|
|
141
|
-
)
|
|
142
|
-
if vector_match:
|
|
143
|
-
ip = vector_match.group(2)
|
|
144
|
-
port = vector_match.group(3)
|
|
145
|
-
return [f"{ip}:{port}"]
|
|
146
|
-
|
|
147
|
-
# Test for ip:port in single-string
|
|
148
|
-
broker_match = re.search(r'net::unresolved_address\("([\d.]+)",\s*(\d+)\)', input_string)
|
|
149
|
-
if broker_match:
|
|
150
|
-
ip = broker_match.group(1)
|
|
151
|
-
port = broker_match.group(2)
|
|
152
|
-
return f"{ip}:{port}"
|
|
153
|
-
|
|
154
|
-
# Handle single time units: seconds, milliseconds, hours, minutes
|
|
155
|
-
time_match = re.search(r"(\d+)(ms|s|min|h)", input_string)
|
|
156
|
-
# Handle complex time expressions like '24h*365'
|
|
157
|
-
complex_match = re.search(r"(\d+)(h|min|s|ms)\s*\*\s*(\d+)", input_string)
|
|
158
|
-
# Handle std::chrono::time expressions
|
|
159
|
-
chrono_match = re.search(r"std::chrono::(\w+)[\{\(](\d+)[\)\}]", input_string)
|
|
160
|
-
|
|
161
|
-
if time_match:
|
|
162
|
-
value = int(time_match.group(1))
|
|
163
|
-
unit = time_match.group(2)
|
|
164
|
-
if suffix == "ms":
|
|
165
|
-
if unit == "ms":
|
|
166
|
-
return value
|
|
167
|
-
elif unit == "s":
|
|
168
|
-
return value * 1000
|
|
169
|
-
elif unit == "min":
|
|
170
|
-
return value * 60 * 1000
|
|
171
|
-
elif unit == "h":
|
|
172
|
-
return value * 60 * 60 * 1000
|
|
173
|
-
elif suffix == "sec":
|
|
174
|
-
if unit == "s":
|
|
175
|
-
return value
|
|
176
|
-
elif unit == "min":
|
|
177
|
-
return value * 60
|
|
178
|
-
elif unit == "h":
|
|
179
|
-
return value * 60 * 60
|
|
180
|
-
elif unit == "ms":
|
|
181
|
-
return value / 1000
|
|
182
|
-
|
|
183
|
-
if complex_match:
|
|
184
|
-
value = int(complex_match.group(1))
|
|
185
|
-
unit = complex_match.group(2)
|
|
186
|
-
multiplier = int(complex_match.group(3))
|
|
187
|
-
if suffix == "ms":
|
|
188
|
-
if unit == "h":
|
|
189
|
-
return value * 60 * 60 * 1000 * multiplier
|
|
190
|
-
elif unit == "min":
|
|
191
|
-
return value * 60 * 1000 * multiplier
|
|
192
|
-
elif unit == "s":
|
|
193
|
-
return value * 1000 * multiplier
|
|
194
|
-
elif unit == "ms":
|
|
195
|
-
return value * multiplier
|
|
196
|
-
elif suffix == "sec":
|
|
197
|
-
if unit == "h":
|
|
198
|
-
return value * 60 * 60 * multiplier
|
|
199
|
-
elif unit == "min":
|
|
200
|
-
return value * 60 * multiplier
|
|
201
|
-
elif unit == "s":
|
|
202
|
-
return value * multiplier
|
|
203
|
-
elif unit == "ms":
|
|
204
|
-
return (value * multiplier) / 1000
|
|
205
|
-
|
|
206
|
-
if chrono_match:
|
|
207
|
-
chrono_unit = chrono_match.group(1)
|
|
208
|
-
chrono_value = int(chrono_match.group(2))
|
|
209
|
-
chrono_conversion = {
|
|
210
|
-
"milliseconds": 1,
|
|
211
|
-
"seconds": 1000,
|
|
212
|
-
"minutes": 60 * 1000,
|
|
213
|
-
"hours": 60 * 60 * 1000,
|
|
214
|
-
"days": 24 * 60 * 60 * 1000,
|
|
215
|
-
"weeks": 7 * 24 * 60 * 60 * 1000,
|
|
216
|
-
}
|
|
217
|
-
if suffix == "ms":
|
|
218
|
-
return chrono_value * chrono_conversion.get(chrono_unit, 1)
|
|
219
|
-
elif suffix == "sec":
|
|
220
|
-
if chrono_unit == "milliseconds":
|
|
221
|
-
return chrono_value / 1000
|
|
222
|
-
else:
|
|
223
|
-
return (chrono_value * chrono_conversion.get(chrono_unit, 1)) / 1000
|
|
224
|
-
|
|
225
|
-
# Return the original string if no pattern matches
|
|
226
|
-
return input_string
|
|
227
|
-
|
|
228
|
-
def generate_property_doc(key, value):
|
|
229
|
-
"""
|
|
230
|
-
Generate documentation string for a single property.
|
|
231
|
-
Returns None if required fields are missing.
|
|
232
|
-
"""
|
|
233
|
-
description = value.get("description", "").strip()
|
|
234
|
-
prop_type = value.get("type", "").strip()
|
|
235
|
-
if not description or not prop_type:
|
|
236
|
-
return None
|
|
237
|
-
|
|
238
|
-
# Capitalize first letter and ensure a period at the end.
|
|
239
|
-
description = description[0].upper() + description[1:]
|
|
240
|
-
if not description.endswith('.'):
|
|
241
|
-
description += '.'
|
|
242
|
-
|
|
243
|
-
lines = [f"=== {value.get('name')}\n\n", f"{description}\n\n"]
|
|
244
|
-
|
|
245
|
-
property_suffix = value.get("name").split('_')[-1]
|
|
246
|
-
if property_suffix in SUFFIX_TO_UNIT:
|
|
247
|
-
lines.append(f"*Unit:* {SUFFIX_TO_UNIT[property_suffix]}\n\n")
|
|
248
|
-
|
|
249
|
-
# For non-broker properties (node_config.cc indicates broker), add restart info.
|
|
250
|
-
if value.get("defined_in") != "src/v/config/node_config.cc":
|
|
251
|
-
restart = "Yes" if value.get("needs_restart", False) else "No"
|
|
252
|
-
lines.append(f"*Requires restart:* {restart}\n\n")
|
|
253
|
-
|
|
254
|
-
if "gets_restored" in value:
|
|
255
|
-
restored = "Yes" if value.get("gets_restored", False) else "No"
|
|
256
|
-
lines.append(f"*Gets restored during cluster restore:* {restored}\n\n")
|
|
257
|
-
|
|
258
|
-
visibility = value.get("visibility") or "user"
|
|
259
|
-
lines.append(f"*Visibility:* `{visibility}`\n\n")
|
|
260
|
-
|
|
261
|
-
if prop_type in ["string", "array", "number", "boolean", "integer"]:
|
|
262
|
-
lines.append(f"*Type:* {prop_type}\n\n")
|
|
263
|
-
|
|
264
|
-
# Add aliases if they exist
|
|
265
|
-
aliases = value.get("aliases")
|
|
266
|
-
if aliases and len(aliases) > 0:
|
|
267
|
-
aliases_str = ", ".join(f"`{alias}`" for alias in aliases)
|
|
268
|
-
lines.append(f"*Aliases:* {aliases_str}\n\n")
|
|
269
|
-
|
|
270
|
-
if value.get("maximum") is not None and value.get("minimum") is not None:
|
|
271
|
-
lines.append(
|
|
272
|
-
f"*Accepted values:* [`{value.get('minimum')}`, `{value.get('maximum')}`]\n\n"
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
default = value.get("default")
|
|
276
|
-
if default is None or default == "":
|
|
277
|
-
default_str = "null"
|
|
278
|
-
elif isinstance(default, bool):
|
|
279
|
-
default_str = "true" if default else "false"
|
|
280
|
-
else:
|
|
281
|
-
default_str = str(default).replace("'", "").lower()
|
|
282
|
-
default_str = process_defaults(default_str, property_suffix)
|
|
283
|
-
lines.append(f"*Default:* `{default_str}`\n\n")
|
|
284
|
-
lines.append("---\n\n")
|
|
285
|
-
return "".join(lines)
|
|
286
|
-
|
|
287
|
-
def write_data_to_file(output_dir, filename, data):
|
|
288
|
-
file_path = os.path.join(output_dir, filename)
|
|
289
|
-
ensure_directory_exists(os.path.dirname(file_path))
|
|
290
|
-
try:
|
|
291
|
-
with open(file_path, "w+", encoding="utf-8") as output:
|
|
292
|
-
output.write(data)
|
|
293
|
-
print(f"Data written to {file_path} successfully.")
|
|
294
|
-
return True
|
|
295
|
-
except Exception as e:
|
|
296
|
-
print(f"Error writing data to {filename}: {str(e)}")
|
|
297
|
-
return False
|
|
298
|
-
|
|
299
|
-
def write_error_file(output_dir, filename, error_content, total_properties):
|
|
300
|
-
file_path = os.path.join(output_dir, filename)
|
|
301
|
-
ensure_directory_exists(os.path.dirname(file_path))
|
|
302
|
-
try:
|
|
303
|
-
if os.path.exists(file_path):
|
|
304
|
-
os.remove(file_path)
|
|
305
|
-
if error_content:
|
|
306
|
-
error_content = error_content.rstrip("\n")
|
|
307
|
-
with open(file_path, "w+", encoding="utf-8") as output:
|
|
308
|
-
output.write(error_content)
|
|
309
|
-
error_count = len(error_content.split("\n"))
|
|
310
|
-
if error_count > 0:
|
|
311
|
-
empty_name = filename.replace("empty_", "").replace(".txt", "")
|
|
312
|
-
error_type = (
|
|
313
|
-
"deprecated properties"
|
|
314
|
-
if empty_name == "deprecated_properties"
|
|
315
|
-
else f"properties with empty {empty_name}"
|
|
316
|
-
)
|
|
317
|
-
error_percentage = round((error_count / total_properties) * 100, 2)
|
|
318
|
-
print(
|
|
319
|
-
f"You have {error_count} {error_type}. Percentage of errors: {error_percentage}%. Data written in '{filename}'."
|
|
320
|
-
)
|
|
321
|
-
except Exception as e:
|
|
322
|
-
print(f"Error writing error data to '{filename}': {str(e)}")
|
|
323
|
-
|
|
324
|
-
# --- Main Processing ---
|
|
325
|
-
def main():
|
|
326
|
-
args = parse_arguments()
|
|
327
|
-
output_dir = args.output_dir
|
|
328
|
-
page_folder = os.path.join(output_dir, PAGE_FOLDER_NAME)
|
|
329
|
-
error_folder = os.path.join(output_dir, ERROR_FOLDER_NAME)
|
|
330
|
-
|
|
331
|
-
data = load_json(INPUT_JSON_PATH, INPUT_JSON_FILE)
|
|
332
|
-
properties = data.get("properties", {})
|
|
333
|
-
total_properties = len(properties)
|
|
334
|
-
|
|
335
|
-
# Accumulators for property documentation and error logs.
|
|
336
|
-
broker_config_content = []
|
|
337
|
-
schema_registry_content = []
|
|
338
|
-
pandaproxy_content = []
|
|
339
|
-
kafka_client_content = []
|
|
340
|
-
cluster_config_content = []
|
|
341
|
-
cloud_config_content = []
|
|
342
|
-
deprecated_broker_content = []
|
|
343
|
-
deprecated_cluster_content = []
|
|
344
|
-
all_properties = []
|
|
345
|
-
empty_description_errors = []
|
|
346
|
-
empty_type_errors = []
|
|
347
|
-
max_without_min_errors = []
|
|
348
|
-
min_without_max_errors = []
|
|
349
|
-
deprecated_properties_errors = []
|
|
350
|
-
|
|
351
|
-
for key, value in properties.items():
|
|
352
|
-
all_properties.append(key)
|
|
353
|
-
group = None
|
|
354
|
-
if key.startswith("cloud_"):
|
|
355
|
-
group = "cloud"
|
|
356
|
-
else:
|
|
357
|
-
group = DEFINED_IN_MAPPING.get(value.get("defined_in"))
|
|
358
|
-
|
|
359
|
-
# Handle deprecated properties.
|
|
360
|
-
if value.get("is_deprecated") is True:
|
|
361
|
-
deprecated_properties_errors.append(key)
|
|
362
|
-
if group == "broker":
|
|
363
|
-
deprecated_broker_content.append(f"- {key}\n\n")
|
|
364
|
-
elif group in ["cluster", "cloud"]:
|
|
365
|
-
deprecated_cluster_content.append(f"- {key}\n\n")
|
|
366
|
-
continue
|
|
367
|
-
|
|
368
|
-
# Log errors for missing description or type.
|
|
369
|
-
if not value.get("description", "").strip():
|
|
370
|
-
empty_description_errors.append(key)
|
|
371
|
-
if not value.get("type", "").strip():
|
|
372
|
-
empty_type_errors.append(key)
|
|
373
|
-
|
|
374
|
-
# Check for max/min inconsistencies.
|
|
375
|
-
if value.get("maximum") is not None and value.get("minimum") is None:
|
|
376
|
-
max_without_min_errors.append(key)
|
|
377
|
-
if value.get("minimum") is not None and value.get("maximum") is None:
|
|
378
|
-
min_without_max_errors.append(key)
|
|
379
|
-
|
|
380
|
-
property_doc = generate_property_doc(key, value)
|
|
381
|
-
if property_doc is None:
|
|
382
|
-
continue
|
|
383
|
-
|
|
384
|
-
group_mapping = {
|
|
385
|
-
"broker": broker_config_content,
|
|
386
|
-
"schema reg": schema_registry_content,
|
|
387
|
-
"http proxy": pandaproxy_content,
|
|
388
|
-
"http client": kafka_client_content,
|
|
389
|
-
"cluster": cluster_config_content,
|
|
390
|
-
"cloud": cloud_config_content,
|
|
391
|
-
}
|
|
392
|
-
if group in group_mapping:
|
|
393
|
-
group_mapping[group].append(property_doc)
|
|
394
|
-
|
|
395
|
-
# Construct final documentation pages.
|
|
396
|
-
broker_page = (
|
|
397
|
-
BROKER_PAGE_TITLE
|
|
398
|
-
+ BROKER_INTRO
|
|
399
|
-
+ BROKER_TITLE
|
|
400
|
-
+ "".join(broker_config_content)
|
|
401
|
-
+ "\n\n"
|
|
402
|
-
+ SCHEMA_REGISTRY_TITLE
|
|
403
|
-
+ SCHEMA_REGISTRY_INTRO
|
|
404
|
-
+ "".join(schema_registry_content)
|
|
405
|
-
+ "\n\n"
|
|
406
|
-
+ PANDAPROXY_TITLE
|
|
407
|
-
+ PANDAPROXY_INTRO
|
|
408
|
-
+ "".join(pandaproxy_content)
|
|
409
|
-
+ "\n\n"
|
|
410
|
-
+ KAFKA_CLIENT_TITLE
|
|
411
|
-
+ KAFKA_CLIENT_INTRO
|
|
412
|
-
+ "".join(kafka_client_content)
|
|
413
|
-
)
|
|
414
|
-
cluster_page = (
|
|
415
|
-
CLUSTER_PAGE_TITLE
|
|
416
|
-
+ CLUSTER_CONFIG_INTRO
|
|
417
|
-
+ CLUSTER_CONFIG_TITLE
|
|
418
|
-
+ "".join(cluster_config_content)
|
|
419
|
-
)
|
|
420
|
-
cloud_page = (
|
|
421
|
-
CLOUD_PAGE_TITLE
|
|
422
|
-
+ CLOUD_CONFIG_INTRO
|
|
423
|
-
+ CLOUD_CONFIG_TITLE
|
|
424
|
-
+ "".join(cloud_config_content)
|
|
425
|
-
)
|
|
426
|
-
deprecated_page = (
|
|
427
|
-
DEPRECATED_PROPERTIES_TITLE
|
|
428
|
-
+ DEPRECATED_PROPERTIES_INTRO
|
|
429
|
-
+ DEPRECATED_BROKER_TITLE
|
|
430
|
-
+ "".join(deprecated_broker_content)
|
|
431
|
-
+ DEPRECATED_CLUSTER_TITLE
|
|
432
|
-
+ "".join(deprecated_cluster_content)
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
# Write output files.
|
|
436
|
-
write_data_to_file(page_folder, OUTPUT_FILE_BROKER, broker_page)
|
|
437
|
-
write_data_to_file(page_folder, OUTPUT_FILE_CLUSTER, cluster_page)
|
|
438
|
-
write_data_to_file(page_folder, OUTPUT_FILE_CLOUD, cloud_page)
|
|
439
|
-
write_data_to_file(page_folder, OUTPUT_FILE_DEPRECATED, deprecated_page)
|
|
440
|
-
write_data_to_file(output_dir, ALL_PROPERTIES_FILE, "\n".join(all_properties))
|
|
441
|
-
|
|
442
|
-
# Write error files.
|
|
443
|
-
write_error_file(
|
|
444
|
-
error_folder, ERROR_FILE_DESCRIPTION, "\n".join(empty_description_errors), total_properties
|
|
445
|
-
)
|
|
446
|
-
write_error_file(
|
|
447
|
-
error_folder, ERROR_FILE_TYPE, "\n".join(empty_type_errors), total_properties
|
|
448
|
-
)
|
|
449
|
-
write_error_file(
|
|
450
|
-
error_folder, ERROR_FILE_MAX_WITHOUT_MIN, "\n".join(max_without_min_errors), total_properties
|
|
451
|
-
)
|
|
452
|
-
write_error_file(
|
|
453
|
-
error_folder, ERROR_FILE_MIN_WITHOUT_MAX, "\n".join(min_without_max_errors), total_properties
|
|
454
|
-
)
|
|
455
|
-
write_error_file(
|
|
456
|
-
error_folder, "deprecated_properties.txt", "\n".join(deprecated_properties_errors), total_properties
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
# Print summary.
|
|
460
|
-
print(f"Total properties read: {total_properties}")
|
|
461
|
-
print(f"Total Broker properties: {len(broker_config_content)}")
|
|
462
|
-
print(f"Total Cluster properties: {len(cluster_config_content)}")
|
|
463
|
-
print(f"Total Cloud properties: {len(cloud_config_content)}")
|
|
464
|
-
|
|
465
|
-
if __name__ == "__main__":
|
|
466
|
-
main()
|