diff --git a/.gitea/workflows/workflow.yaml b/.gitea/workflows/workflow.yaml index a30f514..2105173 100644 --- a/.gitea/workflows/workflow.yaml +++ b/.gitea/workflows/workflow.yaml @@ -12,6 +12,7 @@ env: REMOTE_DEPLOY_PATH: /var/app/traefik/test REMOTE_PROD_PATH: /var/app/traefik/prod REMOTE_STAGING_PATH: /var/app/traefik/staging + REMOTE_PREPROD_PATH: /var/app/traefik/preprod # --- SECRETS --- SSH_HOST: ${{ secrets.SSH_HOST }} @@ -93,13 +94,75 @@ jobs: cd ${{ env.REMOTE_STAGING_PATH }} docker compose --env-file staging.env -f docker-compose.yaml up -d --build --remove-orphans --wait + # ------------------------------------------------------------------ + # STAGE 3.5: DEPLOY PRE-PROD + # ------------------------------------------------------------------ + deploy_preprod: + name: Deploy (Pre-Prod) + runs-on: ubuntu-latest + # needs: [deploy_staging] + # if: github.ref == 'refs/heads/test_preprod' + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + + - name: Deploy via Rsync & Docker + uses: easingthemes/ssh-deploy@a1aa0b6cf96ce2406eef90faa35007a4a7bf0ac0 # v5.1.1 + env: + SSH_PRIVATE_KEY: ${{ env.SSH_PRIVATE_KEY }} + REMOTE_HOST: ${{ env.SSH_HOST }} + REMOTE_USER: ${{ env.SSH_USER }} + REMOTE_PORT: ${{ env.SSH_PORT }} + TARGET: ${{ env.REMOTE_PREPROD_PATH }} + EXCLUDE: ".git/, .github/" + SCRIPT_BEFORE: | + mkdir -p ${{ env.REMOTE_PREPROD_PATH }} + SCRIPT_AFTER: | + set -e + cd ${{ env.REMOTE_PREPROD_PATH }} + docker compose --env-file preprod.env -f docker-compose.yaml -f docker-compose.prod.yaml up -d --build --remove-orphans --wait + + - name: Run E2E Tests + uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4 + with: + host: ${{ env.SSH_HOST }} + username: ${{ env.SSH_USER }} + key: ${{ env.SSH_PRIVATE_KEY }} + port: ${{ env.SSH_PORT }} + script: | + set -e + cd ${{ env.REMOTE_PREPROD_PATH }} + + echo "Running E2E tests..." + python3 -m venv .venv + . .venv/bin/activate + + # Export env vars + set -a + . preprod.env + set +a + + pip install -r tests/e2e/requirements.txt + + # Run tests + if pytest tests/e2e/; then + echo "Tests passed!" + # Cleanup on success + docker compose --env-file preprod.env -f docker-compose.yaml -f docker-compose.prod.yaml down --remove-orphans + else + echo "Tests failed!" + # Optional: Cleanup on failure? Or keep for debugging? + # User's previous script had it after, implying it runs if pytest succeeds (due to set -e). + # I will fail the step. + exit 1 + fi + # ------------------------------------------------------------------ # STAGE 4: DEPLOY PRODUCTION # ------------------------------------------------------------------ deploy_prod: name: Deploy (Production) runs-on: ubuntu-latest - needs: [deploy_staging] + needs: [deploy_preprod] if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 @@ -126,6 +189,10 @@ jobs: # Create venv to avoid polluting system python python3 -m venv .venv . .venv/bin/activate + # Export env vars so pytest can see them + set -a + . prod.env + set +a pip install -r tests/e2e/requirements.txt pytest tests/e2e/ diff --git a/preprod.env b/preprod.env new file mode 100644 index 0000000..38d0670 --- /dev/null +++ b/preprod.env @@ -0,0 +1,9 @@ +PORT=8081 +HTTPS_PORT=447 +ENV=preprod +NETWORK_NAME=proxy +CERTBOT_CA_RESOLVER=https://acme-staging-v02.api.letsencrypt.org/directory +DOMAIN=preprod.kovagoadi.hu +ACME_BYPASS=false +TRAEFIK_LEGACY_OPT="--providers.file.directory=/etc/traefik" +CI=true \ No newline at end of file diff --git a/preprod/forward-to-legacy-nginx.yaml b/preprod/forward-to-legacy-nginx.yaml new file mode 100644 index 0000000..4da1e72 --- /dev/null +++ b/preprod/forward-to-legacy-nginx.yaml @@ -0,0 +1,47 @@ +# ./traefik/forward-to-legacy-nginx.yaml + +tcp: + routers: + # Router for HTTPS (Passthrough) + nginx-legacy-router-secure: + rule: "HostSNI(`*`)" + service: nginx-legacy-service-secure + # Passthrough must be true for SSL to reach Nginx encrypted + tls: + passthrough: true + priority: 10 + entryPoints: + - "https" + + services: + # Service defining the external IP + nginx-legacy-service-secure: + loadBalancer: + servers: + # This is the actual external IP and Port of your Nginx + - address: "webserver:443" + +http: + routers: + # 1. TRAEFIK-MANAGED ACME HANDLER (Removed manual router) + traefik-acme-handler: + rule: "Host(`test-whoami.kovagoadi.hu`) && PathPrefix(`/.well-known/acme-challenge/`)" + entryPoints: + - "web" + service: "acme-http@internal" # This is the internal service name + priority: 1000 # High priority to ensure it wins + + # 2. THE HTTP CATCH-ALL (Sends other ACME and HTTP to Nginx) + nginx-legacy-router: + rule: "HostRegexp(`^.+$`)" + service: nginx-legacy-service + # Low priority ensures specific containers are handled first, but before the default acme-handler + priority: 90 + entryPoints: + - "web" + + services: + nginx-legacy-service: + loadBalancer: + servers: + - url: "http://webserver:80" \ No newline at end of file diff --git a/preprod/route-to-staging-dev.yaml b/preprod/route-to-staging-dev.yaml new file mode 100644 index 0000000..fd12671 --- /dev/null +++ b/preprod/route-to-staging-dev.yaml @@ -0,0 +1,33 @@ +http: + routers: + # Router for HTTP (Port 80) + staging: + rule: "HostRegexp(`^.+\\.staging\\.kovagoadi\\.hu$`) || HostRegexp(`^.+\\.dev\\.kovagoadi\\.hu$`)" + entryPoints: + - "web" + service: "dev-staging" + priority: 1000 + services: + dev-staging: + loadBalancer: + servers: + - url: "http://staging:8080" + +tcp: + routers: + # Router for HTTPS (Passthrough) + dev-staging-secure: + rule: "HostSNIRegexp(`^.+\\.staging\\.kovagoadi\\.hu$`) || HostSNIRegexp(`^.+\\.dev\\.kovagoadi\\.hu$`)" + service: "dev-staging-secure" + # Passthrough must be true for SSL to reach Nginx encrypted + tls: + passthrough: true + priority: 1000 + entryPoints: + - "https" + services: + dev-staging-secure: + loadBalancer: + servers: + # Note: Ensure Traefik trusts the cert at .85 or set insecureSkipVerify + - address: "staging:445" \ No newline at end of file diff --git a/tests/e2e/test_traefik.py b/tests/e2e/test_traefik.py index b8ab1db..49f086b 100644 --- a/tests/e2e/test_traefik.py +++ b/tests/e2e/test_traefik.py @@ -3,9 +3,9 @@ import requests import os # Configuration -DOMAIN = os.getenv("DOMAIN", "dev.kovagoadi.hu") -PORT = os.getenv("PORT", "898") -HTTPS_PORT = os.getenv("HTTPS_PORT", "446") +DOMAIN = os.getenv("DOMAIN", "asdasd.kovagoadi.hu") +PORT = os.getenv("PORT", "10000") +HTTPS_PORT = os.getenv("HTTPS_PORT", "10001") BASE_URL = f"http://127.0.0.1:{PORT}" HTTPS_BASE_URL = f"https://127.0.0.1:{HTTPS_PORT}" @@ -88,7 +88,7 @@ def mock_webserver(): # We need to recreate the container to pick up extra_hosts changes try: - subprocess.run(["docker-compose", "--env-file", "dev.env", "up", "-d", "--force-recreate", "--no-deps", "traefik"], env=env, check=True, capture_output=True, text=True) + subprocess.run(["docker-compose", "--env-file", "preprod.env", "up", "-d", "--force-recreate", "--no-deps", "traefik"], env=env, check=True, capture_output=True, text=True) except subprocess.CalledProcessError as e: print(f"Docker Compose STDOUT: {e.stdout}") print(f"Docker Compose STDERR: {e.stderr}") @@ -104,7 +104,7 @@ def mock_webserver(): subprocess.run(["docker", "stop", mock["name"]], capture_output=True) # Restore Traefik to default (optional) - subprocess.run(["docker-compose", "--env-file", "dev.env", "up", "-d", "--force-recreate", "--no-deps", "traefik"], check=True) + subprocess.run(["docker-compose", "--env-file", "preprod.env", "up", "-d", "--force-recreate", "--no-deps", "traefik"], check=True) def test_whoami_http_reachable(): """Verify that the whoami service is reachable via HTTP."""