From c12598515072c8ff3e09feba028ed3391cbf13de Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Thu, 12 Feb 2026 18:48:24 -0500 Subject: [PATCH] Refactor Docker Compose E2E tests: consolidate file creation and optimize build strategy - Added `create_cloudflare_ips_file` to centralize `cloudflare_ips.lst` creation and avoid redundant logic. - Introduced a global flag `_docker_image_built` for smart Docker image build management. - Improved test output readability with refined print formatting. - Simplified `docker_compose_cloud --- tests_e2e/test_docker_compose.py | 115 +++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 35 deletions(-) diff --git a/tests_e2e/test_docker_compose.py b/tests_e2e/test_docker_compose.py index 247840d..44061da 100644 --- a/tests_e2e/test_docker_compose.py +++ b/tests_e2e/test_docker_compose.py @@ -38,19 +38,78 @@ from utils import extract_backend_block # Base directory for docker-compose files BASE_DIR = Path(__file__).parent.absolute() +# Track if cloudflare_ips.lst has been created in this test session +_cloudflare_ips_created = False + +# Track if Docker image has been built in this test session +_docker_image_built = False + + +def create_cloudflare_ips_file(): + """ + Create cloudflare_ips.lst file with Cloudflare IP ranges and Docker network. + + This file is required by docker-compose files that use the Cloudflare plugin. + Downloads real Cloudflare IPs and adds Docker private network for testing. + + Strategy: + - First call: Always create (fresh download) + - Subsequent calls: Skip if file exists (reuse from first call) + """ + global _cloudflare_ips_created + + cloudflare_ips_path = BASE_DIR / "docker" / "cloudflare_ips.lst" + + # On subsequent calls, skip if file exists + if _cloudflare_ips_created and cloudflare_ips_path.exists() and cloudflare_ips_path.is_file(): + return + + # Download Cloudflare IPv4 ranges + subprocess.run( + ["curl", "-s", "https://www.cloudflare.com/ips-v4"], + stdout=open(cloudflare_ips_path, 'w'), + check=True + ) + with open(cloudflare_ips_path, 'a') as f: + f.write("\n") + + # Download Cloudflare IPv6 ranges + subprocess.run( + ["curl", "-s", "https://www.cloudflare.com/ips-v6"], + stdout=open(cloudflare_ips_path, 'a'), + check=True + ) + + # Add Docker private network range so HAProxy treats test requests as from Cloudflare + with open(cloudflare_ips_path, 'a') as f: + f.write("\n") + f.write("172.16.0.0/12\n") # Docker bridge networks are typically in this range + + # Mark as created for this test session + _cloudflare_ips_created = True + class DockerComposeFixture: """Helper class to manage docker-compose lifecycle""" - def __init__(self, compose_file: str, startup_wait: int = 3, build: bool = True): + def __init__(self, compose_file: str, startup_wait: int = 3, build: bool = None): self.compose_file = str(BASE_DIR / "docker" / compose_file) self.startup_wait = startup_wait - self.build = build + + # Smart build strategy: build on first call, skip on subsequent calls + global _docker_image_built + if build is None: + self.build = not _docker_image_built + else: + self.build = build def up(self): """Start docker-compose services""" + global _docker_image_built + compose_name = Path(self.compose_file).name - print(f"\n[Docker] Starting services from {compose_name}...") + print() # Newline for better test output formatting + print(f" → Starting services from {compose_name}...") cmd = ["docker", "compose", "-f", self.compose_file, "up", "-d"] if self.build: @@ -63,19 +122,23 @@ class DockerComposeFixture: ) if result.returncode != 0: - print(f"[Docker] ERROR: Failed to start services!") - print(f"[Docker] stdout: {result.stdout}") - print(f"[Docker] stderr: {result.stderr}") + print(f" ✗ ERROR: Failed to start services!") + print(f" stdout: {result.stdout}") + print(f" stderr: {result.stderr}") raise subprocess.CalledProcessError(result.returncode, cmd, result.stdout, result.stderr) - print(f"[Docker] ✓ Services started, waiting {self.startup_wait}s for initialization...") + # Mark image as built for this test session + if self.build: + _docker_image_built = True + + print(f" ✓ Services started, waiting {self.startup_wait}s for initialization...") time.sleep(self.startup_wait) - print(f"[Docker] ✓ Services ready") + print(f" ✓ Services ready") def down(self): """Stop and remove docker-compose services""" compose_name = Path(self.compose_file).name - print(f"[Docker] Stopping services from {compose_name}...") + print(f" → Stopping services from {compose_name}...") result = subprocess.run( ["docker", "compose", "-f", self.compose_file, "down", "--remove-orphans"], @@ -84,11 +147,11 @@ class DockerComposeFixture: ) if result.returncode != 0: - print(f"[Docker] WARNING: Failed to stop services cleanly") - print(f"[Docker] stderr: {result.stderr}") + print(f" ⚠ WARNING: Failed to stop services cleanly") + print(f" stderr: {result.stderr}") # Don't raise error on cleanup, just warn else: - print(f"[Docker] ✓ Services stopped and cleaned up") + print(f" ✓ Services stopped and cleaned up") @pytest.fixture @@ -130,6 +193,9 @@ def docker_compose_php_fpm() -> Generator[None, None, None]: @pytest.fixture def docker_compose_plugins_combined() -> Generator[None, None, None]: """Fixture for docker-compose-plugins-combined.yml""" + # Create cloudflare_ips.lst (required by this compose file) + create_cloudflare_ips_file() + fixture = DockerComposeFixture("docker-compose-plugins-combined.yml") fixture.up() yield @@ -148,29 +214,8 @@ def docker_compose_ip_whitelist() -> Generator[None, None, None]: @pytest.fixture def docker_compose_cloudflare() -> Generator[None, None, None]: """Fixture for docker-compose-cloudflare.yml""" - # Set up cloudflare_ips.lst with Docker network for testing - cloudflare_ips_path = BASE_DIR / "docker" / "cloudflare_ips.lst" - - # Download Cloudflare IPs - subprocess.run( - ["curl", "-s", "https://www.cloudflare.com/ips-v4"], - stdout=open(cloudflare_ips_path, 'w'), - check=True - ) - with open(cloudflare_ips_path, 'a') as f: - f.write("\n") - - subprocess.run( - ["curl", "-s", "https://www.cloudflare.com/ips-v6"], - stdout=open(cloudflare_ips_path, 'a'), - check=True - ) - - # Add Docker private network range so HAProxy treats test requests as from Cloudflare - # Docker bridge networks are typically in 172.16.0.0/12 range - with open(cloudflare_ips_path, 'a') as f: - f.write("\n") - f.write("172.16.0.0/12\n") # Docker private network range + # Create cloudflare_ips.lst (required by this compose file) + create_cloudflare_ips_file() fixture = DockerComposeFixture("docker-compose-cloudflare.yml") fixture.up()