Files
CI-templates/scripts/validate-templates.sh
T
Brian Bjarke Jensen 243a5432d8
Test CI Templates / validate-syntax (push) Successful in 11s
Test CI Templates / test-python-code-quality (3.11) (push) Failing after 5s
Test CI Templates / test-python-pytest (true, 3.12) (push) Failing after 4s
Test CI Templates / test-web-formatting (true, false, 20) (push) Failing after 5s
Test CI Templates / test-python-code-quality (3.10) (push) Failing after 5s
Test CI Templates / test-python-code-quality (3.12) (push) Failing after 5s
Test CI Templates / test-python-code-quality (false, false, basic) (push) Failing after 5s
Test CI Templates / test-python-code-quality (false, true, skip-pyupgrade) (push) Failing after 5s
Test CI Templates / test-python-pytest (false, 3.11) (push) Failing after 5s
Test CI Templates / test-python-pytest (false, 3.12) (push) Failing after 5s
Test CI Templates / test-python-pytest (true, 3.11) (push) Failing after 5s
Test CI Templates / test-web-formatting (false, false, 20) (push) Failing after 5s
Test CI Templates / test-web-formatting (false, true, 18) (push) Failing after 5s
Test CI Templates / test-web-formatting (false, true, 20) (push) Failing after 5s
Test CI Templates / test-web-formatting (true, false, 18) (push) Failing after 5s
Test CI Templates / test-monorepo (push) Failing after 5s
Test CI Templates / test-web-formatting (true, true, 18) (push) Failing after 5s
Test CI Templates / test-web-formatting (true, true, 20) (push) Failing after 5s
Test CI Templates / test-results (push) Successful in 2s
Test CI Templates / test-python-code-quality (true, false, skip-mypy) (push) Failing after 5s
Test CI Templates / test-web-formatting (false, false, 18) (push) Failing after 5s
Test CI Templates / test-edge-cases (push) Failing after 5s
fixed tests
2025-09-19 20:25:51 +02:00

