@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.
@@ -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
- Dynamically resolve C++ function calls to their return values by searching the source code.
199
+ Resolve certain small, known C++ zero-argument functions to their literal return values by searching Redpanda source files.
85
200
 
86
- Args:
87
- function_name: The C++ function name (e.g., "model::kafka_audit_logging_topic")
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 value or None if not found
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 description override
372
- if "description" in override:
373
- properties[prop]["description"] = override["description"]
374
-
375
- # Apply version override (introduced in version)
376
- if "version" in override:
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
- print(f"Warning: Example file not found: {override['example_file']}")
448
- print(f"Searched in: {', '.join(search_paths)}")
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
- print(f"Error reading example file {file_path}: {e}")
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
- defined_in = prop.get("defined_in", "")
505
- if defined_in == "src/v/config/configuration.cc":
506
- prop["config_scope"] = "cluster"
507
- elif defined_in == "src/v/config/node_config.cc":
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["config_scope"] = None
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 type references and expand default values for all properties.
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
- 1. **Type Resolution**: Converts C++ type names to JSON schema types
521
- - model::broker_endpoint -> "object"
522
- - std::string -> "string"
523
- - Handles both direct type names and JSON pointer references (#/definitions/...)
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
- 2. **Default Value Expansion**: Transforms C++ constructor syntax to JSON objects
526
- - model::broker_endpoint(net::unresolved_address("127.0.0.1", 9644))
527
- -> {address: "127.0.0.1", port: 9644}
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
- 3. **Array Default Handling**: Ensures one_or_many_property defaults are arrays
530
- - For properties with type="array", wraps single object defaults in arrays
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="Extract all properties from the Redpanda's source code and generate a JSON output with their definitions"
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
- "--definitions",
1254
- type=str,
1255
- required=False,
1256
- default=os.path.dirname(os.path.realpath(__file__)) + "/definitions.json",
1257
- help='JSON file with the type definitions. This file will be merged in the output under the "definitions" field',
1258
- )
1259
-
1260
- arg_parser.add_argument(
1261
- "--overrides",
1262
- type=str,
1263
- required=False,
1264
- help='Optional JSON file with property description overrides',
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="INFO")
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. Resolve type references and expand default values for original properties
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. Resolve type references and expand default values
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
@@ -1,3 +1,4 @@
1
1
  tree_sitter==0.21.1
2
2
  setuptools>=42.0.0
3
3
  pyyaml>=6.0
4
+ requests>=2.32.5