@@ -3,16 +3,16 @@ name: Release - Northern Thailand Ping River Monitor
on :
push :
tags :
- ' v*.*.*'
- " v*.*.*"
workflow_dispatch :
inputs :
version :
description : ' Release version (e.g., v3.1.3)'
description : " Release version (e.g., v3.1.3)"
required : true
type : string
env :
PYTHON_VERSION : ' 3.11'
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
@@ -27,42 +27,42 @@ jobs:
version : ${{ steps.version.outputs.version }}
steps :
- name : Checkout code
uses : actions/checkout@v4
with :
token : ${{ secrets.CI_BOT _TOKEN }}
fetch-depth : 0
- 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 : 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 : 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
- 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 :
@@ -71,41 +71,41 @@ jobs:
needs : create-release
strategy :
matrix :
python-version : [ ' 3.9' , ' 3.10' , ' 3.11' , ' 3.12' ]
python-version : [ " 3.9" , " 3.10" , " 3.11" , " 3.12" ]
steps :
- name : Checkout code
uses : actions/checkout@v4
with :
token : ${{ secrets.CI_BOT _TOKEN }}
- 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 : 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 : 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 : 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 : 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/
- name : Upload Python package
uses : actions/upload-artifact@v3
with :
name : python-package-${{ matrix.python-version }}
path : dist/
# Build release Docker images
build-release :
@@ -114,40 +114,40 @@ jobs:
needs : [ create-release, test-release]
steps :
- name : Checkout code
uses : actions/checkout@v4
with :
token : ${{ secrets.CI_BOT _TOKEN }}
- name : Checkout code
uses : actions/checkout@v4
with :
token : ${{ secrets.GITEA _TOKEN }}
- name : Set up Docker Buildx
uses : docker/setup-buildx-action@v3
- 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 : ${{ github.actor }}
password : ${{ 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 : 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.GITHUB _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 :
@@ -156,136 +156,162 @@ jobs:
needs : build-release
steps :
- name : Checkout code
uses : actions/checkout@v4
with :
token : ${{ secrets.CI_BOT _TOKEN }}
- name : Checkout code
uses : actions/checkout@v4
with :
token : ${{ secrets.GITEA _TOKEN}}
# Deploy release to production
# Test release deployment locally
deploy-release :
name : Deploy Release
name : Test Release Deployment
runs-on : ubuntu-latest
needs : [ create-release, build-release, security-scan]
environment :
name : production
url : https ://ping-river-monitor.b4l.co.th
name : testing
url : http://localhost:8080
steps :
- name : Checkout code
uses : actions/checkout@v4
with :
token : ${{ secrets.CI_BOT _TOKEN }}
- name : Checkout code
uses : actions/checkout@v4
with :
token : ${{ secrets.GITEA _TOKEN }}
- name : Deploy to production
run : |
echo "🚀 Deploying ${{ needs.create-release.outputs.version }} to production..."
- name : Log in to Container Registry
uses : docker/login-action@v3
with :
registry : ${{ env.REGISTRY }}
username : ${{ vars.WORKER_USERNAME}}
password : ${{ secrets.CI_BOT_TOKEN }}
# Example deployment commands (customize for your infrastructure)
# kubectl set image deployment/ping-river-monitor app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }}
# docker-compose pull && docker-compose up -d
# Or webhook call to your deployment system
- name : Deploy to production (Local Test)
run : |
echo "🚀 Testing ${{ needs.create-release.outputs.version }} deployment locally..."
echo "✅ Deployment initiated"
# Pull the built image
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }}
- name : Health check after deployment
run : |
echo "⏳ Waiting for deployment to stabilize..."
sleep 60
# Stop any existing containers
docker stop ping-river-monitor-test || true
docker rm ping-river-monitor-test || true
echo "🔍 Running health checks..."
curl -f https://ping-river-monitor.b4l.co.th/health
curl -f https:// ping-river-monitor.b4l.co.th/stations
# Start the container for testing
docker run -d \
--name ping-river-monitor-test \
-p 8080:8000 \
-e LOG_LEVEL=INFO \
-e DB_TYPE=sqlite \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }}
echo "✅ Health checks passed!"
echo "✅ Container started for testing"
- name : Upda te deployment status
run : |
echo "📊 Deployment Summary: "
echo "Version: ${{ needs.create-release.outputs.version }}"
echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.create-release.outputs.version }}"
echo "URL: https://ping-river-monitor.b4l.co.th"
echo "Grafana: https://grafana.ping-river-monitor.b4l.co.th"
echo "API Docs: https://ping-river-monitor.b4l.co.th/docs"
- name : Health check af ter deployment
run : |
echo "⏳ Waiting for application to start... "
sleep 30
# Post-release validation
validate-release :
name : Validate Release
runs-on : ubuntu-latest
needs : deploy-release
echo "🔍 Running health checks against local container..."
steps :
- name : Comprehensive API test
run : |
echo "🧪 Running comprehensive API tests..."
# Check if container is running
docker ps | grep ping-river-monitor-test || echo "⚠️ Container not found in docker ps"
# Test all major endpoint s
curl -f https://ping-river-monitor.b4l.co.th/health
curl -f https://ping-river-monitor.b4l.co.th/metrics
curl -f https://ping-river-monitor.b4l.co.th/stations
curl -f https://ping-river-monitor.b4l.co.th/measurements/latest?limit=5
curl -f https://ping-river-monitor.b4l.co.th/scraping/status
# Check container logs for any startup issue s
echo "📋 Recent container logs:"
docker logs --tail 10 ping-river-monitor-test || true
echo "✅ All API endpoints responding correctly"
# Wait for the application to be ready with more robust checking
echo "🔍 Testing application readiness..."
for i in {1..15}; do
echo "⏳ Attempt $i/15: Testing health endpoint..."
- name : Performance validation
run : |
echo "⚡ Running performance validation..."
# Test health endpoint with detailed debugging
echo "Testing health endpoint..."
# Install Apache Bench
sudo apt-get update && sudo apt-get install -y apache2-utils
# First try with verbose output to see what's happening
response=$(curl -s --max-time 10 --connect-timeout 5 -w "HTTP_CODE:%{http_code}" http://127.0.0.1:8080/health)
http_code=$(echo "$response" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2)
response_body=$(echo "$response" | sed 's/HTTP_CODE:[0-9]*$//')
# Test response times
ab -n 10 -c 2 https://ping-river-monitor.b4l.co.th/health
ab -n 10 -c 2 https://ping-river-monitor.b4l.co.th/stations
echo "HTTP Code: $http_code"
echo "Response Body: $response_body"
echo "✅ Performance validation completed"
if [ "$http_code" = "200" ] && [ -n "$response_body" ]; then
echo "✅ Health endpoint responding successfully!"
break
else
echo "❌ Health check failed (HTTP: $http_code), waiting 15 seconds..."
# Show what's happening with the container
echo "Container status:"
docker ps | grep ping-river-monitor-test || echo "Container not found"
echo "Recent container logs:"
docker logs --tail 5 ping-river-monitor-test || true
sleep 15
fi
done
- name : Data validation
run : |
echo "📊 Validating data collection..."
# Test API endpoints with better error handling
echo "🧪 Testing API endpoints..."
# Check if recent data is available
response=$(curl -s https://ping-river-monitor.b4l.co.th/measurements/latest?limit=1)
echo "Latest measurement: $response "
endpoints=("health" "docs" "stations" "metrics")
for endpoint in "${endpoints[@]}"; do
echo "Testing /$endpoint... "
response=$(curl -s --max-time 10 -w "HTTP_CODE:%{http_code}" http://127.0.0.1:8080/$endpoint)
http_code=$(echo "$response" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2)
# Validate data structure (basic check)
if echo "$response" | grep -q "water_level"; then
echo "✅ Data structure validation passed"
else
echo "❌ Data structure validation failed "
exit 1
fi
if [ "$http_code" = "200" ]; then
echo "✅ /$endpoint: OK (HTTP $http_code)"
else
echo "❌ /$endpoint: FAILED (HTTP $http_code)"
echo "Response: $(echo "$response" | 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, validate -release]
needs : [ create-release, deploy -release]
if : always()
steps :
- name : Notify success
if : needs.validate -release.result == 'success'
run : |
echo "🎉 Release ${{ needs.create-release.outputs.version }} deploy ed successfully!"
echo "🌐 Production URL: https://ping-river-monitor.b4l.co.th "
echo "📊 Grafana: https://grafana.ping-river-monitor.b4l.co.th "
echo "📚 API Docs: https://ping-river-monitor.b4l.co.th/docs "
- name : Notify success
if : needs.deploy -release.result == 'success'
run : |
echo "🎉 Release ${{ needs.create-release.outputs.version }} test ed successfully!"
echo "🧪 Local Test: Passed all health checks "
echo "<EFBFBD> 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 }} deployed successfully !"}' \
# ${{ secrets.SLACK_WEBHOOK_URL }}
# 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.validate -release.result == 'failure'
run : |
echo "❌ Release ${{ needs.create-release.outputs.version }} deployment failed!"
echo "Please check the logs and take corrective action ."
- 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 }} deployment failed!"}' \
# ${{ secrets.SLACK_WEBHOOK_URL }}
# 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 }}