From 46751e850859b004b0989744ac6b44864494cb04 Mon Sep 17 00:00:00 2001 From: Kai Siren Date: Sat, 18 Nov 2023 23:54:05 -0500 Subject: [PATCH] checkpoint-kai-1700369645 --- .gitignore | 6 ++ .vscode/settings.json | 3 + Dockerfile | 8 +-- README.md | 88 +++++++++++++++++++++++++++-- src/app/__init__.py => __init__.py | 0 app/.coverage | Bin 53248 -> 0 bytes docker-compose.yml | 4 +- infrastructure/main.tf | 3 + infrastructure/state.tf | 11 ++-- pyproject.toml | 13 +++++ src/app/main.py | 13 ----- src/main/__init__.py | 0 src/main/api.py | 14 +++++ src/main/app.py | 10 ++++ src/main/cli.py | 7 +++ src/test/test.py | 3 - src/test/test_api.py | 17 ++++++ 17 files changed, 165 insertions(+), 35 deletions(-) create mode 100644 .vscode/settings.json rename src/app/__init__.py => __init__.py (100%) delete mode 100644 app/.coverage create mode 100644 pyproject.toml delete mode 100644 src/app/main.py create mode 100644 src/main/__init__.py create mode 100644 src/main/api.py create mode 100644 src/main/app.py create mode 100644 src/main/cli.py delete mode 100644 src/test/test.py create mode 100644 src/test/test_api.py diff --git a/.gitignore b/.gitignore index 2a1c3f9..bc002c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,10 @@ .terraform +.mypy_cache +.coveragerc +.coverage + +coverage_report terraform.tfstate terraform.tfstate.backup venv +__pycache__ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..178c4e3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "pylint.args": ["--rcfile=pyproject.toml"] +} diff --git a/Dockerfile b/Dockerfile index 31ee0f7..81c3b84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,9 @@ FROM python:3.11 as base EXPOSE 8080 -WORKDIR /usr/app -COPY ./src/* /usr/app - -ENV PYTHONUNBUFFERED 1 -ENV PYTHONPATH "$(pwd):$PYTHONPATH" +RUN mkdir -p /usr/app/src +WORKDIR /usr/app/src +COPY ./src /usr/app/src EXPOSE 8080 RUN pip install -r requirements.txt diff --git a/README.md b/README.md index ae9e564..3ba64a6 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,93 @@ # Kubernetes GCP Python Starter App +## Install + +OSX system requirements: + +- pyenv - `brew install pyenv` +- docker - `brew install --cask docker` +- httpie - `brew install httpie` + +These are listed individually to avoid polluting your global installation unknowingly. + +After installing the above, you can run the following command to setup your local dependencies. It sets up on a virtualenv for you. + +```bash +make install +``` + +You should run this command every time your python dependencies change. + ## Development -The project runs the application code inside of a pipenv virtualenv, but requires some amount of configuration for your global python installation. +The development workflow involves using one or two terminal windows, depending on the type of work you are doing. + +All of our python commands use `inv` / `invoke` eg [`pyinvoke`](https://www.pyinvoke.org/), a makefile replacement written in python. This allows us a degree of flexibility and comfort with our dev time tooling. + +For the context of this API, all of the work you do should be able to be covered by 100% unit test coverage. Also, the primary mechanism of testing development work should be [`pytest-watch`](https://pypi.org/project/pytest-watch/). + +When testing via a more "hands on" approach, we use a combination of `flask run` (via invoke) and `httpie`. `httpie` is used as a UX friendly alternative to `curl`, although we do utilize `curl` to confirm that we are aligned with the project spec. + +### Pytest + +```bash +$ source ./venv/bin/activate +$ invoke test +``` + +### Pytest Watch + +```bash +$ source ./venv/bin/activate +$ invoke test-watch +``` + +This terminal will now watch for changes, and automatically re-run the tests when it finds them. + +The majority of our tests were written in this way. + +### httpie + +```bash +# first terminal +$ source ./venv/bin/activate +$ invoke serve +``` + +```bash +# second terminal +$ http :8080/api/healthcheck +> HTTP/1.1 200 OK +``` + +### curl ```bash -# inside ~/.zshrc, or similar -export PIPENV_VENV_IN_PROJECT=1 +# first terminal +$ source ./venv/bin/activate +$ invoke serve ``` ```bash -# run once, or whenever you are working on a new machine -pip install invoke pipenv pyyaml +# second terminal +$ curl http://0.0.0.0:8080/api/healthcheck +> OK +``` + +### Updating Dependencies + +You can update dependencies via + +```bash +pipenv install < some package > +make install upgrade install +``` + +## Deployment + +This deployment command assumes you are locally authenticated to both gcloud and kubectl. Directions on how to do so are out of scope for this documentation. Please consult your team's local deployment tooling and instructions! + +```bash +source ./venv/bin/activate +invoke deploy # see tasks.py for source code ``` diff --git a/src/app/__init__.py b/__init__.py similarity index 100% rename from src/app/__init__.py rename to __init__.py diff --git a/app/.coverage b/app/.coverage deleted file mode 100644 index 4f02ce956d4731fc770c6dc9da2ca78851938d7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI)O>f&a7zc2vX|32v;1)q31UZD4%`xh@@Ol`w9ky)Cp;&>gTd>Q3xwK3t+APV@ zOI{Ac;C4WO0s98SzSBNQPdn|h=OHD_PU51s0_tCg9lekg`Qt;bt{(qeF3Y z@a5A(Va|!YhM;YKUmS=yIu{cai;+K8!VibOCsRK>7U_v9QfG-8@n|rRtc9n8bb{<4f_2y6or#`Q1`=k(v`!i1Lp^P9gU4>}?UNk&0F5sl?P`&mXoM z?N`4w9CvfmzEZkPGWO_aZJ{-EqHM~O5f$V`@sMjjh-K)VsH7w0tc6}g^}I+;YvU+* zltyCghq{?z;-`KT3U#5pELFo>8`xpAVk5ly)=*h5xIr&W&ir0;I%Vcta^}hN%pE3EY(2_#?fFg)^x-qiUtVH1x2z^7J58X5^jSRovBzJt8RYPObhQi zkSKI|^_qLQyDW5OA<*y5b{o^}n&a;5*e~lkjPl;fe>n>wRP^L6(`a!iP%_ElVj_9d zBw6GrXUSrWQ7{PB5u~5PgBKP_iFBETgxiWRp|Hlz4q$IJG#nO+gd-MNuOUU3svSbWtG2Cm-}aO zOtZ;E`Y{bznuE;>AUNSrQeruXvQ)<}AL{VrwWqAFDWE(q>?u`SC#T{4ZImQFEvvk2 zC7%{&Me9T+JPRc)F^HnElA&1}D94k8?&&8&*Wu=>^m$oEa>->JpS5(Iip7Pe`6C*1 z#rUF~AdulOU$1x>Hl@^rt&zW=|rny;(ZK(q(~ z5P$##AOHafKmY;|fB*y_@SX}Z?Ufxj|4RTLcOQ2@Y7m6o{MVu%Y!H9|1Rwwb2tWV= z5P$##AOHafR7s$*vQy9h5J3L}|NB*H9NGc_2tWV=5P$##AOHafKmY;|fWUPDe*RDY z=^r)-KmY;|fB*y_009U<00Izz00b&5fam`e?p!nt0uX=z1Rwwb2tWV=5P$##Adm~- z`9DGd0uX=z1Rwwb2tWV=5P$##AW(e)JpZqLAESj3fB*y_009U<00Izz00bZa0X+Xl z3_t(^5P$##AOHafKmY;|fB*!lFM#L&)$e1p5CRZ@00bZa0SG_<0uX=z1R#Lt|A+wy xKmY;|fB*y_009U<00IzzK=lRi{J;8rj21!w0uX=z1Rwwb2tWV=5P$##{s&&i6V?C# diff --git a/docker-compose.yml b/docker-compose.yml index 2c6a400..d14d369 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,9 +5,7 @@ x-app: &app context: . target: dev volumes: - - ./app:/usr/app - - ./coverage_report:/usr/app/coverage_report - - ./.coveragerc:/usr/app/.coveragerc + - ./src:/usr/app/src ports: - '8080:8080' environment: diff --git a/infrastructure/main.tf b/infrastructure/main.tf index 527c4c7..f62457b 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -1,3 +1,6 @@ +# https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/client_config +data "google_client_config" "default" {} + # https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account resource "google_service_account" "gke" { account_id = "gke-test-1" diff --git a/infrastructure/state.tf b/infrastructure/state.tf index 3a87a6a..526881d 100644 --- a/infrastructure/state.tf +++ b/infrastructure/state.tf @@ -1,6 +1,6 @@ terraform { backend "gcs" { - bucket = "coilysiren-zapier-interview-tfstate-0" + bucket = "coilysiren-k8s-gpc-tfstate-0" prefix = "terraform/state" } } @@ -11,17 +11,16 @@ provider "google" { region = "us-central1" } -# https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/client_config -data "google_client_config" "default" {} - # https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project data "google_project" "default" {} # this bucket was created manually, and then imported into terraform afterwards -# `terraform import google_storage_bucket.default coilysiren-zapier-interview-tfstate-0` +# +# $ terraform import google_storage_bucket.default coilysiren-k8s-gpc-tfstate-0 +# # https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket resource "google_storage_bucket" "default" { - name = "coilysiren-zapier-interview-tfstate-0" + name = "coilysiren-k8s-gpc-tfstate-0" location = "US-CENTRAL1" force_destroy = true project = data.google_project.default.project_id diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5ec285b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.pylint.main] +fail-under = 100 +suggestion-mode = true + +[tool.pylint.messages-control] +disable = [ + "missing-module-docstring", + "missing-function-docstring", + "missing-class-docstring", + "broad-exception-raised", + "broad-exception-caught", +] +max-line-length = 100 diff --git a/src/app/main.py b/src/app/main.py deleted file mode 100644 index d5a2264..0000000 --- a/src/app/main.py +++ /dev/null @@ -1,13 +0,0 @@ -import flask - - -app = flask.Flask(__name__) - - -@app.route("/") -def hello_world(): - return flask.jsonify( - { - "message": "Hello, World!", - } - ) diff --git a/src/main/__init__.py b/src/main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/api.py b/src/main/api.py new file mode 100644 index 0000000..3ee219b --- /dev/null +++ b/src/main/api.py @@ -0,0 +1,14 @@ +import flask + + +blueprint = flask.Blueprint("api", __name__, url_prefix="/api") + + +@blueprint.route("/healthcheck") +def healthcheck(): + """used for debugging purposes""" + return flask.jsonify( + { + "status": "ok", + } + ) diff --git a/src/main/app.py b/src/main/app.py new file mode 100644 index 0000000..61506be --- /dev/null +++ b/src/main/app.py @@ -0,0 +1,10 @@ +import flask +import src.main.api + + +def create_app(): + """Create and configure an instance of the Flask application.""" + app = flask.Flask(__name__) + app.register_blueprint(src.main.api.blueprint) + + return app diff --git a/src/main/cli.py b/src/main/cli.py new file mode 100644 index 0000000..ca993a4 --- /dev/null +++ b/src/main/cli.py @@ -0,0 +1,7 @@ +import src.main.app + +app = src.main.app.create_app() + +if __name__ == "__main__": + # Run a debug server. + app.run(debug=True, host="0.0.0.0") diff --git a/src/test/test.py b/src/test/test.py deleted file mode 100644 index 5f88049..0000000 --- a/src/test/test.py +++ /dev/null @@ -1,3 +0,0 @@ -def test_true(): - """When all seems lost, the true test will always be there for you.""" - assert True diff --git a/src/test/test_api.py b/src/test/test_api.py new file mode 100644 index 0000000..3b91597 --- /dev/null +++ b/src/test/test_api.py @@ -0,0 +1,17 @@ +import src.main.app + + +app = src.main.app.create_app() +app.config["TESTING"] = True +client = app.test_client() + + +def test_true(): + """When all seems lost, the true test will always be there for you.""" + assert True + + +def test_healthcheck(): + response = client.get("/api/healthcheck") + assert response.status_code == 200 + assert response.json == {"status": "ok"}