From a438f11fd35d5382d9426470808bef66aa45c62b Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Wed, 15 Apr 2026 14:52:42 +1000 Subject: [PATCH 01/15] Add requirements and clean repo for CI --- .gitignore | 7 ++++++- app.py | 3 ++- requirements.txt | 11 +++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index f8647d7..a821914 100644 --- a/.gitignore +++ b/.gitignore @@ -137,4 +137,9 @@ dmypy.json .pytype/ # Cython debug symbols -cython_debug/ \ No newline at end of file +cython_debug/ + +# VS Code / macOS / JetBrains +.vscode/ +.DS_Store +.idea/ \ No newline at end of file diff --git a/app.py b/app.py index 4b6ccca..5192738 100644 --- a/app.py +++ b/app.py @@ -46,5 +46,6 @@ def delete(todo_id): return redirect(url_for("home")) if __name__ == "__main__": - db.create_all() + with app.app_context(): + db.create_all() app.run(debug=True) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a7ad814 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +blinker==1.9.0 +click==8.3.2 +Flask==3.1.3 +Flask-SQLAlchemy==3.1.1 +greenlet==3.4.0 +itsdangerous==2.2.0 +Jinja2==3.1.6 +MarkupSafe==3.0.3 +SQLAlchemy==2.0.49 +typing_extensions==4.15.0 +Werkzeug==3.1.8 From 43fde45da8b163224f4f69297f06ccc680f45be7 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Thu, 16 Apr 2026 13:55:51 +1000 Subject: [PATCH 02/15] get pytest and SonarQube ready for CI --- .gitignore | 5 ++++- requirements.txt | 19 +++++++++++++++++++ sonar-project.properties | 6 ++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 sonar-project.properties diff --git a/.gitignore b/.gitignore index a821914..2cf533c 100644 --- a/.gitignore +++ b/.gitignore @@ -142,4 +142,7 @@ cython_debug/ # VS Code / macOS / JetBrains .vscode/ .DS_Store -.idea/ \ No newline at end of file +.idea/ + +coverage.xml +.sonar/ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a7ad814..062f045 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,30 @@ blinker==1.9.0 +certifi==2026.2.25 +charset-normalizer==3.4.7 click==8.3.2 +coverage==7.13.5 Flask==3.1.3 Flask-SQLAlchemy==3.1.1 greenlet==3.4.0 +idna==3.11 +iniconfig==2.3.0 itsdangerous==2.2.0 Jinja2==3.1.6 +jproperties==2.1.2 MarkupSafe==3.0.3 +packaging==26.1 +pluggy==1.6.0 +pyfakefs==5.9.3 +Pygments==2.20.0 +pysonar==1.4.0.4676 +pytest==9.0.3 +pytest-cov==7.1.0 +PyYAML==6.0.3 +requests==2.32.5 +responses==0.25.8 +six==1.17.0 SQLAlchemy==2.0.49 +tomli==2.2.1 typing_extensions==4.15.0 +urllib3==2.6.3 Werkzeug==3.1.8 diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..43c12f8 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,6 @@ +sonar.projectKey=flask-todo-local +sonar.projectName=flask-todo +sonar.sources=. +sonar.tests=tests +sonar.exclusions=.venv/**,instance/**,__pycache__/**,.pytest_cache/**,coverage.xml,tests/** +sonar.python.coverage.reportPaths=coverage.xml \ No newline at end of file From 08f8a65b8be2ba5613652692b7b3c20c622ed78a Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Mon, 20 Apr 2026 16:00:29 +1000 Subject: [PATCH 03/15] Add Docker support and test setup for CI --- .dockerignore | 10 ++++++++++ Dockerfile | 16 ++++++++++++++++ app.py | 2 +- tests/test_app.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 tests/test_app.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f91f8ee --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.venv/ +__pycache__/ +.pytest_cache/ +.sonar/ +instance/ +coverage.xml +.git +.gitignore +.vscode/ +README.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..42c68c4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . + +RUN pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir -r requirements.txt + +COPY . . + +RUN mkdir -p instance + +EXPOSE 5000 + +CMD ["python", "app.py"] \ No newline at end of file diff --git a/app.py b/app.py index 5192738..ea70355 100644 --- a/app.py +++ b/app.py @@ -48,4 +48,4 @@ def delete(todo_id): if __name__ == "__main__": with app.app_context(): db.create_all() - app.run(debug=True) + app.run(host="0.0.0.0", port=5000) diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..67fb1fb --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,47 @@ +import os +import sys +import tempfile +import pytest + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from app import app, db + + +@pytest.fixture +def client(): + db_fd, db_path = tempfile.mkstemp() + + app.config["TESTING"] = True + app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" + + with app.app_context(): + db.drop_all() + db.create_all() + + with app.test_client() as client: + yield client + + with app.app_context(): + db.session.remove() + db.drop_all() + + os.close(db_fd) + os.unlink(db_path) + + +def test_home_page_loads(client): + response = client.get("/") + assert response.status_code == 200 + assert b"To Do App" in response.data + + +def test_add_todo(client): + response = client.post( + "/add", + data={"title": "test task"}, + follow_redirects=True + ) + + assert response.status_code == 200 + assert b"test task" in response.data \ No newline at end of file From 44c1f5b12dfbc5da58e444201cec8373d9788fd8 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Mon, 20 Apr 2026 16:28:52 +1000 Subject: [PATCH 04/15] Add Jenkins pipeline for test sonar and docker build --- Jenkinsfile | 67 ++++++++++++++++++++++++++++++++++++++++ sonar-project.properties | 13 +++++--- 2 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..e47d2cd --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,67 @@ +pipeline { + agent any + + options { + timestamps() + } + + environment { + SONAR_HOST_URL = 'http://172.31.14.190:9000' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Set up Python environment') { + steps { + sh ''' + python3 -m venv .venv-ci + . .venv-ci/bin/activate + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-cov pysonar + ''' + } + } + + stage('Run tests') { + steps { + sh ''' + . .venv-ci/bin/activate + pytest -v --cov=app --cov-report=xml + ''' + } + } + + stage('Run SonarQube analysis') { + steps { + withCredentials([string(credentialsId: 'sonar-token-flask-todo', variable: 'SONAR_TOKEN')]) { + sh ''' + . .venv-ci/bin/activate + pysonar \ + --sonar-host-url="${SONAR_HOST_URL}" \ + --sonar-token="${SONAR_TOKEN}" + ''' + } + } + } + + stage('Build Docker image') { + steps { + sh ''' + docker build -t flask-todo:ci . + ''' + } + } + } + + post { + always { + archiveArtifacts artifacts: 'coverage.xml', allowEmptyArchive: true + } + } +} \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties index 43c12f8..f2955d7 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,11 @@ -sonar.projectKey=flask-todo-local +sonar.projectKey=flask-todo sonar.projectName=flask-todo -sonar.sources=. + +sonar.sources=app.py sonar.tests=tests -sonar.exclusions=.venv/**,instance/**,__pycache__/**,.pytest_cache/**,coverage.xml,tests/** -sonar.python.coverage.reportPaths=coverage.xml \ No newline at end of file + +sonar.python.coverage.reportPaths=coverage.xml + +sonar.exclusions=.venv/**,instance/**,__pycache__/**,.pytest_cache/**,.sonar/**,.git/** + +sonar.sourceEncoding=UTF-8 \ No newline at end of file From 7f4da8fde91a432b89e143b29d0622d39d702d54 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Mon, 20 Apr 2026 17:12:55 +1000 Subject: [PATCH 05/15] Modified Jenkinsfile to set python 3.12 to align with project's python version --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index e47d2cd..c110904 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -19,7 +19,7 @@ pipeline { stage('Set up Python environment') { steps { sh ''' - python3 -m venv .venv-ci + python3.12 -m venv .venv-ci . .venv-ci/bin/activate python -m pip install --upgrade pip pip install -r requirements.txt From cc80222da5ef8ec41a6912f99504505ac086f6b5 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Mon, 20 Apr 2026 17:39:19 +1000 Subject: [PATCH 06/15] 2 optimization for CI --- Jenkinsfile | 2 +- sonar-project.properties | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c110904..6741a5d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -19,11 +19,11 @@ pipeline { stage('Set up Python environment') { steps { sh ''' + rm -rf .venv-ci python3.12 -m venv .venv-ci . .venv-ci/bin/activate python -m pip install --upgrade pip pip install -r requirements.txt - pip install pytest pytest-cov pysonar ''' } } diff --git a/sonar-project.properties b/sonar-project.properties index f2955d7..cd27976 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -8,4 +8,6 @@ sonar.python.coverage.reportPaths=coverage.xml sonar.exclusions=.venv/**,instance/**,__pycache__/**,.pytest_cache/**,.sonar/**,.git/** -sonar.sourceEncoding=UTF-8 \ No newline at end of file +sonar.sourceEncoding=UTF-8 + +sonar.python.version=3.12 \ No newline at end of file From 585b30ce72f4db30571b1a9c60cb10a1c867dc12 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Mon, 20 Apr 2026 17:51:03 +1000 Subject: [PATCH 07/15] optimization .dockerignore for CI --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index f91f8ee..1fdcf56 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ .venv/ +.venv-ci/ __pycache__/ .pytest_cache/ .sonar/ From 4dd648ac65fed1519c51e6b0e5df7fd04eebe6cb Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Mon, 20 Apr 2026 20:35:19 +1000 Subject: [PATCH 08/15] Add ECR push stage to Jenkins pipeline --- Jenkinsfile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 6741a5d..49645a0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,6 +7,8 @@ pipeline { environment { SONAR_HOST_URL = 'http://172.31.14.190:9000' + AWS_REGION = 'ap-southeast-2' + ECR_REPO = 'flask-todo' } stages { @@ -57,6 +59,24 @@ pipeline { ''' } } + stage('Push Docker image to ECR') { + steps { + sh ''' + ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + ECR_REGISTRY=${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com + SHORT_SHA=$(git rev-parse --short HEAD) + IMAGE_TAG=${BUILD_NUMBER}-${SHORT_SHA} + + aws ecr get-login-password --region ${AWS_REGION} | \ + docker login --username AWS --password-stdin ${ECR_REGISTRY} + + docker tag flask-todo:ci ${ECR_REGISTRY}/${ECR_REPO}:${IMAGE_TAG} + docker push ${ECR_REGISTRY}/${ECR_REPO}:${IMAGE_TAG} + + echo "Pushed image: ${ECR_REGISTRY}/${ECR_REPO}:${IMAGE_TAG}" + ''' + } + } } post { From 74f6c29de9093577d1f01820a7b6fd6519920e85 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Tue, 21 Apr 2026 14:58:14 +1000 Subject: [PATCH 09/15] Add GitHub Actions CI workflow --- .github/workflows/ci.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e69de29 From 150afb91216d96a72946887d3465844838eb3b04 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Tue, 21 Apr 2026 15:06:38 +1000 Subject: [PATCH 10/15] Fix GitHub Actions workflow content --- .github/workflows/ci.yml | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e69de29..2b63137 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: Flask Todo CI + +on: + pull_request: + branches: [ "master" ] + push: + branches: [ "master" ] + +permissions: + contents: read + id-token: write + +env: + AWS_REGION: ap-southeast-2 + ECR_REPOSITORY: flask-todo + +jobs: + test-build-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + 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 + + - name: Run tests with coverage + run: | + pytest -v --cov=app --cov-report=xml + + - name: Build Docker image + run: | + docker build -t flask-todo:ci . + + - name: Configure AWS credentials + if: github.event_name == 'push' + uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: arn:aws:iam::730335329548:role/github-actions-ecr-role + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + if: github.event_name == 'push' + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Tag and push image to ECR + if: github.event_name == 'push' + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + run: | + SHORT_SHA=$(git rev-parse --short HEAD) + IMAGE_TAG=${{ github.run_number }}-${SHORT_SHA} + + docker tag flask-todo:ci $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + + echo "Pushed image: $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \ No newline at end of file From 7fc93e0cc0e03264de94d77e88b54a3920bf8235 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Tue, 21 Apr 2026 15:22:56 +1000 Subject: [PATCH 11/15] new version CI --- .github/workflows/ci.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b63137..ac38f61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,13 +20,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Set up Python 3.12 - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version: '3.12' - cache: 'pip' - name: Install dependencies run: | @@ -42,27 +41,29 @@ jobs: run: | docker build -t flask-todo:ci . + - name: Set image tag + id: vars + run: | + SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) + echo "IMAGE_TAG=${GITHUB_RUN_NUMBER}-${SHORT_SHA}" >> $GITHUB_ENV + - name: Configure AWS credentials - if: github.event_name == 'push' - uses: aws-actions/configure-aws-credentials@v6 + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::730335329548:role/github-actions-ecr-role aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR - if: github.event_name == 'push' + if: github.event_name == 'push' && github.ref == 'refs/heads/master' id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Tag and push image to ECR - if: github.event_name == 'push' + if: github.event_name == 'push' && github.ref == 'refs/heads/master' env: - REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} run: | - SHORT_SHA=$(git rev-parse --short HEAD) - IMAGE_TAG=${{ github.run_number }}-${SHORT_SHA} - - docker tag flask-todo:ci $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - - echo "Pushed image: $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \ No newline at end of file + docker tag flask-todo:ci $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + echo "Pushed image: $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \ No newline at end of file From 072fce5cd6e0abe87c845bdcb9af1499e03ace4c Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Tue, 21 Apr 2026 15:33:31 +1000 Subject: [PATCH 12/15] Test PR workflow --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e2a98cb..d539cd8 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,4 @@ Run the app ```console $ flask run ``` + From 03e3e9f1426123bf9634086b1867da112a987375 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Tue, 21 Apr 2026 18:36:30 +1000 Subject: [PATCH 13/15] Fix SonarQube Cloud project properties --- sonar-project.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index cd27976..6c675b7 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,5 +1,5 @@ -sonar.projectKey=flask-todo -sonar.projectName=flask-todo +sonar.projectKey=apeCry_flask-todo +sonar.organization=apecry sonar.sources=app.py sonar.tests=tests From 2247b5ffed350d1eeefce7b40f9840f626b559a8 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Tue, 21 Apr 2026 20:32:04 +1000 Subject: [PATCH 14/15] Add SonarQube Cloud scan to GitHub Actions CI --- .github/workflows/ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac38f61..69fc9e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python 3.12 uses: actions/setup-python@v5 @@ -37,12 +39,19 @@ jobs: run: | pytest -v --cov=app --cov-report=xml + - name: SonarQube Cloud Scan + uses: SonarSource/sonarqube-scan-action@v7.1.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: > + -Dsonar.qualitygate.wait=true + - name: Build Docker image run: | docker build -t flask-todo:ci . - name: Set image tag - id: vars run: | SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) echo "IMAGE_TAG=${GITHUB_RUN_NUMBER}-${SHORT_SHA}" >> $GITHUB_ENV From f6122f37a615e31246a86c319d7309098ada34c0 Mon Sep 17 00:00:00 2001 From: Shuyi Zhang Date: Tue, 21 Apr 2026 21:20:08 +1000 Subject: [PATCH 15/15] Make Flask host configurable and fix Sonar security issue --- Dockerfile | 3 +++ app.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 42c68c4..46f7c59 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,9 @@ COPY . . RUN mkdir -p instance +ENV APP_HOST=0.0.0.0 +ENV PORT=5000 + EXPOSE 5000 CMD ["python", "app.py"] \ No newline at end of file diff --git a/app.py b/app.py index ea70355..0fbeced 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,6 @@ from flask import Flask, render_template, request, redirect, url_for from flask_sqlalchemy import SQLAlchemy +import os app = Flask(__name__) @@ -45,7 +46,11 @@ def delete(todo_id): db.session.commit() return redirect(url_for("home")) + if __name__ == "__main__": with app.app_context(): db.create_all() - app.run(host="0.0.0.0", port=5000) + + host = os.getenv("APP_HOST", "127.0.0.1") + port = int(os.getenv("PORT", "5000")) + app.run(host=host, port=port)