diff --git a/.github/workflows/lint-n-test.yaml b/.github/workflows/lint-n-test.yaml index 0a9f0c5..9729d31 100644 --- a/.github/workflows/lint-n-test.yaml +++ b/.github/workflows/lint-n-test.yaml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: "3.11" - name: Install dependencies run: | @@ -52,8 +52,18 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + git add README.md + + # Check if there are any staged changes. If not, exit gracefully. + # `git diff --staged --quiet` exits with 1 if there are changes, 0 if not. + if git diff --staged --quiet; then + echo "No changes to commit. README is up-to-date." + exit 0 + fi + + # If there are changes, commit and push them git commit -m "Update pylint score badge to ${{ steps.pylint.outputs.pylint_score }}" git push env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 2b7df7f..646980b 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -13,32 +13,107 @@ jobs: release: name: Build and Release runs-on: ubuntu-latest - + permissions: + contents: write + packages: write steps: - name: Checkout code uses: actions/checkout@v4 - - name: Extract version from tag run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV - - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . + platforms: linux/amd64,linux/arm64 push: true tags: | ghcr.io/remla25-team21/model-service:${{ env.VERSION }} ghcr.io/remla25-team21/model-service:latest - - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.ref_name }} generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + bump_version_on_main: + name: Bump version on main to next pre-release + needs: release + runs-on: ubuntu-latest + permissions: + contents: write + if: success() + steps: + - name: Checkout main branch + uses: actions/checkout@v4 + with: + ref: main + + - name: Get released version from trigger + id: get_released_version + run: echo "RELEASED_VERSION_TAG=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + + - name: Fetch all tags + run: git fetch --tags + - name: Get current pre-release counter + id: get_pre_release_counter + run: | + base_version="${{ env.RELEASED_VERSION_TAG }}" + # List tags matching pre-release pattern and sort them. + pre_tags=$(git tag --list "v${base_version}-pre.*" --sort=-v:refname) + + if [[ -z "$pre_tags" ]]; then + counter=0 + else + # Extract the last numeric counter from the highest tag + last_tag=$(echo "$pre_tags" | head -n 1) + echo "Last pre-release tag: $last_tag" + + # Extract counter after last dash, e.g. 1.2.3-pre.5 -> 5 + counter=$(echo "$last_tag" | grep -oE '[0-9]+$') + counter=${counter:-0} + fi + + next_counter=$((counter + 1)) + echo "Next counter: $next_counter" + + echo "PRE_RELEASE_COUNTER=$next_counter" >> $GITHUB_ENV + + - name: Calculate next pre-release version with counter + id: calculate_next_version + run: | + current_version="${{ env.RELEASED_VERSION_TAG }}" + pre_counter="${{ env.PRE_RELEASE_COUNTER }}" + IFS='.' read -r major minor patch <<< "$current_version" + + next_patch=$((patch + 1)) + + next_pre_release_version="${major}.${minor}.${next_patch}-pre.${pre_counter}" + + echo "Next pre-release version: $next_pre_release_version" + echo "NEXT_VERSION=$next_pre_release_version" >> $GITHUB_ENV + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Optionally create a tag instead of a version file + - name: Create pre-release tag + run: | + git tag -a "v${{ env.NEXT_VERSION }}" -m "Start next development cycle" + git push origin "v${{ env.NEXT_VERSION }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Dockerfile b/Dockerfile index a6761be..3e83936 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,10 @@ -FROM python:3.11-slim +# Using multi-stage build to minimize the final image size + +# Stage 1: Builder +FROM python:3.11-slim AS builder RUN apt-get update && \ - apt-get install -y git curl && \ + apt-get install -y git curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -12,10 +15,24 @@ RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt COPY . . -ENV FLASK_APP=src/app.py -ENV PYTHONUNBUFFERED=1 -ENV FLASK_ENV=development +# Stage 2: Final image +FROM python:3.11-slim + + +RUN apt-get update && \ + apt-get install -y curl --no-install-recommends && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages +COPY --from=builder /app /app + +ENV PYTHONPATH=/app +ENV PORT=8080 +ENV HOST=0.0.0.0 -EXPOSE 8080 +EXPOSE ${PORT} -CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=8080"] +CMD ["python", "src/app.py"] \ No newline at end of file diff --git a/src/app.py b/src/app.py index 23e8264..067440d 100644 --- a/src/app.py +++ b/src/app.py @@ -1,13 +1,21 @@ from flask import Flask -from flask import request, current_app, jsonify +from flask import request, jsonify from flasgger import Swagger, swag_from import requests -from libml.preprocessing import preprocess_train, preprocess_inference +from libml.preprocessing import preprocess_inference + import pickle import numpy as np import os import logging -from config import BASE_URL, MODEL_VERSION +from config import MODEL_VERSION, BASE_URL + +DEFAULT_PORT = 8080 +DEFAULT_HOST = "127.0.0.1" + +# Set up environment variables +HOST = os.environ.get("HOST", DEFAULT_HOST) +PORT = int(os.environ.get("PORT", DEFAULT_PORT)) if not os.path.exists("models"): @@ -64,6 +72,7 @@ def home(): @app.route("/predict", methods=["POST"]) @swag_from( { + "summary": "Make a sentiment prediction based on input review", "tags": ["Prediction"], "parameters": [ { @@ -122,5 +131,5 @@ def predict(): return jsonify({"error": "Prediction failed"}), 500 -# if __name__ == "__main__": -# app.run(debug=True) +if __name__ == "__main__": + app.run(host=HOST, port=PORT)