Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docker support #584

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ JSON_SCHEMA_DIRECTORY=<FULL PATH TO "schemas directory">
# Database
DATABASE_URL=postgresql://<DATABASE USERNAME>:<DATABASE PASSWORD>@<DATABASE HOSTNAME>:<DATABASE PORT>/<DATABASE NAME>
TEST_DATABASE_URL=postgresql://<DATABASE USERNAME - TEST>:<DATABASE PASSWORD - TEST>@<DATABASE HOSTNAME - TEST>:<DATABASE PORT - TEST>/<DATABASE NAME - TEST>
POSTGRES_USER=<POSTGRES DATABASE USERNAME>
POSTGRES_PASSWORD=<POSTGRES DATABASE PASSWORD>
POSTGRES_DB=<POSTGRES DATABASE NAME>

# E-Mail Settings
MAIL_SERVER=<MAIL SERVER HOSTNAME>
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10.2
3.11.9
44 changes: 44 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# ================== BUILD =================
FROM python:3.11.9-slim-bookworm AS builder

RUN apt-get update \
&& apt-get -y --no-install-recommends install \
libmagic-dev \
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \
libpq-dev gcc build-essential

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

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

COPY app ./app/
COPY migrations ./migrations/
COPY config.py openrecords.py gunicorn_config.py celery_worker.py ./

COPY entrypoint.sh ./
RUN chmod +x ./entrypoint.sh

# ================== PRODUCTION =================
FROM builder AS production

RUN pip install -r requirements/prod.txt

EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]
CMD ["gunicorn", "-c", "python:gunicorn_config", "openrecords:app"]

# ================== DEVELOPMENT =================
FROM builder as development

RUN pip install -r requirements/dev.txt

EXPOSE 5000
ENTRYPOINT ["./entrypoint.sh"]
CMD ["flask", "run", "--host", "0.0.0.0"]

# ================== CELERY =================
FROM builder as celery

RUN pip install celery[redis]
5 changes: 3 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ webencodings = "*"
xmlsec = "*"
business_calendar = "*"
CairoSVG = "*"
Flask = "*"
Flask-Bootstrap = "*"
Flask-Login = "*"
Flask-Mail = "*"
Expand Down Expand Up @@ -116,10 +115,12 @@ python-magic = "*"
gunicorn = "*"
azure-core = "*"
azure-storage-blob = "*"

# Required for additional formats in tablib
xlwt = "*"
xlrd = "*"
flask = "*"
lxml-html-clean = "*"
pytest = "*"

[requires]
python_version = "3.10.2"
2,943 changes: 1,685 additions & 1,258 deletions Pipfile.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ It relies on the following technologies:
- ElasticSearch v5.2
- Redis v3.2

