diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..86daa4325 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +root = true + +# Set default parameters +[*] +charset = utf-8 +indent_style = space + diff --git a/.github/workflows/ci-pipeline.yaml b/.github/workflows/ci-pipeline.yaml new file mode 100644 index 000000000..b413d88d3 --- /dev/null +++ b/.github/workflows/ci-pipeline.yaml @@ -0,0 +1,170 @@ +--- +name: GitHub Actions Pipeline for a Flask App + +on: + push: + branches: + - monday-practice + pull_request: + branches: + - main + +jobs: + scan: + name: GitLeaks scan for secrets + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: gitleaks/gitleaks-action@v2.3.7 + + editorconfig: + name: EditorConfig checker + runs-on: ubuntu-latest + needs: scan + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Use EditorConfig Checker + uses: editorconfig-checker/action-editorconfig-checker@main + + - name: Run EditorConfig Checker + run: editorconfig-checker + + markdownlintcli: + name: Markdownlint CLI + runs-on: ubuntu-latest + needs: scan + steps: + - name: Install Markdownlint CLI + run: npm install -g markdownlint-cli + + - name: Run Markdownlint + run: markdownlint **/*.md + + python-black: + name: Run Python Black + runs-on: ubuntu-latest + needs: scan + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Python Black Check + uses: rodrigogiraoserrao/python-black-check@v3.0 + with: + line-length: '81' + + python-pylint: + name: Run Pylint Github Action + runs-on: ubuntu-latest + needs: scan + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Run Pylint GitHub Action + uses: ReasonSoftware/action-pylint@v2.0.3 + with: + requirements_file: requirements.txt + filepaths: "app/*.py" + options: "-d C0114,C0115,C0116" + + unittest: + name: Run Python unit tests + runs-on: ubuntu-latest + needs: [scan, editorconfig,markdownlintcli, python-black, python-pylint] + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run unit tests + run: python -m unittest discover -s app -p "*.py" + + snyk: + name: Run Snyk + runs-on: ubuntu-latest + needs: [scan, editorconfig,markdownlintcli, python-black, python-pylint] + steps: + - name: Run Snyk + uses: snyk/actions/python@master + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + + sonar: + name: Run SonarCloud + runs-on: ubuntu-latest + needs: [scan, editorconfig,markdownlintcli, python-black, python-pylint] + steps: + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@v3.1.0 + with: + args: + -Dsonar.organization=fannymalinova + -Dsonar.projectKey=FannyMalinova_devops-programme + env: + SONAR_TOKEN: ${{ secrets.SONAR_SECRET }} + + docker-build-trivy-push: + name: Docker build, scan with Trivy, push + runs-on: ubuntu-latest + needs: [unittest, snyk, sonar] + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3.3.0 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build image + uses: docker/build-push-action@v6 + with: + context: . + push: false + tags: ${{ vars.DOCKERHUB_USERNAME }}/flask-app:${{ github.sha }} + + - name: Scan with Trivy + uses: aquasecurity/trivy-action@0.28.0 + with: + image-ref: ${{ vars.DOCKERHUB_USERNAME }}/flask-app:${{ github.sha }} + format: "table" + ignore-unfixed: true + vuln-type: "os,library" + + - name: Push container to Docker Hub + if: ${{ success() }} + run: docker push ${{ vars.DOCKERHUB_USERNAME }}/flask-app:${{ github.sha }} + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index 2d201615e..4e9efd66e 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +#Credentials +vars/credentials.yaml \ No newline at end of file diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 000000000..1dd6ebe47 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,11 @@ +{ + "default": false, + "overrides": [ + { + "files": ["**/*.md"], + "default": true, + "MD012": false, + "MD013": false + } + ] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..abc95f985 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +#Ubuntu 22.04 base image +FROM ubuntu:22.04 + +#Copy the requirements file +COPY requirements.txt . + +#Install python3 +RUN apt-get update && apt-get install -y python3 python3-pip \ + && useradd -m -s /bin/bash nruser \ + && pip install -r requirements.txt \ + && mkdir /app + +#Copy app +COPY --chown=nruser app /app + +#Switch to the non-root user +USER nruser + +#Add work dir +WORKDIR /app + +#Expose the port +EXPOSE 5000 + +#Add entrypoint +ENTRYPOINT [ "python3"] + +#Run the app +CMD ["app.py"] \ No newline at end of file diff --git a/ansible/README.md b/ansible/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml new file mode 100644 index 000000000..d3d4410ad --- /dev/null +++ b/ansible/playbook.yaml @@ -0,0 +1,33 @@ +- hosts: localhost + gather_facts: no + vars: + image_name: fannymalinova/python_app_from_ansible + image_tag: v1.0.1 + listen_port: 5000 + vars_files: + - vars/credentials.yaml + tasks: + - name: Docker login + docker_login: + username: "{{ dockerhub_credentials['username'] }}" + password: "{{ dockerhub_credentials['password'] }}" + - name: Build an image from Dockerfile + docker_image: + build: + path: ./ + name: "{{ image_name }}" + tag: "{{ image_tag }}" + push: yes + source: build + - name: Logout from Docker Hub + docker_login: + username: "{{ dockerhub_credentials['username'] }}" + state: absent + - name: Run a container from this image + docker_container: + name: python_app_from_ansible_container + image: "{{ image_name }}:{{ image_tag }}" + ports: + - "8080:{{ listen_port }}" + env: + PORT: "{{ listen_port | string }}" \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 000000000..a1a99e359 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,12 @@ +ansible==10.3.0 +ansible-compat==24.9.1 +ansible-core==2.17.5 +ansible-lint==24.9.2 +blinker==1.6.3 ; python_version >= "3.10" and python_version < "4.0" +click==8.1.7 ; python_version >= "3.10" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows" +flask==3.0.0 ; python_version >= "3.10" and python_version < "4.0" +itsdangerous==2.1.2 ; python_version >= "3.10" and python_version < "4.0" +jinja2==3.1.2 ; python_version >= "3.10" and python_version < "4.0" +markupsafe==2.1.3 ; python_version >= "3.10" and python_version < "4.0" +werkzeug==3.0.0 ; python_version >= "3.10" and python_version < "4.0" \ No newline at end of file diff --git a/vars/credentials.yaml b/vars/credentials.yaml new file mode 100644 index 000000000..dc73a91af --- /dev/null +++ b/vars/credentials.yaml @@ -0,0 +1,10 @@ +$ANSIBLE_VAULT;1.1;AES256 +63666461366532393264616561363736626230313666623763336563636665623464326661363764 +3065376230393033313664376336383732643138343235390a343936663933613832303963383935 +35313137373262383962373938643564316464356565656466303733393466623733376638316234 +3330333635613638380a613130643932333635616461633761643130653634613365656262313261 +66386464373933616262323835393936633162333538346236306633323736656661326264663237 +37366465343862616532386338383634663833303833333466653535333363316239323266666333 +38646563643937373937313336366339656164326563353831653961393732643261383234373737 +64663863343630366161613635373461306438363136343635356262306332656365643830363436 +6464