diff --git a/Dockerfile b/Dockerfile index 1ed22e61..b66b377e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -121,6 +121,9 @@ COPY etc/configs /usr/local/configs COPY lib /usr/local/lib COPY bin/entrypoint.sh /usr/local/bin/entrypoint.sh +# Make all shell scripts executable +RUN chmod +x /usr/local/lib/*.sh /usr/local/bin/entrypoint.sh + # Set permissions during build RUN chmod +x /usr/local/bin/entrypoint.sh && \ chown -R ${UID}:${GID} /usr/local/configs && \ diff --git a/Makefile b/Makefile index 44843247..c3278fb8 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ clean: # Test Docker container test: clean @echo "Setting up test environment..." - @$(MAKE) run VOLUMES="$(TEST_WORKER_CONFIG):/home/$(USER)/worker.yaml:ro $(TESTS_TASKS_DIR):/home/$(USER)/tasks:ro $(TESTS_MAIN_SCRIPT):/home/$(USER)/main.sh:ro" COMMAND="/home/$(USER)/main.sh" + @$(MAKE) run VOLUMES="$(TEST_WORKER_CONFIG):/home/$(USER)/worker.yaml:ro $(TEST_SERVICES_CONFIG):/home/$(USER)/services.yaml:ro $(TESTS_TASKS_DIR):/home/$(USER)/tasks:ro $(TESTS_MAIN_SCRIPT):/home/$(USER)/main.sh:ro ./src/tests/utils.sh:/usr/local/tests/utils.sh:ro" COMMAND="/home/$(USER)/main.sh" @$(MAKE) log FOLLOW_LOGS=true @$(MAKE) clean diff --git a/Makefile.variables b/Makefile.variables index 39ec891c..cbe7253d 100644 --- a/Makefile.variables +++ b/Makefile.variables @@ -7,6 +7,7 @@ ENV_FILE ?= .env TESTS_MAIN_SCRIPT ?= ./src/tests/main.sh TESTS_TASKS_DIR ?= ./src/tests/tasks TEST_WORKER_CONFIG ?= ./src/tests/configs/worker.yaml +TEST_SERVICES_CONFIG ?= ./src/tests/configs/services.yaml USER = udx VOLUMES ?= ./src/scripts:/home/$(USER) DEBUG ?= false diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index 71aac06d..811c0812 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -3,6 +3,61 @@ # shellcheck disable=SC1091 source /usr/local/lib/utils.sh +# Global variable to track if shutdown is in progress +SHUTDOWN_IN_PROGRESS=0 + +# Signal handlers for graceful shutdown +handle_shutdown() { + local signal=$1 + + # Prevent multiple shutdown attempts + if [ "$SHUTDOWN_IN_PROGRESS" -eq 1 ]; then + log_info "Shutdown already in progress..." + return + fi + SHUTDOWN_IN_PROGRESS=1 + + log_info "⏹️ $signal received - initiating graceful shutdown..." + + # Stop supervisor itself gracefully + if [ -f /var/run/supervisord.pid ]; then + log_info "Stopping all supervisor services..." + supervisorctl stop all + + # Wait for services to stop (max 30 seconds) + local timeout=30 + local elapsed=0 + while [ $elapsed -lt $timeout ]; do + if ! supervisorctl status | grep -Eq 'RUNNING|STOPPING|STARTING'; then + log_info "All services stopped successfully" + break + fi + sleep 1 + elapsed=$((elapsed + 1)) + done + + if [ $elapsed -eq $timeout ]; then + log_error "Entrypoint" "❌ Timeout waiting for services to stop" + fi + + # Stop supervisord itself + log_info "Stopping supervisord..." + kill -TERM "$(cat /var/run/supervisord.pid)" + wait "$(cat /var/run/supervisord.pid)" 2>/dev/null || true + fi + + # Kill any remaining child processes + pkill -P $$ + + log_info "Shutdown complete" + exit 0 +} + +# Set up signal handlers +trap 'handle_shutdown SIGTERM' TERM +trap 'handle_shutdown SIGINT' INT +trap 'handle_shutdown SIGQUIT' QUIT + udx_logo log_info "Welcome to UDX Worker Container. Initializing environment..." @@ -47,18 +102,32 @@ wait_for_services() { return 1 } +# Initialize signal handlers and prepare environment +log_info "Initializing signal handlers for graceful shutdown..." + # Main execution path if [ "$#" -gt 0 ]; then log_info "Executing command: $*" if [[ "$1" =~ \.sh$ ]]; then - "$@" # Execute the provided command - log_info "Shell script execution completed. Exiting." - exit 0 + # Execute shell scripts in a subshell to maintain signal handling + ("$@") + exit_code=$? + log_info "Shell script execution completed with exit code $exit_code" + exit "$exit_code" else handle_services - "$@" # Execute the provided command + # Start the command in background and wait for it + "$@" & + command_pid=$! + wait "$command_pid" + exit_code=$? + exit "$exit_code" fi else handle_services + # Keep the script running and wait for signals + while [ "$SHUTDOWN_IN_PROGRESS" -eq 0 ]; do + sleep 1 + done fi \ No newline at end of file diff --git a/lib/auth.sh b/lib/auth.sh index f22f6839..d76ef66a 100644 --- a/lib/auth.sh +++ b/lib/auth.sh @@ -54,7 +54,7 @@ authenticate_actors() { log_info "Reading base64 encoded JSON credentials" creds=$(echo "$creds" | base64 --decode) else - log_error "Credentials format not recognized for $provider. Skipping..." + log_error "Authentication" "Credentials format not recognized for $provider. Skipping..." continue fi @@ -70,20 +70,20 @@ authenticate_actors() { if command -v "$auth_function" > /dev/null; then if ! authenticate_provider "$provider" "$auth_function" "$creds"; then - log_error "Authentication failed for provider $provider." + log_error "Authentication" "Authentication failed for provider $provider." return 1 fi configured_providers+=("$provider") else - log_error "Authentication function $auth_function not found for $provider. Skipping..." + log_error "Authentication" "Authentication function $auth_function not found for $provider. Skipping..." continue fi else - log_error "Authentication script $auth_script not found for $provider. Skipping..." + log_error "Authentication" "Authentication script $auth_script not found for $provider. Skipping..." continue fi else - log_error "Invalid JSON credentials for $provider. Skipping..." + log_error "Authentication" "Invalid JSON credentials for $provider. Skipping..." continue fi done diff --git a/lib/auth/aws.sh b/lib/auth/aws.sh index 46c5b29d..05b41592 100644 --- a/lib/auth/aws.sh +++ b/lib/auth/aws.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Example usage of the function # aws_authenticate "/path/to/your/aws_creds.json" @@ -12,7 +15,7 @@ aws_authenticate() { creds_content=$(cat "$creds_json") if [[ -z "$creds_content" ]]; then - echo "[ERROR] No AWS credentials provided." >&2 + log_error "AWS Authentication" "No AWS credentials provided." return 1 fi @@ -24,7 +27,7 @@ aws_authenticate() { sessionToken=$(echo "$creds_content" | jq -r '.SessionToken') if [[ -z "$accessKeyId" || -z "$secretAccessKey" ]]; then - echo "[ERROR] Missing required AWS credentials." >&2 + log_error "AWS Authentication" "Missing required AWS credentials." return 1 fi @@ -35,5 +38,5 @@ aws_authenticate() { export AWS_SESSION_TOKEN="$sessionToken" fi - echo "[INFO] AWS credentials set successfully." + log_success "AWS Authentication" "AWS credentials set successfully." } \ No newline at end of file diff --git a/lib/auth/azure.sh b/lib/auth/azure.sh index 9fe3ae71..7286fd7b 100644 --- a/lib/auth/azure.sh +++ b/lib/auth/azure.sh @@ -17,7 +17,7 @@ azure_authenticate() { creds_content=$(cat "$creds_json") if [[ -z "$creds_content" ]]; then - echo "[ERROR] No Azure credentials provided." >&2 + log_error "Azure Authentication" "No Azure credentials provided." return 1 fi @@ -30,20 +30,20 @@ azure_authenticate() { tenantId=$(echo "$creds_content" | jq -r '.tenantId') if [[ -z "$clientId" || -z "$clientSecret" || -z "$subscriptionId" || -z "$tenantId" ]]; then - echo "[ERROR] Missing required Azure credentials." >&2 + log_error "Azure Authentication" "Missing required Azure credentials." return 1 fi log_info "Authenticating Azure service principal..." if ! az login --service-principal -u "$clientId" -p "$clientSecret" --tenant "$tenantId" >/dev/null 2>&1; then - echo "[ERROR] Azure service principal authentication failed." >&2 + log_error "Azure Authentication" "Azure service principal authentication failed." return 1 fi if ! az account set --subscription "$subscriptionId" >/dev/null 2>&1; then - echo "[ERROR] Failed to set Azure subscription." >&2 + log_error "Azure Authentication" "Failed to set Azure subscription." return 1 fi - log_info "Azure service principal authenticated and subscription set." + log_success "Azure Authentication" "Azure service principal authenticated and subscription set." } diff --git a/lib/auth/bitwarden.sh b/lib/auth/bitwarden.sh index b9f7b838..57dbd7d6 100644 --- a/lib/auth/bitwarden.sh +++ b/lib/auth/bitwarden.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Function to authenticate Bitwarden using API key or master password # # Example usage of the function @@ -9,7 +12,7 @@ bitwarden_authenticate() { local creds_file="$1" if [[ ! -f "$creds_file" ]]; then - echo "[ERROR] Credentials file not found: $creds_file" >&2 + log_error "Bitwarden Authentication" "Credentials file not found: $creds_file" return 1 fi @@ -18,7 +21,7 @@ bitwarden_authenticate() { creds_content=$(cat "$creds_file") if [[ -z "$creds_content" ]]; then - echo "[ERROR] Credentials file is empty: $creds_file" >&2 + log_error "Bitwarden Authentication" "Credentials file is empty: $creds_file" return 1 fi @@ -28,7 +31,7 @@ bitwarden_authenticate() { master_password=$(echo "$creds_content" | jq -r '.masterPassword // empty') if [[ -z "$api_key" && -z "$master_password" ]]; then - echo "[ERROR] Either API key or master password must be provided in the credentials file." >&2 + log_error "Bitwarden Authentication" "Either API key or master password must be provided in the credentials file." return 1 fi @@ -40,18 +43,18 @@ bitwarden_authenticate() { local email email=$(echo "$creds_content" | jq -r '.email // empty') if [[ -z "$email" ]]; then - echo "[ERROR] Email must be provided with the master password." >&2 + log_error "Bitwarden Authentication" "Email must be provided with the master password." return 1 fi session_key=$(bw login "$email" "$master_password" --raw 2>/dev/null) fi if [[ -z "$session_key" ]]; then - echo "[ERROR] Failed to authenticate with Bitwarden." >&2 + log_error "Bitwarden Authentication" "Failed to authenticate with Bitwarden." return 1 fi - echo "[INFO] Bitwarden authentication successful. Session key obtained." + log_success "Bitwarden Authentication" "Authenticated with Bitwarden." export BW_SESSION="$session_key" return 0 } \ No newline at end of file diff --git a/lib/auth/gcp.sh b/lib/auth/gcp.sh index fbe6d9da..0299d3df 100644 --- a/lib/auth/gcp.sh +++ b/lib/auth/gcp.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Function to authenticate GCP service accounts # # Example usage of the function @@ -15,7 +18,7 @@ gcp_authenticate() { creds_content=$(cat "$creds_json") if [[ -z "$creds_content" ]]; then - echo "[ERROR] No GCP credentials provided." >&2 + log_error "GCP Authentication" "No GCP credentials provided." return 1 fi @@ -27,7 +30,7 @@ gcp_authenticate() { projectId=$(echo "$creds_content" | jq -r '.project_id') if [[ -z "$clientEmail" || -z "$privateKey" || -z "$projectId" ]]; then - echo "[ERROR] Missing required GCP credentials." >&2 + log_error "GCP Authentication" "Missing required GCP credentials." return 1 fi @@ -41,20 +44,20 @@ gcp_authenticate() { jq -n --arg clientEmail "$clientEmail" --arg privateKey "$privateKey" --arg projectId "$projectId" \ '{client_email: $clientEmail, private_key: $privateKey, project_id: $projectId}' > "$temp_creds_file" - echo "[INFO] Authenticating GCP service account..." + log_info "GCP Authentication" "Authenticating GCP service account..." if ! gcloud auth activate-service-account "$clientEmail" --key-file="$temp_creds_file" >/dev/null 2>&1; then - echo "[ERROR] GCP service account authentication failed." >&2 + log_error "GCP Authentication" "GCP service account authentication failed." rm -f "$temp_creds_file" return 1 fi if ! gcloud config set project "$projectId" >/dev/null 2>&1; then - echo "[ERROR] Failed to set GCP project." >&2 + log_error "GCP Authentication" "Failed to set GCP project." rm -f "$temp_creds_file" return 1 fi - echo "[INFO] GCP service account authenticated and project set." + log_success "GCP Authentication" "GCP service account authenticated and project set." # Clean up temporary credentials file rm -f "$temp_creds_file" diff --git a/lib/cleanup.sh b/lib/cleanup.sh index 2f0bab74..037fa0d9 100644 --- a/lib/cleanup.sh +++ b/lib/cleanup.sh @@ -1,11 +1,12 @@ #!/bin/bash -# Include utility functions and worker config utilities -# shellcheck source=/dev/null -source /usr/local/lib/utils.sh +# Include worker config utilities first # shellcheck source=/dev/null source /usr/local/lib/worker_config.sh +# shellcheck source=/dev/null +source /usr/local/lib/utils.sh + # Generic function to clean up authentication for any provider cleanup_provider() { local provider=$1 @@ -46,7 +47,7 @@ cleanup_provider() { if echo "$logout_output" | grep -q -E "No credentials available to revoke|No active sessions|No active accounts"; then log_info "No active $name credentials to revoke." else - log_error "Failed to log out of $name: $logout_output" + log_error "Cleanup" "Failed to log out of $name: $logout_output" return 1 fi else diff --git a/lib/cli.sh b/lib/cli.sh index 5839bcea..c9152153 100644 --- a/lib/cli.sh +++ b/lib/cli.sh @@ -6,6 +6,9 @@ for module in /usr/local/lib/cli/*.sh; do source "$module" done +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # CLI Interface case $1 in env) @@ -21,7 +24,7 @@ case $1 in service_handler "$@" ;; *) - echo "Usage: $0 {service|env|...}" + log_error "CLI" "Unknown command: $1" exit 1 ;; esac \ No newline at end of file diff --git a/lib/cli/env.sh b/lib/cli/env.sh index c3241495..ba6148b1 100644 --- a/lib/cli/env.sh +++ b/lib/cli/env.sh @@ -1,23 +1,25 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Function to display current environment settings or a specific variable get_environment() { if [ $# -eq 0 ]; then - echo "Current Environment Settings:" - env + log_success "Current Environment Settings:" "$(env)" else - echo "$1=${!1}" + log_success "$1" "${!1}" fi } # Function to set a new environment variable set_environment() { if [ $# -ne 2 ]; then - echo "Usage: $0 env set " + log_warn "CLI" "Usage: $0 env set " return 1 fi export "$1=$2" - echo "Set $1 to '$2'." + log_success "CLI" "Set $1 to '$2'." } # Handle environment commands @@ -32,12 +34,12 @@ env_handler() { if [ $# -eq 2 ]; then set_environment "$@" else - echo "Usage: $0 env set " + log_warn "CLI" "Usage: $0 env set " exit 1 fi ;; *) - echo "Usage: $0 env {show [VARIABLE_NAME]|set }" + log_warn "CLI" "Usage: $0 env {show [VARIABLE_NAME]|set }" exit 1 ;; esac diff --git a/lib/cli/sbom.sh b/lib/cli/sbom.sh index 566b752c..b938e2c1 100644 --- a/lib/cli/sbom.sh +++ b/lib/cli/sbom.sh @@ -1,12 +1,16 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Function to display dpkg packages in a table format using awk generate_sbom() { - echo "Package Name | Version | Architecture" - echo "----------------------------------------------------------" + log_success "Package Name | Version | Architecture" + log_success "----------------------------------------------------------" dpkg-query -W -f='${binary:Package} | ${Version} | ${Architecture}\n' | awk -F'|' '{ printf("%-30s | %-20s | %-10s\n", $1, $2, $3) }' + log_success "----------------------------------------------------------" } # Handler for the sbom command @@ -17,7 +21,7 @@ sbom_handler() { generate_sbom "$@" ;; *) - echo "Usage: $0 sbom show" + log_warn "CLI" "Usage: $0 sbom {generate}" exit 1 ;; esac diff --git a/lib/cli/service.sh b/lib/cli/service.sh index 3f565ac8..af762b9d 100644 --- a/lib/cli/service.sh +++ b/lib/cli/service.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + service_handler() { case $1 in list) @@ -21,7 +24,7 @@ service_handler() { manage_service "$1" "$2" ;; *) - echo "Usage: $0 {list|status|logs|config|start|stop|restart}" + log_warn "CLI" "Usage: $0 {list|status|logs|config|start|stop|restart}" exit 1 ;; esac @@ -35,14 +38,14 @@ list_services() { # Check if Supervisor is not running, not accessible, or if there are no managed services if [[ -z "$services_status" ]] || echo "$services_status" | grep -Eq 'no such|ERROR'; then - echo "No services are currently managed." + log_warn "Service" "No services are currently managed." exit 1 fi - echo "Listing all managed services:" + log_success "Service" "Listing all managed services:" local i=1 echo "$services_status" | while read -r line; do - echo "$i. $line" + log_success "Service" "$i. $line" ((i++)) done } @@ -51,8 +54,8 @@ list_services() { check_status() { # Require a service name for this function if [ -z "$1" ]; then - echo "Error: No service name provided." - echo "Usage: $0 status " + log_warn "Service" "Error: No service name provided." + log_warn "Service" "Usage: $0 status " exit 1 fi @@ -62,7 +65,7 @@ check_status() { # Check if Supervisor is not running, not accessible, or if the service does not exist if [[ -z "$service_status" ]] || echo "$service_status" | grep -Eq 'no such|ERROR'; then - echo "The service '$1' does not exist." + log_warn "Service" "The service '$1' does not exist." exit 1 fi @@ -83,7 +86,7 @@ follow_logs() { logfile="$logfile.$type.log" if [ ! -f "$logfile" ]; then - echo "Log file does not exist: $logfile" + log_error "Service" "Log file does not exist: $logfile" exit 1 fi @@ -93,7 +96,7 @@ follow_logs() { # Function to show supervisor configuration show_config() { if [ ! -f "/etc/supervisord.conf" ]; then - echo "Configuration file is not generated since no services are managed." + log_error "Service" "Configuration file is not generated since no services are managed." exit 1 fi cat /etc/supervisord.conf @@ -102,13 +105,13 @@ show_config() { # Function to start, stop, or restart a service manage_service() { if [ -z "$2" ]; then - echo "Error: No service name provided." - echo "Usage: $0 $1 " + log_error "Service" "Error: No service name provided." + log_warn "Service" "Usage: $0 $1 " exit 1 fi if [ ! -e "/var/run/supervisor/supervisord.sock" ]; then - echo "Error: Service doesn't exist." + log_error "Service" "Error: Service doesn't exist." exit 1 fi diff --git a/lib/environment.sh b/lib/environment.sh index bd2b8e6c..caf97e28 100644 --- a/lib/environment.sh +++ b/lib/environment.sh @@ -1,33 +1,19 @@ #!/bin/bash -# Get the directory of this script -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Include necessary modules +# shellcheck disable=SC1091 +source /usr/local/lib/auth.sh +# shellcheck disable=SC1091 +source /usr/local/lib/secrets.sh +# shellcheck disable=SC1091 +source /usr/local/lib/cleanup.sh +# shellcheck disable=SC1091 +source /usr/local/lib/process_manager.sh +# shellcheck disable=SC1091 +source /usr/local/lib/worker_config.sh -# Source a file if it exists -source_if_exists() { - local file_path="$1" - if [[ -f "$file_path" ]]; then - # shellcheck disable=SC1090 - source "$file_path" - else - echo "[ERROR] Missing file: $file_path" >&2 - exit 1 - fi -} - -# Include necessary modules from the same directory -# shellcheck source=./utils.sh -source_if_exists "$SCRIPT_DIR/utils.sh" -# shellcheck source=./auth.sh -source_if_exists "$SCRIPT_DIR/auth.sh" -# shellcheck source=./secrets.sh -source_if_exists "$SCRIPT_DIR/secrets.sh" -# shellcheck source=./cleanup.sh -source_if_exists "$SCRIPT_DIR/cleanup.sh" -# shellcheck source=./process_manager.sh -source_if_exists "$SCRIPT_DIR/process_manager.sh" -# shellcheck source=./worker_config.sh -source_if_exists "$SCRIPT_DIR/worker_config.sh" +# shellcheck disable=SC1091 +source /usr/local/lib/utils.sh # Main function to coordinate environment setup configure_environment() { @@ -37,13 +23,13 @@ configure_environment() { local resolved_config resolved_config=$(load_and_parse_config) if [[ -z "$resolved_config" ]]; then - log_error "Configuration loading failed. Exiting..." + log_error "Environment" "Configuration loading failed. Exiting..." return 1 fi # Export variables from the configuration if ! export_variables_from_config "$resolved_config"; then - log_error "Failed to export variables." + log_error "Environment" "Failed to export variables." return 1 fi @@ -53,7 +39,7 @@ configure_environment() { if [[ $? -eq 0 && -n "$actors" ]]; then log_info "Authenticating actors from configuration..." if ! authenticate_actors "$actors"; then - log_error "Failed to authenticate actors." + log_error "Environment" "Failed to authenticate actors." return 1 fi else @@ -66,7 +52,7 @@ configure_environment() { if [[ $? -eq 0 && -n "$secrets" ]]; then log_info "Fetching secrets from configuration..." if ! fetch_secrets "$secrets"; then - log_error "Failed to fetch secrets." + log_error "Environment" "Failed to fetch secrets." return 1 fi else @@ -76,7 +62,7 @@ configure_environment() { # Perform cleanup log_info "Cleaning up sensitive data..." if ! cleanup_actors; then - log_error "Failed to clean up actors." + log_error "Environment" "Failed to clean up actors." return 1 fi diff --git a/lib/process_manager.sh b/lib/process_manager.sh index a56c2a9d..46a29558 100644 --- a/lib/process_manager.sh +++ b/lib/process_manager.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Define paths DEFAULT_CONFIG_FILE="/usr/local/configs/worker/services.yaml" # Define the user-specific configuration path search @@ -51,13 +54,13 @@ parse_service_info() { # Check if 'name' is set, log error and return if not if [ -z "$name" ]; then - echo "Error: 'name' not set for a service. Skipping..." + log_error "Process Manager" "Error: 'name' not set for service $name. Skipping..." return fi # Check if 'command' is set, log error and return if not if [ -z "$command" ]; then - echo "Error: 'command' not set for service $name. Skipping..." + log_error "Process Manager" "Error: 'command' not set for service $name. Skipping..." return fi @@ -79,7 +82,7 @@ start_supervisor() { # Function to configure and start the Supervisor configure_and_execute_services() { if ! should_generate_config; then - echo "No services found in $CONFIG_FILE. No Supervisor configuration generated." + log_warn "Process Manager" "No services found in $CONFIG_FILE. No Supervisor configuration generated." return 1 fi @@ -91,7 +94,7 @@ configure_and_execute_services() { services_yaml=$(yq e -o=json '.services[] | select(.ignore != true)' "$CONFIG_FILE" | jq -c .) if [ -z "$services_yaml" ]; then - echo "Failed to parse services from $CONFIG_FILE or no services defined." + log_error "Process Manager" "Failed to parse services from $CONFIG_FILE or no services defined." return 1 fi diff --git a/lib/secrets.sh b/lib/secrets.sh index fdd860ad..f73a7e36 100644 --- a/lib/secrets.sh +++ b/lib/secrets.sh @@ -1,6 +1,5 @@ #!/bin/bash -# Include utility functions and worker config utilities # shellcheck source=/usr/local/lib/utils.sh disable=SC1091 source /usr/local/lib/utils.sh @@ -32,7 +31,7 @@ fetch_secrets() { # Confirm secrets_json is valid JSON before processing if ! echo "$secrets_json" | jq empty > /dev/null 2>&1; then - log_error "Invalid JSON format for secrets configuration." + log_error "Secrets" "Invalid JSON format for secrets configuration." return 1 fi @@ -49,7 +48,7 @@ fetch_secrets() { # Check if the secret has a valid name and URL if [[ -z "$name" || -z "$url" ]]; then - log_error "Secret name or URL is missing or empty." + log_error "Secrets" "Secret name or URL is missing or empty." continue fi @@ -62,7 +61,7 @@ fetch_secrets() { key_vault_name=$(echo "$url" | cut -d '/' -f 2) secret_name=$(echo "$url" | cut -d '/' -f 3) if [[ -z "$secret_name" ]]; then - log_error "Invalid GCP secret name format: $url" + log_error "Secrets" "Invalid GCP secret name format: $url" continue fi ;; @@ -70,7 +69,7 @@ fetch_secrets() { key_vault_name=$(echo "$url" | cut -d '/' -f 2) secret_name=$(echo "$url" | cut -d '/' -f 3) if [[ -z "$key_vault_name" || -z "$secret_name" ]]; then - log_error "Invalid secret format for $provider: $url" + log_error "Secrets" "Invalid secret format for $provider: $url" continue fi ;; @@ -95,9 +94,9 @@ fetch_secrets() { # Export the secret as an environment variable if [[ -n "$value" ]]; then echo "export $name=\"$value\"" >> "$secrets_env_file" - log_info "Resolved secret for $name from $provider." + log_success "Resolved secret for $name from $provider." else - log_error "Failed to resolve secret for $name from $provider." + log_error "Secrets" "Failed to resolve secret for $name from $provider." fi done @@ -109,7 +108,7 @@ fetch_secrets() { set +a log_info "Secrets environment variables sourced successfully." else - log_error "No secrets were written to the environment file." + log_error "Secrets" "No secrets were written to the environment file." return 1 fi diff --git a/lib/secrets/aws.sh b/lib/secrets/aws.sh index b89fc012..3a34d1d7 100644 --- a/lib/secrets/aws.sh +++ b/lib/secrets/aws.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Function to resolve AWS secret resolve_aws_secret() { local secret_name="$1" @@ -7,20 +10,20 @@ resolve_aws_secret() { local secret_value if [[ -z "$secret_name" || -z "$region" ]]; then - echo "[ERROR] Invalid AWS secret name or region" >&2 + log_error "AWS" "Invalid AWS secret name or region" return 1 fi # Retrieve the secret value using AWS CLI with detailed logging - echo "[INFO] Retrieving secret from AWS Secrets Manager: secret_name=$secret_name, region=$region" >&2 + log_info "AWS" "Retrieving secret from AWS Secrets Manager: secret_name=$secret_name, region=$region" if ! secret_value=$(aws secretsmanager get-secret-value --secret-id "$secret_name" --query 'SecretString' --output text --region "$region" 2>&1); then - echo "[ERROR] Failed to retrieve secret from AWS Secrets Manager: secret_name=$secret_name, region=$region" >&2 - echo "[DEBUG] AWS CLI output: $secret_value" >&2 + log_error "AWS" "Failed to retrieve secret from AWS Secrets Manager: secret_name=$secret_name, region=$region" + log_error "AWS" "AWS CLI output: $secret_value" return 1 fi if [ -z "$secret_value" ]; then - echo "[ERROR] Secret value is empty for $secret_name in region $region" >&2 + log_error "AWS" "Secret value is empty for $secret_name in region $region" return 1 fi diff --git a/lib/secrets/azure.sh b/lib/secrets/azure.sh index d58bee41..4289d357 100644 --- a/lib/secrets/azure.sh +++ b/lib/secrets/azure.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Function to resolve Azure secret resolve_azure_secret() { local key_vault_name="$1" @@ -7,20 +10,20 @@ resolve_azure_secret() { local secret_value if [ -z "$key_vault_name" ] || [ -z "$secret_name" ]; then - echo "[ERROR] Invalid Azure Key Vault name or secret name" >&2 + log_error "Azure" "Invalid Azure Key Vault name or secret name" return 1 fi # Retrieve the secret value using Azure CLI with detailed logging - echo "[INFO] Retrieving secret from Azure Key Vault: vault_name=$key_vault_name, secret_name=$secret_name" >&2 + log_info "Azure" "Retrieving secret from Azure Key Vault: vault_name=$key_vault_name, secret_name=$secret_name" if ! secret_value=$(az keyvault secret show --vault-name "$key_vault_name" --name "$secret_name" --query value -o tsv 2>&1); then - echo "[ERROR] Failed to retrieve secret from Azure Key Vault: vault_name=$key_vault_name, secret_name=$secret_name" >&2 - echo "[DEBUG] Azure CLI output: $secret_value" >&2 + log_error "Azure" "Failed to retrieve secret from Azure Key Vault: vault_name=$key_vault_name, secret_name=$secret_name" + log_error "Azure" "Azure CLI output: $secret_value" return 1 fi if [ -z "$secret_value" ]; then - echo "[ERROR] Secret value is empty for $key_vault_name/$secret_name" >&2 + log_error "Azure" "Secret value is empty for $key_vault_name/$secret_name" return 1 fi diff --git a/lib/secrets/bitwarden.sh b/lib/secrets/bitwarden.sh index f6665568..6075aaf0 100644 --- a/lib/secrets/bitwarden.sh +++ b/lib/secrets/bitwarden.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Function to resolve Bitwarden secret resolve_bitwarden_secret() { local collection_name="$1" @@ -7,20 +10,20 @@ resolve_bitwarden_secret() { local secret_value if [[ -z "$collection_name" || -z "$secret_name" ]]; then - echo "[ERROR] Invalid Bitwarden collection name or secret name" >&2 + log_error "Bitwarden" "Invalid Bitwarden collection name or secret name" return 1 fi # Retrieve the secret using Bitwarden CLI - echo "[INFO] Retrieving secret from Bitwarden: collection_name=$collection_name, secret_name=$secret_name" >&2 + log_info "Bitwarden" "Retrieving secret from Bitwarden: collection_name=$collection_name, secret_name=$secret_name" if ! secret_value=$(bw get item "$secret_name" --organizationid "$collection_name" --output text 2>&1); then - echo "[ERROR] Failed to retrieve secret from Bitwarden: collection_name=$collection_name, secret_name=$secret_name" >&2 - echo "[DEBUG] Bitwarden CLI output: $secret_value" >&2 + log_error "Bitwarden" "Failed to retrieve secret from Bitwarden: collection_name=$collection_name, secret_name=$secret_name" + log_error "Bitwarden" "Bitwarden CLI output: $secret_value" return 1 fi if [ -z "$secret_value" ]; then - echo "[ERROR] Secret value is empty for $secret_name in collection $collection_name" >&2 + log_error "Bitwarden" "Secret value is empty for $secret_name in collection $collection_name" return 1 fi diff --git a/lib/secrets/gcp.sh b/lib/secrets/gcp.sh index fdc9dae1..2edff3d2 100644 --- a/lib/secrets/gcp.sh +++ b/lib/secrets/gcp.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Function to resolve GCP secret resolve_gcp_secret() { local project_id="$1" @@ -8,7 +11,7 @@ resolve_gcp_secret() { # Validate input arguments if [[ -z "$project_id" || -z "$secret_name" ]]; then - log_error "Invalid GCP project ID or secret name. project_id: $project_id, secret_name: $secret_name" + log_error "GCP Secret Manager" "Invalid GCP project ID or secret name. project_id: $project_id, secret_name: $secret_name" return 1 fi @@ -16,14 +19,14 @@ resolve_gcp_secret() { # Retrieve the latest version of the secret if ! secret_value=$(gcloud secrets versions access latest --secret="$secret_name" --project="$project_id" 2>/tmp/gcp_secret_error.log); then - log_error "Failed to retrieve secret from GCP Secret Manager for secret: $secret_name" + log_error "GCP Secret Manager" "Failed to retrieve secret from GCP Secret Manager for secret: $secret_name" log_error "GCP CLI output: $(cat /tmp/gcp_secret_error.log)" return 1 fi # Check if the secret value is empty if [[ -z "$secret_value" ]]; then - log_error "Secret value is empty for secret: $secret_name in project: $project_id" + log_error "GCP Secret Manager" "Secret value is empty for secret: $secret_name in project: $project_id" return 1 fi diff --git a/lib/utils.sh b/lib/utils.sh old mode 100644 new mode 100755 index 37cd943f..21e6fa29 --- a/lib/utils.sh +++ b/lib/utils.sh @@ -1,34 +1,32 @@ #!/bin/bash -# Prevent the script from being sourced multiple times -if [ -z "${UTILS_SH_INCLUDED+x}" ]; then - UTILS_SH_INCLUDED=true - - # Function to print the UDX logo - udx_logo() { - cat /etc/logo.txt - } - - # Simple logging functions - log_info() { - echo "[INFO] $1" >&2 # Send to stderr - } - - log_error() { - echo "[ERROR] $1" >&2 # Send to stderr - } - - log_warn() { - echo "[WARN] $1" >&2 # Send to stderr - } - - log_debug() { - echo "[DEBUG] $1" >&2 # Send to stderr - } - - # Function to resolve placeholders with environment variables - resolve_env_vars() { - local value="$1" - eval echo "$value" - } -fi +# Function to resolve placeholders with environment variables +resolve_env_vars() { + local value="$1" + eval echo "$value" +} + +# Logging functions with direct ANSI sequences +log_info() { + if [ $# -eq 1 ]; then + printf "\033[0;34m[INFO] %s\033[0m\n" "$1" >&2 + else + printf "\033[0;34m[INFO] %s: %s\033[0m\n" "$1" "$2" >&2 + fi +} + +log_warn() { + printf "\033[1;33m[WARN] %s: %s\033[0m\n" "$1" "$2" >&2 +} + +log_error() { + printf "\033[0;31m[ERROR] %s: %s\033[0m\n" "$1" "$2" >&2 +} + +log_success() { + printf "\033[0;32m[SUCCESS] %s: %s\033[0m\n" "$1" "$2" >&2 +} + +udx_logo() { + printf "\033[0;34m%s\033[0m\n" "$(cat /etc/logo.txt)" +} \ No newline at end of file diff --git a/lib/worker_config.sh b/lib/worker_config.sh index ea24cfd3..fba37921 100644 --- a/lib/worker_config.sh +++ b/lib/worker_config.sh @@ -1,5 +1,8 @@ #!/bin/bash +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + # Paths for configurations BUILT_IN_CONFIG="/usr/local/configs/worker/default.yaml" # Dynamically find user configuration in any subfolder of $HOME @@ -7,18 +10,9 @@ BUILT_IN_CONFIG="/usr/local/configs/worker/default.yaml" USER_CONFIG=$(find "$HOME" -name 'worker.yaml' 2>/dev/null -print | head -n 1) MERGED_CONFIG="/usr/local/configs/worker/merged_worker.yaml" -# Utility functions for logging -log_info() { - echo "[INFO] $1" >&2 -} - -log_error() { - echo "[ERROR] $1" >&2 -} - # Ensure `yq` is available if ! command -v yq >/dev/null 2>&1; then - log_error "yq is not installed. Please ensure it is available in the PATH." + log_error "Worker configuration" "yq is not installed. Please ensure it is available in the PATH." exit 1 fi @@ -26,7 +20,7 @@ fi ensure_config_exists() { local config_path="$1" if [[ ! -s "$config_path" ]]; then - log_error "Configuration file not found or empty: $config_path" + log_error "Worker configuration" "Configuration file not found or empty: $config_path" return 1 fi } @@ -35,7 +29,7 @@ ensure_config_exists() { merge_worker_configs() { # Ensure the merged configuration file exists if [ ! -f "$MERGED_CONFIG" ]; then - touch "$MERGED_CONFIG" || { log_error "Failed to create merged configuration file at $MERGED_CONFIG"; return 1; } + touch "$MERGED_CONFIG" || { log_error "Worker configuration" "Failed to create merged configuration file at $MERGED_CONFIG"; return 1; } fi # Ensure built-in config exists @@ -46,7 +40,7 @@ merge_worker_configs() { log_info "User configuration detected at $USER_CONFIG" if ! yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' "$BUILT_IN_CONFIG" "$USER_CONFIG" > "$MERGED_CONFIG"; then - log_error "Failed to merge configurations. yq returned an error." + log_error "Worker configuration" "Failed to merge configurations. yq returned an error." return 1 fi else @@ -54,7 +48,7 @@ merge_worker_configs() { # Copy the built-in configuration to the merged configuration if ! cp "$BUILT_IN_CONFIG" "$MERGED_CONFIG"; then - log_error "Failed to copy built-in configuration to merged configuration." + log_error "Worker configuration" "Failed to copy built-in configuration to merged configuration." return 1 fi fi @@ -67,7 +61,7 @@ load_and_parse_config() { # Parse the merged configuration into JSON local json_output if ! json_output=$(yq eval -o=json "$MERGED_CONFIG" 2>/dev/null); then - log_error "Failed to parse merged YAML from $MERGED_CONFIG. yq returned an error." + log_error "Worker configuration" "Failed to parse merged YAML from $MERGED_CONFIG. yq returned an error." return 1 fi @@ -100,14 +94,14 @@ get_config_section() { local section="$2" if [[ -z "$config_json" ]]; then - log_error "Empty configuration JSON provided." + log_error "Worker configuration" "Empty configuration JSON provided." return 1 fi # Attempt to extract the section and handle missing/null cases local extracted_section if ! extracted_section=$(echo "$config_json" | jq -r ".config.${section} // empty" 2>/dev/null); then - log_error "Failed to parse section '${section}' from configuration." + log_error "Worker configuration" "Failed to parse section '${section}' from configuration." return 1 fi diff --git a/src/tests/configs/services.yaml b/src/tests/configs/services.yaml new file mode 100644 index 00000000..84cf2b47 --- /dev/null +++ b/src/tests/configs/services.yaml @@ -0,0 +1,10 @@ +--- +kind: workerService +version: udx.io/worker-v1/service +services: + - name: "test_service" + command: "bash -c 'while true; do echo \"Service running...\"; sleep 1; trap \"echo Starting cleanup...; sleep 5; echo Cleanup completed; exit 0\" TERM; done'" + autostart: "false" + autorestart: "false" + stopsignal: "TERM" + stopwaitsecs: "10" \ No newline at end of file diff --git a/src/tests/main.sh b/src/tests/main.sh index 74ef5af9..870bae99 100755 --- a/src/tests/main.sh +++ b/src/tests/main.sh @@ -1,11 +1,19 @@ #!/bin/bash -echo "Running all tests..." +# Source utils.sh for logging functions +source /usr/local/lib/utils.sh -# Find and execute all test scripts in the /usr/local/tests directory +log_info "Main" "Running all test suites" + +# Find and execute all test scripts in the tasks directory for test_script in ./tasks/*.sh; do - echo "Running $(basename "$test_script")..." - bash "$test_script" + log_info "Running $(basename "$test_script")..." + if bash "$test_script"; then + log_success "$(basename "$test_script")" "Test completed successfully" + else + log_error "$(basename "$test_script")" "Test failed" + exit 1 + fi done -echo "All tests completed." \ No newline at end of file +log_success "Main" "All test suites completed successfully" \ No newline at end of file diff --git a/src/tests/tasks/10_dependencies.sh b/src/tests/tasks/10_dependencies.sh index 5cd03221..ca2b3578 100755 --- a/src/tests/tasks/10_dependencies.sh +++ b/src/tests/tasks/10_dependencies.sh @@ -1,28 +1,34 @@ #!/bin/bash -echo "Starting validation of dependencies..." +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + +log_info "Dependencies: Validating required dependencies and their versions" # Function to test if a command is available and show its version check_command() { local cmd_path cmd_path=$(command -v "$1") if [ -x "$cmd_path" ]; then - echo "[INFO] $1 is installed at $cmd_path." + log_info "$1 is installed at $cmd_path" local version version=$($1 --version 2>&1 | head -n 1) - echo "[INFO] $1 version: $version" + log_success "$1" "Version: $version" else - echo "[ERROR] $1 is not installed or not in PATH." + log_error "$1" "Not installed or not in PATH" exit 1 fi } # Verify gcloud, aws, az, bw, yq, and jq commands are available +log_info "Checking cloud provider CLIs..." check_command gcloud check_command aws check_command az + +log_info "Checking utility tools..." check_command bw check_command yq check_command jq -echo "Dependencies tests passed successfully." \ No newline at end of file +log_success "Dependencies" "All dependencies validated successfully" \ No newline at end of file diff --git a/src/tests/tasks/20_config.sh b/src/tests/tasks/20_config.sh index 4a39eabf..8b80d78d 100644 --- a/src/tests/tasks/20_config.sh +++ b/src/tests/tasks/20_config.sh @@ -1,13 +1,16 @@ #!/bin/bash -echo "Starting validation of config..." +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + +log_info "Starting validation of config..." # Path to the merged configuration file MERGED_CONFIG="/usr/local/configs/worker/merged_worker.yaml" # Test configure_environment function test_configure_environment() { - echo "Running test: configure_environment" + log_info "Running test: configure_environment" # Load the merged configuration merged_config=$(cat "$MERGED_CONFIG") @@ -26,20 +29,21 @@ test_configure_environment() { actual_value="${!key}" if [[ "$actual_value" != "$value" ]]; then - echo "Test failed: $key is not set correctly. Expected: $value, Got: $actual_value" + log_error "Config" "$key is not set correctly. Expected: $value, Got: $actual_value" return 1 else - echo "Test passed: $key is set correctly." + log_success "Config" "$key is set correctly" fi done - echo "Test passed: configure_environment" + log_success "Config" "configure_environment test passed" + return 0 } # Run the test if test_configure_environment; then - echo "Config tests passed successfully." + log_success "Config" "All config tests passed successfully" else - echo "Config tests failed." + log_error "Config" "Config tests failed" exit 1 fi \ No newline at end of file diff --git a/src/tests/tasks/30_secrets.sh b/src/tests/tasks/30_secrets.sh index a94a088e..acbbd826 100644 --- a/src/tests/tasks/30_secrets.sh +++ b/src/tests/tasks/30_secrets.sh @@ -1,13 +1,16 @@ #!/bin/bash -echo "Starting validation of secrets fetching..." +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + +log_info "Starting validation of secrets fetching..." # Path to the merged configuration file MERGED_CONFIG="/usr/local/configs/worker/merged_worker.yaml" # Test verify_secrets function test_verify_secrets() { - echo "Running test: verify_secrets" + log_info "Running test: verify_secrets" # Load the merged configuration merged_config=$(cat "$MERGED_CONFIG") @@ -17,7 +20,7 @@ test_verify_secrets() { # Check if secrets is empty or null if [[ -z "$secrets" || "$secrets" == "null" ]]; then - echo "Info: No secrets found in the configuration." + log_info "No secrets found in the configuration." return 0 fi @@ -34,27 +37,24 @@ test_verify_secrets() { # Verify that the environment variable is set and different from the reference if [[ -z "$actual_value" || "$actual_value" == "$expected_reference" ]]; then - echo "Test failed: $secret_key is not replaced correctly. Got: $actual_value" + log_error "Secrets" "$secret_key is not replaced correctly. Got: $actual_value" return 1 else - echo "Test passed: $secret_key is resolved correctly." + log_success "Secrets" "$secret_key is resolved correctly" fi done - echo "Test passed: verify_secrets" + log_success "Secrets" "verify_secrets test passed" + return 0 } # Run the test if test_verify_secrets; then - echo "Secrets fetching tests passed successfully." + log_success "Secrets" "All secrets fetching tests passed successfully" else - echo "Secrets fetching tests failed." + log_error "Secrets" "Secrets fetching tests failed" exit 1 fi - -# Run the test -if test_verify_secrets; then - echo "Secrets fetching tests passed successfully." else echo "Secrets fetching tests failed." exit 1 diff --git a/src/tests/tasks/40_services.sh b/src/tests/tasks/40_services.sh new file mode 100644 index 00000000..a3081147 --- /dev/null +++ b/src/tests/tasks/40_services.sh @@ -0,0 +1,167 @@ +#!/bin/bash + +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + +# Test service management functionality +test_service_management() { + local test_name="Service Management" + + log_info "$test_name: Test supervisor service management functionality" + + # Test 1: Service Configuration + log_info "Testing service configuration loading..." + if ! supervisorctl status | grep -q "test_service"; then + log_error "$test_name" "test_service not found in supervisor configuration" + return 1 + fi + + # Test 2: Service Start + log_info "Testing service start..." + supervisorctl start test_service + sleep 2 + if ! supervisorctl status test_service | grep -q "RUNNING"; then + log_error "$test_name" "Failed to start test_service" + return 1 + fi + log_success "$test_name" "Service started successfully" + + # Test 3: Service Status + log_info "Testing service status..." + if ! supervisorctl status test_service | grep -q "RUNNING"; then + log_error "$test_name" "Service status check failed" + return 1 + fi + log_success "$test_name" "Service status check passed" + + # Test 4: Service Stop + log_info "Testing service stop..." + supervisorctl stop test_service + sleep 7 # Allow time for graceful shutdown + if supervisorctl status test_service | grep -q "RUNNING"; then + log_error "$test_name" "Failed to stop test_service" + return 1 + fi + log_success "$test_name" "Service stopped successfully" + + # Test 5: Service Restart + log_info "Testing service restart..." + supervisorctl start test_service + sleep 2 + supervisorctl restart test_service + sleep 7 # Allow time for stop and start + if ! supervisorctl status test_service | grep -q "RUNNING"; then + log_error "$test_name" "Failed to restart test_service" + return 1 + fi + log_success "$test_name" "Service restarted successfully" + + # Test 6: Check Service Logs + log_info "Testing service logging..." + if ! grep -q "Service running..." /var/log/supervisor/test_service.out.log; then + log_error "$test_name" "Service logs not found or incorrect" + return 1 + fi + log_success "$test_name" "Service logs verified successfully" + + # Test 7: Graceful Shutdown + log_info "Testing graceful shutdown..." + supervisorctl stop test_service + sleep 1 + + # Check for cleanup initiation + if ! grep -q "Starting cleanup..." /var/log/supervisor/test_service.out.log; then + log_error "$test_name" "Service did not initiate cleanup on stop" + return 1 + fi + log_success "$test_name" "Service cleanup initiated successfully" + + # Wait for cleanup to complete + sleep 6 + if ! grep -q "Cleanup completed" /var/log/supervisor/test_service.out.log; then + log_error "$test_name" "Service did not complete cleanup" + return 1 + fi + log_success "$test_name" "Service cleanup completed successfully" + + # Test 8: Multiple Services + log_info "Testing multiple services..." + # Start both test and app services + supervisorctl start all + sleep 2 + + # Check if both services are running + if ! supervisorctl status | grep -q "RUNNING" | wc -l | grep -q "2"; then + log_error "$test_name" "Failed to run multiple services" + return 1 + fi + log_success "$test_name" "Multiple services running successfully" + + # Stop all services + supervisorctl stop all + sleep 7 + + # Verify all stopped + if supervisorctl status | grep -q "RUNNING"; then + log_error "$test_name" "Failed to stop all services" + return 1 + fi + log_success "$test_name" "All services stopped successfully" + + log_success "$test_name" "All service management tests passed" + return 0 +} + +# Test service configuration +test_service_config() { + local test_name="Service Configuration" + + log_info "$test_name: Test service configuration parsing and validation" + + # Test 1: Check service.yaml parsing + log_info "Testing services.yaml parsing..." + if [ ! -f "/home/udx/services.yaml" ]; then + log_error "$test_name" "services.yaml not found" + return 1 + fi + log_success "$test_name" "services.yaml found and accessible" + + # Test 2: Verify service properties + log_info "Testing service properties..." + local service_conf="/etc/supervisor/conf.d/test_service.conf" + if [ ! -f "$service_conf" ]; then + log_error "$test_name" "Service configuration not generated" + return 1 + fi + log_success "$test_name" "Service configuration file exists" + + # Check required properties + if ! grep -q "program:test_service" "$service_conf"; then + log_error "$test_name" "Invalid service name configuration" + return 1 + fi + log_success "$test_name" "Service name configured correctly" + + if ! grep -q "stopsignal=TERM" "$service_conf"; then + log_error "$test_name" "Invalid stop signal configuration" + return 1 + fi + log_success "$test_name" "Stop signal configured correctly" + + if ! grep -q "stopwaitsecs=10" "$service_conf"; then + log_error "$test_name" "Invalid stop wait configuration" + return 1 + fi + log_success "$test_name" "Stop wait time configured correctly" + + log_success "$test_name" "All service configuration tests passed" + return 0 +} + +# Run all tests +main() { + test_service_config || exit 1 + test_service_management || exit 1 +} + +main diff --git a/src/tests/tasks/50_graceful_shutdown.sh b/src/tests/tasks/50_graceful_shutdown.sh new file mode 100644 index 00000000..92bb9525 --- /dev/null +++ b/src/tests/tasks/50_graceful_shutdown.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# shellcheck source=/usr/local/lib/utils.sh disable=SC1091 +source /usr/local/lib/utils.sh + +# Test graceful shutdown handling +test_graceful_shutdown() { + local test_name="Graceful Shutdown" + + log_info "$test_name: Test graceful shutdown of supervisor processes" + + # Start the long-running service defined in services.yaml + supervisorctl start test_service + + # Wait for service to start + sleep 5 + + # Check if service is running + if ! supervisorctl status test_service | grep -q "RUNNING"; then + log_error "$test_name" "Service failed to start" + return 1 + fi + log_success "$test_name" "Service started successfully" + + # Send SIGTERM to the process + if [ -f /var/run/supervisord.pid ]; then + local supervisor_pid=$(cat /var/run/supervisord.pid) + log_info "$test_name: Sending SIGTERM to supervisord (PID: $supervisor_pid)" + kill -TERM "$supervisor_pid" + + # Wait for graceful shutdown (should take about 5 seconds based on our test service) + sleep 7 + + # Check if process has exited gracefully + if ps -p "$supervisor_pid" > /dev/null 2>&1; then + log_error "$test_name" "Process did not exit gracefully within timeout" + return 1 + fi + log_success "$test_name" "Process exited gracefully" + + # Check service logs for proper shutdown sequence + if ! grep -q "Starting cleanup..." /var/log/supervisor/test_service.out.log; then + log_error "$test_name" "Service did not initiate cleanup" + return 1 + fi + log_success "$test_name" "Service cleanup initiated" + + if ! grep -q "Cleanup completed" /var/log/supervisor/test_service.out.log; then + log_error "$test_name" "Service did not complete cleanup" + return 1 + fi + log_success "$test_name" "Service cleanup completed" + + log_success "$test_name" "Service shut down gracefully" + return 0 + else + log_error "$test_name" "Supervisor PID file not found" + return 1 + fi +} + +# Run the test +test_graceful_shutdown