Skip to content

Commit

Permalink
Inital commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Doyle authored and Tom Doyle committed Aug 18, 2021
0 parents commit ed643b5
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .config/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[app]
secret_key =
6 changes: 6 additions & 0 deletions .config/gunicorn-cfg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
bind = '0.0.0.0:5005'
workers = 1
accesslog = '-'
loglevel = 'debug'
capture_output = True
enable_stdio_inheritance = True
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/config/
src/api.keys
letsencrypt/
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.9

ENV FLASK_APP app.py

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY . ./

EXPOSE 5005
WORKDIR src/
CMD ["gunicorn", "--config", "config/gunicorn-cfg.py", "app:app"]
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
config:
./configure

build:
./configure
docker-compose up -d --build

traefik:
./configure
docker-compose --file docker-compose-traefik.yml up -d --build

clean:
rm -rf src/config/
rm -rf api.keys

local:
./configure local
docker-compose --file docker-compose-traefik-local.yml up -d --build

stop:
docker-compose down
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## Python API Template

This repo is a template for a python api with Flask


### Configure & Install

Configure `config.yml`

**Optional** To generate only the configuration files

```bash
make config
```

To run container on port 5005

```bash
make build
```

To run the container with traefik proxy and letsencrypt

```bash
make traefik
```

**Note:** If you are running this locally you won't be able to get a cert from letsencrypt
You should then build for local

```bash
make local
```

If you want to stop all containers

```bash
make stop
```

If you want to remove all generated config files
```bash
make clean
```


### Issues

If you have any questions or issues please open an issue on this repo
14 changes: 14 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
gunicorn:
bind: '0.0.0.0:5005'
workers: 1
accesslog: '-'
loglevel: 'debug'
capture_output: True
enable_stdio_inheritance: True
api:
example_key: 'w8iqHcy4p1a9xlQL4dQZkxA7Qo8EmcWFretixnvSPzm1iF2wUh'
app:
secret_key: WfQ2mha43Pfzwu1qYu3k4eBaKVDMV6dA9cD54cef
traefik:
letsencrypt:
email: [email protected]
63 changes: 63 additions & 0 deletions configure
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/bin/bash

#
# Configure script
#


#
# Parse config.yml
#

parse_yaml() {
local prefix=$2
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 |
awk -F$fs '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
}
}'
}

if [[ ! -d "src/config" ]]; then
mkdir src/config
fi

#
# Generate gunicorn.cnf
#

eval $(parse_yaml config.yml "config_")

echo "bind = $config_gunicorn_bind" > src/config/gunicorn-cfg.py
echo "workers = $config_gunicorn_workers" >> src/config/gunicorn-cfg.py
echo "accesslog = $config_gunicorn_accesslog" >> src/config/gunicorn-cfg.py
echo "loglevel = $config_gunicorn_loglevel" >> src/config/gunicorn-cfg.py
echo "capture_output = $config_gunicorn_capture_output" >> src/config/gunicorn-cfg.py
echo "enable_stdio_inheritance = $config_gunicorn_enable_stdio_inheritance" >> src/config/gunicorn-cfg.py

#
# Generate api.keys
#

printf "{\n $config_api_example_key : \"example_key\"\n}" > src/api.keys

#
# Generate config.ini
#

cp .config/config.ini src/config/config.ini
sed -i "s/secret_key = /secret_key = ${config_app_secret_key}/" src/config/config.ini


#
# Generate traefik config
#

sed -i -e "s/- \"--certificatesresolvers.myresolver.acme.email=.*\"/- \"--certificatesresolvers.myresolver.acme.email=${config_traefik_letsencrypt_email}\"/" docker-compose-traefik.yml
55 changes: 55 additions & 0 deletions docker-compose-traefik-local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
version: '3'
services:
example_app:
container_name: example_app
restart: always
build: .
networks:
- web
labels:
- "traefik.http.routers.example.rule=Host(`app.internal`)"
- "traefik.http.routers.example.entrypoints=web"
- "traefik.http.routers.example.service=example"
- "traefik.http.services.example.loadbalancer.server.port=5005"
- "traefik.docker.network=web"
- "traefik.http.routers.example.tls=false"

example_traefik:
container_name: "example_traefik"
image: "traefik:latest"
restart: always
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=true"
- "--api.dashboard=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.myresolver.acme.caserver=https://acme-v01.api.letsencrypt.org/directory"
- "--certificatesresolvers.myresolver.acme.email=thomas.doyle9@mail.dcu.ie"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
networks:
- web
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
labels:
# Dashboard
- "traefik.http.routers.traefik.rule=Host(`traefik.internal`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.entrypoints=web"
- "traefik.http.routers.traefik.tls=false"
- "traefik.http.routers.traefik.tls.certresolver=myresolver"

# global redirect to https
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"

networks:
web:
external: true
55 changes: 55 additions & 0 deletions docker-compose-traefik.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
version: '3'
services:
example_app:
container_name: example_app
restart: always
build: .
networks:
- web
labels:
- "traefik.http.routers.example.rule=Host(`app.your.domain`)"
- "traefik.http.routers.example.entrypoints=websecure"
- "traefik.http.routers.example.service=example"
- "traefik.http.services.example.loadbalancer.server.port=5005"
- "traefik.docker.network=web"
- "traefik.http.routers.example.tls=true"

example_traefik:
container_name: "example_traefik"
image: "traefik:latest"
restart: always
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=true"
- "--api.dashboard=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.myresolver.acme.caserver=https://acme-v01.api.letsencrypt.org/directory"
- "--certificatesresolvers.myresolver.acme.email=thomas.doyle9@mail.dcu.ie"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
networks:
- web
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
labels:
# Dashboard
- "traefik.http.routers.traefik.rule=Host(`your.domain`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls=true"
- "traefik.http.routers.traefik.tls.certresolver=myresolver"

# global redirect to https
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"

networks:
web:
external: true
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: '3'
services:
example_app:
container_name: example_app
restart: always
build: .
ports:
- "5005:5005"
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
flask
flask_login
flask_migrate
flask_wtf
flask_sqlalchemy==2.*
email_validator
python-decouple
gunicorn
44 changes: 44 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/local/env python3.9

import json
import configparser
from flask import Blueprint, Flask, render_template, redirect, url_for, request, jsonify, abort
from flask_login import LoginManager, login_user, logout_user, UserMixin, current_user, login_required
from flask import render_template_string, redirect
from functools import wraps

config = configparser.ConfigParser()
config.read('config/config.ini')

app = Flask(__name__)
app.secret_key = config['app']['secret_key']

def is_validkey(key):
with open('api.keys', 'r') as f:
keys = json.load(f)
if key in keys:
return True
return False

def require_appkey(view_function):
@wraps(view_function)
def decorated_function(*args, **kwargs):
if request.headers.get('x-api-key') and is_validkey(request.headers.get('x-api-key')):
return view_function(*args, **kwargs)
else:
abort(401)
return decorated_function

@app.route('/', methods=['GET'])
def index():
return {"msg": "No auth needed"}

@app.route('/auth/', methods=['POST', 'GET'])
@require_appkey
def auth():
if request.method == 'GET':
return {"msg": "API key Valid"}
return {"msg": "API key Not Valid"}

if __name__ == '__main__':
app.run(debug=True)

0 comments on commit ed643b5

Please sign in to comment.