@techwavedev/agi-agent-kit 1.1.7 ā 1.2.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.
Potentially problematic release.
This version of @techwavedev/agi-agent-kit might be problematic. Click here for more details.
- package/CHANGELOG.md +82 -1
- package/README.md +190 -12
- package/bin/init.js +30 -2
- package/package.json +6 -3
- package/templates/base/AGENTS.md +54 -23
- package/templates/base/README.md +325 -0
- package/templates/base/directives/memory_integration.md +95 -0
- package/templates/base/execution/memory_manager.py +309 -0
- package/templates/base/execution/session_boot.py +218 -0
- package/templates/base/execution/session_init.py +320 -0
- package/templates/base/skill-creator/SKILL_skillcreator.md +23 -36
- package/templates/base/skill-creator/scripts/init_skill.py +18 -135
- package/templates/skills/ec/README.md +31 -0
- package/templates/skills/ec/aws/SKILL.md +1020 -0
- package/templates/skills/ec/aws/defaults.yaml +13 -0
- package/templates/skills/ec/aws/references/common_patterns.md +80 -0
- package/templates/skills/ec/aws/references/mcp_servers.md +98 -0
- package/templates/skills/ec/aws-terraform/SKILL.md +349 -0
- package/templates/skills/ec/aws-terraform/references/best_practices.md +394 -0
- package/templates/skills/ec/aws-terraform/references/checkov_reference.md +337 -0
- package/templates/skills/ec/aws-terraform/scripts/configure_mcp.py +150 -0
- package/templates/skills/ec/confluent-kafka/SKILL.md +655 -0
- package/templates/skills/ec/confluent-kafka/references/ansible_playbooks.md +792 -0
- package/templates/skills/ec/confluent-kafka/references/ec_deployment.md +579 -0
- package/templates/skills/ec/confluent-kafka/references/kraft_migration.md +490 -0
- package/templates/skills/ec/confluent-kafka/references/troubleshooting.md +778 -0
- package/templates/skills/ec/confluent-kafka/references/upgrade_7x_to_8x.md +488 -0
- package/templates/skills/ec/confluent-kafka/scripts/kafka_health_check.py +435 -0
- package/templates/skills/ec/confluent-kafka/scripts/upgrade_preflight.py +568 -0
- package/templates/skills/ec/confluent-kafka/scripts/validate_config.py +455 -0
- package/templates/skills/ec/consul/SKILL.md +427 -0
- package/templates/skills/ec/consul/references/acl_setup.md +168 -0
- package/templates/skills/ec/consul/references/ha_config.md +196 -0
- package/templates/skills/ec/consul/references/troubleshooting.md +267 -0
- package/templates/skills/ec/consul/references/upgrades.md +213 -0
- package/templates/skills/ec/consul/scripts/consul_health_report.py +530 -0
- package/templates/skills/ec/consul/scripts/consul_status.py +264 -0
- package/templates/skills/ec/consul/scripts/generate_values.py +170 -0
- package/templates/skills/ec/documentation/SKILL.md +351 -0
- package/templates/skills/ec/documentation/references/best_practices.md +201 -0
- package/templates/skills/ec/documentation/scripts/analyze_code.py +307 -0
- package/templates/skills/ec/documentation/scripts/detect_changes.py +460 -0
- package/templates/skills/ec/documentation/scripts/generate_changelog.py +312 -0
- package/templates/skills/ec/documentation/scripts/sync_docs.py +272 -0
- package/templates/skills/ec/documentation/scripts/update_skill_docs.py +366 -0
- package/templates/skills/ec/gitlab/SKILL.md +529 -0
- package/templates/skills/ec/gitlab/references/agent_installation.md +416 -0
- package/templates/skills/ec/gitlab/references/api_reference.md +508 -0
- package/templates/skills/ec/gitlab/references/gitops_flux.md +465 -0
- package/templates/skills/ec/gitlab/references/troubleshooting.md +518 -0
- package/templates/skills/ec/gitlab/scripts/generate_agent_values.py +329 -0
- package/templates/skills/ec/gitlab/scripts/gitlab_agent_status.py +414 -0
- package/templates/skills/ec/jira/SKILL.md +484 -0
- package/templates/skills/ec/jira/references/jql_reference.md +148 -0
- package/templates/skills/ec/jira/scripts/add_comment.py +91 -0
- package/templates/skills/ec/jira/scripts/bulk_log_work.py +124 -0
- package/templates/skills/ec/jira/scripts/create_ticket.py +162 -0
- package/templates/skills/ec/jira/scripts/get_ticket.py +191 -0
- package/templates/skills/ec/jira/scripts/jira_client.py +383 -0
- package/templates/skills/ec/jira/scripts/log_work.py +154 -0
- package/templates/skills/ec/jira/scripts/search_tickets.py +104 -0
- package/templates/skills/ec/jira/scripts/update_comment.py +67 -0
- package/templates/skills/ec/jira/scripts/update_ticket.py +161 -0
- package/templates/skills/ec/karpenter/SKILL.md +301 -0
- package/templates/skills/ec/karpenter/references/ec2nodeclasses.md +421 -0
- package/templates/skills/ec/karpenter/references/migration.md +396 -0
- package/templates/skills/ec/karpenter/references/nodepools.md +400 -0
- package/templates/skills/ec/karpenter/references/troubleshooting.md +359 -0
- package/templates/skills/ec/karpenter/scripts/generate_ec2nodeclass.py +187 -0
- package/templates/skills/ec/karpenter/scripts/generate_nodepool.py +245 -0
- package/templates/skills/ec/karpenter/scripts/karpenter_status.py +359 -0
- package/templates/skills/ec/opensearch/SKILL.md +720 -0
- package/templates/skills/ec/opensearch/references/ml_neural_search.md +576 -0
- package/templates/skills/ec/opensearch/references/operator.md +532 -0
- package/templates/skills/ec/opensearch/references/query_dsl.md +532 -0
- package/templates/skills/ec/opensearch/scripts/configure_mcp.py +148 -0
- package/templates/skills/ec/victoriametrics/SKILL.md +598 -0
- package/templates/skills/ec/victoriametrics/references/kubernetes.md +531 -0
- package/templates/skills/ec/victoriametrics/references/prometheus_migration.md +333 -0
- package/templates/skills/ec/victoriametrics/references/troubleshooting.md +442 -0
- package/templates/skills/knowledge/SKILLS_CATALOG.md +274 -4
- package/templates/skills/knowledge/intelligent-routing/SKILL.md +237 -164
- package/templates/skills/knowledge/parallel-agents/SKILL.md +345 -73
- package/templates/skills/knowledge/plugin-discovery/SKILL.md +582 -0
- package/templates/skills/knowledge/plugin-discovery/scripts/platform_setup.py +1083 -0
- package/templates/skills/knowledge/design-md/README.md +0 -34
- package/templates/skills/knowledge/design-md/SKILL.md +0 -193
- package/templates/skills/knowledge/design-md/examples/DESIGN.md +0 -154
- package/templates/skills/knowledge/notebooklm-mcp/SKILL.md +0 -71
- package/templates/skills/knowledge/notebooklm-mcp/assets/example_asset.txt +0 -24
- package/templates/skills/knowledge/notebooklm-mcp/references/api_reference.md +0 -34
- package/templates/skills/knowledge/notebooklm-mcp/scripts/example.py +0 -19
- package/templates/skills/knowledge/react-components/README.md +0 -36
- package/templates/skills/knowledge/react-components/SKILL.md +0 -53
- package/templates/skills/knowledge/react-components/examples/gold-standard-card.tsx +0 -80
- package/templates/skills/knowledge/react-components/package-lock.json +0 -231
- package/templates/skills/knowledge/react-components/package.json +0 -16
- package/templates/skills/knowledge/react-components/resources/architecture-checklist.md +0 -15
- package/templates/skills/knowledge/react-components/resources/component-template.tsx +0 -37
- package/templates/skills/knowledge/react-components/resources/stitch-api-reference.md +0 -14
- package/templates/skills/knowledge/react-components/resources/style-guide.json +0 -27
- package/templates/skills/knowledge/react-components/scripts/fetch-stitch.sh +0 -30
- package/templates/skills/knowledge/react-components/scripts/validate.js +0 -68
- package/templates/skills/knowledge/self-update/SKILL.md +0 -60
- package/templates/skills/knowledge/self-update/scripts/update_kit.py +0 -103
- package/templates/skills/knowledge/stitch-loop/README.md +0 -54
- package/templates/skills/knowledge/stitch-loop/SKILL.md +0 -235
- package/templates/skills/knowledge/stitch-loop/examples/SITE.md +0 -73
- package/templates/skills/knowledge/stitch-loop/examples/next-prompt.md +0 -25
- package/templates/skills/knowledge/stitch-loop/resources/baton-schema.md +0 -61
- package/templates/skills/knowledge/stitch-loop/resources/site-template.md +0 -104
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Confluent Kafka Configuration Validator
|
|
4
|
+
|
|
5
|
+
Validates Kafka broker server.properties for:
|
|
6
|
+
- Required settings presence
|
|
7
|
+
- Deprecated configuration detection
|
|
8
|
+
- Version-specific compatibility
|
|
9
|
+
- Security best practices
|
|
10
|
+
- Performance recommendations
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python validate_config.py --config /opt/confluent/etc/kafka/server.properties --version 8.0
|
|
14
|
+
python validate_config.py --compare broker-01:config1.properties broker-02:config2.properties
|
|
15
|
+
|
|
16
|
+
Arguments:
|
|
17
|
+
--config, -c Path to server.properties file to validate
|
|
18
|
+
--version, -v Target Confluent version (7.x, 8.x) for compatibility checks
|
|
19
|
+
--compare Compare configurations between brokers
|
|
20
|
+
--strict Enable strict mode (fail on warnings)
|
|
21
|
+
--json Output results as JSON
|
|
22
|
+
|
|
23
|
+
Exit Codes:
|
|
24
|
+
0 - Valid configuration
|
|
25
|
+
1 - Invalid arguments
|
|
26
|
+
2 - Configuration file not found
|
|
27
|
+
3 - Validation errors found
|
|
28
|
+
4 - Critical errors (deprecated or incompatible settings)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import re
|
|
34
|
+
import sys
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Dict, List, Optional, Tuple
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Deprecated settings by version
|
|
40
|
+
DEPRECATED_SETTINGS = {
|
|
41
|
+
"8.0": [
|
|
42
|
+
"log.message.format.version",
|
|
43
|
+
"inter.broker.protocol.version",
|
|
44
|
+
"kafka.metrics.reporters", # Renamed
|
|
45
|
+
"broker.id.generation.enable", # KRaft handles this
|
|
46
|
+
],
|
|
47
|
+
"7.6": [
|
|
48
|
+
"kafka.metrics.reporters",
|
|
49
|
+
],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Required settings for KRaft mode
|
|
53
|
+
KRAFT_REQUIRED = [
|
|
54
|
+
"process.roles",
|
|
55
|
+
"node.id",
|
|
56
|
+
"controller.quorum.voters",
|
|
57
|
+
"controller.listener.names",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# Required settings for any production broker
|
|
61
|
+
PRODUCTION_REQUIRED = [
|
|
62
|
+
"log.dirs",
|
|
63
|
+
"listeners",
|
|
64
|
+
"advertised.listeners",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
# Security best practices
|
|
68
|
+
SECURITY_RECOMMENDED = {
|
|
69
|
+
"authorizer.class.name": "kafka.security.authorizer.AclAuthorizer",
|
|
70
|
+
"ssl.keystore.location": None, # Just check existence
|
|
71
|
+
"ssl.truststore.location": None,
|
|
72
|
+
"sasl.enabled.mechanisms": None,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Performance recommendations
|
|
76
|
+
PERFORMANCE_DEFAULTS = {
|
|
77
|
+
"num.network.threads": ("8", "Should be at least 8 for production"),
|
|
78
|
+
"num.io.threads": ("16", "Should be 2x network threads or more"),
|
|
79
|
+
"socket.send.buffer.bytes": ("102400", "Increase for high throughput"),
|
|
80
|
+
"socket.receive.buffer.bytes": ("102400", "Increase for high throughput"),
|
|
81
|
+
"num.replica.fetchers": ("4", "Increase for faster replication"),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ConfigValidator:
|
|
86
|
+
"""Validates Kafka broker configuration."""
|
|
87
|
+
|
|
88
|
+
def __init__(self, config_path: str, version: str = "8.0"):
|
|
89
|
+
self.config_path = Path(config_path)
|
|
90
|
+
self.version = version
|
|
91
|
+
self.config: Dict[str, str] = {}
|
|
92
|
+
self.errors: List[str] = []
|
|
93
|
+
self.warnings: List[str] = []
|
|
94
|
+
self.info: List[str] = []
|
|
95
|
+
|
|
96
|
+
def load_config(self) -> bool:
|
|
97
|
+
"""Load and parse the configuration file."""
|
|
98
|
+
if not self.config_path.exists():
|
|
99
|
+
self.errors.append(f"Configuration file not found: {self.config_path}")
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
with open(self.config_path, "r") as f:
|
|
104
|
+
for line_num, line in enumerate(f, 1):
|
|
105
|
+
line = line.strip()
|
|
106
|
+
# Skip comments and empty lines
|
|
107
|
+
if not line or line.startswith("#"):
|
|
108
|
+
continue
|
|
109
|
+
# Parse key=value
|
|
110
|
+
if "=" in line:
|
|
111
|
+
key, value = line.split("=", 1)
|
|
112
|
+
self.config[key.strip()] = value.strip()
|
|
113
|
+
return True
|
|
114
|
+
except Exception as e:
|
|
115
|
+
self.errors.append(f"Failed to parse config: {e}")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def check_deprecated(self) -> None:
|
|
119
|
+
"""Check for deprecated settings."""
|
|
120
|
+
deprecated = DEPRECATED_SETTINGS.get(self.version, [])
|
|
121
|
+
for setting in deprecated:
|
|
122
|
+
if setting in self.config:
|
|
123
|
+
self.errors.append(
|
|
124
|
+
f"DEPRECATED: '{setting}' is deprecated in version {self.version}. "
|
|
125
|
+
f"Current value: {self.config[setting]}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def check_kraft_mode(self) -> None:
|
|
129
|
+
"""Check KRaft mode configuration."""
|
|
130
|
+
# Check if using KRaft
|
|
131
|
+
if "process.roles" in self.config:
|
|
132
|
+
self.info.append("KRaft mode detected")
|
|
133
|
+
|
|
134
|
+
# Verify required KRaft settings
|
|
135
|
+
for setting in KRAFT_REQUIRED:
|
|
136
|
+
if setting not in self.config:
|
|
137
|
+
self.errors.append(f"MISSING: Required KRaft setting '{setting}'")
|
|
138
|
+
|
|
139
|
+
# Check for conflicting ZooKeeper settings
|
|
140
|
+
if "zookeeper.connect" in self.config and "zookeeper.metadata.migration.enable" not in self.config:
|
|
141
|
+
self.errors.append(
|
|
142
|
+
"CONFLICT: 'zookeeper.connect' present without 'zookeeper.metadata.migration.enable'. "
|
|
143
|
+
"Either remove ZK config or enable migration mode."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Validate process.roles
|
|
147
|
+
roles = self.config.get("process.roles", "")
|
|
148
|
+
valid_roles = {"broker", "controller", "broker,controller", "controller,broker"}
|
|
149
|
+
if roles not in valid_roles:
|
|
150
|
+
self.errors.append(f"INVALID: 'process.roles' must be one of {valid_roles}")
|
|
151
|
+
else:
|
|
152
|
+
# ZooKeeper mode
|
|
153
|
+
if "zookeeper.connect" in self.config:
|
|
154
|
+
self.warnings.append(
|
|
155
|
+
"ZooKeeper mode detected. Consider migrating to KRaft for version 8.x+"
|
|
156
|
+
)
|
|
157
|
+
else:
|
|
158
|
+
self.errors.append(
|
|
159
|
+
"MISSING: Neither 'process.roles' (KRaft) nor 'zookeeper.connect' (ZK) found"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def check_required(self) -> None:
|
|
163
|
+
"""Check for required production settings."""
|
|
164
|
+
for setting in PRODUCTION_REQUIRED:
|
|
165
|
+
if setting not in self.config:
|
|
166
|
+
self.errors.append(f"MISSING: Required setting '{setting}'")
|
|
167
|
+
|
|
168
|
+
def check_security(self) -> None:
|
|
169
|
+
"""Check security best practices."""
|
|
170
|
+
# Check for authentication
|
|
171
|
+
if "sasl.enabled.mechanisms" not in self.config and "ssl.keystore.location" not in self.config:
|
|
172
|
+
self.warnings.append(
|
|
173
|
+
"SECURITY: No authentication configured (SASL or SSL client auth)"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Check for authorization
|
|
177
|
+
if "authorizer.class.name" not in self.config:
|
|
178
|
+
self.warnings.append(
|
|
179
|
+
"SECURITY: No authorizer configured. Consider enabling ACLs."
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Check for TLS
|
|
183
|
+
if "ssl.keystore.location" not in self.config:
|
|
184
|
+
self.warnings.append(
|
|
185
|
+
"SECURITY: SSL/TLS not configured for broker"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Check super.users
|
|
189
|
+
if "super.users" not in self.config and "authorizer.class.name" in self.config:
|
|
190
|
+
self.warnings.append(
|
|
191
|
+
"SECURITY: 'super.users' not set while ACLs enabled. "
|
|
192
|
+
"Ensure admin access is configured."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def check_performance(self) -> None:
|
|
196
|
+
"""Check performance recommendations."""
|
|
197
|
+
for setting, (recommended, reason) in PERFORMANCE_DEFAULTS.items():
|
|
198
|
+
if setting in self.config:
|
|
199
|
+
try:
|
|
200
|
+
current = int(self.config[setting])
|
|
201
|
+
rec = int(recommended)
|
|
202
|
+
if current < rec:
|
|
203
|
+
self.info.append(
|
|
204
|
+
f"PERF: '{setting}' is {current}, recommended: {rec}. {reason}"
|
|
205
|
+
)
|
|
206
|
+
except ValueError:
|
|
207
|
+
pass
|
|
208
|
+
else:
|
|
209
|
+
self.info.append(f"PERF: '{setting}' not set. Default may be suboptimal.")
|
|
210
|
+
|
|
211
|
+
def check_listeners(self) -> None:
|
|
212
|
+
"""Validate listener configuration."""
|
|
213
|
+
listeners = self.config.get("listeners", "")
|
|
214
|
+
advertised = self.config.get("advertised.listeners", "")
|
|
215
|
+
security_map = self.config.get("listener.security.protocol.map", "")
|
|
216
|
+
|
|
217
|
+
# Parse listeners
|
|
218
|
+
listener_names = set()
|
|
219
|
+
for listener in listeners.split(","):
|
|
220
|
+
if "://" in listener:
|
|
221
|
+
name = listener.split("://")[0]
|
|
222
|
+
listener_names.add(name)
|
|
223
|
+
|
|
224
|
+
# Check advertised.listeners matches
|
|
225
|
+
if advertised:
|
|
226
|
+
for listener in advertised.split(","):
|
|
227
|
+
if "://" in listener:
|
|
228
|
+
name = listener.split("://")[0]
|
|
229
|
+
if name not in listener_names:
|
|
230
|
+
self.errors.append(
|
|
231
|
+
f"MISMATCH: Advertised listener '{name}' not in listeners"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Check security protocol map
|
|
235
|
+
if listener_names and security_map:
|
|
236
|
+
mapped_names = set()
|
|
237
|
+
for mapping in security_map.split(","):
|
|
238
|
+
if ":" in mapping:
|
|
239
|
+
name = mapping.split(":")[0]
|
|
240
|
+
mapped_names.add(name)
|
|
241
|
+
|
|
242
|
+
unmapped = listener_names - mapped_names
|
|
243
|
+
if unmapped:
|
|
244
|
+
self.errors.append(
|
|
245
|
+
f"MISMATCH: Listeners {unmapped} not in security protocol map"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def check_replication(self) -> None:
|
|
249
|
+
"""Check replication settings."""
|
|
250
|
+
rf = self.config.get("default.replication.factor", "1")
|
|
251
|
+
min_isr = self.config.get("min.insync.replicas", "1")
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
if int(rf) < 3:
|
|
255
|
+
self.warnings.append(
|
|
256
|
+
f"DURABILITY: 'default.replication.factor' is {rf}. "
|
|
257
|
+
f"Recommended: 3 for production."
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if int(min_isr) < 2:
|
|
261
|
+
self.warnings.append(
|
|
262
|
+
f"DURABILITY: 'min.insync.replicas' is {min_isr}. "
|
|
263
|
+
f"Recommended: 2 for production with RF>=3."
|
|
264
|
+
)
|
|
265
|
+
except ValueError:
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
def validate(self) -> Dict:
|
|
269
|
+
"""Run all validation checks."""
|
|
270
|
+
if not self.load_config():
|
|
271
|
+
return self.get_results()
|
|
272
|
+
|
|
273
|
+
self.check_deprecated()
|
|
274
|
+
self.check_kraft_mode()
|
|
275
|
+
self.check_required()
|
|
276
|
+
self.check_listeners()
|
|
277
|
+
self.check_security()
|
|
278
|
+
self.check_replication()
|
|
279
|
+
self.check_performance()
|
|
280
|
+
|
|
281
|
+
return self.get_results()
|
|
282
|
+
|
|
283
|
+
def get_results(self) -> Dict:
|
|
284
|
+
"""Get validation results."""
|
|
285
|
+
status = "valid"
|
|
286
|
+
if self.errors:
|
|
287
|
+
status = "invalid"
|
|
288
|
+
elif self.warnings:
|
|
289
|
+
status = "warnings"
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
"config_path": str(self.config_path),
|
|
293
|
+
"version": self.version,
|
|
294
|
+
"status": status,
|
|
295
|
+
"errors": self.errors,
|
|
296
|
+
"warnings": self.warnings,
|
|
297
|
+
"info": self.info,
|
|
298
|
+
"settings_count": len(self.config),
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def compare_configs(configs: List[str]) -> Dict:
|
|
303
|
+
"""Compare configurations between multiple brokers."""
|
|
304
|
+
parsed = {}
|
|
305
|
+
differences = []
|
|
306
|
+
|
|
307
|
+
for config_spec in configs:
|
|
308
|
+
if ":" in config_spec:
|
|
309
|
+
name, path = config_spec.split(":", 1)
|
|
310
|
+
else:
|
|
311
|
+
name = Path(config_spec).stem
|
|
312
|
+
path = config_spec
|
|
313
|
+
|
|
314
|
+
validator = ConfigValidator(path)
|
|
315
|
+
if validator.load_config():
|
|
316
|
+
parsed[name] = validator.config
|
|
317
|
+
|
|
318
|
+
if len(parsed) < 2:
|
|
319
|
+
return {"error": "Need at least 2 valid configs to compare"}
|
|
320
|
+
|
|
321
|
+
# Find all unique keys
|
|
322
|
+
all_keys = set()
|
|
323
|
+
for config in parsed.values():
|
|
324
|
+
all_keys.update(config.keys())
|
|
325
|
+
|
|
326
|
+
# Compare each key
|
|
327
|
+
for key in sorted(all_keys):
|
|
328
|
+
values = {}
|
|
329
|
+
for name, config in parsed.items():
|
|
330
|
+
values[name] = config.get(key, "<not set>")
|
|
331
|
+
|
|
332
|
+
unique_values = set(values.values())
|
|
333
|
+
if len(unique_values) > 1:
|
|
334
|
+
differences.append({
|
|
335
|
+
"setting": key,
|
|
336
|
+
"values": values,
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
"configs": list(parsed.keys()),
|
|
341
|
+
"differences": differences,
|
|
342
|
+
"identical_settings": len(all_keys) - len(differences),
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def print_results(results: Dict, as_json: bool = False) -> None:
|
|
347
|
+
"""Print validation results."""
|
|
348
|
+
if as_json:
|
|
349
|
+
print(json.dumps(results, indent=2))
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
print(f"\nš Configuration Validation: {results['config_path']}")
|
|
353
|
+
print(f" Target Version: {results['version']}")
|
|
354
|
+
print(f" Settings Count: {results['settings_count']}")
|
|
355
|
+
print(f" Status: {'ā
' + results['status'].upper() if results['status'] == 'valid' else 'ā ļø ' + results['status'].upper() if results['status'] == 'warnings' else 'ā ' + results['status'].upper()}")
|
|
356
|
+
print()
|
|
357
|
+
|
|
358
|
+
if results["errors"]:
|
|
359
|
+
print("ā Errors:")
|
|
360
|
+
for error in results["errors"]:
|
|
361
|
+
print(f" ⢠{error}")
|
|
362
|
+
print()
|
|
363
|
+
|
|
364
|
+
if results["warnings"]:
|
|
365
|
+
print("ā ļø Warnings:")
|
|
366
|
+
for warning in results["warnings"]:
|
|
367
|
+
print(f" ⢠{warning}")
|
|
368
|
+
print()
|
|
369
|
+
|
|
370
|
+
if results["info"]:
|
|
371
|
+
print("ā¹ļø Recommendations:")
|
|
372
|
+
for info in results["info"]:
|
|
373
|
+
print(f" ⢠{info}")
|
|
374
|
+
print()
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def print_comparison(results: Dict, as_json: bool = False) -> None:
|
|
378
|
+
"""Print comparison results."""
|
|
379
|
+
if as_json:
|
|
380
|
+
print(json.dumps(results, indent=2))
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
if "error" in results:
|
|
384
|
+
print(f"ā {results['error']}")
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
print(f"\nš Configuration Comparison")
|
|
388
|
+
print(f" Brokers: {', '.join(results['configs'])}")
|
|
389
|
+
print(f" Identical Settings: {results['identical_settings']}")
|
|
390
|
+
print(f" Differences: {len(results['differences'])}")
|
|
391
|
+
print()
|
|
392
|
+
|
|
393
|
+
if results["differences"]:
|
|
394
|
+
print("š Differences:")
|
|
395
|
+
for diff in results["differences"]:
|
|
396
|
+
print(f"\n {diff['setting']}:")
|
|
397
|
+
for broker, value in diff["values"].items():
|
|
398
|
+
print(f" {broker}: {value}")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def main():
|
|
402
|
+
parser = argparse.ArgumentParser(
|
|
403
|
+
description="Confluent Kafka Configuration Validator",
|
|
404
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
405
|
+
)
|
|
406
|
+
parser.add_argument(
|
|
407
|
+
"--config", "-c",
|
|
408
|
+
help="Path to server.properties file to validate",
|
|
409
|
+
)
|
|
410
|
+
parser.add_argument(
|
|
411
|
+
"--version", "-v",
|
|
412
|
+
default="8.0",
|
|
413
|
+
help="Target Confluent version (default: 8.0)",
|
|
414
|
+
)
|
|
415
|
+
parser.add_argument(
|
|
416
|
+
"--compare",
|
|
417
|
+
nargs="+",
|
|
418
|
+
help="Compare configurations (format: name:path or just path)",
|
|
419
|
+
)
|
|
420
|
+
parser.add_argument(
|
|
421
|
+
"--strict",
|
|
422
|
+
action="store_true",
|
|
423
|
+
help="Fail on warnings (exit code 3)",
|
|
424
|
+
)
|
|
425
|
+
parser.add_argument(
|
|
426
|
+
"--json",
|
|
427
|
+
action="store_true",
|
|
428
|
+
help="Output results as JSON",
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
args = parser.parse_args()
|
|
432
|
+
|
|
433
|
+
if args.compare:
|
|
434
|
+
results = compare_configs(args.compare)
|
|
435
|
+
print_comparison(results, args.json)
|
|
436
|
+
sys.exit(0 if not results.get("differences") else 3)
|
|
437
|
+
|
|
438
|
+
if not args.config:
|
|
439
|
+
parser.error("--config is required when not using --compare")
|
|
440
|
+
|
|
441
|
+
validator = ConfigValidator(args.config, args.version)
|
|
442
|
+
results = validator.validate()
|
|
443
|
+
print_results(results, args.json)
|
|
444
|
+
|
|
445
|
+
# Exit codes
|
|
446
|
+
if results["status"] == "invalid":
|
|
447
|
+
sys.exit(4 if any("DEPRECATED" in e for e in results["errors"]) else 3)
|
|
448
|
+
elif results["status"] == "warnings" and args.strict:
|
|
449
|
+
sys.exit(3)
|
|
450
|
+
else:
|
|
451
|
+
sys.exit(0)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
if __name__ == "__main__":
|
|
455
|
+
main()
|