diff --git a/.gitea/workflows/build-and-push.yaml b/.gitea/workflows/build-and-push.yaml index b29285e..b59462d 100644 --- a/.gitea/workflows/build-and-push.yaml +++ b/.gitea/workflows/build-and-push.yaml @@ -28,10 +28,4 @@ jobs: name: Build Docker image - run: docker push kube-reg.fairfaxmedia.net/papi/svc1:${{ steps.extract_branch.outputs.branch }} # Push the Docker image name: Push Docker image - #- env: - #KUBEB64: '${{ secrets.KUBEB64 }}' - #run: mkdir -p $HOME/.kube &&echo "$KUBEB64" | base64 -d > $HOME/.kube/config && - #curl https://fairfaxmedia.net/helm -o /usr/local/bin/helm && chmod +xxx /usr/local/bin/helm && - #/usr/local/bin/helm upgrade --install papi papi-helm/ --set image.tag=${{ steps.extract_branch.outputs.branch }} # Deploy the Helm chart - diff --git a/.gitignore b/.gitignore index 391f3a2..06fbcc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ __pycache__ -test.db +test*.db .coverage report.xml -coverage.xml \ No newline at end of file +coverage.xml diff --git a/Dockerfile b/Dockerfile index 6c62ee3..a63b3e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ +# +# FROM python:3.12 @@ -11,6 +13,6 @@ COPY ./requirements.txt /code/requirements.txt RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -COPY ./papi.py /code/ +COPY ./*.py /code/ -CMD ["python3", "papi.py", "--port", "8912"] \ No newline at end of file +CMD ["uvicorn", "papi:app","--port", "8912","--host","0.0.0.0"] diff --git a/Makefile b/Makefile index 48ecfce..2150d54 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,35 @@ ENV=dev -VER=2 +VER=0.0.6 TAG=${ENV}-${VER} REGISTRY=kube-reg.fairfaxmedia.net SVC=papi/svc1 DOCK_IMG=${REGISTRY}/${SVC}:${TAG} +LIVE_HOST:="http://localhost:8912" + clean: rm -rf .pytest_cache rm -rf .coverage rm -rf report.xml rm -rf test.db rm -rf htmlcov -coverage: clean + +install: + pip3 install -r requirements.txt +coverage: clean install pytest --cov=papi --cov=posts --cov=users test_papi.py test_posts.py test_users.py --junitxml=report.xml --cov-report=html --cov-report=xml --cov-report=term --cov-fail-under 90 + docker-img: coverage docker build -t ${DOCK_IMG} . + docker-push: docker-img docker push ${DOCK_IMG} + deploy: docker-push - helm upgrade papi ./papi-helm/ -f ./papi-helm/v.${ENV}.yaml --set image.tag=${TAG} \ No newline at end of file + helm upgrade papi ./papi-helm/ -f ./papi-helm/v.${ENV}.yaml --set image.tag=${TAG} --set env.VERSION=${VER} +integration: install + pyresttest ${LIVE_HOST} integration.pyrest.yaml +run: install + uvicorn papi:app --reload --port 8912 --host 0.0.0.0 + + +all: deploy \ No newline at end of file diff --git a/README.md b/README.md index e69de29..4a06a0c 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,119 @@ +# Posts API + +This is the Posts API project, a simple API for managing blog posts. + +## Table of Contents +- [Installation](#installation) +- [Usage](#usage) +- [Endpoints](#endpoints) +- [Contributing](#contributing) +- [License](#license) + +## Installation + +1. Clone the repository: + ```bash + git clone https://git.fairfaxmedia.net/posts-api/papi-py.git + ``` +2. Navigate to the project directory: + ```bash + cd papi-py + ``` +3. Install the dependencies: + ```bash + pip install -r requirements.txt + ``` + +## Usage + +To start the server, run: +```bash +python3 papi.py +``` + + +To start with an alternative database location(postgresql) +Use the following +```bash +DATABASE_URL='postgresql+psycopg2://user:password@hostname/database_name' python3 papi.py +``` +## Endpoints + +### GET /posts +Retrieve a list of all posts. + +### POST /posts +Create a new post. + +### GET /posts/{id} +Retrieve a specific post by ID. + +### PUT /posts/{id} +Update a specific post by ID. + +### DELETE /posts/{id} +Delete a specific post by ID. + + + +### Unit Tests +```bash +make coverage +``` + +This runs coverage tests and stores the html coverage report along with functions tested in htmlcov + + +## deploy... +Please log into the connected container registry on Makefile:4 WHERE it says REGISTRY= +using the `docker login` command + +feel free to edit the VER= tag +ensure you are logged into kubernetes so that commands kubectl and helm both work + +then run +```bash +make deploy +``` + + + +### Integration testing instructions +to test live on papi.kube-dev.fairfaxmedia.net +run the following: +```bash + make integration -e LIVE_HOST=http://papi.kube-dev.fairfaxmedia.net + ``` + + to integration-test on localhost on the default port run + ```bash + make integration + ``` + + + ## openapi spec: + +[openapi.json](https://papi.kube-dev.fairfaxmedia.net/openapi.json) + + [swagger-ui](https://papi.kube-dev.fairfaxmedia.net/docs) + + +### helm deployment +```bash +helm upgrade papi ./papi-helm/ -f ./papi-helm/v.${ENV}.yaml --set image.tag=${TAG} --set env.VERSION=${VER} +``` +or alternatively +```bash +make deploy +``` + + ### postman-like hopscotch collection: + + +hopscotch_collection.json can be imported into the following tool +[hopscotch-ui](https://hopscotch.kube.fairfaxmedia.net/) + +## kubernetes dashboard plane: +[kubernetes](https://kube.fairfaxmedia.net) + +[git-ci server](https://git.fairfaxmedia.net) \ No newline at end of file diff --git a/db.py b/db.py index e38624a..1d4d928 100644 --- a/db.py +++ b/db.py @@ -1,14 +1,17 @@ from sqlalchemy import create_engine, Column, Integer,String, ForeignKey from sqlalchemy.engine import URL from sqlalchemy.orm import sessionmaker, Session, declarative_base - +import os # Database setup DATABASE_URL = "sqlite:///./test.db" +if os.getenv("DATABASE_URL"): + DATABASE_URL = os.getenv("DATABASE_URL") + engine = create_engine(DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() - +version = os.environ.get("VERSION", "0.0.0") diff --git a/integration.pyrest.yaml b/integration.pyrest.yaml new file mode 100644 index 0000000..bb77088 --- /dev/null +++ b/integration.pyrest.yaml @@ -0,0 +1,85 @@ +# Test using included Django test app +# First install python-django +# Then launch the app in another terminal by doing +# cd testapp +# python manage.py testserver test_data.json +# Once launched, tests can be executed via: +# python resttest.py http://localhost:8000 miniapp-test.yaml +--- +- config: + - testset: "Tests using test app" + - variable_binds: + - 'uid': 1 + - 'post_id': 1 + +- test: # create entity + - name: "Basic get" + - url: "/" +- test: # create entity + - name: "Get version" + - url: "/version" + + +- test: # create person by POST + - name: "Create person" + - url: "/users" + - method: "POST" + - body: '{"name": "Willim","email": "theadmiral@gmail.com"}' + - headers: {Content-Type: application/json} + - extract_binds: + - 'uid' : {'jsonpath_mini': "user.id"} + +- test: # get single person + - name: "Get single person" + - url: {'template':'/users/$uid'} # use binded value + - method: 'GET' + +- test: # update person by PUT + - name: "update person by PUT" + - url: {'template':'/users/$uid'} + - method: "PUT" + - body: '{"name": "Gaius"}' + - headers: {'Content-Type': 'application/json'} + + + + + + + +- test: # create post by POST + - name: "Create post" + - url: "/posts" + - method: "POST" + - body: '{"title": "Willims post","content": "william made a post","author_id": 1}' + - headers: {Content-Type: application/json} + - extract_binds: + - 'post_id' : {'jsonpath_mini': "post.id"} + +- test: # get single post + - name: "Get single post" + - url: {'template':'/posts/$post_id'} + - method: 'GET' + +- test: # update post by PUT + - name: "update post by PUT" + - url: {'template':'/posts/$post_id'} + - method: "PUT" + - body: '{"title": "Gaius made a post"}' + - headers: {'Content-Type': 'application/json'} +- test: # delete post + - name: "Delete post" + - url: {'template':'/posts/$post_id'} + - method: "DELETE" + + + + + +- test: # delete person + - name: "Delete person" + - url: {'template':'/users/$uid'} + - method: "DELETE" + + + diff --git a/papi.py b/papi.py index a35ac26..07358f5 100644 --- a/papi.py +++ b/papi.py @@ -1,4 +1,11 @@ #!/bin/env python3 +# +# author: Stephen Schuresko +# date: 2025-02-18 +# file: papi.py +# description: A simple FastAPI app (papi) with a few routes and models for users and posts +# + from fastapi import FastAPI, Depends import uvicorn from sqlalchemy import create_engine, Column, Integer,String, ForeignKey @@ -7,6 +14,7 @@ from sqlalchemy.orm import sessionmaker, Session, declarative_base import db import users import posts +import os @@ -19,9 +27,6 @@ app.include_router(posts.router) async def root(): return {"message": "Hello World"} - - -if __name__ == "__main__": - uvicorn.run("papi:app", host="0.0.0.0", port=8912, reload=True, log_level="debug", - workers=5, limit_concurrency=10, limit_max_requests=100) - +@app.get("/version") +async def ver(): + return {"version":db.version} diff --git a/requirements.txt b/requirements.txt index c1040e6..13519c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ fastapi sqlalchemy uvicorn +pyresttest \ No newline at end of file diff --git a/test_papi.py b/test_papi.py index 33e70e6..e6b423a 100644 --- a/test_papi.py +++ b/test_papi.py @@ -12,3 +12,10 @@ def test_read_main(): assert response.json() == {"message": "Hello World"} + + +def test_read_version(): + response = client.get("/version") + assert response.status_code == 200 + +