feat: Fix discovery system critical issues
Resolved multiple critical discovery system issues: ## Discovery System Fixes - Fixed console commands not being discovered on first run - Implemented fallback discovery for empty caches - Added context-aware caching with separate cache keys - Fixed object serialization preventing __PHP_Incomplete_Class ## Cache System Improvements - Smart caching that only caches meaningful results - Separate caches for different execution contexts (console, web, test) - Proper array serialization/deserialization for cache compatibility - Cache hit logging for debugging and monitoring ## Object Serialization Fixes - Fixed DiscoveredAttribute serialization with proper string conversion - Sanitized additional data to prevent object reference issues - Added fallback for corrupted cache entries ## Performance & Reliability - All 69 console commands properly discovered and cached - 534 total discovery items successfully cached and restored - No more __PHP_Incomplete_Class cache corruption - Improved error handling and graceful fallbacks ## Testing & Quality - Fixed code style issues across discovery components - Enhanced logging for better debugging capabilities - Improved cache validation and error recovery Ready for production deployment with stable discovery system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
428
deployment/applications/scripts/deploy-app.sh
Executable file
428
deployment/applications/scripts/deploy-app.sh
Executable file
@@ -0,0 +1,428 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Application Deployment Script for Custom PHP Framework
|
||||
# Integrates Docker Compose with Ansible infrastructure deployment
|
||||
# Usage: ./deploy-app.sh [staging|production] [options]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Script directory and project root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../../" && pwd)"
|
||||
DEPLOYMENT_DIR="${PROJECT_ROOT}/deployment"
|
||||
APPLICATIONS_DIR="${DEPLOYMENT_DIR}/applications"
|
||||
|
||||
# Default configuration
|
||||
DEFAULT_ENV="staging"
|
||||
DRY_RUN=false
|
||||
SKIP_TESTS=false
|
||||
SKIP_BACKUP=false
|
||||
FORCE_DEPLOY=false
|
||||
VERBOSE=false
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $1${NC}"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
|
||||
}
|
||||
|
||||
debug() {
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] DEBUG: $1${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Usage information
|
||||
show_usage() {
|
||||
cat << EOF
|
||||
Usage: $0 [environment] [options]
|
||||
|
||||
Environment:
|
||||
staging Deploy to staging environment (default)
|
||||
production Deploy to production environment
|
||||
|
||||
Options:
|
||||
--dry-run Show what would be done without making changes
|
||||
--skip-tests Skip running tests before deployment
|
||||
--skip-backup Skip database backup (not recommended for production)
|
||||
--force Force deployment even if validation fails
|
||||
--verbose Enable verbose output
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
$0 staging # Deploy to staging
|
||||
$0 production --skip-tests # Deploy to production without tests
|
||||
$0 staging --dry-run --verbose # Dry run with detailed output
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
parse_arguments() {
|
||||
local environment=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
staging|production)
|
||||
environment="$1"
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--skip-tests)
|
||||
SKIP_TESTS=true
|
||||
shift
|
||||
;;
|
||||
--skip-backup)
|
||||
SKIP_BACKUP=true
|
||||
shift
|
||||
;;
|
||||
--force)
|
||||
FORCE_DEPLOY=true
|
||||
shift
|
||||
;;
|
||||
--verbose)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
error "Unknown argument: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set environment, defaulting to staging
|
||||
DEPLOY_ENV="${environment:-$DEFAULT_ENV}"
|
||||
}
|
||||
|
||||
# Validate environment and prerequisites
|
||||
validate_environment() {
|
||||
log "Validating deployment environment: $DEPLOY_ENV"
|
||||
|
||||
# Check if we're in the project root
|
||||
if [[ ! -f "${PROJECT_ROOT}/docker-compose.yml" ]]; then
|
||||
error "Project root not found. Please run from the correct directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate environment-specific files exist
|
||||
local compose_file="${APPLICATIONS_DIR}/docker-compose.${DEPLOY_ENV}.yml"
|
||||
local env_file="${APPLICATIONS_DIR}/environments/.env.${DEPLOY_ENV}"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
error "Docker Compose overlay not found: $compose_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$env_file" ]]; then
|
||||
error "Environment file not found: $env_file"
|
||||
error "Copy from template: cp ${env_file}.template $env_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for required tools
|
||||
local required_tools=("docker" "docker-compose" "ansible-playbook")
|
||||
for tool in "${required_tools[@]}"; do
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
error "Required tool not found: $tool"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
debug "Environment validation completed"
|
||||
}
|
||||
|
||||
# Validate environment configuration
|
||||
validate_configuration() {
|
||||
log "Validating environment configuration"
|
||||
|
||||
local env_file="${APPLICATIONS_DIR}/environments/.env.${DEPLOY_ENV}"
|
||||
|
||||
# Check for required placeholder values
|
||||
local required_placeholders=(
|
||||
"*** REQUIRED"
|
||||
)
|
||||
|
||||
for placeholder in "${required_placeholders[@]}"; do
|
||||
if grep -q "$placeholder" "$env_file"; then
|
||||
error "Environment file contains unfilled templates:"
|
||||
grep "$placeholder" "$env_file" || true
|
||||
if [ "$FORCE_DEPLOY" != true ]; then
|
||||
error "Fix configuration or use --force to proceed"
|
||||
exit 1
|
||||
else
|
||||
warn "Proceeding with incomplete configuration due to --force flag"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
debug "Configuration validation completed"
|
||||
}
|
||||
|
||||
# Run pre-deployment tests
|
||||
run_tests() {
|
||||
if [ "$SKIP_TESTS" = true ]; then
|
||||
warn "Skipping tests as requested"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Running pre-deployment tests"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# PHP tests
|
||||
if [[ -f "vendor/bin/pest" ]]; then
|
||||
log "Running PHP tests with Pest"
|
||||
./vendor/bin/pest --bail
|
||||
elif [[ -f "vendor/bin/phpunit" ]]; then
|
||||
log "Running PHP tests with PHPUnit"
|
||||
./vendor/bin/phpunit --stop-on-failure
|
||||
else
|
||||
warn "No PHP test framework found"
|
||||
fi
|
||||
|
||||
# JavaScript tests
|
||||
if [[ -f "package.json" ]] && command -v npm &> /dev/null; then
|
||||
log "Running JavaScript tests"
|
||||
npm test
|
||||
fi
|
||||
|
||||
# Code quality checks
|
||||
if [[ -f "composer.json" ]]; then
|
||||
log "Running code style checks"
|
||||
composer cs || {
|
||||
error "Code style checks failed"
|
||||
if [ "$FORCE_DEPLOY" != true ]; then
|
||||
exit 1
|
||||
else
|
||||
warn "Proceeding despite code style issues due to --force flag"
|
||||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
debug "Tests completed successfully"
|
||||
}
|
||||
|
||||
# Create database backup
|
||||
create_backup() {
|
||||
if [ "$SKIP_BACKUP" = true ]; then
|
||||
warn "Skipping database backup as requested"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Creating database backup for $DEPLOY_ENV"
|
||||
|
||||
local backup_dir="${PROJECT_ROOT}/storage/backups"
|
||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local backup_file="${backup_dir}/db_backup_${DEPLOY_ENV}_${timestamp}.sql"
|
||||
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
# Load environment variables
|
||||
set -a
|
||||
source "${APPLICATIONS_DIR}/environments/.env.${DEPLOY_ENV}"
|
||||
set +a
|
||||
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
# Create backup using the running database container
|
||||
docker-compose -f "${PROJECT_ROOT}/docker-compose.yml" \
|
||||
-f "${APPLICATIONS_DIR}/docker-compose.${DEPLOY_ENV}.yml" \
|
||||
exec -T db \
|
||||
mariadb-dump -u root -p"${DB_ROOT_PASSWORD}" --all-databases > "$backup_file"
|
||||
|
||||
log "Database backup created: $backup_file"
|
||||
else
|
||||
debug "DRY RUN: Would create backup at $backup_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Build application assets
|
||||
build_assets() {
|
||||
log "Building application assets"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
if [[ -f "package.json" ]] && command -v npm &> /dev/null; then
|
||||
log "Installing Node.js dependencies"
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
npm ci
|
||||
else
|
||||
debug "DRY RUN: Would run npm ci"
|
||||
fi
|
||||
|
||||
log "Building production assets"
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
npm run build
|
||||
else
|
||||
debug "DRY RUN: Would run npm run build"
|
||||
fi
|
||||
else
|
||||
warn "No package.json found, skipping asset build"
|
||||
fi
|
||||
|
||||
debug "Asset building completed"
|
||||
}
|
||||
|
||||
# Deploy infrastructure using Ansible
|
||||
deploy_infrastructure() {
|
||||
log "Deploying infrastructure with Ansible"
|
||||
|
||||
local ansible_dir="${DEPLOYMENT_DIR}/infrastructure"
|
||||
local inventory="${ansible_dir}/inventories/${DEPLOY_ENV}/hosts.yml"
|
||||
local site_playbook="${ansible_dir}/site.yml"
|
||||
|
||||
if [[ ! -f "$inventory" ]]; then
|
||||
warn "Ansible inventory not found: $inventory"
|
||||
warn "Skipping infrastructure deployment"
|
||||
return 0
|
||||
fi
|
||||
|
||||
cd "$ansible_dir"
|
||||
|
||||
# First run the main site playbook for infrastructure setup
|
||||
local ansible_cmd="ansible-playbook -i $inventory $site_playbook"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
ansible_cmd="$ansible_cmd --check"
|
||||
fi
|
||||
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
ansible_cmd="$ansible_cmd -v"
|
||||
fi
|
||||
|
||||
debug "Running infrastructure setup: $ansible_cmd"
|
||||
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
$ansible_cmd
|
||||
log "Infrastructure setup completed"
|
||||
else
|
||||
debug "DRY RUN: Would run Ansible infrastructure setup"
|
||||
fi
|
||||
}
|
||||
|
||||
# Deploy application using Ansible
|
||||
deploy_application() {
|
||||
log "Deploying application with Ansible"
|
||||
|
||||
local ansible_dir="${DEPLOYMENT_DIR}/infrastructure"
|
||||
local inventory="${ansible_dir}/inventories/${DEPLOY_ENV}/hosts.yml"
|
||||
local app_playbook="${ansible_dir}/playbooks/deploy-application.yml"
|
||||
|
||||
if [[ ! -f "$inventory" ]]; then
|
||||
warn "Ansible inventory not found: $inventory"
|
||||
warn "Skipping application deployment"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$app_playbook" ]]; then
|
||||
error "Application deployment playbook not found: $app_playbook"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$ansible_dir"
|
||||
|
||||
# Run the application deployment playbook with proper environment variable
|
||||
local ansible_cmd="ansible-playbook -i $inventory $app_playbook -e environment=$DEPLOY_ENV"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
ansible_cmd="$ansible_cmd --check"
|
||||
fi
|
||||
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
ansible_cmd="$ansible_cmd -v"
|
||||
fi
|
||||
|
||||
debug "Running application deployment: $ansible_cmd"
|
||||
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
$ansible_cmd
|
||||
log "Application deployment completed"
|
||||
else
|
||||
debug "DRY RUN: Would run Ansible application deployment"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run post-deployment tasks
|
||||
post_deployment() {
|
||||
log "Running post-deployment tasks"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
local compose_files="-f docker-compose.yml -f ${APPLICATIONS_DIR}/docker-compose.${DEPLOY_ENV}.yml"
|
||||
local env_file="--env-file ${APPLICATIONS_DIR}/environments/.env.${DEPLOY_ENV}"
|
||||
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
# Run database migrations
|
||||
log "Running database migrations"
|
||||
docker-compose $compose_files $env_file exec -T php php console.php db:migrate
|
||||
|
||||
# Clear application cache
|
||||
log "Clearing application cache"
|
||||
docker-compose $compose_files $env_file exec -T php php console.php cache:clear || true
|
||||
|
||||
# Warm up application
|
||||
log "Warming up application"
|
||||
sleep 5
|
||||
|
||||
# Run health checks
|
||||
"${SCRIPT_DIR}/health-check.sh" "$DEPLOY_ENV"
|
||||
|
||||
else
|
||||
debug "DRY RUN: Would run post-deployment tasks"
|
||||
fi
|
||||
|
||||
debug "Post-deployment tasks completed"
|
||||
}
|
||||
|
||||
# Main deployment function
|
||||
main() {
|
||||
log "Starting deployment to $DEPLOY_ENV environment"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY RUN MODE - No actual changes will be made"
|
||||
fi
|
||||
|
||||
# Deployment steps
|
||||
validate_environment
|
||||
validate_configuration
|
||||
run_tests
|
||||
create_backup
|
||||
build_assets
|
||||
deploy_infrastructure
|
||||
deploy_application
|
||||
post_deployment
|
||||
|
||||
log "Deployment to $DEPLOY_ENV completed successfully!"
|
||||
|
||||
if [ "$DEPLOY_ENV" = "production" ]; then
|
||||
log "Production deployment complete. Monitor application health and performance."
|
||||
else
|
||||
log "Staging deployment complete. Ready for testing."
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse arguments and run main function
|
||||
parse_arguments "$@"
|
||||
main
|
||||
Reference in New Issue
Block a user