Skip to content

Commit

Permalink
Containerized the build
Browse files Browse the repository at this point in the history
  • Loading branch information
autonomouscereal committed Oct 7, 2024
1 parent bd8fea1 commit dadff8f
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 99 deletions.
6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 0 additions & 35 deletions AuthorizationCodeGrant.py

This file was deleted.

45 changes: 45 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Use the official Python image with slim variant
FROM python:3.9-slim-buster

# Set environment variables directly in the Dockerfile
ENV db_username=postgres
ENV db_password=your_postgres_password
ENV SECRET_KEY=your_secret_key
ENV DB_HOST=localhost
ENV DB_PORT=5432
ENV OAUTHDB=OAUTHDB

# Set the working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
postgresql \
postgresql-contrib \
supervisor \
&& rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application code
COPY . .

# Copy the supervisor configuration file
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Copy the initialization script
COPY init.sh /init.sh
RUN chmod +x /init.sh

# Ensure the logs directory exists
RUN mkdir -p /var/log/postgresql && mkdir -p /var/log/fastapi

# Expose port 3100
EXPOSE 3100

# Set the entrypoint to run the init script before starting supervisord
ENTRYPOINT ["/bin/bash", "-c", "/init.sh && /usr/bin/supervisord -n"]
193 changes: 129 additions & 64 deletions db_helper.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,150 @@
# db_helper.py
import logging

import asyncpg
from datetime import datetime
from credential_manager import CredentialManager


logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.StreamHandler()
]
)


class DBHelper:
def __init__(self):
self.credentials = CredentialManager.get_db_credentials()

async def connect_create_if_not_exists(self, user, database, password, port, host):
"""
Connect to the specified database. If it doesn't exist, connect to 'postgres' and create it.
"""
logging.info(f"Attempting to connect to database '{database}' as user '{user}' at {host}:{port}")
try:
conn = await asyncpg.connect(user=user, database=database, password=password, port=port, host=host)
logging.info(f"Successfully connected to database '{database}' as user '{user}'")
await conn.close()
return
except asyncpg.exceptions.InvalidCatalogNameError:
logging.warning(f"Database '{database}' does not exist. Attempting to create it.")
try:
# Connect to the default 'postgres' database to create the new database
sys_conn = await asyncpg.connect(user=user, database='postgres', password=password, port=port, host=host)
logging.info(f"Connected to 'postgres' database as user '{user}' to create '{database}'")
await sys_conn.execute(f'CREATE DATABASE "{database}" OWNER "{user}"')
logging.info(f"Database '{database}' created successfully with owner '{user}'")
await sys_conn.close()
except Exception as e:
logging.error(f"Failed to create database '{database}': {e}")
raise
except Exception as e:
logging.error(f"Failed to connect to database '{database}' as user '{user}': {e}")
raise

async def init_db(self):
"""
Initialize the database by ensuring it exists and creating necessary tables.
"""
user = self.credentials['user']
database = self.credentials['database']
password = self.credentials['password']
port = self.credentials['port']
host = self.credentials['host']

logging.info("Starting database initialization process.")

# Connect to the database, create if not exists
await self.connect_create_if_not_exists(
user=self.credentials['user'],
database=self.credentials['database'],
password=self.credentials['password'],
port=self.credentials['port'],
host=self.credentials['host']
user=user,
database=database,
password=password,
port=port,
host=host
)

conn = await self.get_db_connection()

# Create tables
await conn.execute("""
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR UNIQUE NOT NULL,
hashed_password VARCHAR NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
""")

await conn.execute("""
CREATE TABLE IF NOT EXISTS oauth2_clients (
client_id VARCHAR PRIMARY KEY,
client_secret VARCHAR,
redirect_uris TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
""")

await conn.execute("""
CREATE TABLE IF NOT EXISTS oauth2_authorization_codes (
code VARCHAR PRIMARY KEY,
client_id VARCHAR REFERENCES oauth2_clients(client_id),
redirect_uri VARCHAR,
scope VARCHAR,
user_id INTEGER REFERENCES users(id),
code_challenge VARCHAR,
code_challenge_method VARCHAR,
expires_at TIMESTAMPTZ
);
""")

await conn.execute("""
CREATE TABLE IF NOT EXISTS refresh_tokens (
token VARCHAR PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
expires_at TIMESTAMPTZ
);
""")

await conn.execute("COMMIT;")
await conn.close()

@staticmethod
async def connect_create_if_not_exists(user, database, password, port, host):
try:
conn = await asyncpg.connect(user=user, database=database, password=password, port=port, host=host)
# Connect to the target database
logging.info(f"Connecting to database '{database}' as user '{user}'")
conn = await self.get_db_connection()
logging.info(f"Successfully connected to database '{database}'")

