diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..2e5e5c88e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.env +.git +.venv +Dockerfile +.vscode +README.md diff --git a/.github/workflows/continousint.yaml b/.github/workflows/continousint.yaml new file mode 100644 index 000000000..f2f6b2f89 --- /dev/null +++ b/.github/workflows/continousint.yaml @@ -0,0 +1,18 @@ +name: Continuous Integration +on: + push: + paths-ignore: + - 'README.md' + + pull_request: + paths-ignore: + - 'README.md' + +jobs: + build: + name: Build and test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: docker build --tag todo-app:test --target test . + - run: docker run todo-app:test \ No newline at end of file diff --git a/Architecturediagrams/C4diagram.drawio b/Architecturediagrams/C4diagram.drawio new file mode 100644 index 000000000..207ad989e --- /dev/null +++ b/Architecturediagrams/C4diagram.drawio @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Architecturediagrams/ComponentDiagram.drawio.png b/Architecturediagrams/ComponentDiagram.drawio.png new file mode 100644 index 000000000..f8e055ba0 Binary files /dev/null and b/Architecturediagrams/ComponentDiagram.drawio.png differ diff --git a/Architecturediagrams/Contextdiagram.drawio.png b/Architecturediagrams/Contextdiagram.drawio.png new file mode 100644 index 000000000..0c00f7e31 Binary files /dev/null and b/Architecturediagrams/Contextdiagram.drawio.png differ diff --git a/Architecturediagrams/codediagram.drawio.png b/Architecturediagrams/codediagram.drawio.png new file mode 100644 index 000000000..655e8799c Binary files /dev/null and b/Architecturediagrams/codediagram.drawio.png differ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..0d4e27b59 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM python AS base +RUN pip install poetry +WORKDIR /app +COPY . . +RUN poetry install +ENTRYPOINT poetry run flask run --host=0.0.0.0 + + +FROM base as production +ENV FLASK_DEBUG=false +ENTRYPOINT poetry run flask run --host=0.0.0.0 +# Configure for production + +FROM base as development +ENV FLASK_DEBUG=true +ENTRYPOINT poetry run flask run --host=0.0.0.0 +# Configure for local development + +FROM base as test + +ENTRYPOINT poetry run pytest diff --git a/README.md b/README.md index e153c3396..750de35b0 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,44 @@ You should see output similar to the following: * Debugger PIN: 226-556-590 ``` Now visit [`http://localhost:5000/`](http://localhost:5000/) in your web browser to view the app. + +```git remote -v ``` +tells us / lists our remotes + + +### See below for how to build the two docker images +``` +docker build --target development --tag todo-app:dev . +docker build --target production --tag todo-app:prod . +``` +### Command to run the production container +``` +docker run --env-file .env -it -p 5001:5000 todo-app:prod +``` +This command pases through the environment variables with the `--env-file` flag, and the `-it` flags make it easier to interact with the container (e.g. allowing us to shut it down with ctrl+c from our host terminal). With the `-p` flag, the app can be accessed at the address `http://localhost:5001`. + +### Command to run dev container +``` +docker run --env-file .env -it -p 5001:5000 --mount "type=bind,source=$(pwd)/todo_app,target=/app/todo_app" todo-app:dev +``` +Bind mount is miroring a folder and the folder in this instance is the todo app folder allowing changes to the code without having to rebuild the container. Running dev changes without having to access the container + +### Running Pytest +```bash +poetry run pytest +``` + + +### Azure Hosting +The container image that is deployed on Azure is hosted on Docker Hub at https://hub.docker.com/repository/docker/nashussain76/todo-app/general + +The website is hosted at https://nashusappservice.azurewebsites.net/ + +To update the website you will need to run the following commands to build and push the updated container image: +```Bash +docker build --target production --tag nashussain76/todo-app:prod . +docker push nashussain76/todo-app:prod +``` +Next you will need to make a POST request to the webhook link provided on the App Service (under the Deployment Centre tab)> This will trigger Azure to pull the updated image from Docker Hub (link not provided as it includes credentials) + + diff --git a/poetry.lock b/poetry.lock index 26406b36e..072560de7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "blinker" version = "1.6.2" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -16,7 +15,6 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -31,7 +29,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -39,11 +36,24 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "flask" version = "2.3.2" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -67,7 +77,6 @@ dotenv = ["python-dotenv"] name = "importlib-metadata" version = "6.6.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -83,11 +92,21 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -99,7 +118,6 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -117,7 +135,6 @@ i18n = ["Babel (>=2.7)"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -141,6 +158,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -173,11 +200,58 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.2.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-dotenv" version = "0.14.0" description = "Add .env support to your django/flask apps in development and deployments" -category = "main" optional = false python-versions = "*" files = [ @@ -188,11 +262,21 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "werkzeug" version = "2.3.6" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -210,7 +294,6 @@ watchdog = ["watchdog (>=2.3)"] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -225,4 +308,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "d8d856f03434ab9857d0c9bd058ef199cc0438a15afe205c0fcd177e92d0691f" +content-hash = "34187741501d30279611f3933c014bfa5ca2cb78f67a3d4911391cdcf774288d" diff --git a/pyproject.toml b/pyproject.toml index 4d9bb90f6..299d93979 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ authors = [] python = "^3.8" Flask = "^2.2.5" python-dotenv = "^0.14.0" +pytest = "^8.2.2" [tool.poetry.dev-dependencies] diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/dummy2_test.py b/test/dummy2_test.py new file mode 100644 index 000000000..db6ab3751 --- /dev/null +++ b/test/dummy2_test.py @@ -0,0 +1,10 @@ +def test_basic_multiplication(): + # Given + num1 = 5 + num2 = 10 + + # When + result = num1 * num2 + + # Then + assert result == 50 \ No newline at end of file diff --git a/test/dummy_test.py b/test/dummy_test.py new file mode 100644 index 000000000..83892bd9d --- /dev/null +++ b/test/dummy_test.py @@ -0,0 +1,10 @@ +def test_that_basic_addition_works(): + # Given + num1 = 2 + num2 = 3 + + # When + result = num1 + num2 + + # Then + assert result == 5 \ No newline at end of file diff --git a/todo_app/app.py b/todo_app/app.py index d71780a32..0375806fc 100644 --- a/todo_app/app.py +++ b/todo_app/app.py @@ -1,11 +1,22 @@ -from flask import Flask +from flask import Flask, redirect, render_template, request from todo_app.flask_config import Config +from todo_app.data.session_items import get_items, add_item + + + app = Flask(__name__) app.config.from_object(Config()) @app.route('/') def index(): - return 'Hello World!' + items = get_items() + return render_template('index.html', html_items = items) + +@app.route('/', methods = ["POST"]) +def new_todo(): + todo = request.form.get("user todo") + item = add_item(todo) + return redirect('/') \ No newline at end of file diff --git a/todo_app/templates/index.html b/todo_app/templates/index.html index d6f8d9c85..a7a167dd5 100644 --- a/todo_app/templates/index.html +++ b/todo_app/templates/index.html @@ -3,15 +3,24 @@ {% block content %}
-

To-Do App

+

Azure To-Do App

Just another to-do app.

Items

+
+ + +