From 0419b36680da3498794a8d7fae3e8bc2925fbbd7 Mon Sep 17 00:00:00 2001 From: Adrien Sigur Date: Thu, 4 Jun 2026 12:15:03 +0200 Subject: [PATCH 01/12] feat: premier commit - activation de la CI --- notes.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 notes.md diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..fe0cc88 --- /dev/null +++ b/notes.md @@ -0,0 +1 @@ + Mon TP Github Actions From 78454c0cd4924864cfb799a0276360b5d12b8b5e Mon Sep 17 00:00:00 2001 From: Adrien Sigur Date: Thu, 4 Jun 2026 13:35:29 +0200 Subject: [PATCH 02/12] feat: premier commit - activation de la CICD --- notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/notes.md b/notes.md index fe0cc88..207097b 100644 --- a/notes.md +++ b/notes.md @@ -1 +1,2 @@ Mon TP Github Actions + Mon TP Github Actions From 5d5c4c83c4290e8edda092e405d1d4f04de878a6 Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Thu, 4 Jun 2026 14:00:29 +0200 Subject: [PATCH 03/12] Exercice 1.2 Workflow hello.yml --- .github/workflows/hello.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/hello.yml diff --git a/.github/workflows/hello.yml b/.github/workflows/hello.yml new file mode 100644 index 0000000..37efbb3 --- /dev/null +++ b/.github/workflows/hello.yml @@ -0,0 +1,30 @@ +name: Hello NexaCloud + +on: + push: + branches: [main] + workflow_dispatch: # permet de déclencher manuellement depuis l'interface GitHub + +jobs: + salutation: + runs-on: ubuntu-latest + + steps: + - name: Checkout du code + uses: actions/checkout@v4 + + - name: Informations sur l'environnement + run: | + echo "Repo : ${{ github.repository }}" + echo "Branche : ${{ github.ref_name }}" + echo "Commit : ${{ github.sha }}" + echo "Acteur : ${{ github.actor }}" + + - name: Get date + run: echo "DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + + - name: Use date + run: echo ${{ env.DATE }} + + - name: Lister les fichiers du repo + run: ls -la From 5ae0479dc955497b472b91fb0a41545cffc298a6 Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Thu, 4 Jun 2026 14:27:33 +0200 Subject: [PATCH 04/12] Add error in app.py test exercise 2.3 --- .github/workflows/ci.yml | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a27357..fe7092b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,43 @@ -name: CI +name: CI — NexaCloud API on: push: - branches: [ "main" ] + branches: [main] pull_request: - branches: [ "main" ] + branches: [main] jobs: - build: + test: runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v4 - - name: Example step - run: echo "Add your build/test steps here!" + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Installer les dépendances + run: pip install -r ressources/requirements.txt + + - name: Lancement du test + run: pytest ressources/ -v + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Installer flake8 + run: pip install flake8 + + - name: "Lint avec flake8" + run: flake8 ressources/ --config ressources/.flake8 From 734b26e2835809d289c27d0df34c8f59a3f715cf Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Thu, 4 Jun 2026 14:32:11 +0200 Subject: [PATCH 05/12] Error added in app.py ex:2.3 --- README.md | 9 +++++++++ ressources/app.py | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4abe121..e830a85 100644 --- a/README.md +++ b/README.md @@ -198,10 +198,19 @@ Ouvrez le fichier `.github/workflows/ci.yml` déjà présent dans ce repo. Répondez aux questions suivantes **sans modifier le fichier** : 1. Sur quelle(s) branche(s) ce workflow se déclenche-t-il ? +# sur la branch main + 2. Combien de jobs contient-il ? + +# un job 3. Sur quel système d'exploitation tourne-t-il ? + +# Ubuntu 4. Quelle action installe Python ? + +# requirements.txt 5. Quelle commande lance les tests ? +# runs on Vérifiez vos réponses en allant dans l'onglet **Actions** de votre repo GitHub après votre premier push. diff --git a/ressources/app.py b/ressources/app.py index d7e75f2..4ef52fa 100644 --- a/ressources/app.py +++ b/ressources/app.py @@ -15,6 +15,8 @@ "critical": 3, } +ma_variable = "une ligne vraiment très très très très très très très très très très très longue qui dépasse 100 caractères" + @app.route("/") def index(): @@ -23,7 +25,7 @@ def index(): @app.route("/health") def health(): - return jsonify({"status": "healthy"}) + return jsonify({" status": " healthy"}) @app.route("/logs/summary") From a7d226664437d0f605b40165bab2c728d071c4c1 Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Thu, 4 Jun 2026 14:47:28 +0200 Subject: [PATCH 06/12] Error patch in app.py ex:2.3 --- README.md | 5 +++++ ressources/app.py | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e830a85..9484f38 100644 --- a/README.md +++ b/README.md @@ -543,6 +543,11 @@ C'est l'exercice le plus important de l'étape. 1. **Introduisez une erreur de style** dans `ressources/app.py` : ajoutez une ligne avec des espaces superflus en fin de ligne ou une ligne trop longue (> 100 caractères). Commitez et pushez. 2. **Observez** : quel job échoue ? Que dit le message d'erreur dans les logs ? + +Run flake8 ressources/ --config ressources/.flake8 +ressources/app.py:18:101: E501 line too long (123 > 100 characters) +Error: Process completed with exit code 1. + 3. **Corrigez** l'erreur, pushez à nouveau. 4. **Introduisez une erreur dans un test** : modifiez `test_app.py` pour qu'un assert soit faux (ex : `assert data["info"] == 999`). Commitez et pushez. 5. **Observez** : cette fois quel job échoue ? diff --git a/ressources/app.py b/ressources/app.py index 4ef52fa..d7e75f2 100644 --- a/ressources/app.py +++ b/ressources/app.py @@ -15,8 +15,6 @@ "critical": 3, } -ma_variable = "une ligne vraiment très très très très très très très très très très très longue qui dépasse 100 caractères" - @app.route("/") def index(): @@ -25,7 +23,7 @@ def index(): @app.route("/health") def health(): - return jsonify({" status": " healthy"}) + return jsonify({"status": "healthy"}) @app.route("/logs/summary") From f1d65e69f2d9f306bdaccbe0f1b5b556e77e0446 Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Thu, 4 Jun 2026 14:50:51 +0200 Subject: [PATCH 07/12] Add Error in test_app.py ex:2.3 --- ressources/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ressources/test_app.py b/ressources/test_app.py index ce56a43..a8ca8d1 100644 --- a/ressources/test_app.py +++ b/ressources/test_app.py @@ -18,7 +18,7 @@ def test_index_status(client): response = client.get("/") assert response.status_code == 200 data = response.get_json() - assert data["status"] == "ok" + assert data["info"] == 999 assert data["service"] == "NexaCloud API" From d91e3acc0bd931b8ec3eeeb84a6e522438842b81 Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Thu, 4 Jun 2026 14:53:23 +0200 Subject: [PATCH 08/12] Fix error test_app.py ex:2.3 --- README.md | 16 ++++++++++++++++ ressources/test_app.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9484f38..aafa2df 100644 --- a/README.md +++ b/README.md @@ -551,6 +551,22 @@ Error: Process completed with exit code 1. 3. **Corrigez** l'erreur, pushez à nouveau. 4. **Introduisez une erreur dans un test** : modifiez `test_app.py` pour qu'un assert soit faux (ex : `assert data["info"] == 999`). Commitez et pushez. 5. **Observez** : cette fois quel job échoue ? + +client = > + + def test_index_status(client): + """La route / retourne 200 avec le statut ok.""" + response = client.get("/") + assert response.status_code == 200 + data = response.get_json() +> assert data["info"] == 999 +E KeyError: 'info' + +ressources/test_app.py:21: KeyError +=========================== short test summary info ============================ +FAILED ressources/test_app.py::test_index_status - KeyError: 'info' +========================= 1 failed, 4 passed in 0.13s ========================== + 6. **Corrigez** et vérifiez que la CI repasse au vert. > 💡 Le but n'est pas de ne jamais casser la CI — c'est de savoir lire les logs et corriger rapidement. Cette compétence s'acquiert en cassant volontairement. diff --git a/ressources/test_app.py b/ressources/test_app.py index a8ca8d1..ce56a43 100644 --- a/ressources/test_app.py +++ b/ressources/test_app.py @@ -18,7 +18,7 @@ def test_index_status(client): response = client.get("/") assert response.status_code == 200 data = response.get_json() - assert data["info"] == 999 + assert data["status"] == "ok" assert data["service"] == "NexaCloud API" From 06825ca818ee7c31456a57a6734787893239fcdf Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Thu, 4 Jun 2026 15:24:54 +0200 Subject: [PATCH 09/12] Add Demo Secret test --- .github/workflows/secrets.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/secrets.yml diff --git a/.github/workflows/secrets.yml b/.github/workflows/secrets.yml new file mode 100644 index 0000000..baf499f --- /dev/null +++ b/.github/workflows/secrets.yml @@ -0,0 +1,15 @@ +name: Demo Secrets + +on: + workflow_dispatch: + +jobs: + demo: + runs-on: ubuntu-latest + + steps: + - name: Utiliser le secret + run: | + echo "La clé existe : ${{ secrets.API_KEY != '' }}" + # ⚠️ Cette ligne sera masquée dans les logs : + echo "Valeur : ${{ secrets.API_KEY }}" From 1ee53aa06650e2e04e396d4699f62a43e020905b Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Fri, 5 Jun 2026 11:13:13 +0200 Subject: [PATCH 10/12] Add Azure script for creating app and connexion to azure cli --- .github/workflows/cicd.yml | 55 +++++++++++++++++++++++++++++++++++ .github/workflows/deploy.yml | 32 ++++++++++++++++++++ .github/workflows/secrets.yml | 14 +++++---- azure.sh | 28 ++++++++++++++++++ 4 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/cicd.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 azure.sh diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml new file mode 100644 index 0000000..67ab034 --- /dev/null +++ b/.github/workflows/cicd.yml @@ -0,0 +1,55 @@ +name: CI/CD — NexaCloud API + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + # ── Job 1 : Qualité ──────────────────────────────────────────────── + qualite: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - run: pip install -r ressources/requirements.txt + + - name: Lint + run: flake8 ressources/ --config ressources/.flake8 + + - name: Tests + run: pytest ressources/ -v --cov=ressources + + # ── Job 2 : Staging ─────────────────────────────────────────────── + staging: + runs-on: ubuntu-latest + needs: qualite # attend que le job qualite réussisse + environment: staging + if: github.ref_name == 'main' # uniquement sur la branche main + + steps: + - uses: actions/checkout@v4 + + - name: "Run Azure webapp deploy action using publish profile credentials" + uses: azure/webapps-deploy@v3 + with: + app-name: "" + publish-profile: ${{ secrets.azureWebAppPublishProfile }} + + # ── Job 3 : Production ──────────────────────────────────────────── + production: + runs-on: ubuntu-latest + needs: staging + environment: production + if: github.ref_name == 'main' + + steps: + - uses: actions/checkout@v4 + + # TODO: même chose pour la production diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..2065412 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,32 @@ +name: Deploy + +on: + workflow_dispatch: + +jobs: + deploy-staging: + runs-on: ubuntu-latest + environment: staging + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Déployer en staging + run: | + echo "✅ Déploiement en staging réussi" + echo "URL : https://staging.nexacloud.example.com" + + deploy-production: + runs-on: ubuntu-latest + environment: production + needs: deploy-staging # attend que staging soit terminé ET approuvé + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Déployer en production + run: | + echo "🚀 Déploiement en production réussi" + echo "URL : https://nexacloud.example.com" diff --git a/.github/workflows/secrets.yml b/.github/workflows/secrets.yml index baf499f..a76250c 100644 --- a/.github/workflows/secrets.yml +++ b/.github/workflows/secrets.yml @@ -7,9 +7,13 @@ jobs: demo: runs-on: ubuntu-latest + env: + API_KEY: ${{ secrets.API_KEY }} + steps: - - name: Utiliser le secret - run: | - echo "La clé existe : ${{ secrets.API_KEY != '' }}" - # ⚠️ Cette ligne sera masquée dans les logs : - echo "Valeur : ${{ secrets.API_KEY }}" + - name: Vérifier que le secret est défini + run: if [ -z "$API_KEY" ]; then + echo " Le secret API_KEY n'est pas défini" + exit 1 + fi + echo "✅ Le secret API_KEY est défini ( caractères)" diff --git a/azure.sh b/azure.sh new file mode 100644 index 0000000..8f46f5b --- /dev/null +++ b/azure.sh @@ -0,0 +1,28 @@ +# Dans votre terminal (Azure CLI doit être installé) + +RESOURCE_GROUP="asigurRG" +APP_NAME="DevAppAdrien" # nom unique obligatoire +LOCATION="francecentral" + +# Créer le resource group +az group create --name "$RESOURCE_GROUP" --location "$LOCATION" + +# Créer le plan App Service (B1 — nécessaire pour Always On et la stabilité) +az appservice plan create \ + --name "pythonappdev" \ + --resource-group "$RESOURCE_GROUP" \ + --sku B1 \ + --is-linux + +# Créer l'App Service Python +az webapp create \ + --name "$APP_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --plan "plan-nexacloud" \ + --runtime "PYTHON:3.11" + +# Récupérer le publish profile (à coller dans les secrets GitHub) +az webapp deployment list-publishing-profiles \ + --name "$APP_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --xml From 28311bfb1fdb7101f8ce286bd8c296fa9292dc74 Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Fri, 5 Jun 2026 11:21:47 +0200 Subject: [PATCH 11/12] Add Ci production and test in github action in the file cicd.yml --- .github/workflows/cicd.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 67ab034..d126532 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -38,12 +38,14 @@ jobs: - name: "Run Azure webapp deploy action using publish profile credentials" uses: azure/webapps-deploy@v3 - with: - app-name: "" - publish-profile: ${{ secrets.azureWebAppPublishProfile }} + with: + app-name: "DevAppAdrien" + publish-profile: ${{ secrets.azureWebAppPublishProfile }} + package: ressources/ # ── Job 3 : Production ──────────────────────────────────────────── production: + runs-on: ubuntu-latest needs: staging environment: production @@ -52,4 +54,9 @@ jobs: steps: - uses: actions/checkout@v4 - # TODO: même chose pour la production + - name: "Run Azure webapp deploy action using publish profile credentials" + uses: azure/webapps-deploy@v3 + with: + app-name: "DevAppAdrien" + publish-profile: ${{ secrets.azureWebAppPublishProfile }} + package: ressources/ From cb8b1b78b8b913735c380bd108d2d0d069f74762 Mon Sep 17 00:00:00 2001 From: Adri-Sig Date: Thu, 11 Jun 2026 09:40:11 +0200 Subject: [PATCH 12/12] feat: ajouter endpoint /logs/stats --- ressources/app.py | 90 +++++++++++++++-------------- ressources/test_app.py | 128 ++++++++++++++++++++++------------------- 2 files changed, 117 insertions(+), 101 deletions(-) diff --git a/ressources/app.py b/ressources/app.py index d7e75f2..5048b73 100644 --- a/ressources/app.py +++ b/ressources/app.py @@ -1,42 +1,48 @@ -""" -app.py — API NexaCloud -Mini API Flask utilisée comme cible du pipeline CI/CD dans le TP GitHub Actions. -""" - -from flask import Flask, jsonify - -app = Flask(__name__) - -# Simule un résumé de logs issu du TP Bash / PowerShell -LOG_SUMMARY = { - "info": 142, - "warning": 28, - "error": 12, - "critical": 3, -} - - -@app.route("/") -def index(): - return jsonify({"status": "ok", "service": "NexaCloud API", "version": "1.1.0"}) - - -@app.route("/health") -def health(): - return jsonify({"status": "healthy"}) - - -@app.route("/logs/summary") -def logs_summary(): - return jsonify(LOG_SUMMARY) - - -@app.route("/logs/critical") -def logs_critical(): - seuil = LOG_SUMMARY["critical"] - alerte = seuil > 0 - return jsonify({"critical_count": seuil, "alerte": alerte}) - - -if __name__ == "__main__": - app.run(debug=True, port=5001) +""" +app.py — API NexaCloud +Mini API Flask utilisée comme cible du pipeline CI/CD dans le TP GitHub Actions. +""" + +from flask import Flask, jsonify + +app = Flask(__name__) + +# Simule un résumé de logs issu du TP Bash / PowerShell +LOG_SUMMARY = { + "info": 142, + "warning": 28, + "error": 12, + "critical": 3, +} + + +@app.route("/") +def index(): + return jsonify({"status": "ok", "service": "NexaCloud API", "version": "1.1.0"}) + + +@app.route("/health") +def health(): + return jsonify({"status": "healthy"}) + + +@app.route("/logs/summary") +def logs_summary(): + return jsonify(LOG_SUMMARY) + + +@app.route("/logs/critical") +def logs_critical(): + seuil = LOG_SUMMARY["critical"] + alerte = seuil > 0 + return jsonify({"critical_count": seuil, "alerte": alerte}) + + +@app.route("/logs/stats") +def logs_stats(): + total = sum(LOG_SUMMARY.values()) + return jsonify({"total": total, "breakdown": LOG_SUMMARY}) + + +if __name__ == "__main__": + app.run(debug=True, port=5001) diff --git a/ressources/test_app.py b/ressources/test_app.py index ce56a43..b796c54 100644 --- a/ressources/test_app.py +++ b/ressources/test_app.py @@ -1,59 +1,69 @@ -""" -test_app.py — Tests unitaires de l'API NexaCloud -Lancés par pytest dans le pipeline CI. -""" - -import pytest -from app import app - - -@pytest.fixture -def client(): - app.testing = True - return app.test_client() - - -def test_index_status(client): - """La route / retourne 200 avec le statut ok.""" - response = client.get("/") - assert response.status_code == 200 - data = response.get_json() - assert data["status"] == "ok" - assert data["service"] == "NexaCloud API" - - -def test_health(client): - """La route /health retourne healthy.""" - response = client.get("/health") - assert response.status_code == 200 - assert response.get_json()["status"] == "healthy" - - -def test_logs_summary_structure(client): - """Le résumé de logs contient les 4 niveaux attendus.""" - response = client.get("/logs/summary") - assert response.status_code == 200 - data = response.get_json() - for niveau in ("info", "warning", "error", "critical"): - assert niveau in data - assert isinstance(data[niveau], int) - - -def test_logs_summary_values(client): - """Les compteurs de logs ont les valeurs attendues.""" - response = client.get("/logs/summary") - data = response.get_json() - assert data["info"] == 142 - assert data["warning"] == 28 - assert data["error"] == 12 - assert data["critical"] == 3 - - -def test_logs_critical_alerte(client): - """L'alerte est active quand il y a des critiques.""" - response = client.get("/logs/critical") - assert response.status_code == 200 - data = response.get_json() - assert "critical_count" in data - assert "alerte" in data - assert data["alerte"] is True +""" +test_app.py — Tests unitaires de l'API NexaCloud +Lancés par pytest dans le pipeline CI. +""" + +import pytest +from app import app + + +@pytest.fixture +def client(): + app.testing = True + return app.test_client() + + +def test_index_status(client): + """La route / retourne 200 avec le statut ok.""" + response = client.get("/") + assert response.status_code == 200 + data = response.get_json() + assert data["status"] == "ok" + assert data["service"] == "NexaCloud API" + + +def test_health(client): + """La route /health retourne healthy.""" + response = client.get("/health") + assert response.status_code == 200 + assert response.get_json()["status"] == "healthy" + + +def test_logs_summary_structure(client): + """Le résumé de logs contient les 4 niveaux attendus.""" + response = client.get("/logs/summary") + assert response.status_code == 200 + data = response.get_json() + for niveau in ("info", "warning", "error", "critical"): + assert niveau in data + assert isinstance(data[niveau], int) + + +def test_logs_summary_values(client): + """Les compteurs de logs ont les valeurs attendues.""" + response = client.get("/logs/summary") + data = response.get_json() + assert data["info"] == 142 + assert data["warning"] == 28 + assert data["error"] == 12 + assert data["critical"] == 3 + + +def test_logs_critical_alerte(client): + """L'alerte est active quand il y a des critiques.""" + response = client.get("/logs/critical") + assert response.status_code == 200 + data = response.get_json() + assert "critical_count" in data + assert "alerte" in data + assert data["alerte"] is True + + +def test_logs_stats(client): + """La route /logs/stats retourne le total et le détail.""" + response = client.get("/logs/stats") + assert response.status_code == 200 + data = response.get_json() + assert "total" in data + assert "breakdown" in data + assert data["total"] == 185