Skip to content

Commit

Permalink
chore: improve docker compose to use volumes
Browse files Browse the repository at this point in the history
  • Loading branch information
vugonz committed Nov 16, 2024
1 parent 71090c3 commit 54e5fe8
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 68 deletions.
2 changes: 0 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,3 @@ README.md
requirements.txt
.env.example
.env

data/
14 changes: 6 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
# Please provided relative paths with ./ or things can break :)

SESSION_DIR="data/flask_sessions/"
SESSION_LIFETIME="10800" # 3 hours
SESSION_LIFETIME="10800"

DATABASE_PATH="data/hackerschool.sqlite3"
DATABASE_PATH="data/db/hackerschool.sqlite3"
ROLES_PATH="data/roles.json"
PHOTOS_DIR="data/photos/"
MAX_FILE_UPLOAD_LENGTH="16777216" # 16 MiB
STATIC_DIR="data/static/"
MAX_FILE_UPLOAD_LENGTH="16777216"

LOG_LEVEL="INFO"
LOGS_PATH="data/logs/app.log"

ADMIN_USERNAME="admin"
ADMIN_PASSWORD="admin"

FRONTEND_URI="http://localhost:3000"
FRONTEND_ORIGIN="http://localhost:3000"

OAUTH_CALLBACK=""
FENIX_REDIRECT_URL=""
CLIENT_ID=""
CLIENT_SECRET=""
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ COPY requirements-prod.txt .
RUN pip install --no-cache-dir -r requirements-prod.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:create_app()"]
RUN chmod u+x entrypoint.sh

ENTRYPOINT [ "./entrypoint.sh" ]
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:create_app()"]
24 changes: 3 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,41 +64,23 @@ flask create-admin
flask run --debug
```


### Docker Setup (Optional)
1. **Create all application-data folders**:
`docker compose` will mount the database and required folders into the container. For this you need to initialize the database, please refer to the [installation steps 1 to 5](#installation) to set this up.

1. **Build the docker image**:
```bash
docker compose build
```
2. **Run the application (with gunicorn)**:
```bash
docker compose up
```

## Environment Variables
**TLDR**: You can just `cp .env.example .env` to get all default environment variables ready.


- `SESSION_DIR`: Where to store session files (defaults to `data/flask_sessions/`)
- `SESSION_LIFETIME`: How long a session should last in seconds (defaults to 3 hours)

- `DATABASE_PATH`: Path to the `sqlite3` database file (defaults to `data/hackerschool.sqlite3`)
- `DATABASE_PATH`: Path to the `sqlite3` database file (defaults to `data/db/hackerschool.sqlite3`)
- `STATIC_DIR`: Path to the folder where user and project images will be stored (defaults to `data/static/`)
- `ROLES_PATH`: Path to the roles configuration json file (defaults to `data/roles.json`)
- `PHOTOS_DIR`: Path to the folder where user and project images will be stored (defaults to `data/photos/`)

- `LOG_LEVEL`: Log level (defaults to INFO)
- `LOGS_PATH` Path to logs file (deault to stdout)
- `LOGS_PATH` Path to logs file (deaults to stdout)

These will only be necessary if you'll be using the `flask create-admin` command
- `ADMIN_USERNAME`: Admin username
- `ADMIN_PASSWORD`: Admin password


**Note**: If you use `docker compose` you will either need the `.env` file or environment variables set (no default values will be used) because `docker compose` will need them to mount the correct volumes.

## Project Structure
The project follows a layered architecture with a controller, service and models layer.
```
Expand Down
16 changes: 11 additions & 5 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from flask import Flask
from flask_cors import CORS

from app.config import Config
from app.config import Config, basedir
from app.extensions import session
from app.extensions import db
from app.extensions import migrate
Expand All @@ -17,6 +17,11 @@ def create_app(config_class=Config):

# Initialize extensions
session.init_app(flask_app)

db_dir = os.path.dirname(flask_app.config.get("DATABASE_PATH"))
if not os.path.exists(db_dir):
os.makedirs(db_dir)

db.init_app(flask_app)
migrate.init_app(flask_app, db)

Expand All @@ -27,7 +32,7 @@ def create_app(config_class=Config):
register_error_handlers(flask_app)
register_commands(flask_app)

if (frontend_uri := flask_app.config.get("FRONTEND_URI", "")) != "":
if (frontend_uri := flask_app.config.get("FRONTEND_ORIGIN", "")) != "":
CORS(flask_app, origins=[frontend_uri], supports_credentials=True)

setup_logger(flask_app)
Expand Down Expand Up @@ -71,16 +76,17 @@ def register_commands(app: Flask):
register_create_admin_user_command(app)

