demosetup-tunnel #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: d2e/demosetup-tunnel | |
| permissions: | |
| contents: read | |
| packages: read | |
| on: | |
| repository_dispatch: | |
| types: [demosetup-tunnel] | |
| concurrency: | |
| group: ${{ github.workflow }} | |
| cancel-in-progress: false | |
| env: | |
| REG_URL: ghcr.io/ohdsi | |
| PREPULL_FLOW_IMAGES: "flow-base flow-i2b2 flow-data-management flow-search-embedding" | |
| jobs: | |
| demosetup_tunnel: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 330 | |
| outputs: | |
| tunnel_url: ${{ steps.tunnel.outputs.tunnel_url }} | |
| env: | |
| VERSION: ${{ github.event.client_payload.version || 'develop' }} | |
| DOCKER_TAG_NAME: ${{ github.event.client_payload.version || 'develop' }} | |
| PLUGINS_IMAGE_TAG: ${{ github.event.client_payload.version || 'develop' }} | |
| DURATION_MINUTES: ${{ github.event.client_payload.duration_minutes || '120' }} | |
| steps: | |
| - name: Free Disk Space (Ubuntu) | |
| uses: jlumbroso/free-disk-space@main | |
| with: | |
| tool-cache: true | |
| android: true | |
| dotnet: true | |
| haskell: true | |
| large-packages: true | |
| docker-images: true | |
| swap-storage: true | |
| - name: Increase swapfile to 15G | |
| run: | | |
| sudo swapoff -a | |
| sudo fallocate -l 15G /swapfile | |
| sudo chmod 600 /swapfile | |
| sudo mkswap /swapfile | |
| sudo swapon /swapfile | |
| sudo swapon --show | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.client_payload.ref || github.ref }} | |
| submodules: recursive | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Install cloudflared | |
| run: | | |
| curl -fsSL -o /tmp/cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb | |
| sudo dpkg -i /tmp/cloudflared.deb | |
| cloudflared --version | |
| - name: Start Cloudflare quick tunnel | |
| id: tunnel | |
| run: | | |
| nohup cloudflared tunnel --url https://localhost:443 --no-tls-verify --logfile /tmp/cloudflared.log > /dev/null 2>&1 & | |
| echo "TUNNEL_PID=$!" >> $GITHUB_ENV | |
| TUNNEL_URL="" | |
| for i in $(seq 1 30); do | |
| TUNNEL_URL=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /tmp/cloudflared.log | head -1 || true) | |
| [ -n "$TUNNEL_URL" ] && break | |
| sleep 2 | |
| done | |
| if [ -z "$TUNNEL_URL" ]; then | |
| echo "::error::Could not obtain tunnel URL" | |
| cat /tmp/cloudflared.log | |
| exit 1 | |
| fi | |
| TUNNEL_HOST=${TUNNEL_URL#https://} | |
| echo "TUNNEL_URL=$TUNNEL_URL" >> $GITHUB_ENV | |
| echo "TUNNEL_HOST=$TUNNEL_HOST" >> $GITHUB_ENV | |
| echo "127.0.0.1 $TUNNEL_HOST" | sudo tee -a /etc/hosts | |
| echo "Tunnel URL: $TUNNEL_URL" | tee -a $GITHUB_STEP_SUMMARY | |
| echo "tunnel_url=$TUNNEL_URL" >> $GITHUB_OUTPUT | |
| printf '%s\n' "$TUNNEL_URL" > /tmp/tunnel-url.txt | |
| # Published immediately so the URL can be fetched while the run is still going: | |
| # job outputs and `gh run view --log` are only readable once the job has finished, | |
| # by which point the tunnel is already torn down. | |
| - name: Publish tunnel URL artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tunnel-url | |
| path: /tmp/tunnel-url.txt | |
| retention-days: 1 | |
| - name: npm install | |
| run: npm install | |
| - name: Install Atlas plugin resources | |
| working-directory: ./plugins/atlas | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: npm install | |
| - name: Initialise d2e | |
| id: init | |
| run: | | |
| init_choice=y ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config CADDY__D2E__PUBLIC_FQDN=$TUNNEL_HOST node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} init | |
| echo "CADDY__ADDITIONAL_HOSTS=https://localhost" >> .env | |
| - name: Pre-pull flow images | |
| run: | | |
| for img in $PREPULL_FLOW_IMAGES; do | |
| docker pull "${REG_URL}/d2e/${img}:${DOCKER_TAG_NAME}" | |
| done | |
| - name: Pull docker compose images | |
| if: success() || failure() | |
| run: | | |
| ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} -d ./plugins/functions pull | |
| docker images --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}" | |
| - name: Start services | |
| if: success() || failure() | |
| uses: nick-fields/retry@v3 | |
| id: start | |
| with: | |
| timeout_seconds: 1800 | |
| retry_wait_seconds: 100 | |
| max_attempts: 2 | |
| retry_on: any | |
| command: | | |
| ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} -d ./plugins/functions start | |
| DC_EXIT_CODE=$? | |
| docker ps --format {{.Names}},{{.Status}} 2> /dev/null | sort | tee -a $GITHUB_STEP_SUMMARY | |
| [ $DC_EXIT_CODE = 0 ] || { echo EXIT DC_EXIT_CODE=$DC_EXIT_CODE && exit $DC_EXIT_CODE; } | |
| - name: Logs | |
| if: success() || failure() | |
| run: | | |
| ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} -d ./plugins/functions logs | |
| - name: Wait for d2e-trex healthy | |
| if: success() || failure() | |
| run: | | |
| status=missing | |
| for i in $(seq 1 60); do | |
| status=$(docker inspect -f '{{.State.Health.Status}}' d2e-trex 2>/dev/null || echo "missing") | |
| echo "[$i] d2e-trex health: $status" | |
| [ "$status" = "healthy" ] && break | |
| sleep 5 | |
| done | |
| docker ps --format '{{.Names}},{{.Status}}' | sort | |
| if [ "$status" != "healthy" ]; then | |
| echo "::error::d2e-trex did not become healthy (last status: $status)" | |
| exit 1 | |
| fi | |
| - name: Pre-pull demo db image | |
| if: success() || failure() | |
| uses: nick-fields/retry@v3 | |
| with: | |
| timeout_seconds: 600 | |
| retry_wait_seconds: 30 | |
| max_attempts: 5 | |
| retry_on: any | |
| command: docker pull postgres:16-alpine | |
| - name: Setupdemo | |
| if: success() || failure() | |
| uses: nick-fields/retry@v3 | |
| id: setupdemo | |
| with: | |
| timeout_seconds: 1800 | |
| retry_wait_seconds: 100 | |
| max_attempts: 3 | |
| retry_on: any | |
| command: | | |
| ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} -d ./plugins/functions setupdemo | |
| DC_EXIT_CODE=$? | |
| docker ps --format {{.Names}},{{.Status}} 2> /dev/null | sort | tee -a $GITHUB_STEP_SUMMARY | |
| [ $DC_EXIT_CODE = 0 ] || { echo EXIT DC_EXIT_CODE=$DC_EXIT_CODE && exit $DC_EXIT_CODE; } | |
| - name: Checkflow | |
| if: success() || failure() | |
| uses: nick-fields/retry@v3 | |
| id: checkflow | |
| with: | |
| timeout_seconds: 1800 | |
| retry_wait_seconds: 100 | |
| max_attempts: 1 | |
| retry_on: any | |
| command: | | |
| ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} -d ./plugins/functions checkflow | |
| DC_EXIT_CODE=$? | |
| [ $DC_EXIT_CODE = 0 ] || { echo EXIT DC_EXIT_CODE=$DC_EXIT_CODE && exit $DC_EXIT_CODE; } | |
| - name: Restart d2e services | |
| if: success() || failure() | |
| run: | | |
| ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} -d ./plugins/functions stop | |
| ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} -d ./plugins/functions start | |
| - name: Wait for webapi-init to complete | |
| if: success() || failure() | |
| run: | | |
| echo "Waiting for webapi-init to complete (ensures logto admin user is created)..." | |
| for i in $(seq 1 60); do | |
| status=$(docker ps -a --filter "name=webapi-init" --format '{{.Status}}') | |
| if echo "$status" | grep -q "(healthy)"; then | |
| echo "webapi-init completed successfully" | |
| break | |
| fi | |
| if echo "$status" | grep -qE "Exited \([1-9]"; then | |
| echo "webapi-init failed!" | |
| docker logs d2e-webapi-init || true | |
| exit 1 | |
| fi | |
| echo "Waiting for webapi-init... ($i/60)" | |
| sleep 5 | |
| done | |
| - name: Verify app is reachable | |
| if: success() || failure() | |
| run: | | |
| echo "Local:" | |
| curl -k -s -o /dev/null -w "%{http_code}\n" https://localhost:443/d2e/portal || echo "App not reachable locally" | |
| echo "Tunnel:" | |
| curl -s -o /dev/null -w "%{http_code}\n" --resolve "$TUNNEL_HOST:443:127.0.0.1" -k "$TUNNEL_URL/d2e/portal" || echo "App not reachable via tunnel host" | |
| - name: Publish access details | |
| if: success() || failure() | |
| run: | | |
| { | |
| echo "## Demo environment is up" | |
| echo "" | |
| echo "- Portal: $TUNNEL_URL/d2e/portal" | |
| echo "- Login: \`admin\` / \`Updatepassword12345\`" | |
| echo "- Open for $DURATION_MINUTES minutes (until the 'Keep tunnel open' step ends)" | |
| echo "" | |
| echo "Cancel the workflow run to tear the environment down early." | |
| } >> $GITHUB_STEP_SUMMARY | |
| - name: Mark environment ready | |
| if: success() || failure() | |
| run: printf '%s\n' "$TUNNEL_URL" > /tmp/tunnel-ready.txt | |
| - name: Publish readiness artifact | |
| if: success() || failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tunnel-ready | |
| path: /tmp/tunnel-ready.txt | |
| retention-days: 1 | |
| - name: Keep tunnel open | |
| if: success() || failure() | |
| run: | | |
| END=$((SECONDS + DURATION_MINUTES * 60)) | |
| echo "Keeping tunnel open for $DURATION_MINUTES minutes: $TUNNEL_URL/d2e/portal" | |
| while [ $SECONDS -lt $END ]; do | |
| sleep 60 | |
| if ! kill -0 "$TUNNEL_PID" 2>/dev/null; then | |
| echo "::warning::cloudflared exited early" | |
| tail -20 /tmp/cloudflared.log || true | |
| break | |
| fi | |
| REMAINING=$(( (END - SECONDS) / 60 )) | |
| if [ $(( REMAINING % 10 )) -eq 0 ] || [ $REMAINING -le 1 ]; then | |
| echo "[$(date -u '+%H:%M:%S')] tunnel open, ~${REMAINING}m remaining" | |
| fi | |
| done | |
| echo "Tunnel window elapsed" | |
| - name: Stop tunnel | |
| if: always() | |
| run: kill "$TUNNEL_PID" 2>/dev/null || true | |
| - name: Stop d2e services | |
| if: always() | |
| run: | | |
| ENV_TYPE=remote CADDY__CONFIG=./deploy/caddy-config node ./scripts/dist/cli.js -e -v ${{ env.VERSION }} -d ./plugins/functions stop | |
| - name: Prune system | |
| if: always() | |
| run: docker system prune -af |