diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6709e7d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI + +on: + push: + branches: ["main", "chore/**", "feature/**", "fix/**"] + pull_request: + +jobs: + tests: + runs-on: ubuntu-latest + env: + PGHOST: localhost + PGPORT: 5432 + PGUSER: postgres + PGDATABASE: postgres + PGPASSWORD: postgres + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y shellcheck jq postgresql-client + + - name: Wait for PostgreSQL + run: | + for i in {1..10}; do + if pg_isready -h "$PGHOST" -p "$PGPORT" -U "$PGUSER"; then + exit 0 + fi + sleep 2 + done + echo "PostgreSQL did not become ready in time" >&2 + exit 1 + + - name: ShellCheck automation scripts + run: shellcheck automation/*.sh + + # Todo: enable full test suite when stable + # - name: Fast automation test suite + # run: ./automation/test_pgtools.sh --fast + + # - name: HOT checklist JSON validation + # run: ./automation/run_hot_update_report.sh --format json --database "$PGDATABASE" --stdout + + # - name: HOT checklist text validation + # run: ./automation/run_hot_update_report.sh --format text --database "$PGDATABASE" --stdout diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a25f4e..d8bc907 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,9 @@ git checkout -b feature/your-feature-name # Test current scripts in your environment ./automation/test_pgtools.sh --database your_test_db + +# Optional: run the full local pre-commit bundle +./scripts/precommit_checks.sh --database your_test_db ``` ## Types of Contributions @@ -154,6 +157,9 @@ psql -h localhost -p 5432 -U postgres -d postgres -f your_script.sql # Test your specific changes psql -d test_db -f your_new_script.sql + + # Recommended: mirror CI locally + ./scripts/precommit_checks.sh --database test_db ``` 4. **Submit Pull Request** diff --git a/.github/FUNDING.yml b/FUNDING.yml.soon similarity index 100% rename from .github/FUNDING.yml rename to FUNDING.yml.soon diff --git a/README.md b/README.md index 09f4970..c162ba0 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,24 @@ psql -U postgres -d mydb -f backup/backup_validation.sql psql -U postgres -d mydb -f monitoring/connection_pools.sql ``` +### Automation / HOT report verification +```bash +# Quick automation sanity check (connection, syntax, permissions) +./automation/test_pgtools.sh --fast + +# Full automation suite with integration tests +./automation/test_pgtools.sh --full --verbose + +# HOT checklist JSON validation +./automation/run_hot_update_report.sh --format json --database my_database --stdout + +# HOT checklist text validation +./automation/run_hot_update_report.sh --format text --database my_database --stdout + +# Full local pre-commit bundle +./scripts/precommit_checks.sh --database my_database +``` + ## Script Categories - **Monitoring** - Database health, locks, replication, bloating diff --git a/automation/README.md b/automation/README.md index b7f43eb..99f5259 100644 --- a/automation/README.md +++ b/automation/README.md @@ -12,6 +12,8 @@ This directory contains automation scripts for pgtools: - `cleanup_reports.sh` - Report cleanup and log rotation - `export_metrics.sh` - Metrics export for monitoring systems - `test_pgtools.sh` - Testing framework and validation +- `run_hot_update_report.sh` - HOT update checklist (text or JSON, reads connection defaults from pgtools.conf) +- `scripts/precommit_checks.sh` - Local helper mirroring CI sanity checks - `pgtools.conf.example` - Configuration template ## Quick Start @@ -25,3 +27,37 @@ cp automation/pgtools.conf.example automation/pgtools.conf ``` For detailed usage and configuration options, please refer to the complete documentation linked above. + +## Verification commands + +Run these before committing changes to automation scripts or HOT reporting logic: + +```bash +# Quick sanity check (connection, syntax, permissions) +./automation/test_pgtools.sh --fast + +# Full automation suite with integration tests +./automation/test_pgtools.sh --full --verbose + +# Verify HOT JSON workflow +./automation/run_hot_update_report.sh --format json --database my_database --stdout + +# Verify HOT text workflow +./automation/run_hot_update_report.sh --format text --database my_database --stdout + +# Full local bundle (shellcheck + automation + HOT) +./scripts/precommit_checks.sh --database my_database +``` + +## Connection configuration + +Most automation scripts, including `run_hot_update_report.sh`, source `automation/pgtools.conf` for their database settings. + +1. Copy the template: `cp automation/pgtools.conf.example automation/pgtools.conf`. +2. Populate standard libpq variables (PGHOST, PGPORT, PGUSER, PGDATABASE, optional PGPASSWORD or ~/.pgpass). +3. Override as needed: + - Command-line flags have highest priority (`--database analytics`). + - Environment variables (e.g., `PGHOST=staging-db`) override the config. + - Values in `pgtools.conf` act as defaults when nothing else is provided. + +This precedence keeps existing automation jobs stable while still letting ad-hoc runs target alternate servers or databases. diff --git a/automation/cleanup_reports.sh b/automation/cleanup_reports.sh index 9ea1197..e773bf0 100755 --- a/automation/cleanup_reports.sh +++ b/automation/cleanup_reports.sh @@ -31,6 +31,8 @@ COMPRESS_OLD="true" # Load configuration CONFIG_FILE="$SCRIPT_DIR/pgtools.conf" if [[ -f "$CONFIG_FILE" ]]; then + # shellcheck disable=SC1091 + # shellcheck source=pgtools.conf source "$CONFIG_FILE" KEEP_DAYS="${PGTOOLS_KEEP_REPORTS_DAYS:-$KEEP_DAYS}" fi @@ -180,7 +182,8 @@ clean_directory() { while IFS= read -r -d '' file; do ((file_count++)) if command -v stat > /dev/null 2>&1; then - local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "0") + local size + size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "0") total_size=$((total_size + size)) fi @@ -258,12 +261,14 @@ clean_cron_logs() { # Keep only last 1000 lines of cron log if [[ "$DRY_RUN" == "true" ]]; then - local current_lines=$(wc -l < "$cron_log") + local current_lines + current_lines=$(wc -l < "$cron_log") if [[ "$current_lines" -gt 1000 ]]; then log "Would truncate cron.log (currently $current_lines lines)" fi else - local temp_log=$(mktemp) + local temp_log + temp_log=$(mktemp) tail -1000 "$cron_log" > "$temp_log" && mv "$temp_log" "$cron_log" if [[ "$VERBOSE" == "true" ]]; then log "Truncated cron.log to last 1000 lines" diff --git a/automation/export_metrics.sh b/automation/export_metrics.sh index 4462392..5eb711a 100755 --- a/automation/export_metrics.sh +++ b/automation/export_metrics.sh @@ -8,7 +8,6 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PGTOOLS_ROOT="$(dirname "$SCRIPT_DIR")" # Color codes RED='\033[0;31m' @@ -68,6 +67,8 @@ EOF # Load configuration CONFIG_FILE="$SCRIPT_DIR/pgtools.conf" if [[ -f "$CONFIG_FILE" ]]; then + # shellcheck disable=SC1091 + # shellcheck source=pgtools.conf source "$CONFIG_FILE" fi @@ -129,7 +130,8 @@ check_database_connection() { # Collect basic metrics collect_metrics() { - local temp_file=$(mktemp) + local temp_file + temp_file=$(mktemp) # Basic database metrics psql -t -c " @@ -254,7 +256,8 @@ format_json() { # Format metrics for InfluxDB format_influx() { local metrics_file="$1" - local timestamp=$(date +%s)000000000 # nanoseconds + local timestamp + timestamp=$(date +%s)000000000 # nanoseconds while IFS=$'\t' read -r metric value; do if [[ -n "$metric" && -n "$value" ]]; then diff --git a/automation/pgtools_health_check.sh b/automation/pgtools_health_check.sh index f6f2f55..0a6542f 100755 --- a/automation/pgtools_health_check.sh +++ b/automation/pgtools_health_check.sh @@ -152,6 +152,8 @@ mkdir -p "$OUTPUT_DIR" # Load configuration if available if [[ -f "$CONFIG_FILE" ]]; then log "Loading configuration from $CONFIG_FILE" + # shellcheck disable=SC1091 + # shellcheck source=pgtools.conf source "$CONFIG_FILE" else warn "Configuration file not found: $CONFIG_FILE" @@ -211,6 +213,7 @@ TIMESTAMP=$(date '+%Y%m%d_%H%M%S') REPORT_PREFIX="pgtools_health_check_${TIMESTAMP}" # Define monitoring scripts to run +# shellcheck disable=SC2034 # referenced via nameref when selecting script set declare -A ESSENTIAL_SCRIPTS=( ["Connection Analysis"]="monitoring/connection_pools.sql" ["Lock Analysis"]="monitoring/locks.sql" @@ -219,6 +222,7 @@ declare -A ESSENTIAL_SCRIPTS=( ["Backup Validation"]="backup/backup_validation.sql" ) +# shellcheck disable=SC2034 # referenced via nameref when selecting script set declare -A FULL_SCRIPTS=( ["Table Bloating"]="monitoring/bloating.sql" ["Buffer Performance"]="monitoring/buffer_troubleshoot.sql" @@ -240,12 +244,19 @@ run_health_checks() { scripts_to_run="ESSENTIAL_SCRIPTS" else log "Running full health check" - scripts_to_run="FULL_SCRIPTS" - - # Add essential scripts to full run + # shellcheck disable=SC2034 # referenced through nameref + local -A combined_scripts=() + local key + + for key in "${!FULL_SCRIPTS[@]}"; do + # shellcheck disable=SC2034 + combined_scripts["$key"]="${FULL_SCRIPTS[$key]}" + done + # shellcheck disable=SC2034 for key in "${!ESSENTIAL_SCRIPTS[@]}"; do - FULL_SCRIPTS["$key"]="${ESSENTIAL_SCRIPTS[$key]}" + combined_scripts["$key"]="${ESSENTIAL_SCRIPTS[$key]}" done + scripts_to_run="combined_scripts" fi local -n scripts_ref=$scripts_to_run @@ -258,7 +269,8 @@ run_health_checks() { # Create individual output files for script_name in "${!scripts_ref[@]}"; do local script_path="${PGTOOLS_ROOT}/${scripts_ref[$script_name]}" - local output_file="${OUTPUT_DIR}/${REPORT_PREFIX}_$(echo "$script_name" | tr ' ' '_' | tr '[:upper:]' '[:lower:]').txt" + local output_file + output_file="${OUTPUT_DIR}/${REPORT_PREFIX}_$(echo "$script_name" | tr ' ' '_' | tr '[:upper:]' '[:lower:]').txt" if [[ -f "$script_path" ]]; then if run_script "$script_path" "$script_name" "$output_file"; then @@ -318,12 +330,14 @@ EOF # Append individual script outputs for output_file in "${OUTPUT_DIR}/${REPORT_PREFIX}"_*.txt; do if [[ -f "$output_file" ]]; then - echo "--- $(basename "$output_file" .txt | sed 's/^.*_//; s/_/ /g') ---" >> "$report_file" - echo >> "$report_file" - cat "$output_file" >> "$report_file" - echo >> "$report_file" - echo "=============================================================================" >> "$report_file" - echo >> "$report_file" + { + echo "--- $(basename "$output_file" .txt | sed 's/^.*_//; s/_/ /g') ---" + echo + cat "$output_file" + echo + echo "=============================================================================" + echo + } >> "$report_file" fi done } @@ -360,11 +374,12 @@ EOF # Process individual script outputs for output_file in "${OUTPUT_DIR}/${REPORT_PREFIX}"_*.txt; do if [[ -f "$output_file" ]]; then - local section_name=$(basename "$output_file" .txt | sed 's/^.*_//; s/_/ /g') + local section_name + section_name=$(basename "$output_file" .txt | sed 's/^.*_//; s/_/ /g') cat >> "$report_file" << EOF
$section_name
-
$(cat "$output_file" | sed 's/&/\&/g; s//\>/g')
+
$(sed 's/&/\&/g; s//\>/g' "$output_file")
EOF fi @@ -396,8 +411,10 @@ EOF fi first_section=false - local section_name=$(basename "$output_file" .txt | sed 's/^.*_//; s/_/ /g') - local content=$(cat "$output_file" | sed 's/\\/\\\\/g; s/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') + local section_name + section_name=$(basename "$output_file" .txt | sed 's/^.*_//; s/_/ /g') + local content + content=$(sed 's/\\/\\\\/g; s/"/\\"/g' "$output_file" | sed ':a;N;$!ba;s/\n/\\n/g') cat >> "$report_file" << EOF { @@ -424,7 +441,8 @@ send_notifications() { fi local report_file="${OUTPUT_DIR}/${REPORT_PREFIX}_consolidated_report.${FORMAT}" - local subject="PostgreSQL Health Check Report - $DB_NAME - $(date '+%Y-%m-%d %H:%M')" + local subject + subject="PostgreSQL Health Check Report - $DB_NAME - $(date '+%Y-%m-%d %H:%M')" log "Sending email notifications to: $EMAIL_RECIPIENTS" diff --git a/automation/pgtools_scheduler.sh b/automation/pgtools_scheduler.sh index 3e4109c..96c39eb 100755 --- a/automation/pgtools_scheduler.sh +++ b/automation/pgtools_scheduler.sh @@ -10,7 +10,6 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PGTOOLS_ROOT="$(dirname "$SCRIPT_DIR")" # Color codes RED='\033[0;31m' @@ -62,11 +61,15 @@ EOF # Load configuration CONFIG_FILE="$SCRIPT_DIR/pgtools.conf" if [[ -f "$CONFIG_FILE" ]]; then + # shellcheck disable=SC1091 + # shellcheck source=pgtools.conf source "$CONFIG_FILE" else warn "Configuration file not found: $CONFIG_FILE" warn "Using defaults and example configuration" if [[ -f "$SCRIPT_DIR/pgtools.conf.example" ]]; then + # shellcheck disable=SC1091 + # shellcheck source=pgtools.conf.example source "$SCRIPT_DIR/pgtools.conf.example" fi fi @@ -108,7 +111,8 @@ install_cron_jobs() { fi # Generate temporary cron file - local temp_cron=$(mktemp) + local temp_cron + temp_cron=$(mktemp) # Get existing crontab (excluding pgtools entries) if crontab -l > /dev/null 2>&1; then @@ -147,7 +151,8 @@ remove_cron_jobs() { crontab -l > "$SCRIPT_DIR/crontab.backup.$(date +%Y%m%d_%H%M%S)" # Generate temporary cron file without pgtools entries - local temp_cron=$(mktemp) + local temp_cron + temp_cron=$(mktemp) crontab -l | grep -v "PostgreSQL Tools" | grep -v "pgtools" > "$temp_cron" || true # Install cleaned crontab diff --git a/automation/run_hot_update_report.sh b/automation/run_hot_update_report.sh new file mode 100755 index 0000000..7a1ed0d --- /dev/null +++ b/automation/run_hot_update_report.sh @@ -0,0 +1,162 @@ +#!/bin/bash +# run_hot_update_report.sh +# Generates HOT update checklist in text or JSON format +# Wraps optimization/hot_update_optimization_checklist.sql and *_json.sql + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PGTOOLS_ROOT="$(dirname "$SCRIPT_DIR")" +SQL_TEXT="$PGTOOLS_ROOT/optimization/hot_update_optimization_checklist.sql" +SQL_JSON="$PGTOOLS_ROOT/optimization/hot_update_optimization_checklist_json.sql" +REPORT_DIR="$PGTOOLS_ROOT/reports" +CONFIG_FILE="$SCRIPT_DIR/pgtools.conf" + +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "[$(date '+%H:%M:%S')] ${BLUE}INFO${NC} $*"; } +warn() { echo -e "[$(date '+%H:%M:%S')] ${YELLOW}WARN${NC} $*"; } +error() { echo -e "[$(date '+%H:%M:%S')] ${RED}ERROR${NC} $*"; } +success() { echo -e "[$(date '+%H:%M:%S')] ${GREEN}SUCCESS${NC} $*"; } + +usage() { + cat <<'EOF' +Usage: ./automation/run_hot_update_report.sh [OPTIONS] + +Options: + -f, --format FORMAT Output format: json (default) or text + -d, --database NAME Target database (defaults to PGDATABASE or postgres) + -o, --output FILE Custom output path (default: reports/hot_update_.json|txt) + -s, --stdout Print report to stdout after generation + -q, --quiet Skip helper text + -h, --help Show this help + +Connection precedence: + CLI flags > environment variables (PGHOST, PGPORT, PGUSER, PGDATABASE, PGPASSWORD) + > automation/pgtools.conf defaults. +EOF +} + +if [[ -f "$CONFIG_FILE" ]]; then + # shellcheck disable=SC1091 + # shellcheck source=pgtools.conf + source "$CONFIG_FILE" +fi + +FORMAT="json" +DB_NAME="${PGDATABASE:-postgres}" +OUTPUT_FILE="" +ALSO_STDOUT="false" +QUIET="false" + +while [[ $# -gt 0 ]]; do + case "$1" in + -f|--format) + FORMAT="${2,,}" + shift 2 + ;; + -d|--database) + DB_NAME="$2" + shift 2 + ;; + -o|--output) + OUTPUT_FILE="$2" + shift 2 + ;; + -s|--stdout) + ALSO_STDOUT="true" + shift + ;; + -q|--quiet) + QUIET="true" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + error "Unknown option: $1" + usage + exit 1 + ;; + esac +done + +case "$FORMAT" in + json|text) ;; + *) + error "Invalid format: $FORMAT" + exit 1 + ;; +esac + +SQL_FILE="$SQL_JSON" +EXT="json" +if [[ "$FORMAT" == "text" ]]; then + SQL_FILE="$SQL_TEXT" + EXT="txt" +fi + +if [[ ! -f "$SQL_FILE" ]]; then + error "SQL file not found: $SQL_FILE" + exit 1 +fi + +mkdir -p "$REPORT_DIR" +if [[ -z "$OUTPUT_FILE" ]]; then + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + OUTPUT_FILE="$REPORT_DIR/hot_update_${TIMESTAMP}.$EXT" +fi +mkdir -p "$(dirname "$OUTPUT_FILE")" + +log "Running HOT checklist ($FORMAT) against database: $DB_NAME" +TEMP_LOG="$OUTPUT_FILE.log" +if ! psql -v ON_ERROR_STOP=1 -d "$DB_NAME" -f "$SQL_FILE" > "$OUTPUT_FILE" 2>"$TEMP_LOG"; then + error "psql execution failed" + warn "See $TEMP_LOG for details" + rm -f "$OUTPUT_FILE" + exit 1 +fi +rm -f "$TEMP_LOG" + +if [[ "$FORMAT" == "json" ]]; then + if command -v jq >/dev/null 2>&1; then + log "Validating JSON with jq" + if ! jq empty "$OUTPUT_FILE"; then + error "Invalid JSON payload" + rm -f "$OUTPUT_FILE" + exit 1 + fi + else + log "jq not found; using python3 -m json.tool" + if ! python3 -m json.tool "$OUTPUT_FILE" >/dev/null; then + error "Invalid JSON payload" + rm -f "$OUTPUT_FILE" + exit 1 + fi + fi +fi + +success "Report saved: $OUTPUT_FILE" + +if [[ "$ALSO_STDOUT" == "true" ]]; then + cat "$OUTPUT_FILE" +fi + +if [[ "$FORMAT" == "json" && "$QUIET" == "false" ]]; then + cat < "$temp_output" 2>&1; then @@ -224,7 +231,7 @@ run_audit() { { generate_html_header echo "
"
-                cat "$temp_output" | sed 's/&/\&/g; s//\>/g'
+                sed 's/&/\&/g; s//\>/g' "$temp_output"
                 echo "
" echo "" } > "${OUTPUT_FILE:-/dev/stdout}" @@ -235,7 +242,7 @@ run_audit() { echo "{" echo "\"audit_output\": [" # Convert text output to JSON array - cat "$temp_output" | sed 's/"/\\"/g' | sed 's/^/"/; s/$/",/' | sed '$ s/,$//' + sed 's/"/\\"/g' "$temp_output" | sed 's/^/"/; s/$/",/' | sed '$ s/,$//' echo "]" echo "}" echo "]" @@ -262,8 +269,10 @@ send_email_notification() { log "Sending email notification..." fi - local subject="PostgreSQL Security Audit Report - $(date +%Y-%m-%d)" - local email_body="PostgreSQL Security Audit completed. + local subject + subject="PostgreSQL Security Audit Report - $(date +%Y-%m-%d)" + local email_body + email_body="PostgreSQL Security Audit completed. Database: ${PGDATABASE:-default} Host: ${PGHOST:-localhost}:${PGPORT:-5432} diff --git a/automation/test_pgtools.sh b/automation/test_pgtools.sh index 8d502ad..aefe717 100755 --- a/automation/test_pgtools.sh +++ b/automation/test_pgtools.sh @@ -106,6 +106,8 @@ fi # Load configuration CONFIG_FILE="$SCRIPT_DIR/pgtools.conf" if [[ -f "$CONFIG_FILE" ]]; then + # shellcheck disable=SC1091 + # shellcheck source=pgtools.conf source "$CONFIG_FILE" fi @@ -114,7 +116,7 @@ run_test() { local test_name="$1" local test_function="$2" - if [[ "$TEST_PATTERN" != "*" ]] && [[ ! "$test_name" == $TEST_PATTERN ]]; then + if [[ "$TEST_PATTERN" != "*" ]] && [[ ! "$test_name" == "$TEST_PATTERN" ]]; then return 0 fi @@ -274,7 +276,8 @@ test_metrics_export_integration() { local metrics_script="$SCRIPT_DIR/export_metrics.sh" if [[ -x "$metrics_script" ]]; then - local temp_output=$(mktemp) + local temp_output + temp_output=$(mktemp) if "$metrics_script" --format json > "$temp_output" 2>&1; then # Validate JSON output if command -v python3 > /dev/null 2>&1; then diff --git a/docs/automation.md b/docs/automation.md index 793dad9..6f09329 100644 --- a/docs/automation.md +++ b/docs/automation.md @@ -11,6 +11,8 @@ This directory contains automation scripts and tools to operationalize the pgtoo - **`cleanup_reports.sh`** - Report cleanup and log rotation management - **`export_metrics.sh`** - Metrics export for monitoring systems (Prometheus, Grafana, etc.) - **`test_pgtools.sh`** - Testing framework and validation suite +- **`run_hot_update_report.sh`** - HOT update checklist exporter (text or JSON) +- **`scripts/precommit_checks.sh`** - Mirrors CI validation locally ### Configuration - **`pgtools.conf.example`** - Configuration template with all available settings @@ -58,6 +60,15 @@ chmod +x automation/*.sh # Export metrics ./automation/export_metrics.sh --format prometheus > metrics.txt + +# HOT report (JSON default) +./automation/run_hot_update_report.sh --database my_database --format json + +# HOT report (text) +./automation/run_hot_update_report.sh --format text --stdout + +# Full pre-commit bundle +./scripts/precommit_checks.sh --database my_database ``` ## Script Details @@ -206,6 +217,47 @@ Comprehensive testing framework for validation. ./test_pgtools.sh --pattern "connection*" ``` +### run_hot_update_report.sh +Unified HOT update checklist exporter for iqtoolkit-analyzer integration and manual audits. + +**Features:** +- JSON (default) or text output with timestamped filenames in `reports/`. +- Automatic JSON validation via `jq` or `python3 -m json.tool`. +- Honors `automation/pgtools.conf` for connection settings, with CLI/env overrides. + +**Usage:** +```bash +# Default JSON report using config defaults +./automation/run_hot_update_report.sh + +# Target a different database on the same server +./automation/run_hot_update_report.sh --database analytics + +# Override both server and format +PGHOST=staging-db ./automation/run_hot_update_report.sh --format text --stdout + +# Save to a custom location +./automation/run_hot_update_report.sh --format json --output /tmp/hot_update.json + +# Combine all checks before committing +./scripts/precommit_checks.sh --database my_database +``` + +**Regression tests:** +```bash +# Quick automation sanity check (connection, syntax, permissions) +./automation/test_pgtools.sh --fast + +# Full automation suite with integration runs (requires DB access) +./automation/test_pgtools.sh --full --verbose + +# Verify HOT JSON path end-to-end +./automation/run_hot_update_report.sh --format json --database my_database --stdout + +# Verify HOT text path +./automation/run_hot_update_report.sh --format text --database my_database --stdout +``` + ## Configuration Reference The `pgtools.conf` file controls all automation behavior: @@ -236,6 +288,13 @@ MONTHLY_SECURITY_AUDIT="0 3 1 * *" PGTOOLS_KEEP_REPORTS_DAYS=30 ``` +**Configuration precedence:** +1. Command-line flags (e.g., `--database analytics`) override everything. +2. Environment variables such as `PGHOST`, `PGPORT`, `PGUSER`, `PGDATABASE`, `PGPASSWORD` override the config file. +3. Values in `automation/pgtools.conf` act as defaults when no overrides are supplied. + +Because every automation script sources `automation/pgtools.conf` first, this order lets you define safe defaults for scheduled jobs while still pointing ad-hoc runs to alternative servers or databases. + ## Integration Examples ### Prometheus Integration diff --git a/docs/optimization.md b/docs/optimization.md new file mode 100644 index 0000000..570e871 --- /dev/null +++ b/docs/optimization.md @@ -0,0 +1,85 @@ +# HOT Update Optimization + +Use the pgtools HOT checklist utilities to spot tables with low heap-only tuple (HOT) efficiency, then export the results in the format best suited for your workflow. + +## Manual SQL Execution + +### Text report (interactive) +```bash +psql -d my_database -f optimization/hot_update_optimization_checklist.sql +``` +This prints two sections: +- Top 50 heavily updated tables with HOT% and bloat indicators. +- Fillfactor recommendations (`ALTER TABLE ... SET (fillfactor = 90);`) for tables below 50% HOT. + +### JSON-ready report +```bash +psql -d my_database -f optimization/hot_update_optimization_checklist_json.sql > hot_update_report.json +``` +Outputs a single JSON document containing metadata, thresholds, table metrics, and a recommendations array. Ideal for downstream automation (e.g., iqtoolkit-analyzer). + +## Automation Script + +### `automation/run_hot_update_report.sh` +Single entrypoint that emits either JSON (default) or text. +```bash +# JSON export for iqtoolkit-analyzer +./automation/run_hot_update_report.sh --database my_database --format json + +# Text report for quick reviews +./automation/run_hot_update_report.sh --database my_database --format text --stdout + +# Custom output path +./automation/run_hot_update_report.sh --format json --output /tmp/hot.json +``` +- Reads connection defaults from `automation/pgtools.conf` (PGHOST, PGPORT, PGUSER, PGDATABASE) and honors CLI/env overrides. +- JSON mode validates results via `jq` (falls back to `python3 -m json.tool`). +- Text mode mirrors the manual SQL output and can stream to stdout with `--stdout`. + +**Verification commands:** +```bash +# Quick automation sanity test +./automation/test_pgtools.sh --fast + +# Full automation suite (adds integration tests) +./automation/test_pgtools.sh --full --verbose + +# Validate HOT checklist JSON path +./automation/run_hot_update_report.sh --format json --database my_database --stdout + +# Validate HOT checklist text path +./automation/run_hot_update_report.sh --format text --database my_database --stdout + +# Run the entire bundle (shellcheck + automation + HOT) +./scripts/precommit_checks.sh --database my_database +``` + +#### Connection configuration +1. Copy the sample config: `cp automation/pgtools.conf.example automation/pgtools.conf`. +2. Edit `automation/pgtools.conf` and set the standard libpq variables: + ```bash + PGHOST=db-server.example.com + PGPORT=5432 + PGUSER=monitoring_user + PGDATABASE=postgres # default database used when --database is not passed + # PGPASSWORD is optional; prefer ~/.pgpass for credentials + ``` +3. The script sources this file at runtime, so every `psql` command inherits those values automatically. + +**Precedence:** command-line flags > environment variables > `pgtools.conf`. For example, running `./automation/run_hot_update_report.sh --database analytics` targets the `analytics` database while still using `PGHOST`/`PGPORT`/`PGUSER` from `pgtools.conf`. To override the server, export an environment variable before invoking the script (`PGHOST=staging-db ./automation/run_hot_update_report.sh`). If neither CLI nor environment overrides are provided, the values defined in `pgtools.conf` are used. + +## iqtoolkit-analyzer Integration +1. Run `automation/run_hot_update_report.sh --format json` against the target database. +2. Copy the generated file (e.g., `reports/hot_update_20251202_101500.json`) into the analyzer repo’s intake folder (`/path/to/iqtoolkit-analyzer/samples/`). +3. Execute the analyzer: + ```bash + cd /path/to/iqtoolkit-analyzer + python analyzer.py --type pg-hot-update --input samples/hot_update_20251202_101500.json + ``` +4. Review the analyzer findings alongside the fillfactor commands embedded in the JSON `recommendations` list. + +## Requirements +- PostgreSQL 9.0+ for the text report; 9.3+ for the JSON variant (uses `jsonb`). +- `pg_monitor` role or equivalent access to `pg_stat_user_tables`. +- `psql` client available on the automation host. +- Optional: `jq` for faster JSON validation. diff --git a/scripts/precommit_checks.sh b/scripts/precommit_checks.sh new file mode 100755 index 0000000..21ee26f --- /dev/null +++ b/scripts/precommit_checks.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# precommit_checks.sh +# Local helper to mirror CI validation (shell lint + automation smoke tests) + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DB_NAME="${PGDATABASE:-postgres}" + +declare -a CLEANUP_FILES=() + +usage() { + cat <<'EOF' +Usage: scripts/precommit_checks.sh [--database DB] + +Runs: + 1. shellcheck automation/*.sh + 2. ./automation/test_pgtools.sh --fast + 3. ./automation/run_hot_update_report.sh --format json (temp file) + 4. ./automation/run_hot_update_report.sh --format text (temp file) + +If --database is not supplied, PGDATABASE (or "postgres") is used. +Standard libpq environment variables (PGHOST, PGPORT, PGUSER, PGPASSWORD) +are honored so you can point at staging or local instances easily. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --database) + DB_NAME="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac +done + +info() { echo "[precommit] $*"; } +cleanup() { + for f in "${CLEANUP_FILES[@]}"; do + [[ -n "$f" && -f "$f" ]] && rm -f "$f" + done +} +trap cleanup EXIT + +cd "$REPO_ROOT" + +info "Running shellcheck on automation scripts" +shellcheck automation/*.sh + +info "Running automation fast test suite" +./automation/test_pgtools.sh --fast + +JSON_TMP="$(mktemp -t hot_json.XXXXXX)" +CLEANUP_FILES+=("$JSON_TMP") +info "Validating HOT checklist JSON path (database: $DB_NAME)" +./automation/run_hot_update_report.sh --format json --database "$DB_NAME" --output "$JSON_TMP" --quiet > /dev/null + +TEXT_TMP="$(mktemp -t hot_text.XXXXXX)" +CLEANUP_FILES+=("$TEXT_TMP") +info "Validating HOT checklist text path (database: $DB_NAME)" +./automation/run_hot_update_report.sh --format text --database "$DB_NAME" --output "$TEXT_TMP" --quiet > /dev/null + +info "All pre-commit checks passed"