feat: enhance Docker setup and API functionality; add integration tests and update README
All checks were successful
Build and Push to Private Registry101 / build (push) Successful in 9s
All checks were successful
Build and Push to Private Registry101 / build (push) Successful in 9s
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
__pycache__
|
||||
test.db
|
||||
test*.db
|
||||
.coverage
|
||||
report.xml
|
||||
coverage.xml
|
||||
coverage.xml
|
||||
|
||||
@@ -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"]
|
||||
CMD ["uvicorn", "papi:app","--port", "8912","--host","0.0.0.0"]
|
||||
|
||||
21
Makefile
21
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}
|
||||
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
|
||||
119
README.md
119
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)
|
||||
7
db.py
7
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")
|
||||
|
||||
|
||||
|
||||
|
||||
85
integration.pyrest.yaml
Normal file
85
integration.pyrest.yaml
Normal file
@@ -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"
|
||||
|
||||
|
||||
|
||||
17
papi.py
17
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}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
fastapi
|
||||
sqlalchemy
|
||||
uvicorn
|
||||
pyresttest
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user