├── .gitattributes ├── .gitignore ├── Dockerfile ├── Jenkinsfile ├── README.md ├── deployment.yaml ├── fastapi_example ├── __init__.py └── app.py ├── requirements.txt ├── run.sh ├── service.yaml ├── setup.py ├── sonar-project.properties └── test ├── __init__.py └── test_app.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | 127 | #IDE 128 | .idea/* 129 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7.8 2 | 3 | RUN mkdir -p /usr/src/fastapi 4 | 5 | WORKDIR /usr/src/fastapi 6 | 7 | USER root 8 | 9 | COPY requirements.txt /usr/src/fastapi/ 10 | 11 | RUN pip install -r requirements.txt 12 | 13 | RUN mkdir -p fastapi_example 14 | 15 | COPY fastapi_example/ /usr/src/fastapi/fastapi_example 16 | 17 | COPY run.sh /usr/src/fastapi/ 18 | 19 | CMD sh run.sh -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline{ 2 | environment { 3 | registry = 'kaisbettaieb/fastapi-example' 4 | registryCredential = 'dockerhub-credentials' 5 | dockerImage = '' 6 | scannerHome = tool 'SonarQube scanner' 7 | sonarToken = credentials('credentials-sonar') 8 | build_version = "1+${BUILD_NUMBER}" 9 | } 10 | agent any 11 | 12 | stages { 13 | stage('Clone github repo'){ 14 | steps { 15 | git credentialsId: 'github-credentials', url: 'https://github.com/kaisbettaieb/fastapi-examle', branch: 'main' 16 | } 17 | } 18 | 19 | stage('Setup') { 20 | steps { 21 | sh 'pip install -r requirements.txt --user' 22 | } 23 | } 24 | 25 | stage('Unit testing') { 26 | steps { 27 | sh 'python -m unittest discover' 28 | } 29 | 30 | } 31 | 32 | stage('SonarQube analysis') { 33 | steps { 34 | withSonarQubeEnv('SonarQube') { 35 | bat "${scannerHome}\\sonar-scanner.bat -Dsonar.login=$sonarToken" 36 | } 37 | } 38 | 39 | } 40 | 41 | stage("Quality Gate") { 42 | steps { 43 | timeout(time: 1, unit: 'HOURS') { 44 | waitForQualityGate abortPipeline: true 45 | } 46 | } 47 | } 48 | 49 | stage ('Artifactory configuration') { 50 | steps { 51 | rtServer ( 52 | id: "ARTIFACTORY_SERVER", 53 | url: "https://kaisbettaieb.jfrog.io/artifactory", 54 | credentialsId: "artifactory-credentials" 55 | ) 56 | } 57 | } 58 | 59 | stage ('Build python package') { 60 | steps { 61 | sh ''' 62 | python setup.py sdist bdist_wheel 63 | ''' 64 | } 65 | } 66 | 67 | stage ('Upload packages') { 68 | steps { 69 | rtUpload ( 70 | serverId: "ARTIFACTORY_SERVER", 71 | spec: '''{ 72 | "files": [ 73 | { 74 | "pattern": "dist/", 75 | "target": "artifactory-python-dev-local/" 76 | } 77 | ] 78 | }''' 79 | ) 80 | } 81 | } 82 | 83 | stage ('Publish build info') { 84 | steps { 85 | rtPublishBuildInfo ( 86 | serverId: "ARTIFACTORY_SERVER" 87 | ) 88 | } 89 | } 90 | 91 | stage ('Commit in prod branch') { 92 | steps { 93 | 94 | sh ''' 95 | echo "commit prod branch" 96 | ''' 97 | } 98 | 99 | } 100 | 101 | 102 | 103 | } 104 | } 105 | 106 | 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastapi-examle 2 | This is a playground to test CI/CD tools such as Jenkins, Sonar, Docker, Jfrog Artifactory and kubernetes 3 | 4 | ## What's working 5 | For now Jenkins pipeline is responsible for : 6 | 1. Jenkins pipeline get trigered by github webhooks when a new commit is pushed. 7 | 2. Jenkins will checkout the new code. 8 | 3. Run unit tests and validate that they passed. 9 | 4. Run the code againt SonarQube and check if any bugs/code smells or any other issues exist on code. 10 | 5. Build the application and push it to a private Jfrog artifactory 11 | 6. Build and push a Docker image to Docker hub registry 12 | -------------------------------------------------------------------------------- /deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: fastapi-example-deployment 5 | labels: 6 | app: fastapi-example 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: fastapi-example 12 | template: 13 | metadata: 14 | labels: 15 | app: fastapi-example 16 | spec: 17 | containers: 18 | - name: fastapi-example 19 | image: kaisbettaieb/fastapi-example:51 20 | ports: 21 | - containerPort: 8000 -------------------------------------------------------------------------------- /fastapi_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisbettaieb/fastapi-examle/24f1b4c3e2c1c44f956a19bd2639ed52f30a80d6/fastapi_example/__init__.py -------------------------------------------------------------------------------- /fastapi_example/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Response 2 | 3 | from pydantic import BaseModel 4 | 5 | tags_metadata = [ 6 | { 7 | "name": "salutations", 8 | "description": "Simple **endpoint** pour saluer le serveur. xD", 9 | } 10 | ] 11 | 12 | app = FastAPI(title="Application pour tester le CI/CD", 13 | description="Realisations d'une application simple pour tester les outils CI/CD", 14 | version="0.1", openapi_tags=tags_metadata) 15 | 16 | 17 | class BaseReponseModele(BaseModel): 18 | status: str 19 | message: str 20 | 21 | 22 | @app.get("/api/0.1/salutations", response_model=BaseReponseModele, tags=["salutations"]) 23 | def salutation(response: Response): 24 | response.status_code = 200 25 | return {"status": "Success", "message": "Salut, J'espère que vous allez bien."} 26 | 27 | 28 | @app.get("/") 29 | def index(response: Response): 30 | response.status_code = 200 31 | return {"url": "/docs", "message": "pour visualiser l'interface SwaggerUI"} 32 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==0.5.0 2 | aniso8601==7.0.0 3 | async-exit-stack==1.0.1 4 | async-generator==1.10 5 | certifi==2020.12.5 6 | chardet==4.0.0 7 | click==7.1.2 8 | colorama==0.4.4 9 | dnspython==2.1.0 10 | email-validator==1.1.2 11 | fastapi==0.63.0 12 | graphene==2.1.8 13 | graphql-core==2.3.2 14 | graphql-relay==2.0.1 15 | h11==0.12.0 16 | idna==2.10 17 | itsdangerous==1.1.0 18 | Jinja2==2.11.2 19 | MarkupSafe==1.1.1 20 | orjson==3.4.7 21 | pip==20.1.1 22 | promise==2.3 23 | pydantic==1.7.3 24 | python-dotenv==0.15.0 25 | python-multipart==0.0.5 26 | PyYAML==5.4.1 27 | requests==2.25.1 28 | Rx==1.6.1 29 | setuptools==47.1.0 30 | six==1.15.0 31 | starlette==0.13.6 32 | typing-extensions==3.7.4.3 33 | ujson==3.2.0 34 | urllib3==1.26.3 35 | uvicorn==0.13.3 36 | watchgod==0.6 37 | websockets==8.1 38 | wheel==0.36.2 39 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | uvicorn fastapi_example.app:app --host 0.0.0.0 --port 8000 -------------------------------------------------------------------------------- /service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: fastapi-example-service 5 | namespace: default 6 | spec: 7 | type: LoadBalancer 8 | selector: 9 | app: fastapi-example 10 | ports: 11 | - port: 8000 12 | targetPort: 8000 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.md", 'r') as f: 4 | long_description = f.read() 5 | 6 | setup( 7 | name='fastapi-example', 8 | version='0.1', 9 | description='A simple fastapi application', 10 | license="MIT", 11 | long_description=long_description, 12 | author='Kais Bettaieb', 13 | author_email='kaisbettaieb@gmail.com', 14 | url="https://kaisbettaieb.me/", 15 | packages=['fastapi_example'], 16 | install_requires=['fastapi[all]'], 17 | 18 | ) -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=Fastapi-example 2 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisbettaieb/fastapi-examle/24f1b4c3e2c1c44f956a19bd2639ed52f30a80d6/test/__init__.py -------------------------------------------------------------------------------- /test/test_app.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from fastapi.testclient import TestClient 3 | 4 | from fastapi_example.app import app 5 | 6 | client = TestClient(app) 7 | 8 | 9 | class UtilTest(unittest.TestCase): 10 | def test_index(self): 11 | response = client.get("/") 12 | assert response.status_code == 200 13 | assert response.json() == {"url": "/docs", "message": "pour visualiser l'interface SwaggerUI"} 14 | --------------------------------------------------------------------------------