# Create tables with logging
tables = {
"users": """
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR UNIQUE NOT NULL,
hashed_password VARCHAR NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
""",
"oauth2_clients": """
CREATE TABLE IF NOT EXISTS oauth2_clients (
client_id VARCHAR PRIMARY KEY,
client_secret VARCHAR,
redirect_uris TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
""",
"oauth2_authorization_codes": """
CREATE TABLE IF NOT EXISTS oauth2_authorization_codes (
code VARCHAR PRIMARY KEY,
client_id VARCHAR REFERENCES oauth2_clients(client_id),
redirect_uri VARCHAR,
scope VARCHAR,
user_id INTEGER REFERENCES users(id),
code_challenge VARCHAR,
code_challenge_method VARCHAR,
expires_at TIMESTAMPTZ
);
""",
"refresh_tokens": """
CREATE TABLE IF NOT EXISTS refresh_tokens (
token VARCHAR PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
expires_at TIMESTAMPTZ
);
"""
}

for table_name, create_stmt in tables.items():
logging.info(f"Creating table '{table_name}' if it does not exist.")
await conn.execute(create_stmt)
logging.info(f"Table '{table_name}' is ready.")

# Commit the transaction
logging.info("Committing the transaction.")
await conn.execute("COMMIT;")
logging.info("Database initialization completed successfully.")

except Exception as e:
logging.error(f"Error during database initialization: {e}")
raise
finally:
await conn.close()
except Exception:
sys_conn = await asyncpg.connect(user=user, database='postgres', password=password, port=port, host=host)
await sys_conn.execute(f'CREATE DATABASE "{database}" OWNER "{user}"')
await sys_conn.close()
logging.info(f"Closed connection to database '{database}'")

async def get_db_connection(self):
return await asyncpg.connect(
user=self.credentials['user'],
database=self.credentials['database'],
password=self.credentials['password'],
port=self.credentials['port'],
host=self.credentials['host']
)
"""
Establish a connection to the database.
"""
try:
conn = await asyncpg.connect(
user=self.credentials['user'],
database=self.credentials['database'],
password=self.credentials['password'],
port=self.credentials['port'],
host=self.credentials['host']
)
logging.info(f"Established connection to database '{self.credentials['database']}' as user '{self.credentials['user']}'")
return conn
except Exception as e:
logging.error(f"Failed to establish database connection: {e}")
raise

# User methods
async def add_user(self, email: str, hashed_password: str):
Expand Down
42 changes: 42 additions & 0 deletions init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
set -e

# Define PostgreSQL version
PG_VERSION=11

# Paths
POSTGRES_BIN_DIR="/usr/lib/postgresql/${PG_VERSION}/bin"

# Initialize PostgreSQL data directory if it doesn't exist
if [ ! -d "/var/lib/postgresql/data" ]; then
echo "Initializing PostgreSQL data directory..."
mkdir -p /var/lib/postgresql/data
chown -R postgres:postgres /var/lib/postgresql
su postgres -c "${POSTGRES_BIN_DIR}/initdb -D /var/lib/postgresql/data"
fi

# Update pg_hba.conf to allow password authentication
PG_HBA=/var/lib/postgresql/data/pg_hba.conf
if [ -f "$PG_HBA" ]; then
echo "Configuring PostgreSQL to use md5 authentication..."
sed -i "s/^\(local\s\+all\s\+all\s\+\)peer/\1md5/" $PG_HBA
fi

# Start PostgreSQL to perform setup
echo "Starting PostgreSQL..."
su postgres -c "${POSTGRES_BIN_DIR}/pg_ctl -D /var/lib/postgresql/data -w start"

# Create PostgreSQL user with SUPERUSER privilege if it doesn't exist
echo "Creating PostgreSQL user with SUPERUSER privilege if it doesn't exist..."
su postgres -c "psql -tc \"SELECT 1 FROM pg_roles WHERE rolname = '$db_username'\" | grep -q 1 || psql -c \"CREATE USER $db_username WITH PASSWORD '$db_password' SUPERUSER;\""

# Create database if it doesn't exist
echo "Creating PostgreSQL database if it doesn't exist..."
su postgres -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname = '$OAUTHDB'\" | grep -q 1 || psql -c \"CREATE DATABASE $OAUTHDB OWNER $db_username;\""

# Grant all privileges on the database to the user (redundant but ensures full access)
su postgres -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE $OAUTHDB TO $db_username;\""

# Stop PostgreSQL (Supervisor will manage it)
echo "Stopping PostgreSQL..."
su postgres -c "${POSTGRES_BIN_DIR}/pg_ctl -D /var/lib/postgresql/data -m fast -w stop"
10 changes: 10 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fastapi
uvicorn[standard]
asyncpg
passlib[bcrypt]
PyJWT
starlette
pydantic
python-dotenv
itsdangerous
python-multipart
20 changes: 20 additions & 0 deletions supervisord.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[supervisord]
nodaemon=true

[program:postgresql]
command=/usr/lib/postgresql/11/bin/postgres -D /var/lib/postgresql/data
user=postgres
stdout_logfile=/var/log/postgresql/postgres_stdout.log
stderr_logfile=/var/log/postgresql/postgres_stderr.log
autostart=true
autorestart=true
priority=10

[program:fastapi]
command=uvicorn main:app --host 0.0.0.0 --port 3100
directory=/app
autostart=true
autorestart=true
stdout_logfile=/var/log/fastapi_stdout.log
stderr_logfile=/var/log/fastapi_stderr.log
priority=20

0 comments on commit dadff8f

Please sign in to comment.