name: Flask CI/CD Pipeline on: push: branches: [ main ] env: IMAGE_NAME: git.onlionel.com/lionel/arcade jobs: format-and-auto-fix: runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install formatting dependencies run: | python -m venv venv . venv/bin/activate pip install --upgrade pip pip install -r requirements.txt pip install ruff - name: Run Ruff auto-fix and formatting run: | . venv/bin/activate python -m ruff check . --fix --exclude venv,.venv python -m ruff format . --exclude venv,.venv - name: Commit and push formatting changes run: | if [ -n "$(git status --porcelain)" ]; then git config --global user.name "gitea-actions[bot]" git config --global user.email "gitea-actions[bot]@local" git add . git commit -m "Auto-fix code style [skip ci]" git push else echo "No formatting changes to commit" fi test-and-verify: needs: format-and-auto-fix runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Scan repository and git history for leaked secrets with Gitleaks run: | docker run --rm \ -v "$PWD":/repo \ ghcr.io/gitleaks/gitleaks:latest \ git /repo --verbose - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install test and security dependencies run: | python -m venv venv . venv/bin/activate pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-cov pip-audit - name: Run pytest with coverage run: | . venv/bin/activate PYTHONPATH=. pytest --cov=. --cov-report=term-missing - name: Run lightweight input fuzzing against Flask test client run: | . venv/bin/activate python - <<'PY' import random import string from app import app client = app.test_client() samples = [ "", "abc", "🔥", "één test", "", "\x00", " " * 100, "a" * 10000, "🙂🙃😅", "漢字かなカナ", "\n\t\r", ] alphabet = string.ascii_letters + string.digits + string.punctuation + " 🙂🔥éäöü漢字\n\t" for _ in range(100): size = random.randint(0, 512) samples.append("".join(random.choice(alphabet) for _ in range(size))) for value in samples: response = client.post("/", data={"tekst": value}) assert response.status_code == 200, f"Unexpected status code for input length {len(value)}" body = response.get_data(as_text=True) assert "Spiegelbeeld:" in body, "Expected response marker not found" print(f"Lightweight fuzzing completed successfully for {len(samples)} inputs.") PY - name: Scan Python dependencies with pip-audit run: | . venv/bin/activate pip-audit - name: Start Flask app and run smoke test run: | . venv/bin/activate python app.py & APP_PID=$! sleep 5 curl -f http://127.0.0.1:5000 kill $APP_PID build-scan-and-push-image: needs: test-and-verify runs-on: ubuntu-latest outputs: image_tag: ${{ steps.meta.outputs.image_tag }} steps: - name: Check out repository uses: actions/checkout@v4 - name: Set image tag id: meta run: | SHORT_SHA=$(echo "${{ gitea.sha }}" | cut -c1-7) echo "image_tag=${SHORT_SHA}" >> "$GITHUB_OUTPUT" - name: Log in to Gitea container registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.onlionel.com \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin - name: Build Docker images run: | docker build --pull -t $IMAGE_NAME:latest . docker build --pull -t $IMAGE_NAME:${{ steps.meta.outputs.image_tag }} . - name: Verify Flask is available in container image run: | docker run --rm $IMAGE_NAME:latest python -c "import flask" - name: Clean up old test containers run: | docker stop arcade-test || true docker rm -f arcade-test || true - name: Install Trivy run: | sudo apt-get update sudo apt-get install -y wget gnupg lsb-release apt-transport-https wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list sudo apt-get update sudo apt-get install -y trivy - name: Scan container image with Trivy run: | trivy image --exit-code 0 --severity HIGH,CRITICAL $IMAGE_NAME:latest - name: Push container images run: | docker push $IMAGE_NAME:latest docker push $IMAGE_NAME:${{ steps.meta.outputs.image_tag }} deploy-to-k3s: needs: build-scan-and-push-image runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 - name: Install kubectl run: | curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl sudo mv kubectl /usr/local/bin/kubectl kubectl version --client - name: Configure kubeconfig run: | mkdir -p ~/.kube echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > ~/.kube/config chmod 600 ~/.kube/config - name: Render manifest with image tag run: | sed "s|image: .*|image: ${IMAGE_NAME}:${{ needs.build-scan-and-push-image.outputs.image_tag }}|g" arcade.yaml > rendered-arcade.yaml - name: Apply Kubernetes manifest run: | kubectl apply -f rendered-arcade.yaml - name: Wait for rollout run: | kubectl -n devsecops rollout status deployment/arcade --timeout=180s