@redpanda-data/docs-extensions-and-macros 4.8.1 → 4.9.0
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 +180 -33
- package/package.json +1 -1
- package/tools/property-extractor/Makefile +8 -18
- package/tools/property-extractor/cloud_config.py +594 -0
- package/tools/property-extractor/compare-properties.js +378 -0
- package/tools/property-extractor/generate-handlebars-docs.js +132 -32
- package/tools/property-extractor/parser.py +27 -1
- package/tools/property-extractor/property_extractor.py +335 -99
- package/tools/property-extractor/requirements.txt +1 -0
- package/tools/property-extractor/templates/property-cloud.hbs +105 -0
- package/tools/property-extractor/templates/property.hbs +16 -1
- package/tools/property-extractor/templates/topic-property-cloud.hbs +97 -0
- package/tools/property-extractor/templates/topic-property.hbs +26 -12
- package/tools/property-extractor/transformers.py +98 -2
|
@@ -59,6 +59,7 @@ import os
|
|
|
59
59
|
import json
|
|
60
60
|
import re
|
|
61
61
|
import yaml
|
|
62
|
+
import ast
|
|
62
63
|
from copy import deepcopy
|
|
63
64
|
|
|
64
65
|
from pathlib import Path
|
|
@@ -76,18 +77,138 @@ except ImportError:
|
|
|
76
77
|
# TopicPropertyExtractor not available, will skip topic property extraction
|
|
77
78
|
TopicPropertyExtractor = None
|
|
78
79
|
|
|
80
|
+
# Import cloud configuration support
|
|
81
|
+
try:
|
|
82
|
+
from cloud_config import fetch_cloud_config, add_cloud_support_metadata
|
|
83
|
+
# Configure cloud_config logger to suppress INFO logs by default
|
|
84
|
+
import logging
|
|
85
|
+
logging.getLogger('cloud_config').setLevel(logging.WARNING)
|
|
86
|
+
except ImportError as e:
|
|
87
|
+
# Cloud configuration support not available due to missing dependencies
|
|
88
|
+
logging.warning(f"Cloud configuration support not available: {e}")
|
|
89
|
+
fetch_cloud_config = None
|
|
90
|
+
add_cloud_support_metadata = None
|
|
91
|
+
|
|
79
92
|
logger = logging.getLogger("viewer")
|
|
80
93
|
|
|
81
94
|
|
|
95
|
+
def process_enterprise_value(enterprise_str):
|
|
96
|
+
"""
|
|
97
|
+
Convert a raw C++ "enterprise" expression into a JSON-friendly value.
|
|
98
|
+
|
|
99
|
+
Accepts a string extracted from C++ sources and returns a value suitable for JSON
|
|
100
|
+
serialization: a Python list for initializer-lists, a simple string for enum-like
|
|
101
|
+
tokens, a boolean-like or quoted string unchanged, or a human-readable hint for
|
|
102
|
+
lambda-based expressions.
|
|
103
|
+
|
|
104
|
+
The function applies pattern matching in the following order (order is significant):
|
|
105
|
+
1. std::vector<...>{...} initializer lists → Python list (quoted strings are unescaped,
|
|
106
|
+
unqualified enum tokens are reduced to their final identifier).
|
|
107
|
+
2. C++ scoped enum-like tokens (foo::bar::BAZ) → "BAZ".
|
|
108
|
+
3. Lambda expressions (strings starting with "[](" and ending with "}") → a short
|
|
109
|
+
human-readable hint such as "Enterprise feature enabled" or context-specific text.
|
|
110
|
+
4. Simple literal values (e.g., "true", "false", "OIDC", or quoted strings) → returned as-is.
|
|
111
|
+
|
|
112
|
+
Parameters:
|
|
113
|
+
enterprise_str (str): Raw C++ expression text to be converted.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Union[str, bool, list]: A JSON-serializable representation of the input.
|
|
117
|
+
"""
|
|
118
|
+
enterprise_str = enterprise_str.strip()
|
|
119
|
+
|
|
120
|
+
# FIRST: Handle std::vector initialization patterns (highest priority)
|
|
121
|
+
# This must come before enum processing because vectors can contain enums
|
|
122
|
+
# Tolerate optional whitespace around braces
|
|
123
|
+
vector_match = re.match(r'std::vector<[^>]+>\s*\{\s*([^}]*)\s*\}', enterprise_str)
|
|
124
|
+
if vector_match:
|
|
125
|
+
content = vector_match.group(1).strip()
|
|
126
|
+
if not content:
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
# Parse the content as a list of values
|
|
130
|
+
values = []
|
|
131
|
+
current_value = ""
|
|
132
|
+
in_quotes = False
|
|
133
|
+
|
|
134
|
+
for char in content:
|
|
135
|
+
if char == '"' and (not current_value or current_value[-1] != '\\'):
|
|
136
|
+
in_quotes = not in_quotes
|
|
137
|
+
current_value += char
|
|
138
|
+
elif char == ',' and not in_quotes:
|
|
139
|
+
if current_value.strip():
|
|
140
|
+
# Clean up the value
|
|
141
|
+
value = current_value.strip()
|
|
142
|
+
if value.startswith('"') and value.endswith('"'):
|
|
143
|
+
values.append(ast.literal_eval(value))
|
|
144
|
+
else:
|
|
145
|
+
# Handle enum values in the vector
|
|
146
|
+
enum_match = re.match(r'[a-zA-Z0-9_:]+::([a-zA-Z0-9_]+)', value)
|
|
147
|
+
if enum_match:
|
|
148
|
+
values.append(enum_match.group(1))
|
|
149
|
+
else:
|
|
150
|
+
values.append(value)
|
|
151
|
+
current_value = ""
|
|
152
|
+
else:
|
|
153
|
+
current_value += char
|
|
154
|
+
|
|
155
|
+
# Add the last value
|
|
156
|
+
if current_value.strip():
|
|
157
|
+
value = current_value.strip()
|
|
158
|
+
if value.startswith('"') and value.endswith('"'):
|
|
159
|
+
values.append(ast.literal_eval(value))
|
|
160
|
+
else:
|
|
161
|
+
# Handle enum values in the vector
|
|
162
|
+
enum_match = re.match(r'[a-zA-Z0-9_:]+::([a-zA-Z0-9_]+)', value)
|
|
163
|
+
if enum_match:
|
|
164
|
+
values.append(enum_match.group(1))
|
|
165
|
+
else:
|
|
166
|
+
values.append(value)
|
|
167
|
+
|
|
168
|
+
return values
|
|
169
|
+
|
|
170
|
+
# SECOND: Handle enum-like patterns (extract the last part after ::)
|
|
171
|
+
enum_match = re.match(r'[a-zA-Z0-9_:]+::([a-zA-Z0-9_]+)', enterprise_str)
|
|
172
|
+
if enum_match:
|
|
173
|
+
enum_value = enum_match.group(1)
|
|
174
|
+
return enum_value
|
|
175
|
+
|
|
176
|
+
# THIRD: Handle C++ lambda expressions - these usually indicate "any non-default value"
|
|
177
|
+
if enterprise_str.startswith("[](") and enterprise_str.endswith("}"):
|
|
178
|
+
# For lambda expressions, try to extract meaningful info from the logic
|
|
179
|
+
if "leaders_preference" in enterprise_str:
|
|
180
|
+
return "Any rack preference (not `none`)"
|
|
181
|
+
else:
|
|
182
|
+
return "Enterprise feature enabled"
|
|
183
|
+
|
|
184
|
+
# FOURTH: Handle simple values with proper JSON types
|
|
185
|
+
# Convert boolean literals to actual boolean values for JSON compatibility
|
|
186
|
+
if enterprise_str == "true":
|
|
187
|
+
return True
|
|
188
|
+
elif enterprise_str == "false":
|
|
189
|
+
return False
|
|
190
|
+
elif enterprise_str == "OIDC" or enterprise_str.startswith('"'):
|
|
191
|
+
return enterprise_str
|
|
192
|
+
|
|
193
|
+
# Fallback: return the original value
|
|
194
|
+
return enterprise_str
|
|
195
|
+
|
|
196
|
+
|
|
82
197
|
def resolve_cpp_function_call(function_name):
|
|
83
198
|
"""
|
|
84
|
-
|
|
199
|
+
Resolve certain small, known C++ zero-argument functions to their literal return values by searching Redpanda source files.
|
|
85
200
|
|
|
86
|
-
|
|
87
|
-
|
|
201
|
+
This function looks up predefined search patterns for well-known functions (currently a small set under `model::*`), locates a local Redpanda source tree from several commonly used paths, and scans the listed files (and, if needed, the broader model directory) for a regex match that captures the string returned by the function. If a match is found the captured string is returned; if the source tree cannot be found or no match is located the function returns None.
|
|
202
|
+
|
|
203
|
+
Parameters:
|
|
204
|
+
function_name (str): Fully-qualified C++ function name to resolve (e.g., "model::kafka_audit_logging_topic").
|
|
88
205
|
|
|
89
206
|
Returns:
|
|
90
|
-
The resolved string
|
|
207
|
+
str or None: The resolved literal string returned by the C++ function, or None when unresolved (source not found or no matching pattern).
|
|
208
|
+
|
|
209
|
+
Notes:
|
|
210
|
+
- The function performs filesystem I/O and regex-based source searching; it does not raise on read errors but logs and continues.
|
|
211
|
+
- Only a small, hard-coded set of function names/patterns is supported; unknown names immediately return None.
|
|
91
212
|
"""
|
|
92
213
|
# Map function names to likely search patterns and file locations
|
|
93
214
|
search_patterns = {
|
|
@@ -327,12 +448,18 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
|
327
448
|
2. version: Add version information showing when the property was introduced
|
|
328
449
|
3. example: Add AsciiDoc example sections with flexible input formats (see below)
|
|
329
450
|
4. default: Override the auto-extracted default value
|
|
330
|
-
|
|
451
|
+
5. related_topics: Add an array of related topic links for cross-referencing
|
|
452
|
+
6. config_scope: Specify the scope for new properties ("topic", "cluster", "broker")
|
|
453
|
+
7. type: Specify the type for new properties
|
|
454
|
+
|
|
455
|
+
Properties that don't exist in the extracted source can be created from overrides.
|
|
456
|
+
This is useful for topic properties or other configurations that aren't auto-detected.
|
|
457
|
+
|
|
331
458
|
Multiple example input formats are supported for user convenience:
|
|
332
|
-
|
|
459
|
+
|
|
333
460
|
1. Direct AsciiDoc string:
|
|
334
461
|
"example": ".Example\n[,yaml]\n----\nredpanda:\n property_name: value\n----"
|
|
335
|
-
|
|
462
|
+
|
|
336
463
|
2. Multi-line array (each element becomes a line):
|
|
337
464
|
"example": [
|
|
338
465
|
".Example",
|
|
@@ -342,10 +469,10 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
|
342
469
|
" property_name: value",
|
|
343
470
|
"----"
|
|
344
471
|
]
|
|
345
|
-
|
|
472
|
+
|
|
346
473
|
3. External file reference:
|
|
347
474
|
"example_file": "examples/property_name.adoc"
|
|
348
|
-
|
|
475
|
+
|
|
349
476
|
4. Auto-formatted YAML with title and description:
|
|
350
477
|
"example_yaml": {
|
|
351
478
|
"title": "Example Configuration",
|
|
@@ -356,37 +483,103 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
|
356
483
|
}
|
|
357
484
|
}
|
|
358
485
|
}
|
|
359
|
-
|
|
486
|
+
|
|
360
487
|
Args:
|
|
361
488
|
properties: Dictionary of extracted properties from C++ source
|
|
362
489
|
overrides: Dictionary loaded from overrides JSON file
|
|
363
490
|
overrides_file_path: Path to the overrides file (for resolving relative example_file paths)
|
|
364
|
-
|
|
491
|
+
|
|
365
492
|
Returns:
|
|
366
|
-
Updated properties dictionary with overrides applied
|
|
493
|
+
Updated properties dictionary with overrides applied and new properties created
|
|
367
494
|
"""
|
|
368
495
|
if overrides and "properties" in overrides:
|
|
369
496
|
for prop, override in overrides["properties"].items():
|
|
370
497
|
if prop in properties:
|
|
371
|
-
# Apply
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
properties[prop]["version"] = override["version"]
|
|
378
|
-
|
|
379
|
-
# Apply example override with multiple input format support
|
|
380
|
-
example_content = _process_example_override(override, overrides_file_path)
|
|
381
|
-
if example_content:
|
|
382
|
-
properties[prop]["example"] = example_content
|
|
383
|
-
|
|
384
|
-
# Apply default override
|
|
385
|
-
if "default" in override:
|
|
386
|
-
properties[prop]["default"] = override["default"]
|
|
498
|
+
# Apply overrides to existing properties
|
|
499
|
+
_apply_override_to_existing_property(properties[prop], override, overrides_file_path)
|
|
500
|
+
else:
|
|
501
|
+
# Create new property from override
|
|
502
|
+
logger.info(f"Creating new property from override: {prop}")
|
|
503
|
+
properties[prop] = _create_property_from_override(prop, override, overrides_file_path)
|
|
387
504
|
return properties
|
|
388
505
|
|
|
389
506
|
|
|
507
|
+
def _apply_override_to_existing_property(property_dict, override, overrides_file_path):
|
|
508
|
+
"""Apply overrides to an existing property."""
|
|
509
|
+
# Apply description override
|
|
510
|
+
if "description" in override:
|
|
511
|
+
property_dict["description"] = override["description"]
|
|
512
|
+
|
|
513
|
+
# Apply version override (introduced in version)
|
|
514
|
+
if "version" in override:
|
|
515
|
+
property_dict["version"] = override["version"]
|
|
516
|
+
|
|
517
|
+
# Apply example override with multiple input format support
|
|
518
|
+
example_content = _process_example_override(override, overrides_file_path)
|
|
519
|
+
if example_content:
|
|
520
|
+
property_dict["example"] = example_content
|
|
521
|
+
|
|
522
|
+
# Apply default override
|
|
523
|
+
if "default" in override:
|
|
524
|
+
property_dict["default"] = override["default"]
|
|
525
|
+
|
|
526
|
+
# Apply type override
|
|
527
|
+
if "type" in override:
|
|
528
|
+
property_dict["type"] = override["type"]
|
|
529
|
+
|
|
530
|
+
# Apply config_scope override
|
|
531
|
+
if "config_scope" in override:
|
|
532
|
+
property_dict["config_scope"] = override["config_scope"]
|
|
533
|
+
|
|
534
|
+
# Apply related_topics override
|
|
535
|
+
if "related_topics" in override:
|
|
536
|
+
if isinstance(override["related_topics"], list):
|
|
537
|
+
property_dict["related_topics"] = override["related_topics"]
|
|
538
|
+
else:
|
|
539
|
+
logger.warning(f"related_topics for property must be an array")
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _create_property_from_override(prop_name, override, overrides_file_path):
|
|
543
|
+
"""Create a new property from override specification."""
|
|
544
|
+
# Create base property structure
|
|
545
|
+
new_property = {
|
|
546
|
+
"name": prop_name,
|
|
547
|
+
"description": override.get("description", f"Configuration property: {prop_name}"),
|
|
548
|
+
"type": override.get("type", "string"),
|
|
549
|
+
"default": override.get("default", None),
|
|
550
|
+
"defined_in": "override", # Mark as override-created
|
|
551
|
+
"config_scope": override.get("config_scope", "topic"), # Default to topic for new properties
|
|
552
|
+
"is_topic_property": override.get("config_scope", "topic") == "topic",
|
|
553
|
+
"is_deprecated": override.get("is_deprecated", False),
|
|
554
|
+
"visibility": override.get("visibility", "user")
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
# Add version if specified
|
|
558
|
+
if "version" in override:
|
|
559
|
+
new_property["version"] = override["version"]
|
|
560
|
+
|
|
561
|
+
# Add example if specified
|
|
562
|
+
example_content = _process_example_override(override, overrides_file_path)
|
|
563
|
+
if example_content:
|
|
564
|
+
new_property["example"] = example_content
|
|
565
|
+
|
|
566
|
+
# Add related_topics if specified
|
|
567
|
+
if "related_topics" in override:
|
|
568
|
+
if isinstance(override["related_topics"], list):
|
|
569
|
+
new_property["related_topics"] = override["related_topics"]
|
|
570
|
+
else:
|
|
571
|
+
logger.warning(f"related_topics for property '{prop_name}' must be an array")
|
|
572
|
+
|
|
573
|
+
# Add any other custom fields from override
|
|
574
|
+
for key, value in override.items():
|
|
575
|
+
if key not in ["description", "type", "default", "config_scope", "version",
|
|
576
|
+
"example", "example_file", "example_yaml", "related_topics",
|
|
577
|
+
"is_deprecated", "visibility"]:
|
|
578
|
+
new_property[key] = value
|
|
579
|
+
|
|
580
|
+
return new_property
|
|
581
|
+
|
|
582
|
+
|
|
390
583
|
def _process_example_override(override, overrides_file_path=None):
|
|
391
584
|
"""
|
|
392
585
|
Process example overrides in various user-friendly formats.
|
|
@@ -444,15 +637,15 @@ def _process_example_override(override, overrides_file_path=None):
|
|
|
444
637
|
if found_path:
|
|
445
638
|
file_path = found_path
|
|
446
639
|
else:
|
|
447
|
-
|
|
448
|
-
|
|
640
|
+
logger.warning(f"Example file not found: {override['example_file']}")
|
|
641
|
+
logger.warning(f"Searched in: {', '.join(search_paths)}")
|
|
449
642
|
return None
|
|
450
643
|
|
|
451
644
|
try:
|
|
452
645
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
453
646
|
return f.read().strip()
|
|
454
647
|
except Exception as e:
|
|
455
|
-
|
|
648
|
+
logger.error(f"Error reading example file {file_path}: {e}")
|
|
456
649
|
return None
|
|
457
650
|
|
|
458
651
|
# Format 4: Auto-formatted YAML configuration
|
|
@@ -495,44 +688,46 @@ def add_config_scope(properties):
|
|
|
495
688
|
'cluster' if defined_in == src/v/config/configuration.cc
|
|
496
689
|
'broker' if defined_in == src/v/config/node_config.cc
|
|
497
690
|
'topic' if is_topic_property == True
|
|
691
|
+
|
|
692
|
+
For override-created properties, preserve existing config_scope if already set.
|
|
498
693
|
"""
|
|
499
694
|
for prop in properties.values():
|
|
500
695
|
# Check if this is a topic property first
|
|
501
696
|
if prop.get("is_topic_property", False):
|
|
502
697
|
prop["config_scope"] = "topic"
|
|
503
698
|
else:
|
|
504
|
-
|
|
505
|
-
if defined_in == "
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
prop["config_scope"] = "broker"
|
|
699
|
+
# For override-created properties, preserve existing config_scope if set
|
|
700
|
+
if prop.get("defined_in") == "override" and prop.get("config_scope") is not None:
|
|
701
|
+
# Keep the existing config_scope from override
|
|
702
|
+
pass
|
|
509
703
|
else:
|
|
510
|
-
prop
|
|
704
|
+
defined_in = prop.get("defined_in", "")
|
|
705
|
+
if defined_in == "src/v/config/configuration.cc":
|
|
706
|
+
prop["config_scope"] = "cluster"
|
|
707
|
+
elif defined_in == "src/v/config/node_config.cc":
|
|
708
|
+
prop["config_scope"] = "broker"
|
|
709
|
+
else:
|
|
710
|
+
prop["config_scope"] = None
|
|
511
711
|
return properties
|
|
512
712
|
|
|
513
713
|
|
|
514
714
|
def resolve_type_and_default(properties, definitions):
|
|
515
715
|
"""
|
|
516
|
-
Resolve
|
|
517
|
-
|
|
518
|
-
This function performs several critical transformations:
|
|
716
|
+
Resolve JSON Schema types and expand C++-style default values for all properties.
|
|
519
717
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
718
|
+
This function:
|
|
719
|
+
- Resolves type references found in `properties` against `definitions` (supports "$ref" and direct type names) and normalizes property "type" to a JSON Schema primitive ("object", "string", "integer", "boolean", "array", "number") with sensible fallbacks.
|
|
720
|
+
- Expands C++ constructor/initializer syntax and common C++ patterns appearing in default values into JSON-compatible Python values (e.g., nested constructor calls -> dicts, initializer lists -> lists, `std::nullopt` -> None, enum-like tokens -> strings).
|
|
721
|
+
- Ensures array-typed properties (including one_or_many_property cases) have array defaults: single-object defaults are wrapped into a one-element list and "{}" string defaults become [].
|
|
722
|
+
- Updates array item type information when item types reference definitions.
|
|
723
|
+
- Applies a final pass to convert any remaining C++-patterned defaults and to transform any `enterprise_value` strings via process_enterprise_value.
|
|
524
724
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
725
|
+
Parameters:
|
|
726
|
+
properties (dict): Mapping of property names to property metadata dictionaries. Each property may include keys like "type", "default", "items", and "enterprise_value".
|
|
727
|
+
definitions (dict): Mapping of type names to JSON Schema definition dictionaries used to resolve $ref targets and to infer property shapes when expanding constructors.
|
|
528
728
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
- Converts empty object strings "{}" to empty arrays []
|
|
532
|
-
|
|
533
|
-
This is essential for one_or_many_property types like 'admin' which should show:
|
|
534
|
-
- Type: array
|
|
535
|
-
- Default: [{address: "127.0.0.1", port: 9644}] (not just {address: ...})
|
|
729
|
+
Returns:
|
|
730
|
+
dict: The same `properties` mapping after in-place normalization and expansion of types and defaults.
|
|
536
731
|
"""
|
|
537
732
|
import ast
|
|
538
733
|
import re
|
|
@@ -1167,6 +1362,14 @@ def resolve_type_and_default(properties, definitions):
|
|
|
1167
1362
|
prop["type"] = "boolean"
|
|
1168
1363
|
else:
|
|
1169
1364
|
prop["type"] = "string"
|
|
1365
|
+
|
|
1366
|
+
# Final pass: process enterprise values
|
|
1367
|
+
for prop in properties.values():
|
|
1368
|
+
if "enterprise_value" in prop:
|
|
1369
|
+
enterprise_value = prop["enterprise_value"]
|
|
1370
|
+
if isinstance(enterprise_value, str):
|
|
1371
|
+
processed_enterprise = process_enterprise_value(enterprise_value)
|
|
1372
|
+
prop["enterprise_value"] = processed_enterprise
|
|
1170
1373
|
|
|
1171
1374
|
return properties
|
|
1172
1375
|
|
|
@@ -1218,53 +1421,62 @@ def extract_topic_properties(source_path):
|
|
|
1218
1421
|
|
|
1219
1422
|
|
|
1220
1423
|
def main():
|
|
1424
|
+
"""
|
|
1425
|
+
CLI entry point that extracts Redpanda configuration properties from C++ sources and emits JSON outputs.
|
|
1426
|
+
|
|
1427
|
+
Runs a full extraction and transformation pipeline:
|
|
1428
|
+
- Parses command-line options (required: --path). Optional flags include --recursive, --output, --enhanced-output, --definitions, --overrides, --cloud-support, and --verbose.
|
|
1429
|
+
- Validates input paths and collects header/.cc file pairs.
|
|
1430
|
+
- Initializes Tree-sitter C++ parser and extracts configuration properties from source files (optionally augmented with topic properties).
|
|
1431
|
+
- Produces two outputs:
|
|
1432
|
+
- Original properties JSON: resolved types, expanded C++ defaults, added config_scope, and optional cloud metadata.
|
|
1433
|
+
- Enhanced properties JSON: same as original but with overrides applied before final resolution.
|
|
1434
|
+
- If --cloud-support is requested, attempts to fetch cloud configuration and add cloud metadata; this requires the cloud_config integration and network access (also requires GITHUB_TOKEN for private access). If cloud support is requested but dependencies are missing, the process will exit with an error.
|
|
1435
|
+
- Writes JSON to files when --output and/or --enhanced-output are provided; otherwise prints the original JSON to stdout.
|
|
1436
|
+
- Exits with non-zero status on fatal errors (missing files, parse errors, missing Tree-sitter parser, I/O failures, or missing cloud dependencies when requested).
|
|
1437
|
+
|
|
1438
|
+
Side effects:
|
|
1439
|
+
- Reads and writes files, may call external cloud config fetchers, logs to the configured logger, and may call sys.exit() on fatal conditions.
|
|
1440
|
+
"""
|
|
1221
1441
|
import argparse
|
|
1222
1442
|
|
|
1223
1443
|
def generate_options():
|
|
1444
|
+
"""
|
|
1445
|
+
Create and return an argparse.ArgumentParser preconfigured for the property extractor CLI.
|
|
1446
|
+
|
|
1447
|
+
The parser understands the following options:
|
|
1448
|
+
- --path (required): path to the Redpanda source directory to scan.
|
|
1449
|
+
- --recursive: scan the path recursively.
|
|
1450
|
+
- --output: file path to write the JSON output (stdout if omitted).
|
|
1451
|
+
- --enhanced-output: file path to write the enhanced JSON output with overrides applied.
|
|
1452
|
+
- --definitions: JSON file containing type definitions (defaults to a definitions.json co-located with this module).
|
|
1453
|
+
- --overrides: optional JSON file with property description/metadata overrides.
|
|
1454
|
+
- --cloud-support: enable fetching cloud metadata from the cloudv2 repository (requires GITHUB_TOKEN and external dependencies such as pyyaml and requests).
|
|
1455
|
+
- -v / --verbose: enable verbose (DEBUG-level) logging.
|
|
1456
|
+
|
|
1457
|
+
Returns:
|
|
1458
|
+
argparse.ArgumentParser: Parser configured with the above options.
|
|
1459
|
+
"""
|
|
1224
1460
|
arg_parser = argparse.ArgumentParser(
|
|
1225
|
-
description="
|
|
1226
|
-
)
|
|
1227
|
-
arg_parser.add_argument(
|
|
1228
|
-
"--path",
|
|
1229
|
-
type=str,
|
|
1230
|
-
required=True,
|
|
1231
|
-
help="Path to the Redpanda's source dir to extract the properties",
|
|
1232
|
-
)
|
|
1233
|
-
|
|
1234
|
-
arg_parser.add_argument(
|
|
1235
|
-
"--recursive", action="store_true", help="Scan the path recursively"
|
|
1236
|
-
)
|
|
1237
|
-
|
|
1238
|
-
arg_parser.add_argument(
|
|
1239
|
-
"--output",
|
|
1240
|
-
type=str,
|
|
1241
|
-
required=False,
|
|
1242
|
-
help="File to store the JSON output. If no file is provided, the JSON will be printed to the standard output",
|
|
1243
|
-
)
|
|
1244
|
-
|
|
1245
|
-
arg_parser.add_argument(
|
|
1246
|
-
"--enhanced-output",
|
|
1247
|
-
type=str,
|
|
1248
|
-
required=False,
|
|
1249
|
-
help="File to store the enhanced JSON output with overrides applied (such as 'dev-properties.json')",
|
|
1461
|
+
description="Internal property extraction tool - use doc-tools.js for user interface"
|
|
1250
1462
|
)
|
|
1251
|
-
|
|
1252
|
-
arg_parser.add_argument(
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
arg_parser.add_argument(
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
)
|
|
1266
|
-
|
|
1267
|
-
arg_parser.add_argument("-v", "--verbose", action="store_true")
|
|
1463
|
+
# Core required parameters
|
|
1464
|
+
arg_parser.add_argument("--path", type=str, required=True, help="Path to Redpanda source directory")
|
|
1465
|
+
arg_parser.add_argument("--recursive", action="store_true", help="Scan path recursively")
|
|
1466
|
+
|
|
1467
|
+
# Output options
|
|
1468
|
+
arg_parser.add_argument("--output", type=str, help="JSON output file path")
|
|
1469
|
+
arg_parser.add_argument("--enhanced-output", type=str, help="Enhanced JSON output file path")
|
|
1470
|
+
|
|
1471
|
+
# Data sources
|
|
1472
|
+
arg_parser.add_argument("--definitions", type=str,
|
|
1473
|
+
default=os.path.dirname(os.path.realpath(__file__)) + "/definitions.json",
|
|
1474
|
+
help="Type definitions JSON file")
|
|
1475
|
+
arg_parser.add_argument("--overrides", type=str, help="Property overrides JSON file")
|
|
1476
|
+
|
|
1477
|
+
# Feature flags (set by Makefile from environment variables)
|
|
1478
|
+
arg_parser.add_argument("--cloud-support", action="store_true", help="Enable cloud metadata")
|
|
1479
|
+
arg_parser.add_argument("-v", "--verbose", action="store_true", help="Verbose logging")
|
|
1268
1480
|
|
|
1269
1481
|
return arg_parser
|
|
1270
1482
|
|
|
@@ -1273,8 +1485,10 @@ def main():
|
|
|
1273
1485
|
|
|
1274
1486
|
if options.verbose:
|
|
1275
1487
|
logging.basicConfig(level="DEBUG")
|
|
1488
|
+
# Also enable INFO logging for cloud_config in verbose mode
|
|
1489
|
+
logging.getLogger('cloud_config').setLevel(logging.INFO)
|
|
1276
1490
|
else:
|
|
1277
|
-
logging.basicConfig(level="
|
|
1491
|
+
logging.basicConfig(level="WARNING") # Suppress INFO logs by default
|
|
1278
1492
|
|
|
1279
1493
|
validate_paths(options)
|
|
1280
1494
|
|
|
@@ -1331,7 +1545,24 @@ def main():
|
|
|
1331
1545
|
# 1. Add config_scope field based on which source file defines the property
|
|
1332
1546
|
original_properties = add_config_scope(deepcopy(properties))
|
|
1333
1547
|
|
|
1334
|
-
# 2.
|
|
1548
|
+
# 2. Fetch cloud configuration and add cloud support metadata if requested
|
|
1549
|
+
# Check both CLI flag and environment variable (CLOUD_SUPPORT=1 from Makefile)
|
|
1550
|
+
cloud_support_enabled = options.cloud_support or os.environ.get('CLOUD_SUPPORT') == '1'
|
|
1551
|
+
cloud_config = None
|
|
1552
|
+
if cloud_support_enabled:
|
|
1553
|
+
if fetch_cloud_config and add_cloud_support_metadata:
|
|
1554
|
+
logging.info("Cloud support enabled, fetching cloud configuration...")
|
|
1555
|
+
cloud_config = fetch_cloud_config() # This will raise an exception if it fails
|
|
1556
|
+
original_properties = add_cloud_support_metadata(original_properties, cloud_config)
|
|
1557
|
+
logging.info(f"✅ Cloud support metadata applied successfully using configuration version {cloud_config.version}")
|
|
1558
|
+
else:
|
|
1559
|
+
logging.error("❌ Cloud support requested but cloud_config module not available")
|
|
1560
|
+
logging.error("This indicates missing Python dependencies for cloud configuration")
|
|
1561
|
+
logging.error("Install required packages: pip install pyyaml requests")
|
|
1562
|
+
logging.error("Or if using a virtual environment, activate it first")
|
|
1563
|
+
sys.exit(1)
|
|
1564
|
+
|
|
1565
|
+
# 3. Resolve type references and expand default values for original properties
|
|
1335
1566
|
original_properties = resolve_type_and_default(original_properties, definitions)
|
|
1336
1567
|
|
|
1337
1568
|
# Generate original properties JSON (without overrides)
|
|
@@ -1347,7 +1578,12 @@ def main():
|
|
|
1347
1578
|
# 2. Add config_scope field based on which source file defines the property
|
|
1348
1579
|
enhanced_properties = add_config_scope(enhanced_properties)
|
|
1349
1580
|
|
|
1350
|
-
# 3.
|
|
1581
|
+
# 3. Add cloud support metadata if requested
|
|
1582
|
+
if cloud_config:
|
|
1583
|
+
enhanced_properties = add_cloud_support_metadata(enhanced_properties, cloud_config)
|
|
1584
|
+
logging.info("✅ Cloud support metadata applied to enhanced properties")
|
|
1585
|
+
|
|
1586
|
+
# 4. Resolve type references and expand default values
|
|
1351
1587
|
# This step converts:
|
|
1352
1588
|
# - C++ type names (model::broker_endpoint) to JSON schema types (object)
|
|
1353
1589
|
# - C++ constructor defaults to structured JSON objects
|