def setup_logger(app: Flask):
if app.debug or app.config.get("LOGS_PATH", "") == "": # don't set logger in debug
logs_path = app.config.get("LOGS_PATH")
if app.debug or logs_path == basedir: # don't set logger in debug or if not log file
return

levels = {"DEBUG": logging.DEBUG, "INFO": logging.INFO, "WARNING": logging.WARNING}
app.logger.setLevel(levels[app.config.get("LOG_LEVEL")])

logs_path = app.config.get("LOGS_PATH")
log_dir = os.path.dirname(logs_path)
if not os.path.exists(log_dir):
os.makedirs(log_dir)

app.logger.setLevel(levels[app.config.get("LOG_LEVEL")])
handler = logging.FileHandler(logs_path)
BASIC_FORMAT = "[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s"
handler.setFormatter(logging.Formatter(BASIC_FORMAT))
Expand Down
6 changes: 3 additions & 3 deletions app/api/auth/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def logout():
def fenix_auth():
if current_app.config.get("CLIENT_ID", "") == "" \
or current_app.config.get("CLIENT_SECRET", "") == "" \
or current_app.config.get("OAUTH_CALLBACK") == "":
or current_app.config.get("FENIX_REDIRECT_URL") == "":
throw_api_error(HTTPStatus.NOT_IMPLEMENTED, {"error": "Unsupported"})

state = generate_random_state()
Expand All @@ -65,7 +65,7 @@ def fenix_auth():
session['state'] = state
params = {
"client_id": current_app.config.get("CLIENT_ID"),
"redirect_uri": current_app.config.get("OAUTH_CALLBACK"),
"redirect_uri": current_app.config.get("FENIX_REDIRECT_URL"),
"state": state
}
return redirect("https://fenix.tecnico.ulisboa.pt/oauth/userdialog?" + urlencode(params))
Expand All @@ -85,7 +85,7 @@ def fenix_auth_callback():
params = {
"client_id": current_app.config.get("CLIENT_ID"),
"client_secret": current_app.config.get("CLIENT_SECRET"),
"redirect_uri": current_app.config.get("OAUTH_CALLBACK"),
"redirect_uri": current_app.config.get("FENIX_REDIRECT_URL"),
"code": code,
"grant_type": "authorization_code"
}
Expand Down
2 changes: 1 addition & 1 deletion app/api/auth/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def fetch_access_token(url: str) -> str | None:

return access_token

def get_user_info(access_token: str) -> Tuple[bool, Optional[str], Optional[str]]:
def get_user_info(access_token: str) -> Tuple[bool, Optional[str], Optional[str], Optional[str]]:
r = requests.get(
"https://fenix.tecnico.ulisboa.pt/api/fenix/v1/person?" + urlencode({"access_token": access_token})
)
Expand Down
10 changes: 5 additions & 5 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from datetime import timedelta
from dotenv import load_dotenv

basedir = os.path.abspath(os.path.dirname(__file__)) + "/.." # the repository folder
basedir = os.path.abspath(os.path.abspath(os.path.dirname(__file__)) + "/..") # the repository folder

load_dotenv(os.path.join(basedir, ".env"))

Expand Down Expand Up @@ -32,22 +32,22 @@ class Config:
PERMANENT_SESSION_LIFETIME = _get_int_env_or_default("SESSION_LIFETIME", 3 * 60 * 60)
SESSION_FILE_DIR = os.path.join(basedir, _get_env_or_default("SESSION_DIR", "data/flask_sessions/")).rstrip("/")

DATABASE_PATH = os.path.join(basedir, _get_env_or_default("DATABASE_PATH", "data/hackerschool.sqlite3"))
DATABASE_PATH = os.path.join(basedir, _get_env_or_default("DATABASE_PATH", "data/db/hackerschool.sqlite3"))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + DATABASE_PATH

ROLES_PATH = os.path.join(basedir, _get_env_or_default("ROLES_PATH", "data/roles.json"))

PHOTOS_DIR = os.path.join(basedir, _get_env_or_default("PHOTOS_DIR", "data/photos/")).rstrip("/")
STATIC_DIR = os.path.join(basedir, _get_env_or_default("STATIC_DIR", "data/static/")).rstrip("/")
MAX_CONTENT_LENGTH = _get_int_env_or_default("MAX_FILE_UPLOAD_LENGTH", 16 * 1024 * 1024)

LOGS_PATH = os.path.join(basedir, _get_env_or_default("LOGS_PATH", ""))
LOG_LEVEL = _get_env_or_default("LOG_LEVEL", "INFO")