The following libraries are needed:
- [Pango](https://pango.gnome.org/)
- libmagic

Authentication
- OpenRecords currently implements LDAP and OAuth for authentication. For development, you can bypass authentication by setting both `'USE_OAUTH'` and `'USE_LDAP'` to `False`.

Expand Down
1 change: 1 addition & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def create_app(config_name='default'):
'''))
app.logger.addHandler(mail_handler)

os.makedirs(app.config['LOGFILE_DIRECTORY'], exist_ok=True)
handler_error = TimedRotatingFileHandler(
os.path.join(app.config['LOGFILE_DIRECTORY'],
'openrecords_{}_error.log'.format(app.config['APP_VERSION_STRING'])),
Expand Down
5 changes: 3 additions & 2 deletions app/lib/recaptcha_utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import logging

import requests
from flask import Markup, current_app, json, request
from flask import current_app, request
from wtforms import ValidationError
from wtforms.fields import HiddenField
from wtforms.widgets import HiddenInput
from markupsafe import Markup
from json import JSONEncoder

logger = logging.getLogger(__name__)

JSONEncoder = json.JSONEncoder

RECAPTCHA_TEMPLATE = """
<script src='https://www.google.com/recaptcha/api.js?render={public_key}&onload=executeRecaptcha{action}' async defer></script>
Expand Down
3 changes: 2 additions & 1 deletion app/report/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import datetime, timedelta

import tablib
from flask import current_app, url_for, request as flask_request, Markup
from flask import current_app, url_for, request as flask_request
from markupsafe import Markup
from sqlalchemy import asc, func, Date, or_
from sqlalchemy.orm import joinedload
from urllib.parse import urljoin
Expand Down
2 changes: 1 addition & 1 deletion app/request/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
render_template,
current_app,
url_for, request as flask_request,
escape
)
from markupsafe import escape
from flask_login import current_user
from werkzeug.utils import secure_filename

Expand Down
3 changes: 1 addition & 2 deletions app/request/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
request as flask_request,
current_app,
flash,
Markup,
jsonify,
abort,
escape
)
from flask_login import current_user
from sqlalchemy import any_
from sqlalchemy.orm.exc import NoResultFound
from markupsafe import Markup, escape

from app.constants import request_status, permission, HIDDEN_AGENCIES
from app.lib.date_utils import DEFAULT_YEARS_HOLIDAY_LIST, get_holidays_date_list
Expand Down
3 changes: 1 addition & 2 deletions app/response/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@
render_template_string,
url_for,
jsonify,
Markup,
escape
)
from markupsafe import Markup, escape
from flask_login import current_user
from sqlalchemy.orm import joinedload
from werkzeug.utils import secure_filename
Expand Down
3 changes: 2 additions & 1 deletion app/search/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from io import StringIO, BytesIO
import re

from flask import current_app, request, render_template, jsonify, Markup
from flask import current_app, request, render_template, jsonify
from markupsafe import Markup
from flask.helpers import send_file
from flask_login import current_user
from sqlalchemy.orm import joinedload
Expand Down
3 changes: 2 additions & 1 deletion app/user/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from collections import defaultdict
from datetime import datetime

from flask import jsonify, request, escape
from flask import jsonify, request
from flask_login import current_user
from markupsafe import escape

from app.constants import (
event_type,
Expand Down
75 changes: 75 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
services:
db:
image: postgres:14-bookworm
ports:
- "5432:5432"
volumes:
- db:/var/lib/postgres/data
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
healthcheck:
test: ["CMD-SHELL", "sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'"]
interval: 10s
timeout: 5s
retries: 3

redis:
image: redis:7-bookworm
volumes:
- redis:/data
ports:
- "6379:6379"

celery:
build:
context: .
target: celery
command: celery -A app worker -l INFO
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
- APP_LAUNCH_DATE=${APP_LAUNCH_DATE}
depends_on:
- db
- redis

production:
build:
context: .
target: production
ports:
- "8080:8080"
environment:
- DATABASE_URL=${DATABASE_URL}
- FLASK_APP=${FLASK_APP}
- APP_LAUNCH_DATE=${APP_LAUNCH_DATE}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
depends_on:
- db
- redis

development:
build:
context: .
target: development
ports:
- "5000:5000"
environment:
- FLASK_DEBUG=1
- DATABASE_URL=${DATABASE_URL}
- FLASK_APP=${FLASK_APP}
- APP_LAUNCH_DATE=${APP_LAUNCH_DATE}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
depends_on:
- db
- redis

volumes:
db:
driver: local
redis:
driver: local
5 changes: 5 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

flask db upgrade

exec "$@"
2 changes: 1 addition & 1 deletion gunicorn_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'.
# An IP is a valid HOST.

bind = "127.0.0.1:8080"
bind = ":8080"

#
# Worker processes
Expand Down
1 change: 0 additions & 1 deletion migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ def process_revision_directives(context, revision, directives):
context.configure(connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
compare_type=True,
**current_app.extensions['migrate'].configure_args)

try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_constraint("events_user_guid_fkey", "events", type_="foreignkey")

op.create_foreign_key(
None,
"events",
Expand All @@ -25,9 +25,6 @@ def upgrade():
["guid", "auth_user_type"],
onupdate="CASCADE",
)
op.drop_constraint(
"user_requests_user_guid_fkey", "user_requests", type_="foreignkey"
)
op.create_foreign_key(
None,
"user_requests",
Expand Down
Loading