#!/usr/bin/env bash # check_packages.sh — Compare Ceph package counts between production and prerelease # Uses Claude API to analyze mismatches and reason about differences set -euo pipefail # ── Config ──────────────────────────────────────────────────────────────────── PROD_BASE="/data/download.ceph.com/www" PRE_BASE="/data/download.ceph.com/www/prerelease/ceph" ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}" CLAUDE_MODEL="claude-sonnet-4-20250514" TMPDIR_BASE=$(mktemp -d) trap 'rm -rf "$TMPDIR_BASE"' EXIT # ── Colors ──────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' # ── Distro → filename pattern mapping ───────────────────────────────────────── distro_pattern() { case "$1" in jammy) echo "*jam*deb" ;; focal) echo "*focal*deb" ;; noble) echo "*noble*deb" ;; bookworm) echo "*book*deb" ;; centos9) echo "" ;; centos10) echo "" ;; *) echo "*${1}*deb" ;; esac } is_rpm_distro() { [[ "$1" == centos* ]] } el_version() { case "$1" in centos9) echo "el9" ;; centos10) echo "el10" ;; *) echo "el9" ;; esac } # ── Auto-decrement patch version ────────────────────────────────────────────── decrement_patch() { local ver="$1" local major minor patch IFS='.' read -r major minor patch <<< "$ver" if [[ "$patch" -gt 0 ]]; then echo "${major}.${minor}.$((patch - 1))" else echo "" fi } # ── Claude API call ─────────────────────────────────────────────────────────── ask_claude() { local prompt="$1" if [[ -z "$ANTHROPIC_API_KEY" ]]; then echo "(Claude API key not set — skipping analysis)" return fi local payload payload=$(printf '%s' "$prompt" | python3 -c " import sys, json text = sys.stdin.read() print(json.dumps({ 'model': '${CLAUDE_MODEL}', 'max_tokens': 1000, 'messages': [{'role': 'user', 'content': text}] })) ") local response response=$(curl -s https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d "$payload") echo "$response" | python3 -c " import sys, json data = json.load(sys.stdin) for block in data.get('content', []): if block.get('type') == 'text': print(block['text']) " 2>/dev/null || echo "(Could not parse Claude response)" } # ── Summary tracking ────────────────────────────────────────────────────────── declare -a SUMMARY_ROWS=() add_summary() { local distro="$1" pattern="$2" prod_count="$3" pre_count="$4" status="$5" SUMMARY_ROWS+=("$distro|$pattern|$prod_count|$pre_count|$status") } print_summary() { if [[ ${#SUMMARY_ROWS[@]} -eq 0 ]]; then echo -e "\n${YELLOW}No distros were compared.${RESET}" return fi echo -e "\n${BOLD}Summary${RESET} (prod: ${PROD_VERSION} → prerelease: ${PRE_VERSION})" echo "────────────────────────────────────────────────────────────────" printf " %-14s %-20s %6s %6s %s\n" "DISTRO" "PATTERN" "PROD" "PRE" "STATUS" echo " ──────────────────────────────────────────────────────────────" for row in "${SUMMARY_ROWS[@]}"; do IFS='|' read -r distro pattern prod pre status <<< "$row" if [[ "$status" == "OK" ]]; then color="$GREEN" else color="$RED" fi printf " %-14s %-20s %6s %6s ${color}%s${RESET}\n" \ "$distro" "($pattern)" "$prod" "$pre" "$status" done echo "────────────────────────────────────────────────────────────────" } # ── Compare a single distro ─────────────────────────────────────────────────── compare() { local label="$1" local prod_dir="$2" local pre_dir="$3" local pattern="$4" local prod_list pre_list prod_list=$(mktemp "$TMPDIR_BASE/prod_XXXXXX") pre_list=$(mktemp "$TMPDIR_BASE/pre_XXXXXX") if [[ -d "$prod_dir" ]]; then find "$prod_dir" -name "$pattern" -printf '%f\n' 2>/dev/null | sort > "$prod_list" else touch "$prod_list" echo -e " ${YELLOW}WARNING: prod dir not found: $prod_dir${RESET}" fi if [[ -d "$pre_dir" ]]; then find "$pre_dir" -name "$pattern" -printf '%f\n' 2>/dev/null | sort > "$pre_list" else touch "$pre_list" echo -e " ${YELLOW}WARNING: prerelease dir not found: $pre_dir${RESET}" fi local prod_count pre_count prod_count=$(wc -l < "$prod_list") pre_count=$(wc -l < "$pre_list") printf " %-30s prod: %4d pre: %4d" "$label" "$prod_count" "$pre_count" if [[ "$prod_count" -eq "$pre_count" ]]; then echo -e " ${GREEN}✓ match${RESET}" add_summary "$label" "$pattern" "$prod_count" "$pre_count" "OK" return fi echo -e " ${RED}✗ MISMATCH (diff: $(( pre_count - prod_count )))${RESET}" add_summary "$label" "$pattern" "$prod_count" "$pre_count" "MISMATCH ($(( pre_count - prod_count )))" local only_prod only_pre only_prod=$(comm -23 "$prod_list" "$pre_list") only_pre=$(comm -13 "$prod_list" "$pre_list") if [[ -n "$only_prod" ]]; then echo -e "\n ${CYAN}Only in PROD (${PROD_VERSION}):${RESET}" echo "$only_prod" | sed 's/^/ /' fi if [[ -n "$only_pre" ]]; then echo -e "\n ${CYAN}Only in PRERELEASE (${PRE_VERSION}):${RESET}" echo "$only_pre" | sed 's/^/ /' fi echo -e "\n ${BOLD}🤖 Claude analysis:${RESET}" local prompt prompt="You are analyzing a Ceph Linux package repository mismatch between two versions. Production version: ${PROD_VERSION} Prerelease version: ${PRE_VERSION} Category: $label Production count: $prod_count Prerelease count: $pre_count Pattern searched: $pattern Packages only in PRODUCTION (missing from prerelease): ${only_prod:-} Packages only in PRERELEASE (missing from production): ${only_pre:-} Based on the filenames and version difference, reason about WHY these packages differ. Consider: - New distros added in the new version (e.g. bookworm was not built in older releases) - Dropped distros - New packages added to the build - Packages removed - Architecture differences (amd64/arm64/noarch) - Version string differences in filenames Be concise — 3-5 sentences max. Start directly with your analysis." ask_claude "$prompt" | sed 's/^/ /' echo } # ── Main ────────────────────────────────────────────────────────────────────── echo -e "\n${BOLD}Ceph Package Comparison Tool${RESET}" echo "────────────────────────────────────────" # 1. Prerelease version read -erp "$(echo -e "${CYAN}Prerelease version (e.g. 20.2.1): ${RESET}")" PRE_VERSION [[ -z "$PRE_VERSION" ]] && { echo "Prerelease version required."; exit 1; } # 2. Prod version — default to patch-1 PROD_DEFAULT=$(decrement_patch "$PRE_VERSION") if [[ -n "$PROD_DEFAULT" ]]; then read -erp "$(echo -e "${CYAN}Production version [${PROD_DEFAULT}]: ${RESET}")" PROD_VERSION PROD_VERSION="${PROD_VERSION:-$PROD_DEFAULT}" else read -erp "$(echo -e "${CYAN}Production version: ${RESET}")" PROD_VERSION fi [[ -z "$PROD_VERSION" ]] && { echo "Production version required."; exit 1; } # 3. Distros echo -e "${CYAN}Available distros: jammy focal noble bookworm centos9 centos10${RESET}" read -erp "$(echo -e "${CYAN}Distros to check (space-separated): ${RESET}")" DISTROS_INPUT read -ra DISTROS <<< "$DISTROS_INPUT" [[ ${#DISTROS[@]} -eq 0 ]] && { echo "At least one distro required."; exit 1; } # API key check if [[ -z "$ANTHROPIC_API_KEY" ]]; then echo -e "${YELLOW}Note: ANTHROPIC_API_KEY not set — mismatch analysis will be skipped.${RESET}" fi echo echo -e "${BOLD}Prod:${RESET} ${PROD_VERSION} → ${PROD_BASE}/debian-${PROD_VERSION} | rpm-${PROD_VERSION}/" echo -e "${BOLD}Prerelease:${RESET} ${PRE_VERSION} → ${PRE_BASE}/debian-${PRE_VERSION} | rpm-${PRE_VERSION}/" echo "────────────────────────────────────────" # 4. Loop distros interactively for distro in "${DISTROS[@]}"; do echo if is_rpm_distro "$distro"; then local_el=$(el_version "$distro") read -erp "$(echo -e "${BOLD}Compare ${distro} (RPM/${local_el})? [Y/n]: ${RESET}")" yn [[ "${yn,,}" == "n" ]] && { echo -e " ${YELLOW}Skipped.${RESET}"; continue; } echo -e "${BOLD}[${distro}]${RESET}" prod_dir="$PROD_BASE/rpm-${PROD_VERSION}/${local_el}" pre_dir="$PRE_BASE/rpm-${PRE_VERSION}/${local_el}" compare "RPM (${local_el})" "$prod_dir" "$pre_dir" "*.rpm" else pattern=$(distro_pattern "$distro") read -erp "$(echo -e "${BOLD}Compare ${distro} (${pattern})? [Y/n]: ${RESET}")" yn [[ "${yn,,}" == "n" ]] && { echo -e " ${YELLOW}Skipped.${RESET}"; continue; } echo -e "${BOLD}[${distro}]${RESET}" prod_dir="$PROD_BASE/debian-${PROD_VERSION}" pre_dir="$PRE_BASE/debian-${PRE_VERSION}" compare "deb ($distro)" "$prod_dir" "$pre_dir" "$pattern" fi done print_summary echo -e "\n${BOLD}Done.${RESET}\n"