718 lines
20 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -e
# CI Template Validation Script
# This script validates the syntax and structure of Gitea Actions workflow templates
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
WORKFLOW_DIR=".gitea/workflows"
REQUIRED_FIELDS=("name" "on" "jobs")
REUSABLE_TRIGGER="workflow_call"
# Counters
TOTAL_FILES=0
VALID_FILES=0
WARNINGS=0
# Helper functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
((WARNINGS++))
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if required tools are installed
check_dependencies() {
log_info "Checking dependencies..."
if ! command -v yamllint &> /dev/null; then
log_warning "yamllint not found. Install with: pip install yamllint"
log_info "Will skip YAML syntax validation"
SKIP_YAMLLINT=true
else
log_success "yamllint found"
fi
if ! command -v yq &> /dev/null; then
log_warning "yq not found. Install with: brew install yq (macOS) or apt install yq (Ubuntu)"
log_info "Will use basic grep validation instead"
SKIP_YQ=true
else
log_success "yq found"
fi
}
# Validate YAML syntax
validate_yaml_syntax() {
local file="$1"
if [[ "$SKIP_YAMLLINT" == "true" ]]; then
return 0
fi
if yamllint "$file" 2>/dev/null; then
return 0
else
log_error "YAML syntax validation failed for $file"
return 1
fi
}
# Check if file is a reusable workflow
is_reusable_workflow() {
local file="$1"
if [[ "$SKIP_YQ" == "true" ]]; then
# Fallback to grep
if grep -q "workflow_call" "$file"; then
return 0
fi
else
# Use yq for precise checking
if yq eval '.on | has("workflow_call")' "$file" 2>/dev/null | grep -q "true"; then
return 0
fi
fi
return 1
}
# Validate required workflow fields
validate_workflow_structure() {
local file="$1"
local valid=true
for field in "${REQUIRED_FIELDS[@]}"; do
if [[ "$SKIP_YQ" == "true" ]]; then
# Fallback to grep
if ! grep -q "^${field}:" "$file"; then
log_error "Missing required field '$field' in $file"
valid=false
fi
else
# Use yq for precise checking
if ! yq eval "has(\"$field\")" "$file" 2>/dev/null | grep -q "true"; then
log_error "Missing required field '$field' in $file"
valid=false
fi
fi
done
if [[ "$valid" == "true" ]]; then
return 0
else
return 1
fi
}
# Validate workflow inputs (for reusable workflows)
validate_workflow_inputs() {
local file="$1"
if ! is_reusable_workflow "$file"; then
return 0
fi
if [[ "$SKIP_YQ" == "true" ]]; then
# Basic check with grep
if grep -q "inputs:" "$file"; then
log_success "Inputs section found in $file"
else
log_warning "No inputs section found in reusable workflow $file"
fi
else
# Detailed validation with yq
local inputs_count
inputs_count=$(yq eval '.on.workflow_call.inputs | length' "$file" 2>/dev/null || echo "0")
if [[ "$inputs_count" -gt 0 ]]; then
log_success "Found $inputs_count input(s) in $file"
# Validate each input has required fields
local input_names
input_names=$(yq eval '.on.workflow_call.inputs | keys | .[]' "$file" 2>/dev/null || echo "")
while IFS= read -r input_name; do
if [[ -n "$input_name" ]]; then
# Check for description
if ! yq eval ".on.workflow_call.inputs.${input_name} | has(\"description\")" "$file" 2>/dev/null | grep -q "true"; then
log_warning "Input '$input_name' missing description in $file"
fi
# Check for type
if ! yq eval ".on.workflow_call.inputs.${input_name} | has(\"type\")" "$file" 2>/dev/null | grep -q "true"; then
log_warning "Input '$input_name' missing type in $file"
fi
fi
done <<< "$input_names"
else
log_warning "No inputs found in reusable workflow $file"
fi
fi
}
# Validate a single workflow file
validate_workflow_file() {
local file="$1"
local filename
filename=$(basename "$file")
log_info "Validating $file..."
((TOTAL_FILES++))
# Check if file exists and is readable
if [[ ! -r "$file" ]]; then
log_error "Cannot read file: $file"
return 1
fi
# Validate YAML syntax
if ! validate_yaml_syntax "$file"; then
return 1
fi
# Validate workflow structure
if ! validate_workflow_structure "$file"; then
return 1
fi
# Additional validations for reusable workflows
if is_reusable_workflow "$file"; then
log_success "$filename is a reusable workflow"
validate_workflow_inputs "$file"
else
# Skip warning for test files
if [[ "$filename" != *"test-"* ]]; then
log_warning "$filename is not a reusable workflow (missing workflow_call trigger)"
fi
fi
# Check for common issues
if grep -q "uses: \./\|uses: \.\\\\" "$file"; then
# Skip warning for test files - they intentionally use relative paths to test local workflows
if [[ "$filename" != *"test-"* ]]; then
log_warning "Found relative path in 'uses' field in $file - this may cause issues"
fi
fi
# Check for hardcoded values that should be inputs
if grep -q "python-version: [\"']3\." "$file"; then
# Skip warning for test files
if [[ "$filename" != *"test-"* ]]; then
log_warning "Found hardcoded Python version in $file - consider making it an input"
fi
fi
((VALID_FILES++))
log_success "$filename validation completed"
return 0
}
# Validate all workflow files
validate_all_workflows() {
log_info "Starting workflow validation..."
if [[ ! -d "$WORKFLOW_DIR" ]]; then
log_error "Workflow directory '$WORKFLOW_DIR' not found"
exit 1
fi
# Find all YAML files in workflow directory
local workflow_files
workflow_files=$(find "$WORKFLOW_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null)
if [[ -z "$workflow_files" ]]; then
log_error "No workflow files found in '$WORKFLOW_DIR'"
exit 1
fi
# Validate each file
local failed_files=0
while IFS= read -r file; do
if [[ -n "$file" ]]; then
if ! validate_workflow_file "$file"; then
((failed_files++))
fi
fi
done <<< "$workflow_files"
# Print summary
echo
log_info "=== Validation Summary ==="
log_info "Total files: $TOTAL_FILES"
log_success "Valid files: $VALID_FILES"
if [[ $WARNINGS -gt 0 ]]; then
log_warning "Warnings: $WARNINGS"
fi
if [[ $failed_files -gt 0 ]]; then
log_error "Failed files: $failed_files"
exit 1
fi
if [[ $WARNINGS -gt 0 ]]; then
log_warning "Validation completed with warnings"
exit 0
fi
log_success "All workflows validated successfully!"
}
# Validate documentation files
validate_documentation() {
log_info "Validating documentation..."
local readme_files=("README.md" "README-python-code-quality.md" "README-python-pytest.md" "README-web-formatting.md")
for readme in "${readme_files[@]}"; do
if [[ -f "$readme" ]]; then
log_success "Found $readme"
# Check for basic sections
if ! grep -q "^# " "$readme"; then
log_warning "$readme missing main heading"
fi
if ! grep -q "## Usage" "$readme"; then
log_warning "$readme missing Usage section"
fi
else
log_warning "Missing documentation file: $readme"
fi
done
}
# Generate workflow file list
list_workflows() {
log_info "Available workflow templates:"
echo
if [[ -d "$WORKFLOW_DIR" ]]; then
find "$WORKFLOW_DIR" -name "*.yml" -o -name "*.yaml" | while read -r file; do
local relative_path
relative_path=$(echo "$file" | sed "s|^$WORKFLOW_DIR/||")
if is_reusable_workflow "$file"; then
echo -e " ${GREEN}${NC} $relative_path (reusable)"
else
echo -e " ${YELLOW}${NC} $relative_path (not reusable)"
fi
done
else
log_error "Workflow directory not found"
fi
echo
}
# Main function
main() {
echo -e "${BLUE}CI Template Validator${NC}"
echo "======================"
echo
# Parse command line arguments
case "${1:-validate}" in
"validate")
check_dependencies
validate_all_workflows
validate_documentation
;;
"list")
list_workflows
;;
"help"|"-h"|"--help")
echo "Usage: $0 [command]"
echo
echo "Commands:"
echo " validate Validate all workflow files (default)"
echo " list List all workflow templates"
echo " help Show this help message"
echo
echo "Examples:"
echo " $0 # Validate all workflows"
echo " $0 validate # Same as above"
echo " $0 list # List all available templates"
;;
*)
log_error "Unknown command: $1"
echo "Use '$0 help' for usage information"
exit 1
;;
esac
}
# Run main function with all arguments
main "$@"
echo -e "${BLUE}🔍 CI Template Validation Script${NC}"
echo "=================================="
# Function to print status
print_status() {
if [ $1 -eq 0 ]; then
echo -e "${GREEN}$2${NC}"
else
echo -e "${RED}$2${NC}"
return 1
fi
}
# Function to print warning
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
# Function to print info
print_info() {
echo -e "${BLUE}$1${NC}"
}
# Check if required tools are installed
check_dependencies() {
echo -e "\n${BLUE}Checking dependencies...${NC}"
local missing_deps=()
if ! command -v yamllint &> /dev/null; then
missing_deps+=("yamllint")
fi
if ! command -v python3 &> /dev/null; then
missing_deps+=("python3")
fi
if [ ${#missing_deps[@]} -gt 0 ]; then
echo -e "${RED}Missing dependencies: ${missing_deps[*]}${NC}"
echo "Install with:"
for dep in "${missing_deps[@]}"; do
case $dep in
yamllint)
echo " pip install yamllint"
;;
python3)
echo " Install Python 3.x"
;;
esac
done
return 1
fi
print_status 0 "All dependencies available"
}
# Validate YAML syntax
validate_yaml_syntax() {
echo -e "\n${BLUE}Validating YAML syntax...${NC}"
local yaml_files=()
while IFS= read -r -d $'\0'; do
yaml_files+=("$REPLY")
done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
if [ ${#yaml_files[@]} -eq 0 ]; then
print_warning "No YAML files found in .gitea/workflows/"
return 1
fi
local errors=0
for file in "${yaml_files[@]}"; do
if yamllint "$file" > /dev/null 2>&1; then
print_status 0 "YAML syntax valid: $file"
else
print_status 1 "YAML syntax error: $file"
yamllint "$file"
((errors++))
fi
done
if [ $errors -eq 0 ]; then
print_status 0 "All YAML files have valid syntax"
else
print_status 1 "$errors YAML files have syntax errors"
return 1
fi
}
# Validate workflow structure
validate_workflow_structure() {
echo -e "\n${BLUE}Validating workflow structure...${NC}"
local workflow_files=()
while IFS= read -r -d $'\0'; do
workflow_files+=("$REPLY")
done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
local errors=0
for file in "${workflow_files[@]}"; do
echo -e "\nChecking $file..."
# Check for workflow_call trigger
if grep -q "workflow_call" "$file"; then
print_status 0 "Has workflow_call trigger"
else
print_status 1 "Missing workflow_call trigger"
((errors++))
fi
# Check for inputs section
if grep -q "inputs:" "$file"; then
print_status 0 "Has inputs section"
else
print_warning "No inputs section (may be intentional)"
fi
# Check for jobs section
if grep -q "jobs:" "$file"; then
print_status 0 "Has jobs section"
else
print_status 1 "Missing jobs section"
((errors++))
fi
# Check for runs-on
if grep -q "runs-on:" "$file"; then
print_status 0 "Has runs-on specified"
else
print_status 1 "Missing runs-on specification"
((errors++))
fi
# Check for checkout action
if grep -q "actions/checkout" "$file"; then
print_status 0 "Uses checkout action"
else
print_warning "No checkout action (may be intentional)"
fi
done
if [ $errors -eq 0 ]; then
print_status 0 "All workflows have valid structure"
else
print_status 1 "$errors workflows have structure issues"
return 1
fi
}
# Validate input parameters
validate_input_parameters() {
echo -e "\n${BLUE}Validating input parameters...${NC}"
local workflow_files=()
while IFS= read -r -d $'\0'; do
workflow_files+=("$REPLY")
done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
for file in "${workflow_files[@]}"; do
echo -e "\nChecking inputs in $file..."
# Extract input definitions and usage
local input_definitions=()
while IFS= read -r line; do
if [[ $line =~ ^[[:space:]]*([a-zA-Z0-9_-]+):[[:space:]]*$ ]]; then
input_name="${BASH_REMATCH[1]}"
if [[ ! $input_name =~ ^(description|required|type|default)$ ]]; then
input_definitions+=("$input_name")
fi
fi
done < <(sed -n '/inputs:/,/^[[:space:]]*[a-zA-Z]/p' "$file" | head -n -1)
# Check if inputs are used in the workflow
for input in "${input_definitions[@]}"; do
if grep -q "\${{ inputs\.$input }}" "$file"; then
print_status 0 "Input '$input' is used in workflow"
else
print_warning "Input '$input' is defined but not used"
fi
done
done
}
# Create test project structure
create_test_project() {
echo -e "\n${BLUE}Creating test project structure...${NC}"
local test_dir="test-validation-project"
if [ -d "$test_dir" ]; then
print_info "Removing existing test project"
rm -rf "$test_dir"
fi
mkdir -p "$test_dir/src/test_package"
cd "$test_dir"
# Create pyproject.toml
cat > pyproject.toml << 'EOF'
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "test-validation-package"
version = "0.1.0"
description = "Test package for CI template validation"
dependencies = []
[project.optional-dependencies]
dev = [
"mypy>=1.0.0",
"ruff>=0.1.0",
"pyupgrade>=3.0.0",
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
]
[tool.ruff]
line-length = 88
target-version = "py310"
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
EOF
# Create source code
mkdir -p src/test_package
cat > src/test_package/__init__.py << 'EOF'
"""Test package for validation."""
__version__ = "0.1.0"
def hello_world() -> str:
"""Return a greeting message."""
return "Hello, World!"
EOF
cat > src/test_package/math_utils.py << 'EOF'
"""Math utility functions."""
from typing import List
def add_numbers(numbers: List[float]) -> float:
"""Add a list of numbers."""
return sum(numbers)
def multiply_numbers(numbers: List[float]) -> float:
"""Multiply a list of numbers."""
result = 1.0
for num in numbers:
result *= num
return result
EOF
# Create tests
mkdir -p tests
cat > tests/test_math_utils.py << 'EOF'
"""Tests for math utilities."""
import pytest
from test_package.math_utils import add_numbers, multiply_numbers
def test_add_numbers():
assert add_numbers([1, 2, 3]) == 6
assert add_numbers([]) == 0
assert add_numbers([5.5, 2.5]) == 8.0
def test_multiply_numbers():
assert multiply_numbers([2, 3, 4]) == 24
assert multiply_numbers([1]) == 1
assert multiply_numbers([2.5, 2]) == 5.0
EOF
cd ..
print_status 0 "Test project created at $test_dir"
}
# Generate validation report
generate_report() {
echo -e "\n${BLUE}Generating validation report...${NC}"
local report_file="validation-report.md"
cat > "$report_file" << 'EOF'
# CI Template Validation Report
Generated on: $(date)
## Summary
This report contains the validation results for the CI template workflows.
## Workflow Files
EOF
# Add workflow file information
find .gitea/workflows -name "*.yml" 2>/dev/null | while read -r file; do
echo "### $file" >> "$report_file"
echo "" >> "$report_file"
echo "- **Size:** $(wc -c < "$file") bytes" >> "$report_file"
echo "- **Lines:** $(wc -l < "$file") lines" >> "$report_file"
if grep -q "workflow_call" "$file"; then
echo "- **Type:** Reusable workflow ✅" >> "$report_file"
else
echo "- **Type:** Standard workflow ⚠️" >> "$report_file"
fi
local input_count=$(grep -c "^[[:space:]]*[a-zA-Z0-9_-]*:[[:space:]]*$" "$file" | head -n 1)
echo "- **Input parameters:** $input_count" >> "$report_file"
echo "" >> "$report_file"
done
print_status 0 "Validation report generated: $report_file"
}
# Main execution
main() {
echo -e "${BLUE}Starting CI template validation...${NC}"
local exit_code=0
# Run all validation steps
check_dependencies || exit_code=1
validate_yaml_syntax || exit_code=1
validate_workflow_structure || exit_code=1
validate_input_parameters || exit_code=1
create_test_project || exit_code=1
generate_report || exit_code=1
echo -e "\n${BLUE}Validation Summary${NC}"
echo "=================="
if [ $exit_code -eq 0 ]; then
echo -e "${GREEN}🎉 All validations passed!${NC}"
echo -e "${GREEN}Your CI templates are ready for use.${NC}"
else
echo -e "${RED}💥 Some validations failed.${NC}"
echo -e "${RED}Please review the errors above and fix them.${NC}"
fi
echo -e "\n${BLUE}Next steps:${NC}"
echo "1. Review the validation report"
echo "2. Test the templates with the created test project"
echo "3. Run the automated test workflow: .gitea/workflows/test-templates.yml"
return $exit_code
}
# Run the main function
main "$@"