name: Release - Northern Thailand Ping River Monitor on: push: tags: - "v*.*.*" workflow_dispatch: inputs: version: description: "Release version (e.g., v3.1.3)" required: true type: string env: PYTHON_VERSION: "3.11" REGISTRY: git.b4l.co.th IMAGE_NAME: b4l/northern-thailand-ping-river-monitor # GitHub token for better rate limits and authentication GH_TOKEN: ${{ secrets.GH_TOKEN }} jobs: # Create release create-release: name: Create Release runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout code uses: actions/checkout@v4 with: token: ${{ secrets.GITEA_TOKEN }} fetch-depth: 0 - name: Get version id: version run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT else echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT fi - name: Generate changelog id: changelog run: | # Generate changelog from git commits echo "## Changes" > CHANGELOG.md git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 HEAD^)..HEAD >> CHANGELOG.md || echo "- Initial release" >> CHANGELOG.md echo "" >> CHANGELOG.md echo "## Docker Images" >> CHANGELOG.md echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}\`" >> CHANGELOG.md echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\`" >> CHANGELOG.md - name: Create Release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }} with: tag_name: ${{ steps.version.outputs.version }} release_name: Northern Thailand Ping River Monitor ${{ steps.version.outputs.version }} body_path: CHANGELOG.md draft: false prerelease: false # Build and test for release test-release: name: Test Release Build runs-on: ubuntu-latest needs: create-release strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code uses: actions/checkout@v4 with: token: ${{ secrets.GITEA_TOKEN }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip --root-user-action=ignore pip install --root-user-action=ignore -r requirements.txt pip install --root-user-action=ignore -r requirements-dev.txt - name: Run full test suite run: | python tests/test_integration.py python tests/test_station_management.py python run.py --test - name: Build Python package run: | pip install --root-user-action=ignore build python -m build - name: Upload Python package uses: actions/upload-artifact@v3 with: name: python-package-${{ matrix.python-version }} path: dist/ # Build release Docker images build-release: name: Build Release Images runs-on: ubuntu-latest needs: [create-release, test-release] steps: - name: Checkout code uses: actions/checkout@v4 with: token: ${{ secrets.GITEA_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ vars.WORKER_USERNAME}} password: ${{ secrets.CI_BOT_TOKEN }} - name: Build and push release images uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest labels: | org.opencontainers.image.title=Northern Thailand Ping River Monitor org.opencontainers.image.description=Real-time water level monitoring for Ping River Basin org.opencontainers.image.version=${{ needs.create-release.outputs.version }} org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.revision=${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max env: GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }} # Security scan for release security-scan: name: Security Scan runs-on: ubuntu-latest needs: build-release steps: - name: Checkout code uses: actions/checkout@v4 with: token: ${{ secrets.GITEA_TOKEN}} # Test release deployment locally deploy-release: name: Test Release Deployment runs-on: ubuntu-latest needs: [create-release, build-release, security-scan] environment: name: testing url: http://localhost:8080 steps: - name: Checkout code uses: actions/checkout@v4 with: token: ${{ secrets.GITEA_TOKEN }} - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ vars.WORKER_USERNAME}} password: ${{ secrets.CI_BOT_TOKEN }} - name: Deploy to production (Local Test) run: | set -euo pipefail echo "πŸš€ Testing ${{ needs.create-release.outputs.version }} deployment locally..." # Create a dedicated network so we can resolve by container name docker network create ci_net || true # Pull the built image docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }} # Stop & remove any existing container docker rm -f ping-river-monitor-test 2>/dev/null || true # Start the container on the user-defined network docker run -d \ --name ping-river-monitor-test \ --network ci_net \ -p 8080:8000 \ -e LOG_LEVEL=INFO \ -e DB_TYPE=sqlite \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }} echo "βœ… Container started for testing" - name: Health check after deployment run: | set -euo pipefail echo "⏳ Waiting for application to start..." # Pull a curl-only image for probing (keeps your app image slim) docker pull curlimages/curl:8.10.1 # Helper: curl via a sibling container on the SAME Docker network probe() { local url="$1" docker run --rm --network ci_net curlimages/curl:8.10.1 \ -sS --max-time 5 --connect-timeout 3 -w "HTTP_CODE:%{http_code}" "$url" || true } # Wait for /health (up to ~3m 45s) for i in {1..15}; do echo "πŸ” Attempt $i/15: checking http://ping-river-monitor-test:8000/health" resp="$(probe http://ping-river-monitor-test:8000/health)" code="$(echo "$resp" | sed -n 's/.*HTTP_CODE:\([0-9]\+\).*/\1/p')" body="$(echo "$resp" | sed 's/HTTP_CODE:[0-9]*$//')" echo "HTTP: ${code:-} | Body: ${body:-}" if [ "${code:-}" = "200" ] && [ -n "${body:-}" ]; then echo "βœ… Health endpoint responding successfully" break fi echo "❌ Not ready yet. Showing recent logs…" docker logs --tail 20 ping-river-monitor-test || true sleep 15 if [ "$i" -eq 15 ]; then echo "❌ Health never reached 200. Failing." exit 1 fi done echo "πŸ§ͺ Testing API endpoints…" endpoints=("health" "docs" "stations" "metrics") for ep in "${endpoints[@]}"; do url="http://ping-river-monitor-test:8000/$ep" resp="$(probe "$url")" code="$(echo "$resp" | sed -n 's/.*HTTP_CODE:\([0-9]\+\).*/\1/p')" if [ "${code:-}" = "200" ]; then echo "βœ… /$ep: OK" else echo "❌ /$ep: FAILED (HTTP ${code:-})" echo "Response: $(echo "$resp" | sed 's/HTTP_CODE:[0-9]*$//')" exit 1 fi done echo "βœ… All health checks passed!" - name: Container logs and cleanup if: always() run: | echo "πŸ“‹ Container logs:" docker logs ping-river-monitor-test || true echo "🧹 Cleaning up test container..." docker stop ping-river-monitor-test || true docker rm ping-river-monitor-test || true echo "πŸ“Š Deployment Test Summary:" echo "Version: ${{ needs.create-release.outputs.version }}" echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }}" echo "Status: Container tested successfully" echo "Ready for production deployment" # Notify stakeholders notify: name: Notify Release runs-on: ubuntu-latest needs: [create-release, deploy-release] if: always() steps: - name: Notify success if: needs.deploy-release.result == 'success' run: | echo "πŸŽ‰ Release ${{ needs.create-release.outputs.version }} tested successfully!" echo "πŸ§ͺ Local Test: Passed all health checks" echo "οΏ½ GDocker Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }}" echo "βœ… Ready for production deployment" # Add notification to Slack, Discord, email, etc. # curl -X POST -H 'Content-type: application/json' \ # --data '{"text":"πŸŽ‰ Northern Thailand Ping River Monitor ${{ needs.create-release.outputs.version }} tested and ready for deployment!"}' \ # ${{ secrets.SLACK_WEBHOOK_URL }} - name: Notify failure if: needs.deploy-release.result == 'failure' run: | echo "❌ Release ${{ needs.create-release.outputs.version }} testing failed!" echo "Please check the logs and fix issues before production deployment." # Add failure notification # curl -X POST -H 'Content-type: application/json' \ # --data '{"text":"❌ Northern Thailand Ping River Monitor ${{ needs.create-release.outputs.version }} testing failed!"}' \ # ${{ secrets.SLACK_WEBHOOK_URL }}