Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# IntelliWeather CI/CD Pipeline
# Runs tests, builds Docker image, and optionally deploys to Kubernetes

name: CI/CD Pipeline

on:
push:
branches: [main, develop]
tags: ['v*']
pull_request:
branches: [main]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
# ==================== LINT & TEST ====================
test:
name: Test
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov pytest-asyncio flake8 black mypy

- name: Lint with flake8
run: |
# Stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# Exit-zero treats all errors as warnings
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=120 --statistics
continue-on-error: true

- name: Check formatting with black
run: black --check --diff . || true
continue-on-error: true

- name: Type check with mypy
run: mypy --ignore-missing-imports . || true
continue-on-error: true

- name: Run tests
run: |
pytest tests/ -v --cov=. --cov-report=xml --cov-report=term-missing
env:
DEBUG: "true"

- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
fail_ci_if_error: false

# ==================== BUILD DOCKER IMAGE ====================
build:
name: Build
runs-on: ubuntu-latest
needs: test
permissions:
contents: read
packages: write

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Container Registry
if: github.event_name != 'pull_request'
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=sha,prefix=

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64

# ==================== SECURITY SCAN ====================
security:
name: Security Scan
runs-on: ubuntu-latest
needs: build
if: github.event_name != 'pull_request'
permissions:
contents: read
security-events: write

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
continue-on-error: true

- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
continue-on-error: true

# ==================== DEPLOY TO STAGING ====================
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [build, security]
permissions:
contents: read
if: github.ref == 'refs/heads/develop'
environment: staging

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up kubectl
uses: azure/setup-kubectl@v3

- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBE_CONFIG_STAGING }}" | base64 -d > ~/.kube/config
if: ${{ secrets.KUBE_CONFIG_STAGING != '' }}

- name: Deploy to staging
run: |
if [ -f ~/.kube/config ]; then
kubectl set image deployment/intelliweather \
intelliweather=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--namespace=staging || echo "Kubectl deployment skipped (no cluster configured)"
else
echo "Staging deployment skipped (no KUBE_CONFIG_STAGING secret)"
fi

# ==================== DEPLOY TO PRODUCTION ====================
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [build, security]
if: startsWith(github.ref, 'refs/tags/v')
environment: production
permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up kubectl
uses: azure/setup-kubectl@v3

- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBE_CONFIG_PRODUCTION }}" | base64 -d > ~/.kube/config
if: ${{ secrets.KUBE_CONFIG_PRODUCTION != '' }}

- name: Deploy to production
run: |
if [ -f ~/.kube/config ]; then
# Extract version from tag
VERSION=${GITHUB_REF#refs/tags/}

# Deploy using Helm
helm upgrade --install intelliweather ./helm/intelliweather \
--namespace=production \
--set image.tag=$VERSION \
--wait --timeout=5m || echo "Helm deployment skipped"
else
echo "Production deployment skipped (no KUBE_CONFIG_PRODUCTION secret)"
fi

# ==================== NOTIFY ====================
notify:
name: Notify
runs-on: ubuntu-latest
needs: [test, build]
if: always()
permissions: {}

steps:
- name: Notify on success
if: needs.test.result == 'success' && needs.build.result == 'success'
run: |
echo "✅ Pipeline succeeded!"
echo "Tests: ${{ needs.test.result }}"
echo "Build: ${{ needs.build.result }}"

- name: Notify on failure
if: needs.test.result == 'failure' || needs.build.result == 'failure'
run: |
echo "❌ Pipeline failed!"
echo "Tests: ${{ needs.test.result }}"
echo "Build: ${{ needs.build.result }}"
56 changes: 56 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Virtual environments
venv/
ENV/
env/
.venv/

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Environment variables
.env
.env.local

# Logs
*.log

# Data directory (CSV storage)
data/

# Test coverage
htmlcov/
.coverage
.pytest_cache/

# Database files (generated)
*.db
weather.db
Loading