FRONTEND_URI = _get_env_or_default("FRONTEND_URI", "http://localhost:3000")
FRONTEND_ORIGIN = _get_env_or_default("FRONTEND_ORIGIN", "http://localhost:3000")

ADMIN_USERNAME = _get_env_or_default("ADMIN_USERNAME", "admin")
ADMIN_PASSWORD = _get_env_or_default("ADMIN_PASSWORD", "admin")

CLIENT_ID = _get_env_or_default("CLIENT_ID", "")
CLIENT_SECRET = _get_env_or_default("CLIENT_SECRET", "")
OAUTH_CALLBACK = _get_env_or_default("OAUTH_CALLBACK", "")
FENIX_REDIRECT_URL = _get_env_or_default("FENIX_REDIRECT_URL", "")
10 changes: 3 additions & 7 deletions app/logos/logos_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ def _is_valid_content_type(content_type: str) -> bool:

class LogosHandler:
def __init__(self):
self.photos_dir = None
self.members_path = None
self.projects_path = None

Expand Down Expand Up @@ -88,12 +87,9 @@ def _get_logo_directory(self, logo_type: str) -> str:
raise InvalidLogoTypeError(logo_type)

def init_app(self, app: Flask) -> None:
self.photos_dir = app.config["PHOTOS_DIR"].rstrip("/")
self.members_path = self.photos_dir+"/members"
self.projects_path = self.photos_dir+"/projects"

if not os.path.exists(self.photos_dir):
os.makedirs(self.photos_dir)
static_dir = app.config["STATIC_DIR"].rstrip("/")
self.members_path = static_dir+"/members"
self.projects_path = static_dir+"/projects"
if not os.path.exists(self.members_path):
os.makedirs(self.members_path)
if not os.path.exists(self.projects_path):
Expand Down
33 changes: 18 additions & 15 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
services:
app:
hs-api:
build: .
ports:
- "8000:8000"
volumes:
- ${DATABASE_PATH}:/hs-api/data/hackerschool.sqlite3
- ${ROLES_PATH}:/hs-api/data/roles.json
- ${SESSION_DIR}:/hs-api/data/flask_sessions/
- ${PHOTOS_DIR}:/hs-api/data/photos
- ${LOGS_PATH}:/hs-api/data/logs/log.app
- hs_db:/hs-api/data/db
- hs_static:/hs-api/data/static/
- hs_logs:/hs-api/data/logs/
environment:
- DATABASE_PATH=/hs-api/data/hackerschool.sqlite3
- ROLES_PATH=/hs-api/data/roles.json
- SESSION_DIR=/hs-api/data/flask_sessions/
- PHOTOS_DIR=/hs-api/data/photos/
- LOGS_PATH=/hs-api/data/logs/app.log
- LOG_LEVEL=${LOG_LEVEL}
- SESSION_LIFETIME=${SESSION_LIFETIME}
- MAX_FILE_UPLOAD_LENGTH=${MAX_FILE_UPLOAD_LENGTH}
- FRONTEND_URI=${FRONTEND_URI}
- DATABASE_PATH=data/db/hackerschool.sqlite3
- STATIC_DIR=data/static/
- ROLES_PATH=data/roles.json
- SESSION_DIR=data/flask_sessions/
- LOGS_PATH=data/logs/app.log
- LOG_LEVEL=INFO
- MAX_FILE_UPLOAD_LENGTH=16777216
- CLIENT_ID=
- CLIENT_SECRET=
- FENIX_REDIRECT_URL=

volumes:
hs_db:
hs_static:
hs_logs:
5 changes: 5 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#! /bin/sh

flask db upgrade

exec "$@"
5 changes: 5 additions & 0 deletions requirements-prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ attrs==24.2.0
bcrypt==4.2.0
blinker==1.9.0
cachelib==0.13.0
certifi==2024.8.30
charset-normalizer==3.4.0
click==8.1.7
Flask==3.1.0
Flask-Cors==5.0.0
Expand All @@ -11,6 +13,7 @@ Flask-Session==0.8.0
Flask-SQLAlchemy==3.1.1
greenlet==3.1.1
gunicorn==23.0.0
idna==3.10
itsdangerous==2.2.0
Jinja2==3.1.4
jsonschema==4.23.0
Expand All @@ -21,7 +24,9 @@ msgspec==0.18.6
packaging==24.2
python-dotenv==1.0.1
referencing==0.35.1
requests==2.32.3
rpds-py==0.21.0
SQLAlchemy==2.0.36
typing_extensions==4.12.2
urllib3==2.2.3
Werkzeug==3.1.3

0 comments on commit 54e5fe8

Please sign in to comment.