mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
Revise tests. Use docker-compose.yml where possible
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
services:
|
||||
netalertx:
|
||||
# Missing capabilities configuration for testing
|
||||
network_mode: ${NETALERTX_NETWORK_MODE:-host}
|
||||
build:
|
||||
context: ../../../
|
||||
dockerfile: Dockerfile
|
||||
image: netalertx-test
|
||||
container_name: netalertx-test-missing-caps
|
||||
read_only: true
|
||||
cap_drop:
|
||||
- ALL # Drop all capabilities to test missing capabilities scenario
|
||||
|
||||
volumes:
|
||||
- type: volume
|
||||
source: netalertx_config
|
||||
target: /app/config
|
||||
read_only: false
|
||||
|
||||
- type: volume
|
||||
source: netalertx_db
|
||||
target: /app/db
|
||||
read_only: false
|
||||
|
||||
- type: bind
|
||||
source: /etc/localtime
|
||||
target: /etc/localtime
|
||||
read_only: true
|
||||
|
||||
environment:
|
||||
LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0}
|
||||
PORT: ${PORT:-20211}
|
||||
APP_CONF_OVERRIDE: ${GRAPHQL_PORT:-20212}
|
||||
ALWAYS_FRESH_INSTALL: ${ALWAYS_FRESH_INSTALL:-false}
|
||||
NETALERTX_DEBUG: ${NETALERTX_DEBUG:-0}
|
||||
|
||||
mem_limit: 2048m
|
||||
mem_reservation: 1024m
|
||||
cpu_shares: 512
|
||||
pids_limit: 512
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
netalertx_config:
|
||||
netalertx_db:
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container starts successfully with proper nginx config mount
|
||||
# - SYSTEM_SERVICES_ACTIVE_CONFIG shows as writable and mounted
|
||||
# - No configuration warnings for nginx config path
|
||||
# - Custom PORT configuration should work when nginx config is writable
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows warning about missing nginx config mount
|
||||
# - SYSTEM_SERVICES_ACTIVE_CONFIG shows as not mounted
|
||||
# - Warning message about nginx configuration mount being missing
|
||||
# - Custom PORT configuration may not work properly
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows performance warning for nginx config on RAM disk
|
||||
# - SYSTEM_SERVICES_ACTIVE_CONFIG shows as mounted on tmpfs (RAM disk)
|
||||
# - Performance issue warning since nginx config should be persistent
|
||||
# - Custom PORT configuration may have performance implications
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container fails to start due to unwritable nginx config partition
|
||||
# - SYSTEM_SERVICES_ACTIVE_CONFIG shows as mounted but unwritable (❌ in Writeable column)
|
||||
# - 35-nginx-config.sh detects permission error and exits with code 1
|
||||
# - Container startup fails because nginx configuration cannot be written for custom ports
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container starts successfully with proper API mount
|
||||
# - NETALERTX_API shows as writable and mounted
|
||||
# - No configuration warnings for API path
|
||||
# - API data persistence works correctly
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows mount error for API directory
|
||||
# - NETALERTX_API shows as not mounted
|
||||
# - Mount error since API directory should be mounted for proper operation
|
||||
# - API functionality may be limited
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows performance warning for API on RAM disk
|
||||
# - NETALERTX_API shows as mounted on tmpfs (RAM disk)
|
||||
# - Performance issue warning since API data should be on persistent storage
|
||||
# - API data will be lost on container restart
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container fails to start due to unwritable API partition
|
||||
# - NETALERTX_API shows as mounted but unwritable (❌ in Writeable column)
|
||||
# - API directory must be writable for proper operation
|
||||
# - Container startup fails because API functionality cannot work without write access
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container starts successfully with proper config mount
|
||||
# - NETALERTX_CONFIG shows as writable and mounted
|
||||
# - No configuration warnings for config path
|
||||
# - Configuration persistence works correctly
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows mount error for config directory
|
||||
# - NETALERTX_CONFIG shows as not mounted
|
||||
# - Mount error since config directory should be mounted for proper operation
|
||||
# - Configuration may not persist across restarts
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows dataloss risk warning for config on RAM disk
|
||||
# - NETALERTX_CONFIG shows as mounted on tmpfs (RAM disk)
|
||||
# - Dataloss risk warning since config data should be persistent
|
||||
# - Configuration will be lost on container restart
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container fails to start due to unwritable config partition
|
||||
# - NETALERTX_CONFIG shows as mounted but unwritable (❌ in Writeable column)
|
||||
# - 30-writable-config.sh detects permission error and exits with code 1
|
||||
# - Container startup fails because config files cannot be written to
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container starts successfully with proper database mount
|
||||
# - NETALERTX_DB shows as writable and mounted
|
||||
# - No configuration warnings for database path
|
||||
# - Database persistence works correctly
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows mount error warning but continues running
|
||||
# - NETALERTX_DB shows as not mounted (❌ in Mount column) but path gets created
|
||||
# - Warning message displayed about configuration issues
|
||||
# - Container continues because database directory can be created in writable filesystem
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows dataloss risk warning for database on RAM disk
|
||||
# - NETALERTX_DB shows as mounted on tmpfs (RAM disk)
|
||||
# - Dataloss risk warning since database should be persistent
|
||||
# - Database will be lost on container restart
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container fails to start due to unwritable database partition
|
||||
# - NETALERTX_DB shows as mounted but unwritable (❌ in Writeable column)
|
||||
# - 30-writable-config.sh detects permission error and exits with code 1
|
||||
# - Container startup fails because database files cannot be written to
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container starts successfully with proper log mount
|
||||
# - NETALERTX_LOG shows as mounted and writable
|
||||
# - No mount warnings since logs can be non-persistent
|
||||
# - Container starts normally with logging enabled
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows mount error warning but continues running
|
||||
# - NETALERTX_LOG shows as not mounted (❌ in Mount column)
|
||||
# - Warning message displayed about configuration issues
|
||||
# - Container continues to run despite the mount error
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows dataloss risk warning for logs on RAM disk
|
||||
# - NETALERTX_LOG shows as mounted on tmpfs (RAM disk)
|
||||
# - Dataloss risk warning since logs may be lost on restart
|
||||
# - Container starts but logs may not persist
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container fails to start due to unwritable log partition
|
||||
# - NETALERTX_LOG shows as mounted but unwritable (❌ in Writeable column)
|
||||
# - 25-mandatory-folders.sh cannot create required log files and fails
|
||||
# - Container startup fails because logging infrastructure cannot be initialized
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container starts successfully with proper run mount
|
||||
# - NETALERTX_RUN shows as mounted and writable
|
||||
# - No mount warnings since run directory can be non-persistent
|
||||
# - Container starts normally with runtime files enabled
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows mount error warning but continues running
|
||||
# - NETALERTX_RUN shows as not mounted (❌ in Mount column)
|
||||
# - Warning message displayed about configuration issues
|
||||
# - Container continues to run despite the mount error
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container shows dataloss risk warning for run on RAM disk
|
||||
# - NETALERTX_RUN shows as mounted on tmpfs (RAM disk)
|
||||
# - Dataloss risk warning since runtime files may be lost on restart
|
||||
# - Container starts but runtime state may not persist
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Expected outcome: Container fails to start due to unwritable run partition
|
||||
# - NETALERTX_RUN shows as mounted but unwritable (❌ in Writeable column)
|
||||
# - 25-mandatory-folders.sh cannot create required runtime files and fails
|
||||
# - Container startup fails because runtime infrastructure cannot be initialized
|
||||
services:
|
||||
netalertx:
|
||||
network_mode: host
|
||||
|
||||
69
test/docker_tests/configurations/test_all_docker_composes.sh
Executable file
69
test/docker_tests/configurations/test_all_docker_composes.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
# test_all_docker_composes.sh - Test all docker-compose configurations
|
||||
# Extracts comments from each file and runs the container for 10 seconds
|
||||
|
||||
LOG_FILE="/workspaces/NetAlertX/test/docker_tests/configurations/test_results.log"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "Starting Docker Compose Tests - $(date)" > "$LOG_FILE"
|
||||
echo "==========================================" >> "$LOG_FILE"
|
||||
|
||||
# Function to extract comments from docker-compose file
|
||||
extract_comments() {
|
||||
local file="$1"
|
||||
echo "File: $(basename "$file")" >> "$LOG_FILE"
|
||||
echo "----------------------------------------" >> "$LOG_FILE"
|
||||
|
||||
# Extract lines starting with # until we hit a non-comment line
|
||||
awk '
|
||||
/^#/ {
|
||||
# Remove the # and any leading/trailing whitespace
|
||||
comment = substr($0, 2)
|
||||
sub(/^ */, "", comment)
|
||||
sub(/ *$/, "", comment)
|
||||
if (comment != "") {
|
||||
print comment
|
||||
}
|
||||
}
|
||||
/^[^#]/ && !/^$/ {
|
||||
exit
|
||||
}
|
||||
' "$file" >> "$LOG_FILE"
|
||||
|
||||
echo "" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Function to run docker-compose test
|
||||
run_test() {
|
||||
local file="$1"
|
||||
local dirname=$(dirname "$file")
|
||||
local basename=$(basename "$file")
|
||||
|
||||
echo "Testing: $basename" >> "$LOG_FILE"
|
||||
echo "Directory: $dirname" >> "$LOG_FILE"
|
||||
echo "" >> "$LOG_FILE"
|
||||
|
||||
# Change to the directory containing the docker-compose file
|
||||
cd "$dirname"
|
||||
|
||||
# Run docker-compose up with timeout
|
||||
echo "Running docker-compose up..." >> "$LOG_FILE"
|
||||
timeout 10s docker-compose -f "$basename" up 2>&1 >> "$LOG_FILE"
|
||||
|
||||
# Clean up
|
||||
docker-compose -f "$basename" down -v 2>/dev/null || true
|
||||
docker volume prune -f 2>/dev/null || true
|
||||
|
||||
echo "" >> "$LOG_FILE"
|
||||
echo "==========================================" >> "$LOG_FILE"
|
||||
echo "" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Find all docker-compose files
|
||||
find "$SCRIPT_DIR" -name "docker-compose*.yml" -type f | sort | while read -r file; do
|
||||
extract_comments "$file"
|
||||
run_test "$file"
|
||||
done
|
||||
|
||||
echo "All tests completed - $(date)" >> "$LOG_FILE"
|
||||
echo "Results saved to: $LOG_FILE"
|
||||
150
test/docker_tests/configurations/test_results.log
Normal file
150
test/docker_tests/configurations/test_results.log
Normal file
@@ -0,0 +1,150 @@
|
||||
Starting Docker Compose Tests - Fri Oct 31 20:00:39 UTC 2025
|
||||
==========================================
|
||||
File: docker-compose.missing-caps.yml
|
||||
----------------------------------------
|
||||
|
||||
Testing: docker-compose.missing-caps.yml
|
||||
Directory: /workspaces/NetAlertX/test/docker_tests/configurations
|
||||
|
||||
Running docker-compose up...
|
||||
Attaching to netalertx-test-missing-caps
|
||||
|
||||
[Knetalertx-test-missing-caps exited with code 255
|
||||
|
||||
==========================================
|
||||
|
||||
File: docker-compose.readonly.yml
|
||||
----------------------------------------
|
||||
|
||||
Testing: docker-compose.readonly.yml
|
||||
Directory: /workspaces/NetAlertX/test/docker_tests/configurations
|
||||
|
||||
Running docker-compose up...
|
||||
Attaching to netalertx-test-readonly
|
||||
netalertx-test-readonly | [1;31m
|
||||
netalertx-test-readonly | _ _ _ ___ _ _ __ __
|
||||
netalertx-test-readonly | | \ | | | | / _ \| | | | \ \ / /
|
||||
netalertx-test-readonly | | \| | ___| |_/ /_\ \ | ___ _ __| |_ \ V /
|
||||
netalertx-test-readonly | | . |/ _ \ __| _ | |/ _ \ __| __|/ \
|
||||
netalertx-test-readonly | | |\ | __/ |_| | | | | __/ | | |_/ /^\ \
|
||||
netalertx-test-readonly | \_| \_/\___|\__\_| |_/_|\___|_| \__\/ \/
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | [0m Network intruder and presence detector.
|
||||
netalertx-test-readonly | https://netalertx.com
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | Startup pre-checks
|
||||
netalertx-test-readonly | --> storage permission
|
||||
netalertx-test-readonly | --> mounts.py
|
||||
netalertx-test-readonly | --> first run config
|
||||
netalertx-test-readonly | --> first run db
|
||||
netalertx-test-readonly | --> mandatory folders
|
||||
netalertx-test-readonly | --> writable config
|
||||
netalertx-test-readonly | --> nginx config
|
||||
netalertx-test-readonly | nginx config: FAILED with 1
|
||||
netalertx-test-readonly | Failure detected in: /entrypoint.d/35-nginx-config.sh
|
||||
netalertx-test-readonly | --> user netalertx
|
||||
netalertx-test-readonly | --> host mode network
|
||||
netalertx-test-readonly | --> layer 2 capabilities
|
||||
netalertx-test-readonly | --> excessive capabilities
|
||||
netalertx-test-readonly | excessive capabilities: FAILED with 2
|
||||
netalertx-test-readonly | Failure detected in: /entrypoint.d/90-excessive-capabilities.sh
|
||||
netalertx-test-readonly | --> appliance integrity
|
||||
netalertx-test-readonly | --> ports available
|
||||
netalertx-test-readonly | ══════════════════════════════════════════════════════════════════════════════
|
||||
netalertx-test-readonly | ⚠️ Port Warning: Application port 20211 is already in use.
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | The main application (defined by $PORT) may fail to start.
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/port-conflicts.md
|
||||
netalertx-test-readonly | ══════════════════════════════════════════════════════════════════════════════
|
||||
netalertx-test-readonly | ══════════════════════════════════════════════════════════════════════════════
|
||||
netalertx-test-readonly | ⚠️ Port Warning: GraphQL API port 20212 is already in use.
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | The GraphQL API (defined by $APP_CONF_OVERRIDE or $GRAPHQL_PORT)
|
||||
netalertx-test-readonly | may fail to start.
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/port-conflicts.md
|
||||
netalertx-test-readonly | ══════════════════════════════════════════════════════════════════════════════
|
||||
netalertx-test-readonly | Container startup checks failed with exit code 2.
|
||||
netalertx-test-readonly | NETALERTX_DEBUG is set to 1, will not shut down other services if one fails.
|
||||
netalertx-test-readonly | Starting /usr/sbin/crond -c "/services/config/crond" -f -L "/app/log/crond.log" >>"/app/log/crond.log" 2>&1 &
|
||||
netalertx-test-readonly | Starting /usr/sbin/php-fpm83 -y "/services/config/php/php-fpm.conf" -F >>"/app/log/app.php_errors.log" 2>/dev/stderr &
|
||||
netalertx-test-readonly | Crond stopped! (exit 1)
|
||||
netalertx-test-readonly | php-fpm stopped! (exit 1)
|
||||
netalertx-test-readonly | Starting python3 -m server > /app/log/stdout.log 2> >(tee /app/log/stderr.log >&2)
|
||||
|
||||
[Knetalertx-test-readonly exited with code 0
|
||||
netalertx-test-readonly | --> first run config
|
||||
netalertx-test-readonly | --> first run db
|
||||
netalertx-test-readonly | --> mandatory folders
|
||||
netalertx-test-readonly | --> writable config
|
||||
netalertx-test-readonly | --> nginx config
|
||||
netalertx-test-readonly | nginx config: FAILED with 1
|
||||
netalertx-test-readonly | Failure detected in: /entrypoint.d/35-nginx-config.sh
|
||||
netalertx-test-readonly | --> user netalertx
|
||||
netalertx-test-readonly | --> host mode network
|
||||
netalertx-test-readonly | --> layer 2 capabilities
|
||||
netalertx-test-readonly | --> excessive capabilities
|
||||
netalertx-test-readonly | excessive capabilities: FAILED with 2
|
||||
netalertx-test-readonly | Failure detected in: /entrypoint.d/90-excessive-capabilities.sh
|
||||
netalertx-test-readonly | --> appliance integrity
|
||||
netalertx-test-readonly | --> ports available
|
||||
netalertx-test-readonly | ══════════════════════════════════════════════════════════════════════════════
|
||||
netalertx-test-readonly | ⚠️ Port Warning: Application port 20211 is already in use.
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | The main application (defined by $PORT) may fail to start.
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/port-conflicts.md
|
||||
netalertx-test-readonly | ══════════════════════════════════════════════════════════════════════════════
|
||||
netalertx-test-readonly | ══════════════════════════════════════════════════════════════════════════════
|
||||
netalertx-test-readonly | ⚠️ Port Warning: GraphQL API port 20212 is already in use.
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | The GraphQL API (defined by $APP_CONF_OVERRIDE or $GRAPHQL_PORT)
|
||||
netalertx-test-readonly | may fail to start.
|
||||
netalertx-test-readonly |
|
||||
netalertx-test-readonly | https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/port-conflicts.md
|
||||
netalertx-test-readonly | ══════════════════════════════════════════════════════════════════════════════
|
||||
netalertx-test-readonly | Container startup checks failed with exit code 2.
|
||||
netalertx-test-readonly | NETALERTX_DEBUG is set to 1, will not shut down other services if one fails.
|
||||
netalertx-test-readonly | Starting /usr/sbin/crond -c "/services/config/crond" -f -L "/app/log/crond.log" >>"/app/log/crond.log" 2>&1 &
|
||||
netalertx-test-readonly | Starting /usr/sbin/php-fpm83 -y "/services/config/php/php-fpm.conf" -F >>"/app/log/app.php_errors.log" 2>/dev/stderr &
|
||||
netalertx-test-readonly | php-fpm stopped! (exit 1)
|
||||
netalertx-test-readonly | Crond stopped! (exit 1)
|
||||
netalertx-test-readonly | Starting python3 -m server > /app/log/stdout.log 2> >(tee /app/log/stderr.log >&2)
|
||||
|
||||
==========================================
|
||||
|
||||
File: docker-compose.writable.yml
|
||||
----------------------------------------
|
||||
|
||||
Testing: docker-compose.writable.yml
|
||||
Directory: /workspaces/NetAlertX/test/docker_tests/configurations
|
||||
|
||||
Running docker-compose up...
|
||||
|
||||
==========================================
|
||||
|
||||
File: docker-compose.mount-test.active_config_mounted.yml
|
||||
----------------------------------------
|
||||
Expected outcome: Container starts successfully with proper nginx config mount
|
||||
- SYSTEM_SERVICES_ACTIVE_CONFIG shows as writable and mounted
|
||||
- No configuration warnings for nginx config path
|
||||
- Custom PORT configuration should work when nginx config is writable
|
||||
|
||||
Testing: docker-compose.mount-test.active_config_mounted.yml
|
||||
Directory: /workspaces/NetAlertX/test/docker_tests/configurations/mount-tests
|
||||
|
||||
Running docker-compose up...
|
||||
|
||||
==========================================
|
||||
|
||||
File: docker-compose.mount-test.active_config_no-mount.yml
|
||||
----------------------------------------
|
||||
Expected outcome: Container shows warning about missing nginx config mount
|
||||
- SYSTEM_SERVICES_ACTIVE_CONFIG shows as not mounted
|
||||
- Warning message about nginx configuration mount being missing
|
||||
- Custom PORT configuration may not work properly
|
||||
|
||||
Testing: docker-compose.mount-test.active_config_no-mount.yml
|
||||
Directory: /workspaces/NetAlertX/test/docker_tests/configurations/mount-tests
|
||||
@@ -263,10 +263,9 @@ def _run_container(
|
||||
)
|
||||
result.output = stdouterr
|
||||
# Print container output for debugging in every test run.
|
||||
try:
|
||||
print("\n--- CONTAINER out ---\n", result.output)
|
||||
except Exception:
|
||||
pass
|
||||
print("\n--- CONTAINER OUTPUT START ---")
|
||||
print(result.output)
|
||||
print("--- CONTAINER OUTPUT END ---\n")
|
||||
|
||||
return result
|
||||
|
||||
@@ -313,485 +312,6 @@ def _restore_zero_perm_dir(paths: dict[str, pathlib.Path], key: str) -> None:
|
||||
|
||||
|
||||
|
||||
def test_root_owned_app_db_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test root-owned mounts - simulates mounting host directories owned by root.
|
||||
|
||||
1. Root-Owned Mounts: Simulates mounting host directories owned by root
|
||||
(common with docker run -v /host/path:/app/db).
|
||||
Tests each required mount point when owned by root user.
|
||||
Expected: Warning about permission issues, guidance to fix ownership.
|
||||
|
||||
Check script: check-app-permissions.sh
|
||||
Sample message: "⚠️ ATTENTION: Write permission denied. The application cannot write to..."
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "root_app_db")
|
||||
_chown_root(paths["app_db"])
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("root-app-db", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_db"]), result.args)
|
||||
finally:
|
||||
_chown_netalertx(paths["app_db"])
|
||||
|
||||
|
||||
def test_root_owned_app_config_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test root-owned mounts - simulates mounting host directories owned by root.
|
||||
|
||||
1. Root-Owned Mounts: Simulates mounting host directories owned by root
|
||||
(common with docker run -v /host/path:/app/db).
|
||||
Tests each required mount point when owned by root user.
|
||||
Expected: Warning about permission issues, guidance to fix ownership.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "root_app_config")
|
||||
_chown_root(paths["app_config"])
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("root-app-config", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_config"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_chown_netalertx(paths["app_config"])
|
||||
|
||||
|
||||
def test_root_owned_app_log_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test root-owned mounts - simulates mounting host directories owned by root.
|
||||
|
||||
1. Root-Owned Mounts: Simulates mounting host directories owned by root
|
||||
(common with docker run -v /host/path:/app/db).
|
||||
Tests each required mount point when owned by root user.
|
||||
Expected: Warning about permission issues, guidance to fix ownership.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "root_app_log")
|
||||
_chown_root(paths["app_log"])
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("root-app-log", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_log"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_chown_netalertx(paths["app_log"])
|
||||
|
||||
|
||||
def test_root_owned_app_api_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test root-owned mounts - simulates mounting host directories owned by root.
|
||||
|
||||
1. Root-Owned Mounts: Simulates mounting host directories owned by root
|
||||
(common with docker run -v /host/path:/app/db).
|
||||
Tests each required mount point when owned by root user.
|
||||
Expected: Warning about permission issues, guidance to fix ownership.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "root_app_api")
|
||||
_chown_root(paths["app_api"])
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("root-app-api", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_api"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_chown_netalertx(paths["app_api"])
|
||||
|
||||
|
||||
def test_root_owned_nginx_conf_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test root-owned mounts - simulates mounting host directories owned by root.
|
||||
|
||||
1. Root-Owned Mounts: Simulates mounting host directories owned by root
|
||||
(common with docker run -v /host/path:/app/db).
|
||||
Tests each required mount point when owned by root user.
|
||||
Expected: Warning about permission issues, guidance to fix ownership.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "root_nginx_conf")
|
||||
_chown_root(paths["nginx_conf"])
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("root-nginx-conf", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["nginx_conf"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_chown_netalertx(paths["nginx_conf"])
|
||||
|
||||
|
||||
def test_root_owned_services_run_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test root-owned mounts - simulates mounting host directories owned by root.
|
||||
|
||||
1. Root-Owned Mounts: Simulates mounting host directories owned by root
|
||||
(common with docker run -v /host/path:/app/db).
|
||||
Tests each required mount point when owned by root user.
|
||||
Expected: Warning about permission issues, guidance to fix ownership.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "root_services_run")
|
||||
_chown_root(paths["services_run"])
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("root-services-run", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["services_run"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_chown_netalertx(paths["services_run"])
|
||||
|
||||
|
||||
def test_zero_permissions_app_db_dir(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: "Write permission denied" error with path, guidance to fix permissions.
|
||||
|
||||
Check script: check-app-permissions.sh
|
||||
Sample messages: "⚠️ ATTENTION: Write permission denied. The application cannot write to..."
|
||||
"⚠️ ATTENTION: Read permission denied. The application cannot read from..."
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_app_db")
|
||||
_setup_zero_perm_dir(paths, "app_db")
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-app-db", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_db"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_restore_zero_perm_dir(paths, "app_db")
|
||||
|
||||
|
||||
def test_zero_permissions_app_db_file(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: "Write permission denied" error with path, guidance to fix permissions.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_app_db_file")
|
||||
(paths["app_db"] / "app.db").chmod(0)
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-app-db-file", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
(paths["app_db"] / "app.db").chmod(0o600)
|
||||
|
||||
|
||||
def test_zero_permissions_app_config_dir(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: "Write permission denied" error with path, guidance to fix permissions.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_app_config")
|
||||
_setup_zero_perm_dir(paths, "app_config")
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-app-config", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_config"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_restore_zero_perm_dir(paths, "app_config")
|
||||
|
||||
|
||||
def test_zero_permissions_app_config_file(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: "Write permission denied" error with path, guidance to fix permissions.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_app_config_file")
|
||||
(paths["app_config"] / "app.conf").chmod(0)
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-app-config-file", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
(paths["app_config"] / "app.conf").chmod(0o600)
|
||||
|
||||
|
||||
def test_zero_permissions_app_log_dir(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: "Write permission denied" error with path, guidance to fix permissions.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_app_log")
|
||||
_setup_zero_perm_dir(paths, "app_log")
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-app-log", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_log"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_restore_zero_perm_dir(paths, "app_log")
|
||||
|
||||
|
||||
def test_zero_permissions_app_api_dir(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: "Write permission denied" error with path, guidance to fix permissions.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_app_api")
|
||||
_setup_zero_perm_dir(paths, "app_api")
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-app-api", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_api"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_restore_zero_perm_dir(paths, "app_api")
|
||||
|
||||
|
||||
def test_zero_permissions_nginx_conf_dir(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: "Write permission denied" error with path, guidance to fix permissions.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_nginx_conf")
|
||||
_setup_zero_perm_dir(paths, "nginx_conf")
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-nginx-conf", volumes, user="20211:20211")
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_restore_zero_perm_dir(paths, "nginx_conf")
|
||||
|
||||
|
||||
def test_zero_permissions_services_run_dir(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: "Write permission denied" error with path, guidance to fix permissions.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_services_run")
|
||||
_setup_zero_perm_dir(paths, "services_run")
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-services-run", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["services_run"]), result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_restore_zero_perm_dir(paths, "services_run")
|
||||
|
||||
|
||||
def test_readonly_app_db_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test readonly mounts - simulates read-only volume mounts in containers.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when mounted read-only.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "readonly_app_db")
|
||||
volumes = _build_volume_args(paths, read_only={"app_db"})
|
||||
result = _run_container("readonly-app-db", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_db"]), result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_readonly_app_config_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test readonly mounts - simulates read-only volume mounts in containers.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when mounted read-only.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "readonly_app_config")
|
||||
volumes = _build_volume_args(paths, read_only={"app_config"})
|
||||
result = _run_container("readonly-app-config", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_config"]), result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_readonly_app_log_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test readonly mounts - simulates read-only volume mounts in containers.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when mounted read-only.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "readonly_app_log")
|
||||
volumes = _build_volume_args(paths, read_only={"app_log"})
|
||||
result = _run_container("readonly-app-log", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_log"]), result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_readonly_app_api_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test readonly mounts - simulates read-only volume mounts in containers.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when mounted read-only.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "readonly_app_api")
|
||||
volumes = _build_volume_args(paths, read_only={"app_api"})
|
||||
result = _run_container("readonly-app-api", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["app_api"]), result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_readonly_nginx_conf_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test readonly mounts - simulates read-only volume mounts in containers.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when mounted read-only.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "readonly_nginx_conf")
|
||||
volumes = _build_volume_args(paths, read_only={"nginx_conf"})
|
||||
result = _run_container("readonly-nginx-conf", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, "/services/config/nginx/conf.active", result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_readonly_services_run_mount(tmp_path: pathlib.Path) -> None:
|
||||
"""Test readonly mounts - simulates read-only volume mounts in containers.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when mounted read-only.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "readonly_services_run")
|
||||
volumes = _build_volume_args(paths, read_only={"services_run"})
|
||||
result = _run_container("readonly-services-run", volumes)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, str(VOLUME_MAP["services_run"]), result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_custom_port_without_writable_conf(tmp_path: pathlib.Path) -> None:
|
||||
"""Test custom port configuration without writable nginx config mount.
|
||||
|
||||
4. Custom Port Without Nginx Config Mount: Simulates setting custom LISTEN_ADDR/PORT
|
||||
without mounting nginx config. Container starts but uses default address.
|
||||
Expected: Container starts but uses default address, warning about missing config mount.
|
||||
|
||||
Check script: check-nginx-config.sh
|
||||
Sample messages: "⚠️ ATTENTION: Nginx configuration mount /services/config/nginx/conf.active is missing."
|
||||
"⚠️ ATTENTION: Unable to write to /services/config/nginx/conf.active/netalertx.conf."
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "custom_port_ro_conf")
|
||||
paths["nginx_conf"].chmod(0o500)
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container(
|
||||
"custom-port-ro-conf",
|
||||
volumes,
|
||||
env={"PORT": "24444", "LISTEN_ADDR": "127.0.0.1"},
|
||||
)
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, "/services/config/nginx/conf.active", result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
paths["nginx_conf"].chmod(0o755)
|
||||
|
||||
def test_missing_mount_app_db(tmp_path: pathlib.Path) -> None:
|
||||
"""Test missing required mounts - simulates forgetting to mount persistent volumes.
|
||||
...
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "missing_mount_app_db")
|
||||
volumes = _build_volume_args(paths, skip={"app_db"})
|
||||
# CHANGE: Run as root (0:0) to bypass all permission checks on other mounts.
|
||||
result = _run_container("missing-mount-app-db", volumes, user="20211:20211")
|
||||
# Acknowledge the original intent to check for permission denial (now implicit via root)
|
||||
# _assert_contains(result, "Write permission denied", result.args) # No longer needed, as root user is used
|
||||
|
||||
# Robust assertion: check for both the warning and the path
|
||||
if "not a persistent mount" not in result.output or "/app/db" not in result.output:
|
||||
print("\n--- DEBUG CONTAINER OUTPUT ---\n", result.output)
|
||||
raise AssertionError("Expected persistent mount warning for /app/db in container output.")
|
||||
|
||||
|
||||
def test_missing_mount_app_config(tmp_path: pathlib.Path) -> None:
|
||||
"""Test missing required mounts - simulates forgetting to mount persistent volumes.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when missing.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "missing_mount_app_config")
|
||||
volumes = _build_volume_args(paths, skip={"app_config"})
|
||||
result = _run_container("missing-mount-app-config", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, "/app/config", result.args)
|
||||
|
||||
|
||||
def test_missing_mount_app_log(tmp_path: pathlib.Path) -> None:
|
||||
"""Test missing required mounts - simulates forgetting to mount persistent volumes.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when missing.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "missing_mount_app_log")
|
||||
volumes = _build_volume_args(paths, skip={"app_log"})
|
||||
result = _run_container("missing-mount-app-log", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, "/app/log", result.args)
|
||||
|
||||
|
||||
def test_missing_mount_app_api(tmp_path: pathlib.Path) -> None:
|
||||
"""Test missing required mounts - simulates forgetting to mount persistent volumes.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when missing.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "missing_mount_app_api")
|
||||
volumes = _build_volume_args(paths, skip={"app_api"})
|
||||
result = _run_container("missing-mount-app-api", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, "/app/api", result.args)
|
||||
|
||||
|
||||
def test_missing_mount_nginx_conf(tmp_path: pathlib.Path) -> None:
|
||||
"""Test missing required mounts - simulates forgetting to mount persistent volumes.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when missing.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "missing_mount_nginx_conf")
|
||||
volumes = _build_volume_args(paths, skip={"nginx_conf"})
|
||||
result = _run_container("missing-mount-nginx-conf", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, "/services/config/nginx/conf.active", result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_missing_mount_services_run(tmp_path: pathlib.Path) -> None:
|
||||
"""Test missing required mounts - simulates forgetting to mount persistent volumes.
|
||||
|
||||
3. Missing Required Mounts: Simulates forgetting to mount required persistent volumes
|
||||
in read-only containers. Tests each required mount point when missing.
|
||||
Expected: "Write permission denied" error with path, guidance to add volume mounts.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "missing_mount_services_run")
|
||||
volumes = _build_volume_args(paths, skip={"services_run"})
|
||||
result = _run_container("missing-mount-services-run", volumes, user="20211:20211")
|
||||
_assert_contains(result, "Write permission denied", result.args)
|
||||
_assert_contains(result, "/services/run", result.args)
|
||||
_assert_contains(result, "Container startup checks failed with exit code", result.args)
|
||||
|
||||
|
||||
def test_missing_capabilities_triggers_warning(tmp_path: pathlib.Path) -> None:
|
||||
"""Test missing required capabilities - simulates insufficient container privileges.
|
||||
|
||||
@@ -799,8 +319,8 @@ def test_missing_capabilities_triggers_warning(tmp_path: pathlib.Path) -> None:
|
||||
NET_BIND_SERVICE capabilities. Required for ARP scanning and network operations.
|
||||
Expected: "exec /bin/sh: operation not permitted" error, guidance to add capabilities.
|
||||
|
||||
Check script: check-cap.sh
|
||||
Sample message: "⚠️ ATTENTION: Raw network capabilities are missing. Tools that rely on NET_RAW..."
|
||||
Check script: N/A (capability check happens at container runtime)
|
||||
Sample message: "exec /bin/sh: operation not permitted"
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "missing_caps")
|
||||
volumes = _build_volume_args(paths)
|
||||
@@ -820,8 +340,8 @@ def test_running_as_root_is_blocked(tmp_path: pathlib.Path) -> None:
|
||||
dedicated netalertx user. Warning about security risks, special permission fix mode.
|
||||
Expected: Warning about security risks, guidance to use UID 20211.
|
||||
|
||||
Check script: check-app-permissions.sh
|
||||
Sample message: "⚠️ ATTENTION: NetAlertX is running as root (UID 0). This defeats every hardening..."
|
||||
Check script: /entrypoint.d/0-storage-permission.sh
|
||||
Sample message: "🚨 CRITICAL SECURITY ALERT: NetAlertX is running as ROOT (UID 0)!"
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "run_as_root")
|
||||
volumes = _build_volume_args(paths)
|
||||
@@ -843,7 +363,7 @@ def test_running_as_uid_1000_warns(tmp_path: pathlib.Path) -> None:
|
||||
of netalertx user. Permission errors due to incorrect user context.
|
||||
Expected: Permission errors, guidance to use correct user.
|
||||
|
||||
Check script: check-user-netalertx.sh
|
||||
Check script: /entrypoint.d/60-user-netalertx.sh
|
||||
Sample message: "⚠️ ATTENTION: NetAlertX is running as UID 1000:1000. Hardened permissions..."
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "run_as_1000")
|
||||
@@ -885,15 +405,21 @@ def test_missing_app_conf_triggers_seed(tmp_path: pathlib.Path) -> None:
|
||||
9. Missing Configuration File: Simulates corrupted/missing app.conf.
|
||||
Container automatically regenerates default configuration on startup.
|
||||
Expected: Automatic regeneration of default configuration.
|
||||
|
||||
Check script: /entrypoint.d/15-first-run-config.sh
|
||||
Sample message: "Default configuration written to"
|
||||
"""
|
||||
base = tmp_path / "missing_app_conf_base"
|
||||
paths = _setup_fixed_mount_tree(base)
|
||||
_chown_netalertx(paths["app_config"])
|
||||
# Ensure directories are writable and owned by netalertx user so container can operate
|
||||
for key in ["app_db", "app_config", "app_log", "app_api", "services_run", "nginx_conf"]:
|
||||
paths[key].chmod(0o777)
|
||||
_chown_netalertx(paths[key])
|
||||
(paths["app_config"] / "testfile.txt").write_text("test")
|
||||
volumes = _build_volume_args(paths)
|
||||
result = _run_container("missing-app-conf", volumes)
|
||||
result = _run_container("missing-app-conf", volumes, sleep_seconds=5)
|
||||
_assert_contains(result, "Default configuration written to", result.args)
|
||||
assert result.returncode != 0
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
def test_missing_app_db_triggers_seed(tmp_path: pathlib.Path) -> None:
|
||||
@@ -902,54 +428,253 @@ def test_missing_app_db_triggers_seed(tmp_path: pathlib.Path) -> None:
|
||||
10. Missing Database File: Simulates corrupted/missing app.db.
|
||||
Container automatically creates initial database schema on startup.
|
||||
Expected: Automatic creation of initial database schema.
|
||||
|
||||
Check script: /entrypoint.d/20-first-run-db.sh
|
||||
Sample message: "Building initial database schema"
|
||||
"""
|
||||
base = tmp_path / "missing_app_db_base"
|
||||
paths = _setup_fixed_mount_tree(base)
|
||||
_chown_netalertx(paths["app_db"])
|
||||
(paths["app_db"] / "testfile.txt").write_text("test")
|
||||
volumes = _build_volume_args(paths)
|
||||
result = _run_container("missing-app-db", volumes, user="20211:20211")
|
||||
result = _run_container("missing-app-db", volumes, user="20211:20211", sleep_seconds=5)
|
||||
_assert_contains(result, "Building initial database schema", result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_tmpfs_config_mount_warns(tmp_path: pathlib.Path) -> None:
|
||||
"""Test tmpfs instead of volumes - simulates using tmpfs for persistent data.
|
||||
def test_custom_port_without_writable_conf(tmp_path: pathlib.Path) -> None:
|
||||
"""Test custom port configuration without writable nginx config mount.
|
||||
|
||||
11. Tmpfs Instead of Volumes: Simulates using tmpfs mounts instead of persistent volumes
|
||||
(data loss on restart). Tests config and db directories mounted as tmpfs.
|
||||
Expected: "Read permission denied" error, guidance to use persistent volumes.
|
||||
4. Custom Port Without Nginx Config Mount: Simulates setting custom LISTEN_ADDR/PORT
|
||||
without mounting nginx config. Container starts but uses default address.
|
||||
Expected: Container starts but uses default address, warning about missing config mount.
|
||||
|
||||
Check scripts: check-storage.sh, check-storage-extra.sh
|
||||
Sample message: "⚠️ ATTENTION: /app/config is not a persistent mount. Your data in this directory..."
|
||||
Check script: check-nginx-config.sh
|
||||
Sample messages: "⚠️ ATTENTION: Nginx configuration mount /services/config/nginx/conf.active is missing."
|
||||
"⚠️ ATTENTION: Unable to write to /services/config/nginx/conf.active/netalertx.conf."
|
||||
|
||||
TODO: Custom ports can only be assigned when we have the PORT=something, and in that case
|
||||
the /config.active partition shows up in the messages. It SHOULD exit if port is specified
|
||||
and not writeable and I'm not sure it will.
|
||||
|
||||
RESOLVED: When PORT is specified but nginx config is not writable, the container warns
|
||||
"Unable to write to /services/config/nginx/conf.active/netalertx.conf" but does NOT exit.
|
||||
It continues with startup and fails later for other reasons if any directories are not writable.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "tmpfs_config")
|
||||
volumes = _build_volume_args(paths, skip={"app_config"})
|
||||
extra = ["--mount", "type=tmpfs,destination=/app/config"]
|
||||
result = _run_container(
|
||||
"tmpfs-config",
|
||||
volumes,
|
||||
extra_args=extra,
|
||||
)
|
||||
_assert_contains(result, "not a persistent mount.", result.args)
|
||||
_assert_contains(result, "/app/config", result.args)
|
||||
paths = _setup_mount_tree(tmp_path, "custom_port_ro_conf")
|
||||
# Ensure other directories are writable so container gets to nginx config check
|
||||
for key in ["app_db", "app_config", "app_log", "app_api", "services_run"]:
|
||||
paths[key].chmod(0o777)
|
||||
paths["nginx_conf"].chmod(0o500)
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container(
|
||||
"custom-port-ro-conf",
|
||||
volumes,
|
||||
env={"PORT": "24444", "LISTEN_ADDR": "127.0.0.1"},
|
||||
user="20211:20211",
|
||||
sleep_seconds=5,
|
||||
)
|
||||
_assert_contains(result, "Unable to write to", result.args)
|
||||
_assert_contains(result, "/services/config/nginx/conf.active/netalertx.conf", result.args)
|
||||
# TODO: Should this exit when PORT is specified but nginx config is not writable?
|
||||
# Currently it just warns and continues
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
paths["nginx_conf"].chmod(0o755)
|
||||
def test_zero_permissions_app_db_dir(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
|
||||
def test_tmpfs_db_mount_warns(tmp_path: pathlib.Path) -> None:
|
||||
"""Test tmpfs instead of volumes - simulates using tmpfs for persistent data.
|
||||
|
||||
11. Tmpfs Instead of Volumes: Simulates using tmpfs mounts instead of persistent volumes
|
||||
(data loss on restart). Tests config and db directories mounted as tmpfs.
|
||||
Expected: "Read permission denied" error, guidance to use persistent volumes.
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: Mounts table shows ❌ for writeable status, configuration issues detected.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "tmpfs_db")
|
||||
volumes = _build_volume_args(paths, skip={"app_db"})
|
||||
extra = ["--mount", "type=tmpfs,destination=/app/db"]
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_app_db")
|
||||
_setup_zero_perm_dir(paths, "app_db")
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-app-db", volumes, user="20211:20211")
|
||||
# Check that the mounts table shows the app_db directory as not writeable
|
||||
_assert_contains(result, "/app/db | ❌ |", result.args)
|
||||
# Check that configuration issues are detected
|
||||
_assert_contains(result, "Configuration issues detected", result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_restore_zero_perm_dir(paths, "app_db")
|
||||
|
||||
|
||||
def test_zero_permissions_app_config_dir(tmp_path: pathlib.Path) -> None:
|
||||
"""Test zero permissions - simulates mounting directories/files with no permissions.
|
||||
|
||||
2. Zero Permissions: Simulates mounting directories/files with no permissions (chmod 000).
|
||||
Tests directories and files with no read/write/execute permissions.
|
||||
Expected: Mounts table shows ❌ for writeable status, configuration issues detected.
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "chmod_app_config")
|
||||
_setup_zero_perm_dir(paths, "app_config")
|
||||
volumes = _build_volume_args(paths)
|
||||
try:
|
||||
result = _run_container("chmod-app-config", volumes, user="20211:20211")
|
||||
# Check that the mounts table shows the app_config directory as not writeable
|
||||
_assert_contains(result, "/app/config | ❌ |", result.args)
|
||||
# Check that configuration issues are detected
|
||||
_assert_contains(result, "Configuration issues detected", result.args)
|
||||
assert result.returncode != 0
|
||||
finally:
|
||||
_restore_zero_perm_dir(paths, "app_config")
|
||||
|
||||
|
||||
def test_mandatory_folders_creation(tmp_path: pathlib.Path) -> None:
|
||||
"""Test mandatory folders creation - simulates missing plugins log directory.
|
||||
|
||||
1. Mandatory Folders: Simulates missing required directories and log files.
|
||||
Container automatically creates plugins log, system services run log/tmp directories,
|
||||
and required log files on startup.
|
||||
Expected: Automatic creation of all required directories and files.
|
||||
|
||||
Check script: 25-mandatory-folders.sh
|
||||
Sample message: "Creating Plugins log"
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "mandatory_folders")
|
||||
# Remove the plugins log directory to simulate missing mandatory folder
|
||||
plugins_log_dir = paths["app_log"] / "plugins"
|
||||
if plugins_log_dir.exists():
|
||||
shutil.rmtree(plugins_log_dir)
|
||||
|
||||
# Ensure other directories are writable and owned by netalertx user so container gets past mounts.py
|
||||
for key in ["app_db", "app_config", "app_log", "app_api", "services_run", "nginx_conf"]:
|
||||
paths[key].chmod(0o777)
|
||||
_chown_netalertx(paths[key]) # Ensure all directories are owned by netalertx
|
||||
|
||||
volumes = _build_volume_args(paths)
|
||||
result = _run_container("mandatory-folders", volumes, user="20211:20211", sleep_seconds=5)
|
||||
_assert_contains(result, "Creating Plugins log", result.args)
|
||||
# The container will fail at writable config due to permission issues, but we just want to verify
|
||||
# that mandatory folders creation ran successfully
|
||||
|
||||
|
||||
def test_writable_config_validation(tmp_path: pathlib.Path) -> None:
|
||||
"""Test writable config validation - simulates read-only config file.
|
||||
|
||||
3. Writable Config Validation: Simulates config file with read-only permissions.
|
||||
Container verifies it can read from and write to critical config and database files.
|
||||
Expected: "Read permission denied" warning for config file.
|
||||
|
||||
Check script: 30-writable-config.sh
|
||||
Sample message: "Read permission denied"
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "writable_config")
|
||||
# Make config file read-only but keep directories writable so container gets past mounts.py
|
||||
config_file = paths["app_config"] / "app.conf"
|
||||
config_file.chmod(0o400) # Read-only for owner
|
||||
|
||||
# Ensure directories are writable and owned by netalertx user so container gets past mounts.py
|
||||
for key in ["app_db", "app_config", "app_log", "app_api", "services_run", "nginx_conf"]:
|
||||
paths[key].chmod(0o777)
|
||||
_chown_netalertx(paths[key])
|
||||
|
||||
volumes = _build_volume_args(paths)
|
||||
result = _run_container("writable-config", volumes, user="20211:20211", sleep_seconds=5.0)
|
||||
_assert_contains(result, "Read permission denied", result.args)
|
||||
|
||||
|
||||
def test_excessive_capabilities_warning(tmp_path: pathlib.Path) -> None:
|
||||
"""Test excessive capabilities detection - simulates container with extra capabilities.
|
||||
|
||||
11. Excessive Capabilities: Simulates container with capabilities beyond the required
|
||||
NET_ADMIN, NET_RAW, and NET_BIND_SERVICE.
|
||||
Expected: Warning about excessive capabilities detected.
|
||||
|
||||
Check script: 90-excessive-capabilities.sh
|
||||
Sample message: "Excessive capabilities detected"
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "excessive_caps")
|
||||
volumes = _build_volume_args(paths)
|
||||
# Add excessive capabilities beyond the required ones
|
||||
result = _run_container(
|
||||
"tmpfs-db",
|
||||
"excessive-caps",
|
||||
volumes,
|
||||
extra_args=extra,
|
||||
extra_args=["--cap-add=SYS_ADMIN", "--cap-add=NET_BROADCAST"],
|
||||
sleep_seconds=5,
|
||||
)
|
||||
_assert_contains(result, "not a persistent mount.", result.args)
|
||||
_assert_contains(result, "/app/db", result.args)
|
||||
_assert_contains(result, "Excessive capabilities detected", result.args)
|
||||
_assert_contains(result, "bounding caps:", result.args)
|
||||
# This warning doesn't cause failure by itself, but other issues might
|
||||
def test_appliance_integrity_read_write_mode(tmp_path: pathlib.Path) -> None:
|
||||
"""Test appliance integrity - simulates running with read-write root filesystem.
|
||||
|
||||
12. Appliance Integrity: Simulates running container with read-write root filesystem
|
||||
instead of read-only mode.
|
||||
Expected: Warning about running in read-write mode instead of read-only.
|
||||
|
||||
Check script: 95-appliance-integrity.sh
|
||||
Sample message: "Container is running as read-write, not in read-only mode"
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "appliance_integrity")
|
||||
volumes = _build_volume_args(paths)
|
||||
# Container runs read-write by default (not mounting root as read-only)
|
||||
result = _run_container("appliance-integrity", volumes, sleep_seconds=5)
|
||||
_assert_contains(result, "Container is running as read-write, not in read-only mode", result.args)
|
||||
_assert_contains(result, "read-only: true", result.args)
|
||||
# This warning doesn't cause failure by itself, but other issues might
|
||||
|
||||
|
||||
def test_mount_analysis_ram_disk_performance(tmp_path: pathlib.Path) -> None:
|
||||
"""Test mount analysis for RAM disk performance issues.
|
||||
|
||||
Tests 10-mounts.py detection of persistent paths on RAM disks (tmpfs) which can cause
|
||||
performance issues and data loss on container restart.
|
||||
Expected: Mounts table shows ❌ for RAMDisk on persistent paths, performance warnings.
|
||||
|
||||
Check script: 10-mounts.py
|
||||
Sample message: "Configuration issues detected"
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "ram_disk_mount")
|
||||
# Mount persistent paths (db, config) on tmpfs to simulate RAM disk
|
||||
volumes = [
|
||||
(str(paths["app_log"]), "/app/log", False),
|
||||
(str(paths["app_api"]), "/app/api", False),
|
||||
(str(paths["services_run"]), "/services/run", False),
|
||||
(str(paths["nginx_conf"]), "/services/config/nginx/conf.active", False),
|
||||
]
|
||||
# Use tmpfs mounts for persistent paths with proper permissions
|
||||
extra_args = ["--tmpfs", "/app/db:uid=20211,gid=20211,mode=755", "--tmpfs", "/app/config:uid=20211,gid=20211,mode=755"]
|
||||
result = _run_container("ram-disk-mount", volumes=volumes, extra_args=extra_args, user="20211:20211")
|
||||
# Check that mounts table shows RAM disk detection for persistent paths
|
||||
_assert_contains(result, "/app/db | ✅ | ✅ | ❌ | ➖ | ❌", result.args)
|
||||
_assert_contains(result, "/app/config | ✅ | ✅ | ❌ | ➖ | ❌", result.args)
|
||||
# Check that configuration issues are detected due to dataloss risk
|
||||
_assert_contains(result, "Configuration issues detected", result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_mount_analysis_dataloss_risk(tmp_path: pathlib.Path) -> None:
|
||||
"""Test mount analysis for dataloss risk on non-persistent filesystems.
|
||||
|
||||
Tests 10-mounts.py detection when persistent database/config paths are
|
||||
mounted on non-persistent filesystems (tmpfs, ramfs).
|
||||
Expected: Mounts table shows dataloss risk warnings for persistent paths on tmpfs.
|
||||
|
||||
Check script: 10-mounts.py
|
||||
Sample message: "Configuration issues detected"
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "dataloss_risk")
|
||||
# Mount persistent paths (db, config) on tmpfs to simulate non-persistent storage
|
||||
volumes = [
|
||||
(str(paths["app_log"]), "/app/log", False),
|
||||
(str(paths["app_api"]), "/app/api", False),
|
||||
(str(paths["services_run"]), "/services/run", False),
|
||||
(str(paths["nginx_conf"]), "/services/config/nginx/conf.active", False),
|
||||
]
|
||||
# Use tmpfs mounts for persistent paths with proper permissions
|
||||
extra_args = ["--tmpfs", "/app/db:uid=20211,gid=20211,mode=755", "--tmpfs", "/app/config:uid=20211,gid=20211,mode=755"]
|
||||
result = _run_container("dataloss-risk", volumes=volumes, extra_args=extra_args, user="20211:20211")
|
||||
# Check that mounts table shows dataloss risk for persistent paths on tmpfs
|
||||
_assert_contains(result, "/app/db | ✅ | ✅ | ❌ | ➖ | ❌", result.args)
|
||||
_assert_contains(result, "/app/config | ✅ | ✅ | ❌ | ➖ | ❌", result.args)
|
||||
# Check that configuration issues are detected due to dataloss risk
|
||||
_assert_contains(result, "Configuration issues detected", result.args)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
|
||||
|
||||
440
test/docker_tests/test_docker_compose_scenarios.py
Normal file
440
test/docker_tests/test_docker_compose_scenarios.py
Normal file
@@ -0,0 +1,440 @@
|
||||
'''
|
||||
Docker Compose integration tests for NetAlertX startup scenarios.
|
||||
|
||||
This set of tests requires netalertx-test image built and docker compose.
|
||||
Ensure netalertx-test image is built prior to starting these tests.
|
||||
'''
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import time
|
||||
import pytest
|
||||
|
||||
IMAGE = os.environ.get("NETALERTX_TEST_IMAGE", "netalertx-test")
|
||||
|
||||
# Path to test configurations
|
||||
CONFIG_DIR = pathlib.Path(__file__).parent / "configurations"
|
||||
|
||||
pytestmark = [pytest.mark.docker, pytest.mark.compose]
|
||||
|
||||
|
||||
def _run_docker_compose(compose_file: pathlib.Path, project_name: str, timeout: int = 30, env_vars: dict = None) -> subprocess.CompletedProcess:
|
||||
"""Run docker compose up and capture output."""
|
||||
cmd = [
|
||||
"docker", "compose",
|
||||
"-f", str(compose_file),
|
||||
"-p", project_name,
|
||||
"up",
|
||||
"--abort-on-container-exit",
|
||||
"--timeout", str(timeout)
|
||||
]
|
||||
|
||||
env = os.environ.copy()
|
||||
if env_vars:
|
||||
env.update(env_vars)
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=compose_file.parent,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=timeout + 10,
|
||||
env=env,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
# Clean up on timeout
|
||||
subprocess.run(["docker", "compose", "-f", str(compose_file), "-p", project_name, "down", "-v"],
|
||||
cwd=compose_file.parent, check=False)
|
||||
raise
|
||||
|
||||
# Always clean up
|
||||
subprocess.run(["docker", "compose", "-f", str(compose_file), "-p", project_name, "down", "-v"],
|
||||
cwd=compose_file.parent, check=False)
|
||||
|
||||
# Combine stdout and stderr
|
||||
result.output = result.stdout + result.stderr
|
||||
return result
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
IMAGE = os.environ.get("NETALERTX_TEST_IMAGE", "netalertx-test")
|
||||
|
||||
# Docker Compose configurations for different test scenarios
|
||||
COMPOSE_CONFIGS = {
|
||||
"missing_capabilities": {
|
||||
"services": {
|
||||
"netalertx": {
|
||||
"image": IMAGE,
|
||||
"network_mode": "host",
|
||||
"userns_mode": "host",
|
||||
"cap_drop": ["ALL"], # Drop all capabilities
|
||||
"tmpfs": ["/tmp:mode=777"],
|
||||
"volumes": [
|
||||
"./test_data/app_db:/app/db",
|
||||
"./test_data/app_config:/app/config",
|
||||
"./test_data/app_log:/app/log",
|
||||
"./test_data/app_api:/app/api",
|
||||
"./test_data/nginx_conf:/services/config/nginx/conf.active",
|
||||
"./test_data/services_run:/services/run"
|
||||
],
|
||||
"environment": {
|
||||
"TZ": "UTC"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"host_network": {
|
||||
"services": {
|
||||
"netalertx": {
|
||||
"image": IMAGE,
|
||||
"network_mode": "host",
|
||||
"userns_mode": "host",
|
||||
"cap_add": ["NET_RAW", "NET_ADMIN", "NET_BIND_SERVICE"],
|
||||
"tmpfs": ["/tmp:mode=777"],
|
||||
"volumes": [
|
||||
"./test_data/app_db:/app/db",
|
||||
"./test_data/app_config:/app/config",
|
||||
"./test_data/app_log:/app/log",
|
||||
"./test_data/app_api:/app/api",
|
||||
"./test_data/nginx_conf:/services/config/nginx/conf.active",
|
||||
"./test_data/services_run:/services/run"
|
||||
],
|
||||
"environment": {
|
||||
"TZ": "UTC"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"normal_startup": {
|
||||
"services": {
|
||||
"netalertx": {
|
||||
"image": IMAGE,
|
||||
"network_mode": "host",
|
||||
"userns_mode": "host",
|
||||
"cap_add": ["NET_RAW", "NET_ADMIN", "NET_BIND_SERVICE"],
|
||||
"user": "20211:20211",
|
||||
"tmpfs": ["/tmp:mode=777"],
|
||||
"volumes": [
|
||||
"./test_data/app_db:/app/db",
|
||||
"./test_data/app_config:/app/config",
|
||||
"./test_data/app_log:/app/log",
|
||||
"./test_data/app_api:/app/api",
|
||||
"./test_data/nginx_conf:/services/config/nginx/conf.active",
|
||||
"./test_data/services_run:/services/run"
|
||||
],
|
||||
"environment": {
|
||||
"TZ": "UTC"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pytestmark = [pytest.mark.docker, pytest.mark.compose]
|
||||
|
||||
|
||||
def _create_test_data_dirs(base_dir: pathlib.Path) -> None:
|
||||
"""Create test data directories with proper permissions."""
|
||||
dirs = ["app_db", "app_config", "app_log", "app_api", "nginx_conf", "services_run"]
|
||||
for dir_name in dirs:
|
||||
dir_path = base_dir / "test_data" / dir_name
|
||||
dir_path.mkdir(parents=True, exist_ok=True)
|
||||
dir_path.chmod(0o755)
|
||||
|
||||
# Create basic config file
|
||||
config_file = base_dir / "test_data" / "app_config" / "app.conf"
|
||||
if not config_file.exists():
|
||||
config_file.write_text("# Test configuration\n")
|
||||
|
||||
# Create basic db file
|
||||
db_file = base_dir / "test_data" / "app_db" / "app.db"
|
||||
if not db_file.exists():
|
||||
# Create a minimal SQLite database
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(str(db_file))
|
||||
conn.close()
|
||||
|
||||
|
||||
def _run_docker_compose(compose_file: pathlib.Path, project_name: str, timeout: int = 30, env_vars: dict = None) -> subprocess.CompletedProcess:
|
||||
"""Run docker compose up and capture output."""
|
||||
cmd = [
|
||||
"docker", "compose",
|
||||
"-f", str(compose_file),
|
||||
"-p", project_name,
|
||||
"up",
|
||||
"--abort-on-container-exit",
|
||||
"--timeout", str(timeout)
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=compose_file.parent,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=timeout + 10,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
# Clean up on timeout
|
||||
subprocess.run(["docker", "compose", "-f", str(compose_file), "-p", project_name, "down", "-v"],
|
||||
cwd=compose_file.parent, check=False)
|
||||
raise
|
||||
|
||||
# Always clean up
|
||||
subprocess.run(["docker", "compose", "-f", str(compose_file), "-p", project_name, "down", "-v"],
|
||||
cwd=compose_file.parent, check=False)
|
||||
|
||||
# Combine stdout and stderr
|
||||
result.output = result.stdout + result.stderr
|
||||
return result
|
||||
|
||||
|
||||
def test_missing_capabilities_compose() -> None:
|
||||
"""Test missing required capabilities using docker compose.
|
||||
|
||||
Uses docker-compose.missing-caps.yml which drops all capabilities.
|
||||
Expected: "exec /bin/sh: operation not permitted" error.
|
||||
"""
|
||||
compose_file = CONFIG_DIR / "docker-compose.missing-caps.yml"
|
||||
result = _run_docker_compose(compose_file, "netalertx-missing-caps")
|
||||
|
||||
# Check for expected error
|
||||
assert "exec /bin/sh: operation not permitted" in result.output
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_host_network_compose(tmp_path: pathlib.Path) -> None:
|
||||
"""Test host networking mode using docker compose.
|
||||
|
||||
Simulates running with network_mode: host.
|
||||
Expected: Container starts successfully with host networking.
|
||||
"""
|
||||
base_dir = tmp_path / "host_network"
|
||||
base_dir.mkdir()
|
||||
|
||||
# Create test data directories
|
||||
_create_test_data_dirs(base_dir)
|
||||
|
||||
# Create compose file
|
||||
compose_config = COMPOSE_CONFIGS["host_network"].copy()
|
||||
compose_file = base_dir / "docker-compose.yml"
|
||||
with open(compose_file, 'w') as f:
|
||||
yaml.dump(compose_config, f)
|
||||
|
||||
# Run docker compose
|
||||
result = _run_docker_compose(compose_file, "netalertx-host-net")
|
||||
|
||||
# Check that it doesn't fail with network-related errors
|
||||
assert "not running with --network=host" not in result.output
|
||||
# Container should start (may fail later for other reasons, but network should be OK)
|
||||
|
||||
|
||||
def test_host_network_compose() -> None:
|
||||
"""Test host networking mode using docker compose.
|
||||
|
||||
Uses docker-compose.readonly.yml with host networking.
|
||||
Expected: Container starts successfully with host networking.
|
||||
"""
|
||||
compose_file = CONFIG_DIR / "docker-compose.readonly.yml"
|
||||
result = _run_docker_compose(compose_file, "netalertx-host-net")
|
||||
|
||||
# Check that it doesn't fail with network-related errors
|
||||
assert "not running with --network=host" not in result.output
|
||||
# Container should start (may fail later for other reasons, but network should be OK)
|
||||
|
||||
|
||||
def test_custom_port_with_unwritable_nginx_config_compose() -> None:
|
||||
"""Test custom port configuration with unwritable nginx config using docker compose.
|
||||
|
||||
Uses docker-compose.mount-test.active_config_unwritable.yml with PORT=24444.
|
||||
Expected: Container shows warning about unable to write nginx config.
|
||||
"""
|
||||
compose_file = CONFIG_DIR / "mount-tests" / "docker-compose.mount-test.active_config_unwritable.yml"
|
||||
result = _run_docker_compose(compose_file, "netalertx-custom-port", env_vars={"PORT": "24444"})
|
||||
|
||||
# Check for nginx config write failure warning
|
||||
assert "Unable to write to /services/config/nginx/conf.active/netalertx.conf" in result.output
|
||||
# Container should still attempt to start but may fail for other reasons
|
||||
# The key is that the nginx config write warning appears
|
||||
|
||||
|
||||
def test_host_network_compose(tmp_path: pathlib.Path) -> None:
|
||||
"""Test host networking mode using docker compose.
|
||||
|
||||
Simulates running with network_mode: host.
|
||||
Expected: Container starts successfully with host networking.
|
||||
"""
|
||||
base_dir = tmp_path / "host_network"
|
||||
base_dir.mkdir()
|
||||
|
||||
# Create test data directories
|
||||
_create_test_data_dirs(base_dir)
|
||||
|
||||
# Create compose file
|
||||
compose_config = COMPOSE_CONFIGS["host_network"].copy()
|
||||
compose_file = base_dir / "docker-compose.yml"
|
||||
with open(compose_file, 'w') as f:
|
||||
yaml.dump(compose_config, f)
|
||||
|
||||
# Run docker compose
|
||||
result = _run_docker_compose(compose_file, "netalertx-host-net")
|
||||
|
||||
# Check that it doesn't fail with network-related errors
|
||||
assert "not running with --network=host" not in result.output
|
||||
# Container should start (may fail later for other reasons, but network should be OK)
|
||||
|
||||
|
||||
def test_normal_startup_no_warnings_compose(tmp_path: pathlib.Path) -> None:
|
||||
"""Test normal startup with expected warnings using docker compose.
|
||||
|
||||
Simulates proper configuration with all required settings.
|
||||
Expected: Container starts and shows expected warnings with pipe characters (═).
|
||||
This demonstrates what a "normal" startup looks like with warnings.
|
||||
"""
|
||||
base_dir = tmp_path / "normal_startup"
|
||||
base_dir.mkdir()
|
||||
|
||||
# Create test data directories with proper permissions
|
||||
_create_test_data_dirs(base_dir)
|
||||
|
||||
# Make sure directories are writable by netalertx user
|
||||
for dir_name in ["app_db", "app_config", "app_log", "app_api", "nginx_conf", "services_run"]:
|
||||
dir_path = base_dir / "test_data" / dir_name
|
||||
dir_path.chmod(0o777) # Allow all users to write
|
||||
|
||||
# Create compose file
|
||||
compose_config = COMPOSE_CONFIGS["normal_startup"].copy()
|
||||
compose_file = base_dir / "docker-compose.yml"
|
||||
with open(compose_file, 'w') as f:
|
||||
yaml.dump(compose_config, f)
|
||||
|
||||
# Run docker compose
|
||||
result = _run_docker_compose(compose_file, "netalertx-normal")
|
||||
|
||||
# Check that expected warnings with pipe characters appear
|
||||
# These are the typical warnings that appear in a "normal" startup
|
||||
assert "⚠️ Warning: Excessive capabilities detected" in result.output
|
||||
assert "⚠️ Warning: Container is running as read-write" in result.output
|
||||
assert "═══" in result.output # Box drawing characters in warnings
|
||||
|
||||
# Should not have critical permission errors (these indicate test setup issues)
|
||||
assert "Write permission denied" not in result.output
|
||||
assert "CRITICAL" in result.output # CRITICAL messages are expected when permissions fail
|
||||
|
||||
|
||||
def test_ram_disk_mount_analysis_compose(tmp_path: pathlib.Path) -> None:
|
||||
"""Test mount analysis for RAM disk detection using docker compose.
|
||||
|
||||
Simulates mounting persistent paths on tmpfs (RAM disk).
|
||||
Expected: Mounts table shows ❌ for RAMDisk on persistent paths, dataloss warnings.
|
||||
"""
|
||||
base_dir = tmp_path / "ram_disk_test"
|
||||
base_dir.mkdir()
|
||||
|
||||
# Create test data directories
|
||||
_create_test_data_dirs(base_dir)
|
||||
|
||||
# Create compose file with tmpfs mounts for persistent paths
|
||||
compose_config = {
|
||||
"services": {
|
||||
"netalertx": {
|
||||
"image": IMAGE,
|
||||
"network_mode": "host",
|
||||
"userns_mode": "host",
|
||||
"cap_add": ["NET_RAW", "NET_ADMIN", "NET_BIND_SERVICE"],
|
||||
"user": "20211:20211",
|
||||
"tmpfs": [
|
||||
"/tmp:mode=777",
|
||||
"/app/db", # RAM disk for persistent DB
|
||||
"/app/config" # RAM disk for persistent config
|
||||
],
|
||||
"volumes": [
|
||||
f"./test_data/app_log:/app/log",
|
||||
f"./test_data/app_api:/app/api",
|
||||
f"./test_data/nginx_conf:/services/config/nginx/conf.active",
|
||||
f"./test_data/services_run:/services/run"
|
||||
],
|
||||
"environment": {
|
||||
"TZ": "UTC"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose_file = base_dir / "docker-compose.yml"
|
||||
with open(compose_file, 'w') as f:
|
||||
yaml.dump(compose_config, f)
|
||||
|
||||
# Run docker compose
|
||||
result = _run_docker_compose(compose_file, "netalertx-ram-disk")
|
||||
|
||||
# Check that mounts table shows RAM disk detection and dataloss warnings
|
||||
assert "Configuration issues detected" in result.output
|
||||
assert "/app/db" in result.output
|
||||
assert "/app/config" in result.output
|
||||
assert result.returncode != 0 # Should fail due to dataloss risk
|
||||
|
||||
|
||||
def test_dataloss_risk_mount_analysis_compose(tmp_path: pathlib.Path) -> None:
|
||||
"""Test mount analysis for dataloss risk using docker compose.
|
||||
|
||||
Simulates mounting persistent paths on non-persistent tmpfs.
|
||||
Expected: Mounts table shows dataloss risk warnings for persistent paths.
|
||||
"""
|
||||
base_dir = tmp_path / "dataloss_test"
|
||||
base_dir.mkdir()
|
||||
|
||||
# Create test data directories
|
||||
_create_test_data_dirs(base_dir)
|
||||
|
||||
# Create compose file with tmpfs for persistent data
|
||||
compose_config = {
|
||||
"services": {
|
||||
"netalertx": {
|
||||
"image": IMAGE,
|
||||
"network_mode": "host",
|
||||
"userns_mode": "host",
|
||||
"cap_add": ["NET_RAW", "NET_ADMIN", "NET_BIND_SERVICE"],
|
||||
"user": "20211:20211",
|
||||
"tmpfs": [
|
||||
"/tmp:mode=777",
|
||||
"/app/db:uid=20211,gid=20211", # Non-persistent for DB
|
||||
"/app/config:uid=20211,gid=20211" # Non-persistent for config
|
||||
],
|
||||
"volumes": [
|
||||
f"./test_data/app_log:/app/log",
|
||||
f"./test_data/app_api:/app/api",
|
||||
f"./test_data/nginx_conf:/services/config/nginx/conf.active",
|
||||
f"./test_data/services_run:/services/run"
|
||||
],
|
||||
"environment": {
|
||||
"TZ": "UTC"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose_file = base_dir / "docker-compose.yml"
|
||||
with open(compose_file, 'w') as f:
|
||||
yaml.dump(compose_config, f)
|
||||
|
||||
# Run docker compose
|
||||
result = _run_docker_compose(compose_file, "netalertx-dataloss")
|
||||
|
||||
# Check that mounts table shows dataloss risk detection
|
||||
assert "Configuration issues detected" in result.output
|
||||
assert "/app/db" in result.output
|
||||
assert "/app/config" in result.output
|
||||
assert result.returncode != 0 # Should fail due to dataloss risk
|
||||
@@ -149,6 +149,7 @@ class TestScenario:
|
||||
is_persistent: bool
|
||||
docker_compose: str
|
||||
expected_issues: List[str] # List of expected issue types
|
||||
expected_exit_code: int # Expected container exit code
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def netalertx_test_image():
|
||||
@@ -209,13 +210,17 @@ def create_test_scenarios() -> List[TestScenario]:
|
||||
|
||||
compose_file = f"docker-compose.mount-test.{path_name}_{scenario_name}.yml"
|
||||
|
||||
# Determine expected exit code
|
||||
expected_exit_code = 1 if scenario_name == "unwritable" else 0
|
||||
|
||||
scenarios.append(TestScenario(
|
||||
name=f"{path_name}_{scenario_name}",
|
||||
path_var=env_var,
|
||||
container_path=container_path,
|
||||
is_persistent=is_persistent,
|
||||
docker_compose=compose_file,
|
||||
expected_issues=expected_issues
|
||||
expected_issues=expected_issues,
|
||||
expected_exit_code=expected_exit_code
|
||||
))
|
||||
|
||||
return scenarios
|
||||
@@ -246,7 +251,7 @@ def test_mount_diagnostic(netalertx_test_image, test_scenario):
|
||||
try:
|
||||
# Wait for container to be ready
|
||||
import time
|
||||
time.sleep(5)
|
||||
time.sleep(3)
|
||||
|
||||
# Check if container is still running
|
||||
container_name = f"netalertx-test-mount-{test_scenario.name}"
|
||||
@@ -256,7 +261,18 @@ def test_mount_diagnostic(netalertx_test_image, test_scenario):
|
||||
)
|
||||
|
||||
if not result_ps.stdout.strip():
|
||||
# Container exited - this is expected for configurations with issues
|
||||
# Container exited - check the exit code
|
||||
result_inspect = subprocess.run(
|
||||
["docker", "inspect", container_name, "--format={{.State.ExitCode}}"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
actual_exit_code = int(result_inspect.stdout.strip())
|
||||
|
||||
# Assert the exit code matches expected
|
||||
assert actual_exit_code == test_scenario.expected_exit_code, (
|
||||
f"Container {container_name} exited with code {actual_exit_code}, "
|
||||
f"expected {test_scenario.expected_exit_code}"
|
||||
)
|
||||
# Check the logs to see if it detected the expected issues
|
||||
result_logs = subprocess.run(
|
||||
["docker", "logs", container_name],
|
||||
|
||||
240
test/docker_tests/test_ports_available.py
Normal file
240
test/docker_tests/test_ports_available.py
Normal file
@@ -0,0 +1,240 @@
|
||||
'''
|
||||
Tests for 99-ports-available.sh entrypoint script.
|
||||
This script checks for port conflicts and availability.
|
||||
'''
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import time
|
||||
import pytest
|
||||
|
||||
IMAGE = os.environ.get("NETALERTX_TEST_IMAGE", "netalertx-test")
|
||||
GRACE_SECONDS = float(os.environ.get("NETALERTX_TEST_GRACE", "2"))
|
||||
|
||||
VOLUME_MAP = {
|
||||
"app_db": "/app/db",
|
||||
"app_config": "/app/config",
|
||||
"app_log": "/app/log",
|
||||
"app_api": "/app/api",
|
||||
"nginx_conf": "/services/config/nginx/conf.active",
|
||||
"services_run": "/services/run",
|
||||
}
|
||||
|
||||
pytestmark = [pytest.mark.docker, pytest.mark.feature_complete]
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def dummy_container(tmp_path):
|
||||
"""Fixture that starts a dummy container to occupy ports for testing."""
|
||||
# Create a simple docker-compose file for the dummy container
|
||||
compose_file = tmp_path / "docker-compose-dummy.yml"
|
||||
with open(compose_file, 'w') as f:
|
||||
f.write("version: '3.8'\n")
|
||||
f.write("services:\n")
|
||||
f.write(" dummy:\n")
|
||||
f.write(" image: alpine:latest\n")
|
||||
f.write(" network_mode: host\n")
|
||||
f.write(" userns_mode: host\n")
|
||||
f.write(" command: sh -c \"while true; do nc -l -p 20211 < /dev/null > /dev/null; done & while true; do nc -l -p 20212 < /dev/null > /dev/null; done & sleep 30\"\n")
|
||||
|
||||
# Start the dummy container
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["docker-compose", "-f", str(compose_file), "up", "-d"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
pytest.fail(f"Failed to start dummy container: {result.stderr}")
|
||||
|
||||
# Wait a bit for the container to start listening
|
||||
time.sleep(3)
|
||||
|
||||
yield "dummy"
|
||||
|
||||
# Cleanup
|
||||
subprocess.run(["docker-compose", "-f", str(compose_file), "down"], capture_output=True)
|
||||
|
||||
|
||||
def _setup_mount_tree(tmp_path: pathlib.Path, label: str) -> dict[str, pathlib.Path]:
|
||||
"""Set up mount tree for testing."""
|
||||
import uuid
|
||||
import shutil
|
||||
|
||||
base = tmp_path / f"{label}_mount_root"
|
||||
if base.exists():
|
||||
shutil.rmtree(base)
|
||||
base.mkdir(parents=True)
|
||||
|
||||
paths = {}
|
||||
for key, target in VOLUME_MAP.items():
|
||||
folder_name = f"{label}_{key.upper()}_INTENTIONAL_NETALERTX_TEST"
|
||||
host_path = base / folder_name
|
||||
host_path.mkdir(parents=True, exist_ok=True)
|
||||
host_path.chmod(0o777)
|
||||
paths[key] = host_path
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
def _build_volume_args(paths: dict[str, pathlib.Path]) -> list[tuple[str, str, bool]]:
|
||||
"""Build volume arguments for docker run."""
|
||||
bindings = []
|
||||
for key, target in VOLUME_MAP.items():
|
||||
bindings.append((str(paths[key]), target, False))
|
||||
return bindings
|
||||
|
||||
|
||||
def _run_container(
|
||||
label: str,
|
||||
volumes: list[tuple[str, str, bool]] | None = None,
|
||||
*,
|
||||
env: dict[str, str] | None = None,
|
||||
user: str | None = None,
|
||||
network_mode: str | None = "host",
|
||||
extra_args: list[str] | None = None,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
"""Run a container and return the result."""
|
||||
import uuid
|
||||
import re
|
||||
|
||||
name = f"netalertx-test-{label}-{uuid.uuid4().hex[:8]}".lower()
|
||||
cmd = ["docker", "run", "--rm", "--name", name]
|
||||
|
||||
if network_mode:
|
||||
cmd.extend(["--network", network_mode])
|
||||
cmd.extend(["--userns", "host"])
|
||||
cmd.extend(["--tmpfs", "/tmp:mode=777"])
|
||||
if user:
|
||||
cmd.extend(["--user", user])
|
||||
if env:
|
||||
for key, value in env.items():
|
||||
cmd.extend(["-e", f"{key}={value}"])
|
||||
if extra_args:
|
||||
cmd.extend(extra_args)
|
||||
for host_path, target, readonly in volumes or []:
|
||||
mount = f"{host_path}:{target}"
|
||||
if readonly:
|
||||
mount += ":ro"
|
||||
cmd.extend(["-v", mount])
|
||||
|
||||
# Copy the script content and run it
|
||||
script_path = "/workspaces/NetAlertX/install/production-filesystem/entrypoint.d/99-ports-available.sh"
|
||||
with open(script_path, 'r') as f:
|
||||
script_content = f.read()
|
||||
|
||||
# Use printf to avoid shell interpretation issues
|
||||
script = f"printf '%s\\n' '{script_content.replace(chr(39), chr(39)+chr(92)+chr(39)+chr(39))}' > /tmp/ports-check.sh && chmod +x /tmp/ports-check.sh && sh /tmp/ports-check.sh"
|
||||
cmd.extend(["--entrypoint", "/bin/sh", IMAGE, "-c", script])
|
||||
|
||||
print(f"\n--- DOCKER CMD ---\n{' '.join(cmd)}\n--- END CMD ---\n")
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=30,
|
||||
check=False,
|
||||
)
|
||||
|
||||
# Combine and clean stdout and stderr
|
||||
stdouterr = (
|
||||
re.sub(r'\x1b\[[0-9;]*m', '', result.stdout or '') +
|
||||
re.sub(r'\x1b\[[0-9;]*m', '', result.stderr or '')
|
||||
)
|
||||
result.output = stdouterr
|
||||
print(f"\n--- CONTAINER stdout ---\n{result.stdout}")
|
||||
print(f"\n--- CONTAINER stderr ---\n{result.stderr}")
|
||||
print(f"\n--- CONTAINER combined ---\n{result.output}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _assert_contains(result, snippet: str, cmd: list[str] = None) -> None:
|
||||
"""Assert that the result output contains the given snippet."""
|
||||
if snippet not in result.output:
|
||||
cmd_str = " ".join(cmd) if cmd else ""
|
||||
raise AssertionError(
|
||||
f"Expected to find '{snippet}' in container output.\n"
|
||||
f"Got:\n{result.output}\n"
|
||||
f"Container command:\n{cmd_str}"
|
||||
)
|
||||
|
||||
|
||||
def _assert_not_contains(result, snippet: str, cmd: list[str] = None) -> None:
|
||||
"""Assert that the result output does not contain the given snippet."""
|
||||
if snippet in result.output:
|
||||
cmd_str = " ".join(cmd) if cmd else ""
|
||||
raise AssertionError(
|
||||
f"Expected NOT to find '{snippet}' in container output.\n"
|
||||
f"Got:\n{result.output}\n"
|
||||
f"Container command:\n{cmd_str}"
|
||||
)
|
||||
|
||||
|
||||
def test_ports_available_normal_case(tmp_path: pathlib.Path) -> None:
|
||||
"""Test ports available script with default ports (should pass without warnings).
|
||||
|
||||
99. Ports Available Check: Tests that the script runs without warnings
|
||||
when ports 20211 and 20212 are available and not conflicting.
|
||||
Expected: No warnings about port conflicts or ports in use.
|
||||
|
||||
Check script: 99-ports-available.sh
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "ports_normal")
|
||||
volumes = _build_volume_args(paths)
|
||||
result = _run_container("ports-normal", volumes, user="20211:20211", env={"PORT": "99991", "GRAPHQL_PORT": "99992"})
|
||||
|
||||
# Should not contain any port warnings
|
||||
_assert_not_contains(result, "Configuration Warning: Both ports are set to")
|
||||
_assert_not_contains(result, "Port Warning: Application port")
|
||||
_assert_not_contains(result, "Port Warning: GraphQL API port")
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
def test_ports_conflict_same_number(tmp_path: pathlib.Path) -> None:
|
||||
"""Test ports available script when both ports are set to the same number.
|
||||
|
||||
99. Ports Available Check: Tests warning when PORT and GRAPHQL_PORT
|
||||
are configured to the same value.
|
||||
Expected: Warning about port conflict.
|
||||
|
||||
Check script: 99-ports-available.sh
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "ports_conflict")
|
||||
volumes = _build_volume_args(paths)
|
||||
result = _run_container(
|
||||
"ports-conflict",
|
||||
volumes,
|
||||
user="20211:20211",
|
||||
env={"PORT": "20211", "GRAPHQL_PORT": "20211"}
|
||||
)
|
||||
|
||||
_assert_contains(result, "Configuration Warning: Both ports are set to 20211")
|
||||
_assert_contains(result, "The Application port ($PORT) and the GraphQL API port")
|
||||
_assert_contains(result, "are configured to use the")
|
||||
_assert_contains(result, "same port. This will cause a conflict.")
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
def test_ports_in_use_warning(dummy_container, tmp_path: pathlib.Path) -> None:
|
||||
"""Test ports available script when ports are already in use.
|
||||
|
||||
99. Ports Available Check: Tests warning when configured ports
|
||||
are already bound by another process.
|
||||
Expected: Warning about ports being in use.
|
||||
|
||||
Check script: 99-ports-available.sh
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "ports_in_use")
|
||||
volumes = _build_volume_args(paths)
|
||||
result = _run_container(
|
||||
"ports-in-use",
|
||||
volumes,
|
||||
user="20211:20211",
|
||||
env={"PORT": "20211", "GRAPHQL_PORT": "20212"}
|
||||
)
|
||||
|
||||
_assert_contains(result, "Port Warning: Application port 20211 is already in use")
|
||||
_assert_contains(result, "Port Warning: GraphQL API port 20212 is already in use")
|
||||
assert result.returncode == 0
|
||||
Reference in New Issue
Block a user