mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-06 03:30:25 -06:00
improvements to release process.
This commit is contained in:
251
.github/workflows/ci.yml
vendored
Normal file
251
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: '3.11'
|
||||
|
||||
jobs:
|
||||
lint-and-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
pip install flake8 black isort mypy
|
||||
|
||||
- name: Run black (code formatting)
|
||||
run: black --check --diff app/ migrations/ scripts/
|
||||
|
||||
- name: Run isort (import sorting)
|
||||
run: isort --check-only --diff app/ migrations/ scripts/
|
||||
|
||||
- name: Run flake8 (linting)
|
||||
run: flake8 app/ migrations/ scripts/ --max-line-length=88 --extend-ignore=E203,W503
|
||||
|
||||
- name: Run mypy (type checking)
|
||||
run: mypy app/ --ignore-missing-imports
|
||||
|
||||
test-database-migrations:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
db_type: [postgresql, sqlite]
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_PASSWORD: test_password
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_DB: test_db
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
- name: Test PostgreSQL migrations
|
||||
if: matrix.db_type == 'postgresql'
|
||||
env:
|
||||
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
|
||||
FLASK_APP: app.py
|
||||
run: |
|
||||
echo "Testing PostgreSQL migrations..."
|
||||
flask db upgrade
|
||||
python -c "from app import create_app, db; app = create_app(); app.app_context().push(); print('✅ PostgreSQL migration successful')"
|
||||
flask db downgrade base
|
||||
flask db upgrade
|
||||
echo "✅ PostgreSQL migration rollback/upgrade test passed"
|
||||
|
||||
- name: Test SQLite migrations
|
||||
if: matrix.db_type == 'sqlite'
|
||||
env:
|
||||
DATABASE_URL: sqlite:///test.db
|
||||
FLASK_APP: app.py
|
||||
run: |
|
||||
echo "Testing SQLite migrations..."
|
||||
flask db upgrade
|
||||
python -c "from app import create_app, db; app = create_app(); app.app_context().push(); print('✅ SQLite migration successful')"
|
||||
flask db downgrade base
|
||||
flask db upgrade
|
||||
echo "✅ SQLite migration rollback/upgrade test passed"
|
||||
|
||||
test-docker-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Test Docker build
|
||||
run: |
|
||||
docker build -t timetracker-test:latest .
|
||||
echo "✅ Docker build successful"
|
||||
|
||||
- name: Test Docker container startup
|
||||
run: |
|
||||
# Start container in background
|
||||
docker run -d --name test-container -p 8080:8080 \
|
||||
-e DATABASE_URL="sqlite:///test.db" \
|
||||
timetracker-test:latest
|
||||
|
||||
# Wait for container to be ready
|
||||
for i in {1..30}; do
|
||||
if curl -f http://localhost:8080/_health >/dev/null 2>&1; then
|
||||
echo "✅ Container health check passed"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for container to be ready... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Show container logs for debugging
|
||||
docker logs test-container
|
||||
|
||||
# Stop container
|
||||
docker stop test-container
|
||||
docker rm test-container
|
||||
|
||||
security-scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install security tools
|
||||
run: |
|
||||
pip install safety bandit
|
||||
|
||||
- name: Run safety (dependency vulnerability scan)
|
||||
run: safety check --file requirements.txt
|
||||
|
||||
- name: Run bandit (security linting)
|
||||
run: bandit -r app/ -f json -o bandit-report.json || true
|
||||
|
||||
- name: Upload security report
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: security-report
|
||||
path: bandit-report.json
|
||||
|
||||
validate-version-management:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Test version manager script
|
||||
run: |
|
||||
python scripts/version-manager.py status
|
||||
python scripts/version-manager.py suggest
|
||||
|
||||
- name: Validate version format
|
||||
run: |
|
||||
# Test various version formats
|
||||
python -c "
|
||||
import sys
|
||||
sys.path.append('scripts')
|
||||
from version_manager import VersionManager
|
||||
vm = VersionManager()
|
||||
|
||||
test_versions = ['v1.2.3', '1.2.3', 'v1.2', 'build-123', 'rc1', 'beta1', 'alpha1']
|
||||
for version in test_versions:
|
||||
if vm.validate_version_format(version):
|
||||
print(f'✅ {version} is valid')
|
||||
else:
|
||||
print(f'❌ {version} is invalid')
|
||||
sys.exit(1)
|
||||
"
|
||||
|
||||
create-pr-preview:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
needs: [lint-and-format, test-database-migrations, test-docker-build]
|
||||
steps:
|
||||
- name: Comment on PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment => comment.user.type === 'Bot' && comment.body.includes('CI Pipeline Status'));
|
||||
|
||||
const commentBody = \`## 🔍 CI Pipeline Status
|
||||
|
||||
**All checks passed!** ✅
|
||||
|
||||
**Completed Checks:**
|
||||
- ✅ Code formatting and linting
|
||||
- ✅ Database migration tests (PostgreSQL & SQLite)
|
||||
- ✅ Docker build and startup test
|
||||
- ✅ Security vulnerability scan
|
||||
- ✅ Version management validation
|
||||
|
||||
**Ready for review and merge** 🚀
|
||||
|
||||
---
|
||||
*This comment was automatically generated by the CI pipeline.*\`;
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
comment_id: botComment.id,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody,
|
||||
});
|
||||
}
|
||||
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -31,6 +31,8 @@ jobs:
|
||||
include:
|
||||
- name: amd64
|
||||
platform: linux/amd64
|
||||
- name: arm64
|
||||
platform: linux/arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
281
.github/workflows/migration-check.yml
vendored
Normal file
281
.github/workflows/migration-check.yml
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
name: Database Migration Validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'app/models/**'
|
||||
- 'migrations/**'
|
||||
- 'requirements.txt'
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'app/models/**'
|
||||
- 'migrations/**'
|
||||
|
||||
jobs:
|
||||
validate-migrations:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_PASSWORD: test_password
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_DB: test_db
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
- name: Check for migration changes
|
||||
id: migration_check
|
||||
run: |
|
||||
# Check if there are changes to models or migrations
|
||||
if git diff --name-only HEAD~1 | grep -E "(app/models/|migrations/)" > /dev/null; then
|
||||
echo "migration_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 Migration-related changes detected"
|
||||
else
|
||||
echo "migration_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "ℹ️ No migration-related changes detected"
|
||||
fi
|
||||
|
||||
- name: Validate migration consistency
|
||||
if: steps.migration_check.outputs.migration_changes == 'true'
|
||||
env:
|
||||
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
|
||||
FLASK_APP: app.py
|
||||
run: |
|
||||
echo "🔍 Validating migration consistency..."
|
||||
|
||||
# Initialize fresh database
|
||||
flask db upgrade
|
||||
|
||||
# Generate a new migration from current models
|
||||
flask db migrate -m "Test migration consistency" --rev-id test_consistency
|
||||
|
||||
# Check if the generated migration is empty (no changes needed)
|
||||
MIGRATION_FILE=$(find migrations/versions -name "*test_consistency*.py" | head -1)
|
||||
|
||||
if [ -f "$MIGRATION_FILE" ]; then
|
||||
# Check if migration has actual changes
|
||||
if grep -q "op\." "$MIGRATION_FILE"; then
|
||||
echo "❌ Migration inconsistency detected!"
|
||||
echo "The database schema doesn't match the models."
|
||||
echo "Generated migration file: $MIGRATION_FILE"
|
||||
cat "$MIGRATION_FILE"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ Migration consistency validated - no schema drift detected"
|
||||
# Clean up test migration
|
||||
rm "$MIGRATION_FILE"
|
||||
fi
|
||||
else
|
||||
echo "✅ No migration file generated - models are in sync"
|
||||
fi
|
||||
|
||||
- name: Test migration rollback safety
|
||||
if: steps.migration_check.outputs.migration_changes == 'true'
|
||||
env:
|
||||
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
|
||||
FLASK_APP: app.py
|
||||
run: |
|
||||
echo "🔄 Testing migration rollback safety..."
|
||||
|
||||
# Get current migration
|
||||
CURRENT_MIGRATION=$(flask db current)
|
||||
echo "Current migration: $CURRENT_MIGRATION"
|
||||
|
||||
if [ -n "$CURRENT_MIGRATION" ] && [ "$CURRENT_MIGRATION" != "None" ]; then
|
||||
# Try to rollback one step
|
||||
echo "Testing rollback..."
|
||||
flask db downgrade -1
|
||||
|
||||
# Try to upgrade back
|
||||
echo "Testing re-upgrade..."
|
||||
flask db upgrade
|
||||
|
||||
echo "✅ Migration rollback test passed"
|
||||
else
|
||||
echo "ℹ️ No migrations to test rollback on"
|
||||
fi
|
||||
|
||||
- name: Test migration with sample data
|
||||
if: steps.migration_check.outputs.migration_changes == 'true'
|
||||
env:
|
||||
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
|
||||
FLASK_APP: app.py
|
||||
run: |
|
||||
echo "📊 Testing migration with sample data..."
|
||||
|
||||
# Create sample data
|
||||
python -c "
|
||||
from app import create_app, db
|
||||
from app.models.user import User
|
||||
from app.models.project import Project
|
||||
import datetime
|
||||
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
# Create test user
|
||||
user = User(
|
||||
username='test_user',
|
||||
email='test@example.com',
|
||||
role='user'
|
||||
)
|
||||
user.set_password('test_password')
|
||||
db.session.add(user)
|
||||
|
||||
# Create test project
|
||||
project = Project(
|
||||
name='Test Project',
|
||||
description='Test project for migration validation',
|
||||
user_id=1
|
||||
)
|
||||
db.session.add(project)
|
||||
|
||||
db.session.commit()
|
||||
print('✅ Sample data created successfully')
|
||||
"
|
||||
|
||||
# Verify data integrity after migration
|
||||
python -c "
|
||||
from app import create_app, db
|
||||
from app.models.user import User
|
||||
from app.models.project import Project
|
||||
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
user_count = User.query.count()
|
||||
project_count = Project.query.count()
|
||||
print(f'Users: {user_count}, Projects: {project_count}')
|
||||
|
||||
if user_count > 0 and project_count > 0:
|
||||
print('✅ Data integrity verified after migration')
|
||||
else:
|
||||
print('❌ Data integrity check failed')
|
||||
exit(1)
|
||||
"
|
||||
|
||||
- name: Generate migration report
|
||||
if: steps.migration_check.outputs.migration_changes == 'true'
|
||||
env:
|
||||
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
|
||||
FLASK_APP: app.py
|
||||
run: |
|
||||
echo "📋 Generating migration report..."
|
||||
|
||||
# Get migration history
|
||||
echo "## Migration History" > migration_report.md
|
||||
echo "" >> migration_report.md
|
||||
flask db history --verbose >> migration_report.md
|
||||
|
||||
# Get current schema info
|
||||
echo "" >> migration_report.md
|
||||
echo "## Current Schema" >> migration_report.md
|
||||
echo "" >> migration_report.md
|
||||
python -c "
|
||||
from app import create_app, db
|
||||
from sqlalchemy import inspect
|
||||
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
inspector = inspect(db.engine)
|
||||
tables = inspector.get_table_names()
|
||||
print('### Tables:')
|
||||
for table in sorted(tables):
|
||||
print(f'- {table}')
|
||||
columns = inspector.get_columns(table)
|
||||
for column in columns:
|
||||
print(f' - {column[\"name\"]}: {column[\"type\"]}')
|
||||
" >> migration_report.md
|
||||
|
||||
cat migration_report.md
|
||||
|
||||
- name: Upload migration report
|
||||
if: steps.migration_check.outputs.migration_changes == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: migration-report
|
||||
path: migration_report.md
|
||||
|
||||
comment-on-pr:
|
||||
runs-on: ubuntu-latest
|
||||
needs: validate-migrations
|
||||
if: github.event_name == 'pull_request' && always()
|
||||
steps:
|
||||
- name: Comment migration status on PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const success = '${{ needs.validate-migrations.result }}' === 'success';
|
||||
const migrationChanges = '${{ needs.validate-migrations.outputs.migration_changes }}' === 'true';
|
||||
|
||||
let commentBody = '## 🗄️ Database Migration Validation\n\n';
|
||||
|
||||
if (migrationChanges) {
|
||||
if (success) {
|
||||
commentBody += '✅ **Migration validation passed!**\n\n';
|
||||
commentBody += '**Completed checks:**\n';
|
||||
commentBody += '- ✅ Migration consistency validation\n';
|
||||
commentBody += '- ✅ Rollback safety test\n';
|
||||
commentBody += '- ✅ Data integrity verification\n\n';
|
||||
commentBody += '**The database migrations are safe to apply.** 🚀\n';
|
||||
} else {
|
||||
commentBody += '❌ **Migration validation failed!**\n\n';
|
||||
commentBody += '**Issues detected:**\n';
|
||||
commentBody += '- Migration consistency problems\n';
|
||||
commentBody += '- Rollback safety issues\n';
|
||||
commentBody += '- Data integrity concerns\n\n';
|
||||
commentBody += '**Please review the migration files and fix the issues before merging.** ⚠️\n';
|
||||
}
|
||||
} else {
|
||||
commentBody += 'ℹ️ **No migration-related changes detected.**\n\n';
|
||||
commentBody += 'This PR does not modify database models or migrations.\n';
|
||||
}
|
||||
|
||||
commentBody += '\n---\n*This comment was automatically generated by the Migration Validation workflow.*';
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('Database Migration Validation')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
comment_id: botComment.id,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody,
|
||||
});
|
||||
}
|
||||
260
.github/workflows/release.yml
vendored
Normal file
260
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
name: Release Management
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version (e.g., v1.2.3)'
|
||||
required: true
|
||||
type: string
|
||||
pre_release:
|
||||
description: 'Mark as pre-release'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
generate_changelog:
|
||||
description: 'Auto-generate changelog'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: drytrix/timetracker
|
||||
|
||||
jobs:
|
||||
validate-release:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.validate.outputs.version }}
|
||||
is_prerelease: ${{ steps.validate.outputs.is_prerelease }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate release version
|
||||
id: validate
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
IS_PRERELEASE="${{ github.event.inputs.pre_release }}"
|
||||
else
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
IS_PRERELEASE="${{ github.event.release.prerelease }}"
|
||||
fi
|
||||
|
||||
# Validate semantic version format
|
||||
if [[ ! "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
|
||||
echo "❌ Invalid version format: $VERSION"
|
||||
echo "Expected format: v1.2.3 or v1.2.3-alpha.1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Version validated: $VERSION"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT
|
||||
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
needs: validate-release
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_PASSWORD: test_password
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_DB: test_db
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
pip install pytest pytest-cov
|
||||
|
||||
- name: Run database migrations test
|
||||
env:
|
||||
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
|
||||
FLASK_APP: app.py
|
||||
run: |
|
||||
flask db upgrade
|
||||
python -c "from app import create_app, db; app = create_app(); app.app_context().push(); print('✅ Database connection successful')"
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
|
||||
run: |
|
||||
if [ -d "tests" ]; then
|
||||
pytest tests/ -v --cov=app --cov-report=xml
|
||||
else
|
||||
echo "⚠️ No tests directory found, skipping tests"
|
||||
fi
|
||||
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [validate-release, run-tests]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
APP_VERSION=${{ needs.validate-release.outputs.version }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
generate-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
needs: validate-release
|
||||
if: github.event.inputs.generate_changelog == 'true' || github.event_name == 'release'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
run: |
|
||||
# Get the previous release tag
|
||||
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
|
||||
CURRENT_TAG="${{ needs.validate-release.outputs.version }}"
|
||||
|
||||
if [ -n "$PREVIOUS_TAG" ]; then
|
||||
echo "## Changes since $PREVIOUS_TAG" > changelog.md
|
||||
echo "" >> changelog.md
|
||||
|
||||
# Get commits since last tag
|
||||
git log --pretty=format:"- %s (%h)" $PREVIOUS_TAG..HEAD >> changelog.md
|
||||
else
|
||||
echo "## Initial Release" > changelog.md
|
||||
echo "" >> changelog.md
|
||||
echo "- Initial release of TimeTracker" >> changelog.md
|
||||
fi
|
||||
|
||||
# Upload changelog as artifact
|
||||
cat changelog.md
|
||||
|
||||
- name: Upload changelog
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: changelog
|
||||
path: changelog.md
|
||||
|
||||
update-documentation:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [validate-release, build-and-push]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update version in documentation
|
||||
run: |
|
||||
VERSION="${{ needs.validate-release.outputs.version }}"
|
||||
|
||||
# Update README.md with new version
|
||||
if grep -q "Version:" README.md; then
|
||||
sed -i "s/Version: .*/Version: $VERSION/" README.md
|
||||
else
|
||||
echo "Version: $VERSION" >> README.md
|
||||
fi
|
||||
|
||||
# Update docker-compose examples with new version
|
||||
find . -name "docker-compose*.yml" -exec sed -i "s|ghcr.io/drytrix/timetracker:.*|ghcr.io/drytrix/timetracker:$VERSION|g" {} \;
|
||||
|
||||
- name: Commit version updates
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add -A
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git commit -m "docs: update version references to ${{ needs.validate-release.outputs.version }}"
|
||||
git push
|
||||
fi
|
||||
|
||||
notify-deployment:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [validate-release, build-and-push, update-documentation]
|
||||
if: always() && needs.build-and-push.result == 'success'
|
||||
steps:
|
||||
- name: Create deployment summary
|
||||
run: |
|
||||
echo "# 🚀 Release Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ needs.validate-release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Pre-release:** ${{ needs.validate-release.outputs.is_prerelease }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Docker Image:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.validate-release.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## 📦 Deployment Commands" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Docker Run" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
|
||||
echo "docker run -d -p 8080:8080 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.validate-release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Docker Compose" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`yaml" >> $GITHUB_STEP_SUMMARY
|
||||
echo "services:" >> $GITHUB_STEP_SUMMARY
|
||||
echo " app:" >> $GITHUB_STEP_SUMMARY
|
||||
echo " image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.validate-release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo " ports:" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - \"8080:8080\"" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
Reference in New Issue
Block a user