blob: f0f415069a9da492a1f0dc20430ef2ebe1a63e93 [file] [log] [blame]
#!/usr/bin/env bash
################################################################################
# Apache HugeGraph Release Validation Script
################################################################################
#
# This script validates Apache HugeGraph release packages:
# 1. Check package integrity (SHA512, GPG signatures)
# 2. Validate package names and required files
# 3. Check license compliance (ASF categories)
# 4. Validate package contents
# 5. Compile source packages
# 6. Run server and toolchain tests
#
# Usage:
# validate-release.sh <version> <user> [local-path] [java-version]
# validate-release.sh --help
#
# Arguments:
# version Release version (e.g., 1.7.0)
# user Apache username for GPG key trust
# local-path (Optional) Local directory containing release files
# If omitted, downloads from Apache SVN
# java-version (Optional) Java version to validate (default: 11)
#
# Examples:
# # Validate from Apache SVN
# ./validate-release.sh 1.7.0 pengjunzhi
#
# # Validate from local directory
# ./validate-release.sh 1.7.0 pengjunzhi /path/to/dist
#
# # Specify Java version
# ./validate-release.sh 1.7.0 pengjunzhi /path/to/dist 11
#
################################################################################
# Strict mode - but don't exit on error yet (we collect all errors)
set -o pipefail
set -o nounset
################################################################################
# Configuration Constants
################################################################################
readonly SCRIPT_VERSION="2.2.0"
readonly SCRIPT_NAME=$(basename "$0")
# URLs
readonly SVN_URL_PREFIX="https://dist.apache.org/repos/dist/dev/hugegraph"
readonly KEYS_URL="https://downloads.apache.org/hugegraph/KEYS"
# Validation Rules
readonly MAX_FILE_SIZE="800k"
readonly SERVER_START_DELAY=3
readonly SERVICE_HEALTH_TIMEOUT=30
# License Patterns (ASF Category X - Prohibited)
readonly CATEGORY_X="\bGPL|\bLGPL|Sleepycat License|BSD-4-Clause|\bBCL\b|JSR-275|Amazon Software License|\bRSAL\b|\bQPL\b|\bSSPL|\bCPOL|\bNPL1|Creative Commons Non-Commercial|JSON\.org"
# License Patterns (ASF Category B - Must be documented)
readonly CATEGORY_B="\bCDDL1|\bCPL|\bEPL|\bIPL|\bMPL|\bSPL|OSL-3.0|UnRAR License|Erlang Public License|\bOFL\b|Ubuntu Font License Version 1.0|IPA Font License Agreement v1.0|EPL2.0|CC-BY"
# Color Definitions
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[0;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
################################################################################
# Global Variables
################################################################################
# Script state
WORK_DIR=""
LOG_FILE=""
DIST_DIR=""
RELEASE_VERSION=""
USER=""
LOCAL_DIST_PATH=""
JAVA_VERSION=11
NON_INTERACTIVE=0
# Error tracking
declare -a VALIDATION_ERRORS=()
declare -a VALIDATION_WARNINGS=()
TOTAL_CHECKS=0
PASSED_CHECKS=0
FAILED_CHECKS=0
CURRENT_STEP=""
CURRENT_PACKAGE=""
# Service tracking for cleanup
SERVER_STARTED=0
HUBBLE_STARTED=0
# Script execution time tracking
SCRIPT_START_TIME=0
ENABLE_CLEANUP=0
################################################################################
# Helper Functions - Output & Logging
################################################################################
show_usage() {
cat << EOF
Apache HugeGraph Release Validation Script v${SCRIPT_VERSION}
Usage: ${SCRIPT_NAME} <version> <user> [local-path] [java-version]
${SCRIPT_NAME} --help | -h
${SCRIPT_NAME} --version | -v
Validates Apache HugeGraph release packages including:
- Package integrity (SHA512, GPG signatures)
- License compliance (ASF categories)
- Package contents and structure
- Compilation and runtime testing
Arguments:
version Release version (e.g., 1.7.0)
user Apache username for GPG key trust
local-path (Optional) Local directory path containing release files
If omitted, downloads from Apache SVN
java-version (Optional) Java version to validate (default: 11)
Options:
--help, -h Show this help message
--version, -v Show script version
--non-interactive Run without prompts (for CI/CD)
Examples:
# Validate from Apache SVN (downloads files)
${SCRIPT_NAME} 1.7.0 pengjunzhi
# Validate from local directory
${SCRIPT_NAME} 1.7.0 pengjunzhi /path/to/dist
# Specify Java version
${SCRIPT_NAME} 1.7.0 pengjunzhi "" 11
${SCRIPT_NAME} 1.7.0 pengjunzhi /path/to/dist 11
# Non-interactive mode for CI
${SCRIPT_NAME} --non-interactive 1.7.0 pengjunzhi
For more information, visit:
https://github.com/apache/hugegraph-doc/tree/master/dist
EOF
}
log() {
local level=$1
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[${timestamp}] [${level}] ${message}" | tee -a "${LOG_FILE:-/dev/null}"
}
info() {
echo -e "$*"
log "INFO" "$*"
}
success() {
echo -e "${GREEN}✓ $*${NC}"
log "SUCCESS" "$*"
}
warn() {
echo -e "${YELLOW}⚠ $*${NC}" >&2
log "WARN" "$*"
}
error() {
echo -e "${RED}✗ $*${NC}" >&2
log "ERROR" "$*"
}
print_step() {
local step=$1
local total=$2
local description=$3
CURRENT_STEP="Step $step: $description"
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}Step [$step/$total]: $description${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
log "STEP" "[$step/$total] $description"
}
print_progress() {
local current=$1
local total=$2
local item=$3
echo -e " [${current}/${total}] ${item}"
}
collect_error() {
local error_msg="$1"
local context=""
# Build context string
if [[ -n "$CURRENT_STEP" ]]; then
context="[$CURRENT_STEP]"
fi
if [[ -n "$CURRENT_PACKAGE" ]]; then
if [[ -n "$context" ]]; then
context="$context [$CURRENT_PACKAGE]"
else
context="[$CURRENT_PACKAGE]"
fi
fi
# Store error with context
if [[ -n "$context" ]]; then
VALIDATION_ERRORS+=("$context $error_msg")
else
VALIDATION_ERRORS+=("$error_msg")
fi
FAILED_CHECKS=$((FAILED_CHECKS + 1))
error "$error_msg"
}
collect_warning() {
local warning_msg="$1"
local context=""
# Build context string
if [[ -n "$CURRENT_STEP" ]]; then
context="[$CURRENT_STEP]"
fi
if [[ -n "$CURRENT_PACKAGE" ]]; then
if [[ -n "$context" ]]; then
context="$context [$CURRENT_PACKAGE]"
else
context="[$CURRENT_PACKAGE]"
fi
fi
# Store warning with context
if [[ -n "$context" ]]; then
VALIDATION_WARNINGS+=("$context $warning_msg")
else
VALIDATION_WARNINGS+=("$warning_msg")
fi
warn "$warning_msg"
}
mark_check_passed() {
PASSED_CHECKS=$((PASSED_CHECKS + 1))
}
mark_check_failed() {
FAILED_CHECKS=$((FAILED_CHECKS + 1))
}
################################################################################
# Helper Functions - System & Environment
################################################################################
setup_logging() {
local log_dir="${WORK_DIR}/logs"
mkdir -p "$log_dir"
LOG_FILE="$log_dir/validate-${RELEASE_VERSION}-$(date +%Y%m%d-%H%M%S).log"
ENABLE_CLEANUP=1
info "Logging to: ${LOG_FILE}"
log "INIT" "Starting validation for HugeGraph ${RELEASE_VERSION}"
log "INIT" "User: ${USER}, Java: ${JAVA_VERSION}"
}
check_dependencies() {
local missing_deps=()
local required_commands=("svn" "gpg" "shasum" "mvn" "java" "wget" "tar" "curl" "awk" "grep" "find" "perl")
info "Checking required dependencies..."
for cmd in "${required_commands[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
missing_deps+=("$cmd")
error "Missing: $cmd"
else
local version_info
case "$cmd" in
java)
version_info=$(java -version 2>&1 | head -n1 | cut -d'"' -f2)
;;
mvn)
version_info=$(mvn --version 2>&1 | head -n1 | awk '{print $3}')
;;
*)
version_info=$($cmd --version 2>&1 | head -n1 || echo "installed")
;;
esac
success "$cmd: $version_info"
fi
done
if [[ ${#missing_deps[@]} -gt 0 ]]; then
error "Missing required dependencies: ${missing_deps[*]}"
echo ""
echo "Please install missing dependencies:"
echo " Ubuntu/Debian: sudo apt-get install ${missing_deps[*]}"
echo " macOS: brew install ${missing_deps[*]}"
exit 1
fi
success "All dependencies are installed"
}
check_java_version() {
local required_version=$1
info "Checking Java version..."
if ! command -v java &> /dev/null; then
collect_error "Java is not installed or not in PATH"
return 1
fi
local current_version=$(java -version 2>&1 | head -n 1 | awk -F '"' '{print $2}' | awk -F '.' '{print $1}')
info "Current Java version: $current_version (Required: ${required_version})"
if [[ "$current_version" != "$required_version" ]]; then
collect_error "Java version mismatch! Current: Java $current_version, Required: Java ${required_version}"
collect_error "Please switch to Java ${required_version} before running this script"
return 1
fi
success "Java version check passed: Java $current_version"
mark_check_passed
return 0
}
find_package_dir() {
local pattern=$1
local base_dir=${2:-"${DIST_DIR}"}
local found=$(find "$base_dir" -maxdepth 3 -type d -path "$pattern" 2>/dev/null | head -n1)
if [[ -z "$found" ]]; then
collect_error "Could not find directory matching pattern: $pattern"
return 1
fi
echo "$found"
}
find_package_dir_silent() {
local pattern=$1
local base_dir=${2:-"${DIST_DIR}"}
find "$base_dir" -maxdepth 3 -type d -path "$pattern" 2>/dev/null | head -n1
}
################################################################################
# Helper Functions - GPG & Signatures
################################################################################
import_and_trust_gpg_keys() {
local user=$1
info "Downloading KEYS file from ${KEYS_URL}..."
if ! wget -q "${KEYS_URL}" -O KEYS; then
collect_error "Failed to download KEYS file from ${KEYS_URL}"
return 1
fi
success "KEYS file downloaded"
info "Importing GPG keys..."
local import_output=$(gpg --import KEYS 2>&1)
local imported_count=$(echo "$import_output" | grep -c "imported" || echo "0")
if [[ "$imported_count" == "0" ]]; then
warn "No new keys imported (may already exist in keyring)"
else
success "Imported GPG keys"
fi
# Trust specific user key
if ! gpg --list-keys "$user" &>/dev/null; then
collect_error "User '$user' key not found in imported keys. Please verify the username."
return 1
fi
info "Trusting GPG key for user: $user"
echo -e "5\ny\n" | gpg --batch --command-fd 0 --edit-key "$user" trust 2>/dev/null
success "Trusted key for $user"
# Trust all imported keys
info "Trusting all imported public keys..."
local trusted=0
for key in $(gpg --no-tty --list-keys --with-colons | awk -F: '/^pub/ {print $5}'); do
echo -e "5\ny\n" | gpg --batch --command-fd 0 --edit-key "$key" trust 2>/dev/null
trusted=$((trusted + 1))
done
success "Trusted $trusted GPG keys"
mark_check_passed
return 0
}
################################################################################
# Validation Functions - Package Checks
################################################################################
check_package_name() {
local package=$1
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
if [[ "$package" != apache-hugegraph* ]]; then
collect_error "Package name '$package' should start with 'apache-hugegraph'"
return 1
fi
if [[ "$package" != *"${RELEASE_VERSION}"* ]]; then
collect_error "Package name '$package' does not include release version '${RELEASE_VERSION}'"
return 1
fi
if [[ "$package" =~ incubating ]]; then
collect_error "Package '$package' should not contain 'incubating' for post-graduation releases"
return 1
fi
mark_check_passed
return 0
}
check_required_files() {
local package=$1
local has_error=0
if [[ ! -f "LICENSE" ]]; then
collect_error "Package '$package' missing LICENSE file"
has_error=1
else
mark_check_passed
fi
if [[ ! -f "NOTICE" ]]; then
collect_error "Package '$package' missing NOTICE file"
has_error=1
else
mark_check_passed
fi
return $has_error
}
check_license_categories() {
local package=$1
local files=$2
local has_error=0
# Check Category X (Prohibited)
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
local cat_x_matches=$(grep -r -E "$CATEGORY_X" $files 2>/dev/null)
local cat_x_count=$(echo "$cat_x_matches" | grep -v '^$' | wc -l | tr -d ' ')
if [[ $cat_x_count -ne 0 ]]; then
# Build detailed error message with license information
local error_details="Package '$package' contains $cat_x_count prohibited ASF Category X license(s):"
# Extract and format each violation
while IFS= read -r match_line; do
if [[ -n "$match_line" ]]; then
# Parse file:content format
local file_name=$(echo "$match_line" | cut -d':' -f1)
local license_info=$(echo "$match_line" | cut -d':' -f2-)
# Try to extract specific license name
local license_name=$(echo "$license_info" | grep -oE "$CATEGORY_X" | head -n1)
error_details="${error_details}\n - File: ${file_name}\n License: ${license_name}\n Context: ${license_info}"
fi
done <<< "$cat_x_matches"
collect_error "$error_details"
has_error=1
else
mark_check_passed
fi
# Check Category B (Must be documented - warning only)
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
local cat_b_count=$(grep -r -E "$CATEGORY_B" $files 2>/dev/null | wc -l | tr -d ' ')
if [[ $cat_b_count -ne 0 ]]; then
collect_warning "Package '$package' contains $cat_b_count ASF Category B license(s) - please verify documentation"
else
mark_check_passed
fi
return $has_error
}
check_empty_files_and_dirs() {
local package=$1
local has_error=0
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
# Find empty directories
local empty_dirs=()
while IFS= read -r empty_dir; do
empty_dirs+=("$empty_dir")
done < <(find . -type d -empty 2>/dev/null)
# Find empty files
local empty_files=()
while IFS= read -r empty_file; do
empty_files+=("$empty_file")
done < <(find . -type f -empty 2>/dev/null)
if [[ ${#empty_dirs[@]} -gt 0 ]]; then
collect_error "Package '$package' contains ${#empty_dirs[@]} empty director(y/ies):"
printf ' %s\n' "${empty_dirs[@]}"
has_error=1
fi
if [[ ${#empty_files[@]} -gt 0 ]]; then
collect_error "Package '$package' contains ${#empty_files[@]} empty file(s):"
printf ' %s\n' "${empty_files[@]}"
has_error=1
fi
if [[ $has_error -eq 0 ]]; then
mark_check_passed
fi
return $has_error
}
check_file_sizes() {
local package=$1
local max_size=$2
local has_error=0
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
local large_files=()
while IFS= read -r large_file; do
large_files+=("$large_file")
done < <(find . -type f -size "+${max_size}" 2>/dev/null)
if [[ ${#large_files[@]} -gt 0 ]]; then
collect_error "Package '$package' contains ${#large_files[@]} file(s) larger than ${max_size}:"
for file in "${large_files[@]}"; do
local size=$(du -h "$file" | awk '{print $1}')
echo " $file ($size)"
done
has_error=1
else
mark_check_passed
fi
return $has_error
}
check_binary_files() {
local package=$1
local has_error=0
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
info "Checking for undocumented binary files..."
local binary_count=0
local undocumented_count=0
# Find binary files using perl
while IFS= read -r binary_file; do
binary_count=$((binary_count + 1))
local file_name=$(basename "$binary_file")
# Check if documented in LICENSE
if grep -q "$file_name" LICENSE 2>/dev/null; then
success "Binary file '$binary_file' is documented in LICENSE"
else
collect_error "Undocumented binary file: $binary_file"
undocumented_count=$((undocumented_count + 1))
has_error=1
fi
done < <(find . -type f 2>/dev/null | perl -lne 'print if -B $_')
if [[ $binary_count -eq 0 ]]; then
success "No binary files found"
mark_check_passed
elif [[ $undocumented_count -eq 0 ]]; then
success "All $binary_count binary file(s) are documented"
mark_check_passed
fi
return $has_error
}
check_license_headers() {
local package=$1
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
info "Checking for ASF license headers in source files..."
# Define file patterns to check for license headers
# Including: Java, Shell scripts, Python, Go, JavaScript, TypeScript, C/C++, Scala, Groovy, etc.
local -a file_patterns=(
"*.java" # Java files
"*.sh" # Shell scripts
"*.py" # Python files
"*.go" # Go files
"*.js" # JavaScript files
"*.ts" # TypeScript files
"*.jsx" # React JSX files
"*.tsx" # React TypeScript files
"*.c" # C files
"*.h" # C header files
"*.cpp" # C++ files
"*.cc" # C++ files
"*.cxx" # C++ files
"*.hpp" # C++ header files
"*.scala" # Scala files
"*.groovy" # Groovy files
"*.gradle" # Gradle build files
"*.rs" # Rust files
"*.kt" # Kotlin files
"*.proto" # Protocol buffer files
)
# Files to exclude from license header check
local -a exclude_patterns=(
"*.min.js" # Minified JavaScript
"*.min.css" # Minified CSS
"*node_modules*" # Node.js dependencies
"*target*" # Maven build output
"*build*" # Build directories
"*.pb.go" # Generated protobuf files
"*generated*" # Generated code
"*third_party*" # Third party code
"*vendor*" # Vendor dependencies
)
local files_without_license=()
local total_checked=0
local excluded_count=0
# Build find command with all patterns
local find_cmd="find . -type f \\("
local first=1
for pattern in "${file_patterns[@]}"; do
if [[ $first -eq 1 ]]; then
find_cmd="$find_cmd -name \"$pattern\""
first=0
else
find_cmd="$find_cmd -o -name \"$pattern\""
fi
done
find_cmd="$find_cmd \\) 2>/dev/null"
# Check each source file for ASF license header
local documented_count=0
while IFS= read -r source_file; do
# Skip if file matches exclude patterns
local should_exclude=0
for exclude_pattern in "${exclude_patterns[@]}"; do
if [[ "$source_file" == $exclude_pattern ]]; then
should_exclude=1
excluded_count=$((excluded_count + 1))
break
fi
done
if [[ $should_exclude -eq 1 ]]; then
continue
fi
total_checked=$((total_checked + 1))
# Check first 30 lines for Apache license header
# Looking for the standard ASF license header text
if ! head -n 30 "$source_file" | grep -q "Licensed to the Apache Software Foundation"; then
# No ASF header found - check if it's documented in LICENSE file as third-party code
local file_name=$(basename "$source_file")
local file_path_relative=$(echo "$source_file" | sed 's|^\./||')
# Check if file name or path is mentioned in LICENSE file
if [[ -f "LICENSE" ]] && (grep -q "$file_name" LICENSE 2>/dev/null || grep -q "$file_path_relative" LICENSE 2>/dev/null); then
# File is documented in LICENSE as third-party code - this is allowed
documented_count=$((documented_count + 1))
else
# Not documented - this is an error
files_without_license+=("$source_file")
fi
fi
done < <(eval "$find_cmd")
# Report results
info "Checked $total_checked source file(s) for ASF license headers (excluded $excluded_count generated/vendored files)"
if [[ $documented_count -gt 0 ]]; then
info "Found $documented_count source file(s) documented in LICENSE as third-party code (allowed)"
fi
if [[ ${#files_without_license[@]} -gt 0 ]]; then
collect_error "Found ${#files_without_license[@]} source file(s) without ASF license headers:"
# Show first 20 files without headers (to avoid overwhelming output)
local show_count=${#files_without_license[@]}
if [[ $show_count -gt 20 ]]; then
show_count=20
fi
for ((i=0; i<show_count; i++)); do
echo " ${files_without_license[$i]}"
done
if [[ ${#files_without_license[@]} -gt 20 ]]; then
echo " ... and $((${#files_without_license[@]} - 20)) more files"
fi
echo ""
collect_error "All source files must include the Apache License header or be documented in LICENSE file"
collect_error "You can use 'mvn apache-rat:check' for detailed license header analysis"
return 1
else
success "All $total_checked source file(s) have ASF license headers or are documented in LICENSE"
mark_check_passed
return 0
fi
}
check_version_consistency() {
local package=$1
local expected_version=$2
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
# Skip version check for Python projects (hugegraph-ai)
if [[ "$package" =~ 'hugegraph-ai' ]]; then
info "Skipping version check for Python project: $package"
mark_check_passed
return 0
fi
info "Checking version consistency (revision property)..."
# Find the parent/root pom.xml that defines the revision property
local root_pom=""
local revision_value=""
# Look for pom.xml files that define the revision property
while IFS= read -r pom_file; do
if grep -q "<revision>" "$pom_file" 2>/dev/null; then
# Extract the revision value
revision_value=$(grep "<revision>" "$pom_file" | head -1 | sed 's/.*<revision>\(.*\)<\/revision>.*/\1/')
root_pom="$pom_file"
break
fi
done < <(find . -name "pom.xml" -type f 2>/dev/null)
if [[ -z "$root_pom" ]]; then
collect_warning "No <revision> property found in pom.xml files - skipping version check"
mark_check_passed
return 0
fi
info "Found revision property in $root_pom: <revision>$revision_value</revision>"
# Check if revision matches expected version
if [[ "$revision_value" != "$expected_version" ]]; then
collect_error "Version mismatch: <revision>$revision_value</revision> in $root_pom (expected: $expected_version)"
return 1
fi
success "Version consistency check passed: revision=$revision_value"
mark_check_passed
return 0
}
check_notice_year() {
local package=$1
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
if [[ ! -f "NOTICE" ]]; then
return 0 # Already checked in check_required_files
fi
local current_year=$(date +%Y)
if ! grep -q "$current_year" NOTICE; then
collect_warning "Package '$package': NOTICE file may not contain current year ($current_year). Please verify copyright dates."
else
mark_check_passed
fi
}
################################################################################
# Main Validation Functions
################################################################################
validate_source_package() {
local package_file=$1
local package_dir=$(basename "$package_file" .tar.gz)
# Set current package context for error reporting
CURRENT_PACKAGE="$package_file"
info "Validating source package: $package_file"
# Extract package
rm -rf "$package_dir"
tar -xzf "$package_file"
if [[ ! -d "$package_dir" ]]; then
collect_error "Failed to extract package: $package_file"
CURRENT_PACKAGE=""
return 1
fi
pushd "$package_dir" > /dev/null
# Run all checks
check_package_name "$package_file"
check_required_files "$package_file"
check_license_categories "$package_file" "LICENSE NOTICE"
check_empty_files_and_dirs "$package_file"
check_file_sizes "$package_file" "$MAX_FILE_SIZE"
check_binary_files "$package_file"
check_license_headers "$package_file"
check_version_consistency "$package_file" "$RELEASE_VERSION"
check_notice_year "$package_file"
# Compile check
info "Compiling source package: $package_file"
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
if [[ "$package_file" =~ 'hugegraph-ai' ]]; then
warn "Skipping compilation for AI module (not required)"
mark_check_passed
elif [[ "$package_file" =~ "hugegraph-computer" ]]; then
if cd computer 2>/dev/null && mvn clean package -DskipTests -Dcheckstyle.skip=true -ntp -e; then
success "Compilation successful: $package_file"
mark_check_passed
else
collect_error "Compilation failed: $package_file"
fi
cd ..
else
if mvn clean package -DskipTests -Dcheckstyle.skip=true -ntp -e; then
success "Compilation successful: $package_file"
mark_check_passed
else
collect_error "Compilation failed: $package_file"
fi
fi
popd > /dev/null
# Clear package context
CURRENT_PACKAGE=""
info "Finished validating source package: $package_file"
}
validate_binary_package() {
local package_file=$1
local package_dir=$(basename "$package_file" .tar.gz)
# Set current package context for error reporting
CURRENT_PACKAGE="$package_file"
info "Validating binary package: $package_file"
# Extract package
rm -rf "$package_dir"
tar -xzf "$package_file"
if [[ ! -d "$package_dir" ]]; then
collect_error "Failed to extract package: $package_file"
CURRENT_PACKAGE=""
return 1
fi
pushd "$package_dir" > /dev/null
# Run checks
check_package_name "$package_file"
check_required_files "$package_file"
# Binary packages should have licenses directory
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
if [[ ! -d "licenses" ]]; then
collect_error "Package '$package_file' missing licenses directory"
else
mark_check_passed
fi
check_license_categories "$package_file" "LICENSE NOTICE licenses"
check_empty_files_and_dirs "$package_file"
popd > /dev/null
# Clear package context
CURRENT_PACKAGE=""
info "Finished validating binary package: $package_file"
}
################################################################################
# Cleanup Function
################################################################################
cleanup() {
local exit_code=$?
if [[ $ENABLE_CLEANUP -eq 0 ]]; then
return "$exit_code"
fi
log "CLEANUP" "Starting cleanup (exit code: $exit_code)"
# Stop running services
if [[ $SERVER_STARTED -eq 1 ]]; then
info "Stopping HugeGraph server..."
local server_dir=$(find_package_dir_silent "*hugegraph*${RELEASE_VERSION}*src/hugegraph-server/*hugegraph-server*${RELEASE_VERSION}*")
if [[ -n "$server_dir" ]] && [[ -d "$server_dir" ]]; then
pushd "$server_dir" > /dev/null 2>&1
bin/stop-hugegraph.sh || true
popd > /dev/null 2>&1
fi
fi
if [[ $HUBBLE_STARTED -eq 1 ]]; then
info "Stopping Hubble..."
# Hubble stop is handled in the test flow
fi
# Show final report
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " VALIDATION SUMMARY "
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Calculate execution time
local script_end_time=$(date +%s)
local execution_seconds=$((script_end_time - SCRIPT_START_TIME))
local execution_minutes=$((execution_seconds / 60))
local execution_seconds_remainder=$((execution_seconds % 60))
echo "Execution Time: ${execution_minutes}m ${execution_seconds_remainder}s"
echo "Total Checks: $TOTAL_CHECKS"
echo -e "${GREEN}Passed: $PASSED_CHECKS${NC}"
echo -e "${RED}Failed: $FAILED_CHECKS${NC}"
echo -e "${YELLOW}Warnings: ${#VALIDATION_WARNINGS[@]}${NC}"
echo ""
if [[ ${#VALIDATION_ERRORS[@]} -gt 0 ]]; then
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${RED} ERRORS ${NC}"
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
local err_index=1
for err in "${VALIDATION_ERRORS[@]}"; do
echo -e "${RED}[E${err_index}] $err${NC}"
echo "" # Blank line between errors for readability
err_index=$((err_index + 1))
done
fi
if [[ ${#VALIDATION_WARNINGS[@]} -gt 0 ]]; then
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW} WARNINGS ${NC}"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
local warn_index=1
for warn in "${VALIDATION_WARNINGS[@]}"; do
echo -e "${YELLOW}[W${warn_index}] $warn${NC}"
echo "" # Blank line between warnings for readability
warn_index=$((warn_index + 1))
done
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [[ ${#VALIDATION_ERRORS[@]} -gt 0 ]]; then
echo -e "${RED}VALIDATION FAILED${NC}"
echo -e "Log file: ${LOG_FILE}"
echo ""
exit 1
else
echo -e "${GREEN}✓ VALIDATION PASSED${NC}"
echo -e "Log file: ${LOG_FILE}"
echo ""
echo "Please review the validation results and provide feedback in the"
echo "release voting thread on the mailing list."
echo ""
exit 0
fi
}
# Set trap for cleanup
trap cleanup EXIT
trap 'echo -e "${RED}Script interrupted${NC}"; exit 130' INT TERM
################################################################################
# Main Execution
################################################################################
main() {
# Record script start time
SCRIPT_START_TIME=$(date +%s)
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--help|-h)
show_usage
exit 0
;;
--version|-v)
echo "Apache HugeGraph Release Validation Script v${SCRIPT_VERSION}"
exit 0
;;
--non-interactive)
NON_INTERACTIVE=1
shift
;;
*)
break
;;
esac
done
# Parse positional arguments
RELEASE_VERSION=${1:-}
USER=${2:-}
LOCAL_DIST_PATH=${3:-}
JAVA_VERSION=${4:-11}
# Validate required arguments
if [[ -z "$RELEASE_VERSION" ]]; then
error "Missing required argument: version"
echo ""
show_usage
exit 1
fi
if [[ -z "$USER" ]]; then
error "Missing required argument: user"
echo ""
show_usage
exit 1
fi
# Initialize
WORK_DIR=$(cd "$(dirname "$0")" && pwd)
cd "${WORK_DIR}"
setup_logging
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Apache HugeGraph Release Validation v${SCRIPT_VERSION}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo " Version: ${RELEASE_VERSION}"
echo " User: ${USER}"
echo " Java: ${JAVA_VERSION}"
echo " Mode: $([ -n "${LOCAL_DIST_PATH}" ] && echo "Local (${LOCAL_DIST_PATH})" || echo "SVN Download")"
echo " Log: ${LOG_FILE}"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
####################################################
# Step 1: Check Dependencies
####################################################
print_step 1 9 "Check Dependencies"
check_dependencies
check_java_version "$JAVA_VERSION"
####################################################
# Step 2: Prepare Release Files
####################################################
print_step 2 9 "Prepare Release Files"
if [[ -n "${LOCAL_DIST_PATH}" ]]; then
# Use local directory
DIST_DIR="${LOCAL_DIST_PATH}"
info "Using local directory: ${DIST_DIR}"
if [[ ! -d "${DIST_DIR}" ]]; then
collect_error "Directory ${DIST_DIR} does not exist"
exit 1
fi
info "Contents of ${DIST_DIR}:"
ls -lh "${DIST_DIR}"
else
# Download from SVN
if ! svn ls "${SVN_URL_PREFIX}/${RELEASE_VERSION}" &>/dev/null; then
collect_error "Release version '${RELEASE_VERSION}' not found in TLP dist path: ${SVN_URL_PREFIX}/${RELEASE_VERSION}"
exit 1
fi
DIST_DIR="${WORK_DIR}/dist/${RELEASE_VERSION}"
info "Downloading from SVN to: ${DIST_DIR}"
rm -rf "${DIST_DIR}"
mkdir -p "${DIST_DIR}"
if ! svn co "${SVN_URL_PREFIX}/${RELEASE_VERSION}" "${DIST_DIR}"; then
collect_error "Failed to download from SVN: ${SVN_URL_PREFIX}/${RELEASE_VERSION}"
exit 1
fi
success "Downloaded release files from SVN"
fi
cd "${DIST_DIR}"
####################################################
# Step 3: Import GPG Keys
####################################################
print_step 3 9 "Import & Trust GPG Keys"
import_and_trust_gpg_keys "$USER"
####################################################
# Step 4: Check SHA512 & GPG Signatures
####################################################
print_step 4 9 "Verify SHA512 & GPG Signatures"
local package_count=0
local packages=()
for pkg in *.tar.gz; do
if [[ -f "$pkg" ]]; then
packages+=("$pkg")
package_count=$((package_count + 1))
fi
done
local current=0
for pkg in "${packages[@]}"; do
current=$((current + 1))
print_progress $current $package_count "$pkg"
# Check SHA512
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
if shasum -a 512 --check "${pkg}.sha512"; then
success "SHA512 verified: $pkg"
mark_check_passed
else
collect_error "SHA512 verification failed: $pkg"
fi
# Check GPG signature
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
if gpg --verify "${pkg}.asc" "$pkg" 2>&1 | grep -q "Good signature"; then
success "GPG signature verified: $pkg"
mark_check_passed
else
collect_error "GPG signature verification failed: $pkg"
fi
done
####################################################
# Step 5: Validate Source Packages
####################################################
print_step 5 9 "Validate Source Packages"
local src_packages=()
for pkg in *-src.tar.gz; do
if [[ -f "$pkg" ]]; then
src_packages+=("$pkg")
fi
done
info "Found ${#src_packages[@]} source package(s)"
for src_pkg in "${src_packages[@]}"; do
validate_source_package "$src_pkg"
done
####################################################
# Step 6: Run Compiled Packages (Server)
####################################################
print_step 6 9 "Test Compiled Server Package"
local server_dir=$(find_package_dir "*hugegraph*${RELEASE_VERSION}*src/hugegraph-server/*hugegraph-server*${RELEASE_VERSION}*")
if [[ -n "$server_dir" ]]; then
info "Starting HugeGraph server from: $server_dir"
pushd "$server_dir" > /dev/null
if bin/init-store.sh; then
success "Store initialized"
else
collect_error "Failed to initialize store"
fi
sleep $SERVER_START_DELAY
if bin/start-hugegraph.sh; then
success "Server started"
SERVER_STARTED=1
else
collect_error "Failed to start server"
fi
popd > /dev/null
else
collect_error "Could not find compiled server directory"
fi
####################################################
# Step 7: Test Toolchain (Loader, Tool, Hubble)
####################################################
print_step 7 9 "Test Compiled Toolchain Packages"
local toolchain_src=$(find_package_dir "*toolchain*src")
if [[ -n "$toolchain_src" ]]; then
pushd "$toolchain_src" > /dev/null
local toolchain_dir=$(find . -maxdepth 1 -type d -name "*toolchain*${RELEASE_VERSION}" | head -n1)
if [[ -n "$toolchain_dir" ]]; then
pushd "$toolchain_dir" > /dev/null
# Test Loader
info "Testing HugeGraph Loader..."
local loader_dir=$(find . -maxdepth 1 -type d -name "*loader*${RELEASE_VERSION}" | head -n1)
if [[ -n "$loader_dir" ]]; then
pushd "$loader_dir" > /dev/null
if bin/hugegraph-loader.sh -f ./example/file/struct.json -s ./example/file/schema.groovy -g hugegraph; then
success "Loader test passed"
else
collect_error "Loader test failed"
fi
popd > /dev/null
fi
# Test Tool
info "Testing HugeGraph Tool..."
local tool_dir=$(find . -maxdepth 1 -type d -name "*tool*${RELEASE_VERSION}" | head -n1)
if [[ -n "$tool_dir" ]]; then
pushd "$tool_dir" > /dev/null
if bin/hugegraph gremlin-execute --script 'g.V().count()' && \
bin/hugegraph task-list && \
bin/hugegraph backup -t all --directory ./backup-test; then
success "Tool test passed"
else
collect_error "Tool test failed"
fi
popd > /dev/null
fi
# Test Hubble
info "Testing HugeGraph Hubble..."
local hubble_dir=$(find . -maxdepth 1 -type d -name "*hubble*${RELEASE_VERSION}" | head -n1)
if [[ -n "$hubble_dir" ]]; then
pushd "$hubble_dir" > /dev/null
if bin/start-hubble.sh; then
HUBBLE_STARTED=1
success "Hubble started"
sleep 2
bin/stop-hubble.sh
HUBBLE_STARTED=0
success "Hubble stopped"
else
collect_error "Hubble test failed"
fi
popd > /dev/null
fi
popd > /dev/null
fi
popd > /dev/null
fi
# Stop server after toolchain tests
if [[ $SERVER_STARTED -eq 1 ]] && [[ -n "$server_dir" ]]; then
info "Stopping server..."
pushd "$server_dir" > /dev/null
bin/stop-hugegraph.sh
SERVER_STARTED=0
success "Server stopped"
popd > /dev/null
fi
####################################################
# Step 8: Validate Binary Packages
####################################################
print_step 8 9 "Validate Binary Packages"
cd "${DIST_DIR}"
local bin_packages=()
for pkg in *.tar.gz; do
if [[ "$pkg" != *-src.tar.gz ]]; then
bin_packages+=("$pkg")
fi
done
info "Found ${#bin_packages[@]} binary package(s)"
for bin_pkg in "${bin_packages[@]}"; do
validate_binary_package "$bin_pkg"
done
####################################################
# Step 9: Test Binary Packages
####################################################
print_step 9 9 "Test Binary Server & Toolchain"
# Test binary server
local bin_server_dir=$(find_package_dir "*hugegraph*${RELEASE_VERSION}/*hugegraph-server*${RELEASE_VERSION}*")
if [[ -n "$bin_server_dir" ]]; then
info "Testing binary server package..."
pushd "$bin_server_dir" > /dev/null
if bin/init-store.sh && sleep $SERVER_START_DELAY && bin/start-hugegraph.sh; then
success "Binary server started"
SERVER_STARTED=1
else
collect_error "Failed to start binary server"
fi
popd > /dev/null
fi
# Test binary toolchain
local bin_toolchain=$(find_package_dir "*toolchain*${RELEASE_VERSION}" "${DIST_DIR}")
if [[ -n "$bin_toolchain" ]]; then
pushd "$bin_toolchain" > /dev/null
# Test binary loader
local bin_loader=$(find . -maxdepth 1 -type d -name "*loader*${RELEASE_VERSION}" | head -n1)
if [[ -n "$bin_loader" ]]; then
pushd "$bin_loader" > /dev/null
if bin/hugegraph-loader.sh -f ./example/file/struct.json -s ./example/file/schema.groovy -g hugegraph; then
success "Binary loader test passed"
else
collect_error "Binary loader test failed"
fi
popd > /dev/null
fi
# Test binary tool
local bin_tool=$(find . -maxdepth 1 -type d -name "*tool*${RELEASE_VERSION}" | head -n1)
if [[ -n "$bin_tool" ]]; then
pushd "$bin_tool" > /dev/null
if bin/hugegraph gremlin-execute --script 'g.V().count()' && \
bin/hugegraph task-list && \
bin/hugegraph backup -t all --directory ./backup-test; then
success "Binary tool test passed"
else
collect_error "Binary tool test failed"
fi
popd > /dev/null
fi
# Test binary hubble
local bin_hubble=$(find . -maxdepth 1 -type d -name "*hubble*${RELEASE_VERSION}" | head -n1)
if [[ -n "$bin_hubble" ]]; then
pushd "$bin_hubble" > /dev/null
if bin/start-hubble.sh; then
HUBBLE_STARTED=1
success "Binary hubble started"
sleep 2
bin/stop-hubble.sh
HUBBLE_STARTED=0
success "Binary hubble stopped"
else
collect_error "Binary hubble test failed"
fi
popd > /dev/null
fi
popd > /dev/null
fi
# Stop binary server
if [[ $SERVER_STARTED -eq 1 ]] && [[ -n "$bin_server_dir" ]]; then
pushd "$bin_server_dir" > /dev/null
bin/stop-hugegraph.sh
SERVER_STARTED=0
success "Binary server stopped"
popd > /dev/null
fi
####################################################
# Validation Complete
####################################################
success "All validation steps completed!"
# Cleanup function will show the final report
}
# Run main function
main "$@"