Skip to content

Commit 4b0a858

Browse files
committed
add claude skills to create the maestro cluster
Signed-off-by: hchenxa <huichen@redhat.com>
1 parent 9f44c62 commit 4b0a858

File tree

5 files changed

+563
-0
lines changed

5 files changed

+563
-0
lines changed

.claude/hooks/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
config.sh

.claude/hooks/config.sh.example

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
#
3+
# Deployment Monitor Hook Configuration
4+
#
5+
# Copy this file to config.sh and set your values:
6+
# cp config.sh.example config.sh
7+
#
8+
# Note: config.sh is gitignored to keep your webhook URL secure
9+
10+
# Slack Webhook URL for deployment notifications
11+
# Get this from: https://api.slack.com/messaging/webhooks
12+
# Example: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
13+
SLACK_WEBHOOK_URL=""
14+
15+
# Optional: Customize notification behavior
16+
# NOTIFY_ON_START=false
17+
# NOTIFY_ON_PROGRESS=false
18+
# NOTIFY_ON_COMPLETE=true
19+
# NOTIFY_ON_FAILURE=true
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
#!/bin/bash
2+
#
3+
# Deployment Monitor Hook
4+
# This hook monitors long-running deployment processes and notifies when complete
5+
#
6+
# Usage: Can be called after triggering a deployment to monitor its progress
7+
#
8+
# Dependencies:
9+
# - Required: bash, wc, tail, sed, grep, cat, tr, date, sleep
10+
# - Optional: curl (for Slack notifications), osascript (for macOS notifications), notify-send (for Linux notifications)
11+
#
12+
# Configuration:
13+
# Set SLACK_WEBHOOK_URL environment variable or create .claude/hooks/config.sh
14+
15+
set -e
16+
17+
HOOK_NAME="deployment-monitor"
18+
19+
# Load configuration if exists
20+
# WARNING: This file will be executed. Ensure config.sh is not writable by untrusted users.
21+
CONFIG_FILE="$(dirname "$0")/config.sh"
22+
if [ -f "$CONFIG_FILE" ]; then
23+
# Validate config file security before sourcing
24+
if [ -O "$CONFIG_FILE" ] || [ "$(stat -f %Su "$CONFIG_FILE" 2>/dev/null || stat -c %U "$CONFIG_FILE" 2>/dev/null)" = "$USER" ]; then
25+
# Check if file is not world-writable
26+
if [ "$(stat -f %OLp "$CONFIG_FILE" 2>/dev/null || stat -c %a "$CONFIG_FILE" 2>/dev/null | cut -c3)" != "2" ] && \
27+
[ "$(stat -f %OLp "$CONFIG_FILE" 2>/dev/null || stat -c %a "$CONFIG_FILE" 2>/dev/null | cut -c3)" != "6" ]; then
28+
# shellcheck disable=SC1090
29+
source "$CONFIG_FILE"
30+
else
31+
echo "[$HOOK_NAME] WARNING: Skipping config.sh - file is world-writable (security risk)"
32+
fi
33+
else
34+
echo "[$HOOK_NAME] WARNING: Skipping config.sh - file not owned by current user (security risk)"
35+
fi
36+
fi
37+
38+
# Function to check if required commands are available
39+
check_command() {
40+
local cmd=$1
41+
local required=$2
42+
43+
if ! command -v "$cmd" &> /dev/null; then
44+
if [ "$required" = "true" ]; then
45+
echo "[$HOOK_NAME] ERROR: Required command '$cmd' is not installed"
46+
return 1
47+
else
48+
echo "[$HOOK_NAME] WARNING: Optional command '$cmd' is not installed"
49+
return 0
50+
fi
51+
fi
52+
return 0
53+
}
54+
55+
# Function to monitor deployment
56+
monitor_deployment() {
57+
local task_id=$1
58+
59+
if [ -z "$task_id" ]; then
60+
echo "[$HOOK_NAME] ERROR: Task ID required"
61+
echo "Usage: $0 monitor <task_id>"
62+
exit 1
63+
fi
64+
65+
# Build task output paths dynamically based on current working directory
66+
local cwd_sanitized
67+
cwd_sanitized=$(pwd | tr '/' '-' | sed 's/^-//')
68+
local task_dir="/tmp/claude/-${cwd_sanitized}/tasks"
69+
70+
# Ensure task directory exists
71+
if [ ! -d "$task_dir" ]; then
72+
echo "[$HOOK_NAME] WARNING: Task directory does not exist: $task_dir"
73+
echo "[$HOOK_NAME] Creating directory..."
74+
mkdir -p "$task_dir"
75+
fi
76+
77+
local output_file="${task_dir}/${task_id}.output"
78+
local exit_code_file="${task_dir}/${task_id}.exit_code"
79+
local start_time
80+
start_time=$(date +%s)
81+
82+
echo "[$HOOK_NAME] Monitoring deployment task: $task_id"
83+
echo "[$HOOK_NAME] Started at: $(date)"
84+
echo ""
85+
86+
# Wait for the deployment to complete
87+
local last_line_count=0
88+
while true; do
89+
# Check if exit code file exists (task completed)
90+
if [ -f "$exit_code_file" ]; then
91+
local exit_code
92+
exit_code=$(cat "$exit_code_file")
93+
echo "[$HOOK_NAME] Deployment process finished with exit code: $exit_code"
94+
break
95+
fi
96+
97+
# Show progress if output file exists
98+
if [ -f "$output_file" ]; then
99+
local current_lines
100+
current_lines=$(wc -l < "$output_file" | tr -d ' ')
101+
if [ "$current_lines" != "$last_line_count" ]; then
102+
local elapsed=$(($(date +%s) - start_time))
103+
local minutes=$((elapsed / 60))
104+
local seconds=$((elapsed % 60))
105+
echo "[$HOOK_NAME] Progress: $current_lines lines | Elapsed: ${minutes}m ${seconds}s | $(date +%H:%M:%S)"
106+
107+
# Show latest activity
108+
tail -3 "$output_file" | sed 's/\x1b\[[0-9;]*m//g' | grep -v "^$" | tail -1 | sed "s/^/[$HOOK_NAME] Latest: /"
109+
110+
last_line_count=$current_lines
111+
fi
112+
fi
113+
114+
# Sleep before next check
115+
sleep 15
116+
done
117+
118+
# Calculate total time
119+
local end_time
120+
end_time=$(date +%s)
121+
local total_time=$((end_time - start_time))
122+
local minutes=$((total_time / 60))
123+
local seconds=$((total_time % 60))
124+
125+
# Determine status and send notification
126+
if [ "$exit_code" -eq 0 ]; then
127+
notify_completion "COMPLETE" "Maestro cluster deployment completed successfully in ${minutes}m ${seconds}s!"
128+
echo ""
129+
echo "[$HOOK_NAME] Total deployment time: ${minutes}m ${seconds}s"
130+
echo "[$HOOK_NAME] Output file: $output_file"
131+
return 0
132+
else
133+
notify_completion "FAILED" "Deployment failed with exit code $exit_code after ${minutes}m ${seconds}s"
134+
echo ""
135+
echo "[$HOOK_NAME] Total deployment time: ${minutes}m ${seconds}s"
136+
echo "[$HOOK_NAME] Output file: $output_file"
137+
return 1
138+
fi
139+
}
140+
141+
# Function to send Slack notification
142+
send_slack_notification() {
143+
local status=$1
144+
local message=$2
145+
local webhook_url=$3
146+
147+
if [ -z "$webhook_url" ]; then
148+
return 1
149+
fi
150+
151+
# Check if curl is available
152+
if ! check_command "curl" "false"; then
153+
echo "[$HOOK_NAME] Skipping Slack notification - curl not available"
154+
return 1
155+
fi
156+
157+
# Determine color based on status
158+
local color="good"
159+
local emoji=":white_check_mark:"
160+
if [[ "$status" == "FAILED" ]]; then
161+
color="danger"
162+
emoji=":x:"
163+
elif [[ "$status" == "COMPLETE" ]]; then
164+
color="good"
165+
emoji=":white_check_mark:"
166+
fi
167+
168+
# Create JSON payload using jq for safe escaping
169+
local payload
170+
if command -v jq &> /dev/null; then
171+
# Use jq for safe JSON construction
172+
payload=$(jq -n \
173+
--arg color "$color" \
174+
--arg title "$emoji Maestro Deployment $status" \
175+
--arg text "$message" \
176+
--arg footer "Maestro Deployment Monitor" \
177+
--argjson ts "$(date +%s)" \
178+
'{attachments: [{color: $color, title: $title, text: $text, footer: $footer, ts: $ts}]}')
179+
else
180+
# Fallback: manually escape quotes and backslashes
181+
local escaped_message="${message//\\/\\\\}"
182+
escaped_message="${escaped_message//\"/\\\"}"
183+
local escaped_status="${status//\\/\\\\}"
184+
escaped_status="${escaped_status//\"/\\\"}"
185+
payload=$(cat <<EOF
186+
{
187+
"attachments": [
188+
{
189+
"color": "$color",
190+
"title": "$emoji Maestro Deployment $escaped_status",
191+
"text": "$escaped_message",
192+
"footer": "Maestro Deployment Monitor",
193+
"ts": $(date +%s)
194+
}
195+
]
196+
}
197+
EOF
198+
)
199+
fi
200+
201+
# Send to Slack and capture exit status
202+
local curl_exit_code
203+
if curl -X POST -H 'Content-type: application/json' \
204+
--data "$payload" \
205+
"$webhook_url" \
206+
--silent --show-error; then
207+
curl_exit_code=0
208+
else
209+
curl_exit_code=$?
210+
echo "[$HOOK_NAME] ERROR: Failed to send Slack notification (curl exit code: $curl_exit_code)"
211+
fi
212+
213+
return $curl_exit_code
214+
}
215+
216+
# Function to send notification
217+
notify_completion() {
218+
local status=$1
219+
local message=$2
220+
221+
echo ""
222+
echo "=========================================="
223+
echo "[$HOOK_NAME] DEPLOYMENT $status"
224+
echo "Message: $message"
225+
echo "Time: $(date)"
226+
echo "=========================================="
227+
echo ""
228+
229+
# Send Slack notification if webhook is configured
230+
if [ -n "$SLACK_WEBHOOK_URL" ]; then
231+
echo "[$HOOK_NAME] Sending Slack notification..."
232+
if send_slack_notification "$status" "$message" "$SLACK_WEBHOOK_URL"; then
233+
echo "[$HOOK_NAME] Slack notification sent successfully"
234+
else
235+
echo "[$HOOK_NAME] Failed to send Slack notification"
236+
fi
237+
fi
238+
239+
# Also send system notification if available
240+
if command -v osascript &> /dev/null; then
241+
# macOS notification - escape message for AppleScript
242+
local safe_message="${message//\\/\\\\}"
243+
safe_message="${safe_message//\"/\\\"}"
244+
osascript -e "display notification \"$safe_message\" with title \"Maestro Deployment $status\""
245+
elif command -v notify-send &> /dev/null; then
246+
# Linux notification - use safe argument passing
247+
notify-send -- "Maestro Deployment $status" "$message"
248+
fi
249+
}
250+
251+
# Main execution
252+
case "${1:-notify}" in
253+
monitor)
254+
monitor_deployment "$2"
255+
exit $?
256+
;;
257+
notify)
258+
notify_completion "${2:-COMPLETE}" "${3:-Deployment finished}"
259+
;;
260+
*)
261+
echo "Usage: $0 {monitor <task_id>|notify <status> <message>}"
262+
exit 1
263+
;;
264+
esac

0 commit comments

Comments
 (0)