@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,530 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Consul Health Report Generator
|
|
4
|
+
|
|
5
|
+
Generates a PDF health report for Consul clusters running on EKS.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python consul_health_report.py --environment nonprod --output ./reports/
|
|
9
|
+
python consul_health_report.py --environment prod --output ./reports/ --format pdf
|
|
10
|
+
python consul_health_report.py --environment nonprod --format markdown
|
|
11
|
+
|
|
12
|
+
Arguments:
|
|
13
|
+
--environment, -e Environment name (nonprod, prod, etc.)
|
|
14
|
+
--output, -o Output directory (default: current directory)
|
|
15
|
+
--format, -f Output format: pdf, markdown, or both (default: both)
|
|
16
|
+
--namespace, -n Consul namespace (default: consul)
|
|
17
|
+
|
|
18
|
+
Exit Codes:
|
|
19
|
+
0 - Success
|
|
20
|
+
1 - Invalid arguments
|
|
21
|
+
2 - Kubectl not available or cluster access failed
|
|
22
|
+
3 - Report generation failed
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
import os
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
# Environment to EKS cluster mapping
|
|
34
|
+
CLUSTER_MAP = {
|
|
35
|
+
'nonprod': 'eks-nonprod',
|
|
36
|
+
'prod': 'eks-prod',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def run_kubectl(cmd, namespace='consul'):
|
|
40
|
+
"""Execute kubectl command and return output."""
|
|
41
|
+
full_cmd = f"kubectl {cmd}"
|
|
42
|
+
try:
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
full_cmd, shell=True, capture_output=True, text=True, timeout=30
|
|
45
|
+
)
|
|
46
|
+
return result.stdout.strip(), result.returncode
|
|
47
|
+
except subprocess.TimeoutExpired:
|
|
48
|
+
return "Command timed out", 1
|
|
49
|
+
except Exception as e:
|
|
50
|
+
return str(e), 1
|
|
51
|
+
|
|
52
|
+
def run_consul_cmd(cmd, namespace='consul', token=None):
|
|
53
|
+
"""Execute command inside Consul server pod."""
|
|
54
|
+
token_flag = f'-token="{token}"' if token else ''
|
|
55
|
+
full_cmd = f'kubectl exec -n {namespace} consul-server-0 -- consul {cmd} {token_flag}'
|
|
56
|
+
return run_kubectl(full_cmd.replace('kubectl ', ''))
|
|
57
|
+
|
|
58
|
+
def get_bootstrap_token(namespace='consul'):
|
|
59
|
+
"""Retrieve Consul bootstrap ACL token from Kubernetes secret."""
|
|
60
|
+
cmd = f"get secret -n {namespace} consul-bootstrap-acl-token -o jsonpath='{{.data.token}}'"
|
|
61
|
+
output, rc = run_kubectl(cmd)
|
|
62
|
+
if rc == 0 and output:
|
|
63
|
+
import base64
|
|
64
|
+
try:
|
|
65
|
+
return base64.b64decode(output).decode('utf-8')
|
|
66
|
+
except:
|
|
67
|
+
return None
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def set_cluster_context(environment, region='eu-west-1'):
|
|
71
|
+
"""Set kubectl context to the target EKS cluster."""
|
|
72
|
+
cluster_name = CLUSTER_MAP.get(environment, f'eks-{environment}')
|
|
73
|
+
cmd = f"aws eks update-kubeconfig --name {cluster_name} --region {region}"
|
|
74
|
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
75
|
+
return result.returncode == 0
|
|
76
|
+
|
|
77
|
+
def collect_health_data(namespace='consul'):
|
|
78
|
+
"""Collect all health data from Consul cluster."""
|
|
79
|
+
data = {
|
|
80
|
+
'timestamp': datetime.now().isoformat(),
|
|
81
|
+
'pods': {},
|
|
82
|
+
'helm': {},
|
|
83
|
+
'members': [],
|
|
84
|
+
'raft_peers': [],
|
|
85
|
+
'autopilot': {},
|
|
86
|
+
'services': [],
|
|
87
|
+
'ca_config': {},
|
|
88
|
+
'warnings': [],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Get pods
|
|
92
|
+
output, rc = run_kubectl(f'-n {namespace} get pods -o json')
|
|
93
|
+
if rc == 0:
|
|
94
|
+
try:
|
|
95
|
+
pods_json = json.loads(output)
|
|
96
|
+
data['pods'] = pods_json
|
|
97
|
+
except:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
# Get helm release info
|
|
101
|
+
output, rc = run_kubectl(f'-n {namespace} get pods -o wide'.replace('kubectl ', ''))
|
|
102
|
+
result = subprocess.run(f'helm list -n {namespace} -o json', shell=True, capture_output=True, text=True)
|
|
103
|
+
if result.returncode == 0:
|
|
104
|
+
try:
|
|
105
|
+
data['helm'] = json.loads(result.stdout)
|
|
106
|
+
except:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
# Get Consul members
|
|
110
|
+
output, rc = run_consul_cmd('members')
|
|
111
|
+
if rc == 0:
|
|
112
|
+
lines = output.strip().split('\n')
|
|
113
|
+
for line in lines[1:]: # Skip header
|
|
114
|
+
if line.strip():
|
|
115
|
+
parts = line.split()
|
|
116
|
+
if len(parts) >= 6:
|
|
117
|
+
data['members'].append({
|
|
118
|
+
'node': parts[0],
|
|
119
|
+
'address': parts[1],
|
|
120
|
+
'status': parts[2],
|
|
121
|
+
'type': parts[3],
|
|
122
|
+
'build': parts[4],
|
|
123
|
+
'dc': parts[6] if len(parts) > 6 else '',
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
# Get bootstrap token for ACL-protected commands
|
|
127
|
+
token = get_bootstrap_token(namespace)
|
|
128
|
+
|
|
129
|
+
# Get Raft peers
|
|
130
|
+
if token:
|
|
131
|
+
output, rc = run_consul_cmd('operator raft list-peers', token=token)
|
|
132
|
+
if rc == 0:
|
|
133
|
+
lines = output.strip().split('\n')
|
|
134
|
+
for line in lines[1:]: # Skip header
|
|
135
|
+
if line.strip() and 'Defaulted' not in line:
|
|
136
|
+
parts = line.split()
|
|
137
|
+
if len(parts) >= 6:
|
|
138
|
+
data['raft_peers'].append({
|
|
139
|
+
'node': parts[0],
|
|
140
|
+
'id': parts[1],
|
|
141
|
+
'address': parts[2],
|
|
142
|
+
'state': parts[3],
|
|
143
|
+
'voter': parts[4],
|
|
144
|
+
'commit_index': parts[6] if len(parts) > 6 else '',
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
# Get autopilot state
|
|
148
|
+
output, rc = run_consul_cmd('operator autopilot state', token=token)
|
|
149
|
+
if rc == 0:
|
|
150
|
+
data['autopilot']['raw'] = output
|
|
151
|
+
data['autopilot']['healthy'] = 'Healthy: true' in output
|
|
152
|
+
|
|
153
|
+
# Get CA config
|
|
154
|
+
output, rc = run_consul_cmd('connect ca get-config', token=token)
|
|
155
|
+
if rc == 0:
|
|
156
|
+
try:
|
|
157
|
+
# Find JSON in output
|
|
158
|
+
json_start = output.find('{')
|
|
159
|
+
if json_start >= 0:
|
|
160
|
+
data['ca_config'] = json.loads(output[json_start:])
|
|
161
|
+
except:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
# Get registered services
|
|
165
|
+
output, rc = run_consul_cmd('catalog services')
|
|
166
|
+
if rc == 0:
|
|
167
|
+
services = [s.strip() for s in output.split('\n') if s.strip() and 'Defaulted' not in s]
|
|
168
|
+
data['services'] = services
|
|
169
|
+
|
|
170
|
+
# Check for warnings in logs
|
|
171
|
+
result = subprocess.run(
|
|
172
|
+
f'kubectl logs -n {namespace} -l app=consul,component=server --tail=100 2>&1 | grep -iE "(error|warn|fail)" | tail -20',
|
|
173
|
+
shell=True, capture_output=True, text=True
|
|
174
|
+
)
|
|
175
|
+
if result.returncode == 0 and result.stdout:
|
|
176
|
+
for line in result.stdout.strip().split('\n'):
|
|
177
|
+
if 'Check is now critical' in line:
|
|
178
|
+
# Extract service name
|
|
179
|
+
if 'check=service:' in line:
|
|
180
|
+
svc = line.split('check=service:')[1].strip()
|
|
181
|
+
data['warnings'].append({
|
|
182
|
+
'type': 'critical_check',
|
|
183
|
+
'service': svc,
|
|
184
|
+
'message': f'Health check failing for {svc}'
|
|
185
|
+
})
|
|
186
|
+
elif 'deprecated' in line.lower():
|
|
187
|
+
if not any(w['type'] == 'deprecated_token' for w in data['warnings']):
|
|
188
|
+
data['warnings'].append({
|
|
189
|
+
'type': 'deprecated_token',
|
|
190
|
+
'message': 'Deprecated token query parameter in use'
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
return data
|
|
194
|
+
|
|
195
|
+
def generate_pdf_report(data, environment, output_path):
|
|
196
|
+
"""Generate PDF report from collected data."""
|
|
197
|
+
try:
|
|
198
|
+
from fpdf import FPDF
|
|
199
|
+
from fpdf.enums import XPos, YPos
|
|
200
|
+
except ImportError:
|
|
201
|
+
print("Error: fpdf2 not installed. Install with: pip install fpdf2", file=sys.stderr)
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
class ConsulHealthReport(FPDF):
|
|
205
|
+
def __init__(self, env):
|
|
206
|
+
super().__init__()
|
|
207
|
+
self.env = env
|
|
208
|
+
|
|
209
|
+
def header(self):
|
|
210
|
+
self.set_font('Helvetica', 'B', 16)
|
|
211
|
+
self.set_text_color(0, 102, 204)
|
|
212
|
+
self.cell(0, 10, 'Consul Health Report', border=0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
|
|
213
|
+
self.set_font('Helvetica', '', 10)
|
|
214
|
+
self.set_text_color(100, 100, 100)
|
|
215
|
+
self.cell(0, 6, f'Environment: {self.env}', border=0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
|
|
216
|
+
self.cell(0, 6, f'Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', border=0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
|
|
217
|
+
self.ln(5)
|
|
218
|
+
self.set_draw_color(0, 102, 204)
|
|
219
|
+
self.line(10, self.get_y(), 200, self.get_y())
|
|
220
|
+
self.ln(5)
|
|
221
|
+
|
|
222
|
+
def footer(self):
|
|
223
|
+
self.set_y(-15)
|
|
224
|
+
self.set_font('Helvetica', 'I', 8)
|
|
225
|
+
self.set_text_color(128, 128, 128)
|
|
226
|
+
self.cell(0, 10, f'Page {self.page_no()}', align='C')
|
|
227
|
+
|
|
228
|
+
def section_title(self, title):
|
|
229
|
+
self.set_font('Helvetica', 'B', 14)
|
|
230
|
+
self.set_text_color(0, 51, 102)
|
|
231
|
+
self.cell(0, 10, title, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
|
|
232
|
+
self.ln(2)
|
|
233
|
+
|
|
234
|
+
def status_badge(self, status):
|
|
235
|
+
if status.upper() in ['HEALTHY', 'ALIVE', 'RUNNING', 'LEADER', 'OK']:
|
|
236
|
+
self.set_fill_color(40, 167, 69)
|
|
237
|
+
elif status.upper() in ['WARNING', 'WARN', 'FOLLOWER']:
|
|
238
|
+
self.set_fill_color(255, 193, 7)
|
|
239
|
+
else:
|
|
240
|
+
self.set_fill_color(220, 53, 69)
|
|
241
|
+
self.set_text_color(255, 255, 255)
|
|
242
|
+
self.set_font('Helvetica', 'B', 10)
|
|
243
|
+
self.cell(25, 6, status.upper(), fill=True, align='C')
|
|
244
|
+
self.set_text_color(0, 0, 0)
|
|
245
|
+
|
|
246
|
+
def add_table(self, headers, rows, col_widths=None):
|
|
247
|
+
self.set_font('Helvetica', 'B', 9)
|
|
248
|
+
self.set_fill_color(240, 240, 240)
|
|
249
|
+
self.set_text_color(0, 0, 0)
|
|
250
|
+
if col_widths is None:
|
|
251
|
+
col_widths = [190 / len(headers)] * len(headers)
|
|
252
|
+
for i, header in enumerate(headers):
|
|
253
|
+
self.cell(col_widths[i], 7, header, border=1, fill=True, align='C')
|
|
254
|
+
self.ln()
|
|
255
|
+
self.set_font('Helvetica', '', 8)
|
|
256
|
+
for row in rows:
|
|
257
|
+
for i, cell in enumerate(row):
|
|
258
|
+
self.cell(col_widths[i], 6, str(cell), border=1, align='C')
|
|
259
|
+
self.ln()
|
|
260
|
+
self.ln(3)
|
|
261
|
+
|
|
262
|
+
def add_key_value(self, key, value, indent=0):
|
|
263
|
+
self.set_font('Helvetica', 'B', 10)
|
|
264
|
+
self.set_x(10 + indent)
|
|
265
|
+
self.cell(50, 6, f'{key}:', new_x=XPos.RIGHT, new_y=YPos.TOP)
|
|
266
|
+
self.set_font('Helvetica', '', 10)
|
|
267
|
+
self.cell(0, 6, str(value), new_x=XPos.LMARGIN, new_y=YPos.NEXT)
|
|
268
|
+
|
|
269
|
+
pdf = ConsulHealthReport(environment)
|
|
270
|
+
pdf.add_page()
|
|
271
|
+
|
|
272
|
+
# Overall Status
|
|
273
|
+
is_healthy = data.get('autopilot', {}).get('healthy', False) and len(data.get('members', [])) >= 3
|
|
274
|
+
pdf.section_title('Overall Status')
|
|
275
|
+
pdf.status_badge('HEALTHY' if is_healthy else 'UNHEALTHY')
|
|
276
|
+
pdf.ln(10)
|
|
277
|
+
|
|
278
|
+
# Cluster Summary
|
|
279
|
+
pdf.section_title('Cluster Summary')
|
|
280
|
+
pdf.add_key_value('Datacenter', CLUSTER_MAP.get(environment, environment))
|
|
281
|
+
helm_info = data.get('helm', [{}])
|
|
282
|
+
if helm_info and len(helm_info) > 0:
|
|
283
|
+
h = helm_info[0]
|
|
284
|
+
pdf.add_key_value('Chart Version', h.get('chart', 'N/A'))
|
|
285
|
+
pdf.add_key_value('App Version', h.get('app_version', 'N/A'))
|
|
286
|
+
pdf.add_key_value('Revision', h.get('revision', 'N/A'))
|
|
287
|
+
pdf.add_key_value('Autopilot Health', 'Healthy' if data.get('autopilot', {}).get('healthy') else 'Unknown')
|
|
288
|
+
pdf.ln(5)
|
|
289
|
+
|
|
290
|
+
# Raft Consensus
|
|
291
|
+
if data.get('raft_peers'):
|
|
292
|
+
pdf.section_title('Raft Consensus (HA Health)')
|
|
293
|
+
headers = ['Node', 'Address', 'State', 'Voter', 'Commit Index']
|
|
294
|
+
rows = [[p['node'], p['address'], p['state'], p['voter'], p['commit_index']] for p in data['raft_peers']]
|
|
295
|
+
pdf.add_table(headers, rows, [40, 45, 25, 20, 30])
|
|
296
|
+
|
|
297
|
+
# Consul Members
|
|
298
|
+
if data.get('members'):
|
|
299
|
+
pdf.section_title('Consul Members')
|
|
300
|
+
headers = ['Node', 'Address', 'Status', 'Type', 'Build']
|
|
301
|
+
rows = [[m['node'], m['address'], m['status'], m['type'], m['build']] for m in data['members']]
|
|
302
|
+
pdf.add_table(headers, rows, [40, 45, 25, 25, 25])
|
|
303
|
+
|
|
304
|
+
# Services
|
|
305
|
+
if data.get('services'):
|
|
306
|
+
pdf.section_title(f'Registered Services ({len(data["services"])} total)')
|
|
307
|
+
pdf.set_font('Helvetica', '', 9)
|
|
308
|
+
for i, svc in enumerate(data['services']):
|
|
309
|
+
if i % 3 == 0 and i > 0:
|
|
310
|
+
pdf.ln()
|
|
311
|
+
pdf.cell(60, 5, f'- {svc}', new_x=XPos.RIGHT, new_y=YPos.TOP)
|
|
312
|
+
pdf.ln(10)
|
|
313
|
+
|
|
314
|
+
# CA Config
|
|
315
|
+
if data.get('ca_config'):
|
|
316
|
+
pdf.section_title('Connect CA (mTLS)')
|
|
317
|
+
cfg = data['ca_config'].get('Config', {})
|
|
318
|
+
pdf.add_key_value('Provider', data['ca_config'].get('Provider', 'N/A'))
|
|
319
|
+
pdf.add_key_value('Root Cert TTL', cfg.get('RootCertTTL', 'N/A'))
|
|
320
|
+
pdf.add_key_value('Intermediate TTL', cfg.get('IntermediateCertTTL', 'N/A'))
|
|
321
|
+
pdf.add_key_value('Leaf Cert TTL', cfg.get('LeafCertTTL', 'N/A'))
|
|
322
|
+
pdf.ln(5)
|
|
323
|
+
|
|
324
|
+
# Warnings
|
|
325
|
+
if data.get('warnings'):
|
|
326
|
+
pdf.add_page()
|
|
327
|
+
pdf.section_title('Warnings Detected')
|
|
328
|
+
for w in data['warnings']:
|
|
329
|
+
pdf.set_font('Helvetica', 'B', 10)
|
|
330
|
+
pdf.set_fill_color(255, 243, 205)
|
|
331
|
+
pdf.set_text_color(133, 100, 4)
|
|
332
|
+
pdf.multi_cell(190, 6, f"WARNING: {w.get('message', w.get('type', 'Unknown'))}", fill=True)
|
|
333
|
+
pdf.set_text_color(0, 0, 0)
|
|
334
|
+
pdf.set_font('Helvetica', '', 9)
|
|
335
|
+
if w.get('service'):
|
|
336
|
+
pdf.add_key_value('Service', w['service'], indent=5)
|
|
337
|
+
pdf.ln(5)
|
|
338
|
+
|
|
339
|
+
# Save
|
|
340
|
+
pdf.output(output_path)
|
|
341
|
+
return True
|
|
342
|
+
|
|
343
|
+
def generate_markdown_report(data, environment, output_path):
|
|
344
|
+
"""Generate Markdown report from collected data."""
|
|
345
|
+
is_healthy = data.get('autopilot', {}).get('healthy', False) and len(data.get('members', [])) >= 3
|
|
346
|
+
|
|
347
|
+
lines = [
|
|
348
|
+
f"# Consul Health Report",
|
|
349
|
+
"",
|
|
350
|
+
f"**Environment:** {environment} ({CLUSTER_MAP.get(environment, environment)})",
|
|
351
|
+
f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
|
|
352
|
+
"",
|
|
353
|
+
"---",
|
|
354
|
+
"",
|
|
355
|
+
"## Overall Status",
|
|
356
|
+
"",
|
|
357
|
+
f"| Status | Result |",
|
|
358
|
+
f"|--------|--------|",
|
|
359
|
+
f"| **Cluster Health** | {'✅ **HEALTHY**' if is_healthy else '❌ **UNHEALTHY**'} |",
|
|
360
|
+
"",
|
|
361
|
+
"---",
|
|
362
|
+
"",
|
|
363
|
+
"## Cluster Summary",
|
|
364
|
+
"",
|
|
365
|
+
"| Metric | Value |",
|
|
366
|
+
"|--------|-------|",
|
|
367
|
+
f"| **Datacenter** | `{CLUSTER_MAP.get(environment, environment)}` |",
|
|
368
|
+
]
|
|
369
|
+
|
|
370
|
+
helm_info = data.get('helm', [{}])
|
|
371
|
+
if helm_info and len(helm_info) > 0:
|
|
372
|
+
h = helm_info[0]
|
|
373
|
+
lines.append(f"| **Chart Version** | `{h.get('chart', 'N/A')}` |")
|
|
374
|
+
lines.append(f"| **App Version** | `{h.get('app_version', 'N/A')}` |")
|
|
375
|
+
lines.append(f"| **Revision** | {h.get('revision', 'N/A')} |")
|
|
376
|
+
|
|
377
|
+
lines.append(f"| **Autopilot Health** | {'✅ Healthy' if data.get('autopilot', {}).get('healthy') else 'Unknown'} |")
|
|
378
|
+
lines.extend(["", "---", ""])
|
|
379
|
+
|
|
380
|
+
# Raft peers
|
|
381
|
+
if data.get('raft_peers'):
|
|
382
|
+
lines.extend([
|
|
383
|
+
"## Raft Consensus (HA Health)",
|
|
384
|
+
"",
|
|
385
|
+
"| Node | Address | State | Voter | Commit Index |",
|
|
386
|
+
"|------|---------|-------|-------|--------------|",
|
|
387
|
+
])
|
|
388
|
+
for p in data['raft_peers']:
|
|
389
|
+
state = f"**{p['state']}**" if p['state'].lower() == 'leader' else p['state']
|
|
390
|
+
lines.append(f"| `{p['node']}` | {p['address']} | {state} | {p['voter']} | {p['commit_index']} |")
|
|
391
|
+
lines.extend(["", "---", ""])
|
|
392
|
+
|
|
393
|
+
# Members
|
|
394
|
+
if data.get('members'):
|
|
395
|
+
lines.extend([
|
|
396
|
+
"## Consul Members",
|
|
397
|
+
"",
|
|
398
|
+
"| Node | Address | Status | Type | Build |",
|
|
399
|
+
"|------|---------|--------|------|-------|",
|
|
400
|
+
])
|
|
401
|
+
for m in data['members']:
|
|
402
|
+
lines.append(f"| `{m['node']}` | {m['address']} | {m['status']} | {m['type']} | {m['build']} |")
|
|
403
|
+
lines.extend(["", "---", ""])
|
|
404
|
+
|
|
405
|
+
# Services
|
|
406
|
+
if data.get('services'):
|
|
407
|
+
lines.extend([
|
|
408
|
+
f"## Registered Services ({len(data['services'])} total)",
|
|
409
|
+
"",
|
|
410
|
+
])
|
|
411
|
+
for svc in data['services']:
|
|
412
|
+
lines.append(f"- `{svc}`")
|
|
413
|
+
lines.extend(["", "---", ""])
|
|
414
|
+
|
|
415
|
+
# CA Config
|
|
416
|
+
if data.get('ca_config'):
|
|
417
|
+
cfg = data['ca_config'].get('Config', {})
|
|
418
|
+
lines.extend([
|
|
419
|
+
"## Connect CA (mTLS)",
|
|
420
|
+
"",
|
|
421
|
+
"| Setting | Value |",
|
|
422
|
+
"|---------|-------|",
|
|
423
|
+
f"| **Provider** | {data['ca_config'].get('Provider', 'N/A')} |",
|
|
424
|
+
f"| **Root Cert TTL** | {cfg.get('RootCertTTL', 'N/A')} |",
|
|
425
|
+
f"| **Intermediate TTL** | {cfg.get('IntermediateCertTTL', 'N/A')} |",
|
|
426
|
+
f"| **Leaf Cert TTL** | {cfg.get('LeafCertTTL', 'N/A')} |",
|
|
427
|
+
"",
|
|
428
|
+
"---",
|
|
429
|
+
"",
|
|
430
|
+
])
|
|
431
|
+
|
|
432
|
+
# Warnings
|
|
433
|
+
if data.get('warnings'):
|
|
434
|
+
lines.extend([
|
|
435
|
+
"## Warnings Detected",
|
|
436
|
+
"",
|
|
437
|
+
])
|
|
438
|
+
for w in data['warnings']:
|
|
439
|
+
lines.append(f"### ⚠️ {w.get('message', w.get('type', 'Unknown'))}")
|
|
440
|
+
lines.append("")
|
|
441
|
+
if w.get('service'):
|
|
442
|
+
lines.append(f"- **Service:** `{w['service']}`")
|
|
443
|
+
lines.append("")
|
|
444
|
+
lines.extend(["---", ""])
|
|
445
|
+
|
|
446
|
+
lines.extend([
|
|
447
|
+
"",
|
|
448
|
+
"*Report generated by Consul health monitoring skill*",
|
|
449
|
+
])
|
|
450
|
+
|
|
451
|
+
with open(output_path, 'w') as f:
|
|
452
|
+
f.write('\n'.join(lines))
|
|
453
|
+
return True
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def main():
|
|
457
|
+
parser = argparse.ArgumentParser(
|
|
458
|
+
description='Generate Consul health report for EKS clusters',
|
|
459
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
460
|
+
epilog=__doc__
|
|
461
|
+
)
|
|
462
|
+
parser.add_argument('-e', '--environment', required=True,
|
|
463
|
+
help='Environment name (nonprod, prod)')
|
|
464
|
+
parser.add_argument('-o', '--output', default='.',
|
|
465
|
+
help='Output directory (default: current)')
|
|
466
|
+
parser.add_argument('-f', '--format', choices=['pdf', 'markdown', 'both'], default='both',
|
|
467
|
+
help='Output format (default: both)')
|
|
468
|
+
parser.add_argument('-n', '--namespace', default='consul',
|
|
469
|
+
help='Consul namespace (default: consul)')
|
|
470
|
+
parser.add_argument('-r', '--region', default='eu-west-1',
|
|
471
|
+
help='AWS region (default: eu-west-1)')
|
|
472
|
+
|
|
473
|
+
args = parser.parse_args()
|
|
474
|
+
|
|
475
|
+
# Set cluster context
|
|
476
|
+
print(f"Setting context to {args.environment} cluster...")
|
|
477
|
+
if not set_cluster_context(args.environment, args.region):
|
|
478
|
+
print(f"Error: Failed to set cluster context for {args.environment}", file=sys.stderr)
|
|
479
|
+
sys.exit(2)
|
|
480
|
+
|
|
481
|
+
# Collect health data
|
|
482
|
+
print("Collecting health data...")
|
|
483
|
+
data = collect_health_data(args.namespace)
|
|
484
|
+
|
|
485
|
+
# Create output directory
|
|
486
|
+
output_dir = Path(args.output)
|
|
487
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
488
|
+
|
|
489
|
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
490
|
+
base_name = f'consul_health_{args.environment}_{timestamp}'
|
|
491
|
+
|
|
492
|
+
# Generate reports
|
|
493
|
+
if args.format in ['pdf', 'both']:
|
|
494
|
+
pdf_path = output_dir / f'{base_name}.pdf'
|
|
495
|
+
print(f"Generating PDF report: {pdf_path}")
|
|
496
|
+
if generate_pdf_report(data, args.environment, str(pdf_path)):
|
|
497
|
+
print(f" ✓ PDF saved: {pdf_path}")
|
|
498
|
+
else:
|
|
499
|
+
print(f" ✗ PDF generation failed", file=sys.stderr)
|
|
500
|
+
|
|
501
|
+
if args.format in ['markdown', 'both']:
|
|
502
|
+
md_path = output_dir / f'{base_name}.md'
|
|
503
|
+
print(f"Generating Markdown report: {md_path}")
|
|
504
|
+
if generate_markdown_report(data, args.environment, str(md_path)):
|
|
505
|
+
print(f" ✓ Markdown saved: {md_path}")
|
|
506
|
+
else:
|
|
507
|
+
print(f" ✗ Markdown generation failed", file=sys.stderr)
|
|
508
|
+
|
|
509
|
+
# Also save raw data as JSON
|
|
510
|
+
json_path = output_dir / f'{base_name}.json'
|
|
511
|
+
with open(json_path, 'w') as f:
|
|
512
|
+
json.dump(data, f, indent=2, default=str)
|
|
513
|
+
print(f" ✓ Raw data saved: {json_path}")
|
|
514
|
+
|
|
515
|
+
print("\nDone!")
|
|
516
|
+
|
|
517
|
+
# Print summary
|
|
518
|
+
is_healthy = data.get('autopilot', {}).get('healthy', False)
|
|
519
|
+
print(f"\n{'='*50}")
|
|
520
|
+
print(f"Cluster Status: {'✓ HEALTHY' if is_healthy else '✗ ISSUES DETECTED'}")
|
|
521
|
+
print(f"Members: {len(data.get('members', []))}")
|
|
522
|
+
print(f"Services: {len(data.get('services', []))}")
|
|
523
|
+
print(f"Warnings: {len(data.get('warnings', []))}")
|
|
524
|
+
print(f"{'='*50}")
|
|
525
|
+
|
|
526
|
+
sys.exit(0)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
if __name__ == '__main__':
|
|
530
|
+
main()
|