diff --git a/.github/workflows/correction-exercice-1.yml b/.github/workflows/correction-exercice-1.yml new file mode 100644 index 0000000..0a36f75 --- /dev/null +++ b/.github/workflows/correction-exercice-1.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application - Correction exercice 1 + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 html-testRunner + if [ -f app/requirements.txt ]; then pip install -r app/requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 app/ --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 app/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with unittest + run: | + cd app + python -m unittest test/unit/test.py diff --git a/.github/workflows/correction-exercice-2.yml b/.github/workflows/correction-exercice-2.yml new file mode 100644 index 0000000..effb863 --- /dev/null +++ b/.github/workflows/correction-exercice-2.yml @@ -0,0 +1,56 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application - Correction + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 html-testRunner + if [ -f app/requirements.txt ]; then pip install -r app/requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 app/ --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 app/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + #- name: Test with unittest + # run: | + # cd app + # python -m unittest test/unit/test.py + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v6 + with: + push: true + tags: vanessakovalsky/python-flask-app-github:latest + file: docker-app/python/Dockerfile + context: app diff --git a/.github/workflows/correction-exercice-3.yml b/.github/workflows/correction-exercice-3.yml new file mode 100644 index 0000000..10f204e --- /dev/null +++ b/.github/workflows/correction-exercice-3.yml @@ -0,0 +1,33 @@ +name: Correction exercice 3 +on: [push] +jobs: + + qa: + name: Quality check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@master + with: + python-version: 3.8 + - name: Run unit tests + run: | + pip install html-testRunner coverage + cd app + python -m unittest test/unit/test.py + python -m coverage run -m unittest test/unit/test.py + python -m coverage xml + - name: Wemake Python Stylguide + uses: wemake-services/wemake-python-styleguide@0.13.4 + continue-on-error: true + with: + reporter: 'github-pr-review' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: app/coverage.xml + flags: unittests diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index 12e0345..0000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build the Docker image - run: docker build . --file docker-app/python/Dockerfile --tag my-image-name:$(date +%s) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml deleted file mode 100644 index 383e65c..0000000 --- a/.github/workflows/pylint.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Pylint - -on: [push] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10"] - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pylint - - name: Analysing the code with pylint - run: | - pylint $(git ls-files '*.py') diff --git a/Readme.md b/Readme.md index e8524b0..5f5c9ac 100644 --- a/Readme.md +++ b/Readme.md @@ -1,7 +1,6 @@ # Application exemple en Python - ## Doc API - Swagger https://app.swaggerhub.com/apis/vanessakovalsky/IT-Management/1.0.0#/ diff --git a/app/application/application.py b/app/application/application.py index 08ad2c6..c4d2643 100644 --- a/app/application/application.py +++ b/app/application/application.py @@ -9,8 +9,8 @@ def __init__(self): def add(data): return 'Application ajoutée' - def get(nom): - return self + def get(self): + return self.nom def liste(): return 'Liste des applications' diff --git a/app/test/unit/test.py b/app/test/unit/test.py index 55daa2c..2398737 100644 --- a/app/test/unit/test.py +++ b/app/test/unit/test.py @@ -2,8 +2,8 @@ import json import HtmlTestRunner -from application.application import Application from machine.machine import Machine +from application.application import Application class TestApplication(unittest.TestCase): # Initialisation de données pour mes tests @@ -18,8 +18,8 @@ class TestApplication(unittest.TestCase): # Exemple de test avec un echec (failure) # def test_liste_application_ko(self): - # contenu_liste = Application.liste() - # self.assertEqual(contenu_liste,'Une phrase') + # contenu_liste = Application.liste() + # self.assertEqual(contenu_liste,'Une phrase') def test_liste_application_ok(self): contenu_liste = Application.liste() @@ -50,4 +50,4 @@ def test_add_application(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..895117b --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,67 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: +- master + +jobs: + - job: build + displayName: Build + pool: + vmImage: ubuntu-latest + steps: + - task: ArchiveFiles@2 + inputs: + rootFolderOrFile: 'app/' + includeRootFolder: false + archiveType: 'zip' + archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip' + replaceExistingArchive: true + - task: UniversalPackages@0 + inputs: + command: 'publish' + publishDirectory: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip' + feedsToUsePublish: 'internal' + vstsFeedPublish: 'bd8e0f81-b92c-4ad5-8333-c9c1800cd9bd/68a8a337-87ce-4623-87a1-a0a9b9b6ad2d' + vstsFeedPackagePublish: 'myfirstpythonapp' + versionOption: 'patch' + - task: AzureWebApp@1 + inputs: + azureSubscription: 'Paiement à l’utilisation(78b9a3d9-a777-4dad-8f72-5fc24f431d13)' + appType: 'webAppLinux' + appName: 'VanessaDemo' + package: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip' + customDeployFolder: ROOT + startUpCommand: gunicorn --bind=0.0.0.0 main:app + + # - job: waitForValidation + # displayName: Wait for external validation + # pool: server + # timeoutInMinutes: 4320 # job times out in 3 days + # steps: + # - task: ManualValidation@1 + # inputs: + # notifyUsers: 'v.david@kovalibre.com' + + - job: Deployment + displayName: Deployment + # dependsOn: waitForValidation + pool: + vmImage: ubuntu-latest + steps: + + - task: AzureRmWebAppDeployment@5 + inputs: + ConnectionType: 'AzureRM' + azureSubscription: 'Paiement à l’utilisation(78b9a3d9-a777-4dad-8f72-5fc24f431d13)' + appType: 'webAppContainer' + WebAppName: 'VanessaApp2' + DockerNamespace: 'vanessakovalsky' + DockerRepository: 'mypythonapp' + DockerImageTag: 'latest' + + - task: CmdLine@2 + inputs: + script: 'wget https://vanessaapp2-awb2d4d4d3dug5cp.canadacentral-01.azurewebsites.net/ | grep ''Bienvenue sur l''application de gestion"' \ No newline at end of file diff --git a/bandit.ignore b/bandit.ignore new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/bandit.ignore @@ -0,0 +1 @@ + diff --git a/continuous-integration/Jenkinsfile b/continuous-integration/Jenkinsfile index dcd4d87..9405e02 100644 --- a/continuous-integration/Jenkinsfile +++ b/continuous-integration/Jenkinsfile @@ -1,56 +1,92 @@ pipeline { - environment { + parameters{ + string(name: 'environnement', defaultValue: 'dev', description: 'choisir l\'environnement cible' ) + booleanParam(name: 'ouinon', defaultValue: false, description: '') + } + + environment { registry = "vanessakovalsky/my-image-python" registryCredential = 'docker' - } + APP_CONTAINER_NAME = "myapppython" + DOCKER_IMAGE = "vanessakovalsky/my-image-python" + DOCKER_TAG = "${env.BUILD_NUMBER}" + } + + // triggers { + // cron('H */4 * * 1-5') + // pollSCM('H */4 * * 1-5') + // upstream(upstreamProjects: 'Correction exercice 3', thresold: hudson.model.Result.SUCCESS) + // } + + agent any - agent any - stages { + stages { stage('Clone sources') { steps { git url: 'https://github.com/vanessakovalsky/python-api-handle-it.git' } } - /*stage('continuous integration') { // Compile and do unit testing - tools { - gradle 'installGradle' - } + //stage('Security analysis') { + // steps{ + // sh "mkdir report" + // sh "docker run --rm --volume \"\$(pwd)\":/src --volume report:/report secfigo/bandit:latest" + // } + //} + stage ("Dependency Check with Python Safety"){ + steps{ + catchError { + sh "docker run --rm --volume \"\$(pwd)\" pyupio/safety:latest safety check" + sh "docker run --rm --volume \"\$(pwd)\" pyupio/safety:latest safety check --json > report.json" + } + echo currentBuild.result + } + } + stage ("Static Analysis with python-taint"){ + steps{ + sh "docker run --rm --volume \"\$(pwd)\" vickyrajagopal/python-taint-docker pyt ." + } + } + stage('continuous integration') { // Compile and do unit testing + + tools { + gradle 'installGradle' + } + steps { + parallel ( + // run Gradle to execute compile and unit testing + pylint: { + sh 'cd continuous-integration && gradle lint' + }, + pycode: { + sh 'cd continuous-integration && gradle pycode' + } + ) + } + } + + stage('testcode') { + tools { + gradle 'installGradle' + } steps { - parallel ( - // run Gradle to execute compile and unit testing - pylint: { - sh 'gradle lint' - }, - pycode: { - sh 'gradle pycode' - } - ) + sh 'gradle test' } - }*/ - - // stage('testcode') { - // tools { - // gradle 'installGradle' - // } - // steps { - // sh 'gradle test' - // } - // } + } /*stage('Package and deploy') { tools { gradle 'installGradle' } steps { - sh 'gradle up' + sh 'cd continuous-integration && gradle up' } }*/ stage('Build docker image') { steps{ script { - dockerImage = docker.build registry + ":$BUILD_NUMBER" + dockerImage = docker.build(registry + ":$BUILD_NUMBER","-f docker-app/python/Dockerfile .") } } } @@ -65,9 +101,46 @@ pipeline { } } + + stage('Arrêt du Conteneur Existant') { + steps { + script { + // Arrêter et supprimer le conteneur existant + sh ''' + docker stop ${APP_CONTAINER_NAME} || true + docker rm ${APP_CONTAINER_NAME} || true + ''' + } + } + } + stage('Test image docker') { steps { - sh 'docker run -d --name my-image-python -p 3002:3002 --rm vanessakovalsky/my-image-python' + sh 'docker run -d --name ${APP_CONTAINER_NAME} -p 3002:5000 --rm vanessakovalsky/my-image-python:${DOCKER_TAG}' + } + } + + stage('Vérification du Déploiement') { + steps { + script { + // Attendre le démarrage du conteneur + sh 'sleep 30' + + // Test de santé basique + sh 'curl http://localhost:3002/ || exit 1' + } + } + } + + stage('Nettoyage') { + steps { + script { + // Supprimer les anciennes images + sh ''' + docker image prune -f + docker images | grep ${DOCKER_IMAGE} | grep -v ${DOCKER_TAG} | awk \'{print $3}\' | xargs -r docker rmi || true + ''' + } } } diff --git a/continuous-integration/build.gradle b/continuous-integration/build.gradle index 370d7c2..f3eb5e0 100644 --- a/continuous-integration/build.gradle +++ b/continuous-integration/build.gradle @@ -13,7 +13,7 @@ task purge(type:Delete) { task dep(type:Exec, dependsOn: purge) { //println 'Installing dependencies' executable 'sh' - args '-c', 'pip3 install -r app/requirements.txt' + args '-c', 'pip install -r app/requirements.txt' standardOutput = new ByteArrayOutputStream() ext.output = { return standardOutput.toString() } } @@ -62,4 +62,4 @@ task updateItManagement(type:Exec, dependsOn: up) { args '-c', 'curl -v http://localhost:5000/application' standardOutput = new ByteArrayOutputStream() ext.output = { return standardOutput.toString() } -} \ No newline at end of file +} diff --git a/docker-app/python/Dockerfile b/docker-app/python/Dockerfile index bcaf3f8..d4c77f0 100644 --- a/docker-app/python/Dockerfile +++ b/docker-app/python/Dockerfile @@ -1,11 +1,11 @@ FROM python:3 - +#Commentaire WORKDIR /app -COPY app/ . +COPY ../app/ . RUN pip install --no-cache-dir -r requirements.txt ENV FLASK_APP="main" -CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"] \ No newline at end of file +CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]