Skip to content

Commit

Permalink
feat: add support for database url (#4)
Browse files Browse the repository at this point in the history
* feat: add support for database url

* Add support for db_url parameter in entrypoint.sh

* Improve documentation usage example for `db_url` vs. individual parameters

Also updated version tag.

---------

Co-authored-by: D̷e̵v̷G̸l̶i̸t̷c̸h̶ <[email protected]>
  • Loading branch information
luong-komorebi and DevGlitch authored Jun 13, 2024
1 parent 226b48e commit 3554713
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 51 deletions.
70 changes: 56 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ This action supports various inputs to accommodate different database configurat

_Note that some inputs are not required for all database types, such as SQLite._

- `db_url`: The url of database to check. Alternative to `db_host`, `db_port`, `db_user`, `db_password`, and `db_name`.
- `db_type`: The database type. Supported values are `postgresql`, `mysql`, and `sqlite`. Default: `postgresql`.
- `db_host`: The database host address.
- `db_port`: The database port. Defaults to `5432`.
Expand Down Expand Up @@ -55,17 +56,33 @@ ___
Below are usage examples on how to use the Alembic Migration Checker for different types of supported databases within
your GitHub Actions workflows.

### 🐘 PostgreSQL Example
### 🐘 PostgreSQL

Please note that `db_type` is by default `postgresql`. So, for this type of database it is not necessary to specify it.
Also, `db_port` is by default `5432`, specify the `db_port` only if you use a different one.
You can specify the database connection using either a single connection string (`db_url`) or individual parameters.
Using `db_url` is convenient when the connection string is available as a single secret.

#### Example Usage with `db_url`:

```yaml
- name: Check Alembic Migration Version
uses: DevGlitch/[email protected]
with:
db_url: ${{ secrets.DB_URL }} # Format: postgresql+psycopg2://user:password@host:port/dbname
migrations_path: ./migrations/
```
#### Example Usage with Individual Parameters:
If you prefer, you can specify individual parameters for the database connection. Please note that `db_type` defaults to
`postgresql`, so it is not necessary to specify it. Also, `db_port` defaults to `5432`, so specify `db_port` only if you are
using a different port.

```yaml
- name: Check Alembic Migration Version
uses: DevGlitch/alembic-migration-checker@v1
uses: DevGlitch/alembic-migration-checker@v1.1
with:
db_host: ${{ secrets.DB_HOST }}
db_port: ${{ secrets.DB_PORT }} # Only if not using 5432 default port
db_port: ${{ secrets.DB_PORT }} # Only if not using the default port 5432
db_user: ${{ secrets.DB_USER }}
db_password: ${{ secrets.DB_PASSWORD }}
db_name: ${{ secrets.DB_NAME }}
Expand All @@ -74,12 +91,26 @@ Also, `db_port` is by default `5432`, specify the `db_port` only if you use a di

### 🐬 MySQL

When working with MySQL, change the `db_type` to `mysql`. This example includes all necessary parameters for a MySQL
database connection.
When working with MySQL, set the `db_type` to `mysql`. You can specify the database connection using either a single
connection string (`db_url`) or individual parameters.

#### Example Usage with `db_url`:

```yaml
- name: Check Alembic Migration Version
uses: DevGlitch/alembic-migration-checker@v1
uses: DevGlitch/[email protected]
with:
db_url: ${{ secrets.DB_URL }} # Format: mysql://user:password@host:port/dbname
migrations_path: ./migrations/
```

#### Example Usage with Individual Parameters:

If you prefer, you can specify individual parameters for the database connection.

```yaml
- name: Check Alembic Migration Version
uses: DevGlitch/[email protected]
with:
db_type: mysql
db_host: ${{ secrets.DB_HOST }}
Expand All @@ -92,15 +123,26 @@ database connection.

### 🪶 SQLite

For SQLite databases, change the `db_type` to `sqlite`. The configuration is simplified
as `db_host`, `db_port`, `db_user`, and `db_password` are not needed.
For SQLite databases, you can either use a `db_url` to specify the entire database connection string or provide individual parameters. When using individual parameters, set the `db_type` to `sqlite`. Note that `db_host`, `db_port`, `db_user`, and `db_password` are not needed for SQLite.

#### Example Usage with `db_url`:

```yaml
- name: Check Alembic Migration Version
uses: DevGlitch/alembic-migration-checker@v1
uses: DevGlitch/[email protected]
with:
db_url: ${{ secrets.DB_URL }} # Format: sqlite:///path_to_db_file
migrations_path: ./migrations/
```

#### Example Usage with Individual Parameters:

```yaml
- name: Check Alembic Migration Version
uses: DevGlitch/[email protected]
with:
db_type: sqlite
db_name: ${{ secrets.DB_NAME }}
db_name: ${{ secrets.DB_NAME }} # Path to the SQLite database file
migrations_path: ./migrations/
```

Expand Down Expand Up @@ -138,7 +180,7 @@ jobs:
uses: actions/checkout@v4
- name: Check Alembic Migration Version
uses: DevGlitch/alembic-migration-checker@v1
uses: DevGlitch/alembic-migration-checker@v1.1
with:
db_host: ${{ secrets.DB_HOST }}
db_name: ${{ secrets.DB_NAME }}
Expand Down Expand Up @@ -170,7 +212,7 @@ jobs:
uses: actions/checkout@v4
- name: Check Alembic Migration Version
uses: DevGlitch/alembic-migration-checker@v1
uses: DevGlitch/alembic-migration-checker@v1.1
with:
db_host: ${{ secrets.STAGING_DB_HOST }}
db_name: ${{ secrets.STAGING_DB_NAME }}
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ branding:
icon: "check-circle"
color: "blue"
inputs:
db_url:
description: "Database URL. Alternative to specifying individual database connection parameters. Default: ''"
required: false
default: ""
db_type:
description: "Database type. Supported types are 'postgresql', 'mysql', and 'sqlite'. Default: 'postgresql'"
required: false
Expand Down Expand Up @@ -36,6 +40,7 @@ runs:
using: "docker"
image: "Dockerfile"
args:
- ${{ inputs.db_url }}
- ${{ inputs.db_type }}
- ${{ inputs.db_host }}
- ${{ inputs.db_port }}
Expand Down
100 changes: 64 additions & 36 deletions action/check_alembic_migration.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""
SCRIPT_DESCRIPTION = """
This script checks the Alembic version of the latest migration against the database and evaluates its readiness.
It supports PostgreSQL, MySQL, and SQLite databases.
"""

import argparse
import os
import sys

from alembic.config import Config
from alembic.script import ScriptDirectory
from sqlalchemy import create_engine, MetaData
from sqlalchemy import MetaData, create_engine
from sqlalchemy.sql import select


Expand All @@ -33,11 +34,21 @@ class AlembicMigrationChecker:
"""

def __init__(
self, db_type, db_host, db_port, db_user, db_password, db_name, migrations_path
self,
db_url,
db_type,
db_host,
db_port,
db_user,
db_password,
db_name,
migrations_path,
):
"""
Initializes the AlembicMigrationChecker with database connection details and migrations folder path.
If a db_url is given, no other params are required
:param db_url: The database URL
:param db_type: The database type (postgresql, mysql, sqlite)
:param db_host: The database host address
:param db_port: The database port
Expand All @@ -55,17 +66,19 @@ def __init__(
self.db_name = db_name
self.migrations_path = migrations_path

validation_error = self._validate_inputs()
if validation_error:
raise ValueError(validation_error)

self.db_url = self._get_database_url()
self.engine = create_engine(self.db_url)
if db_url:
self.db_url = db_url
else:
validation_error = self._validate_db_inputs()
if validation_error:
raise ValueError(validation_error)
self.db_url = self._get_database_url()

self.engine = self._get_database_engine()
self._alembic_cfg = None
self._script_directory = None

def _validate_inputs(self):
def _validate_db_inputs(self):
"""
Validates the necessary inputs for connecting to a database and accessing the migrations folder path.
Expand All @@ -87,10 +100,10 @@ def _validate_inputs(self):

# Validate inputs for non-SQLite databases
if self.db_type != "sqlite" and (
not self.db_host
or not self.db_port
or not self.db_user
or not self.db_password
not self.db_host
or not self.db_port
or not self.db_user
or not self.db_password
):
return "\nERROR: Database host, port, user, and password are required for non-SQLite databases."

Expand All @@ -113,6 +126,19 @@ def _get_database_url(self):
else:
return f"{self.db_type}://{self.db_user}:{self.db_password}@{self.db_host}:{self.db_port}/{self.db_name}"

def _get_database_engine(self):
"""Creates and returns a SQLAlchemy database engine."""
print("Creating a SQLAlchemy database engine...")
try:
engine = create_engine(self.db_url)
print("Database engine created successfully.")
engine.connect()
print("Database engine connected successfully.")
return engine
except Exception as e:
print("\nERROR creating database engine:", e)
sys.exit(1)

@property
def alembic_config(self):
"""Creates a custom Alembic Config object in memory for accessing migration information."""
Expand Down Expand Up @@ -161,9 +187,7 @@ def get_db_version(self):

def evaluate_migration_alignment(self):
"""Assesses the database against the latest migration script to determine migration readiness and alignment."""
print(
"Starting migration alignement evaluation..."
)
print("Starting migration alignement evaluation...")
latest_migration_version = self.get_latest_migration_version()
db_version = self.get_db_version()
print(
Expand All @@ -179,7 +203,9 @@ def evaluate_migration_alignment(self):
)
sys.exit(0)
else:
current_revision = self.script_directory.get_revision(latest_migration_version)
current_revision = self.script_directory.get_revision(
latest_migration_version
)
found_revision = False
pending_migrations_count = 0
while current_revision is not None:
Expand Down Expand Up @@ -222,27 +248,29 @@ def evaluate_migration_alignment(self):
def main():
"""The main function of the script."""

# Check if the correct number of inputs is provided (7 inputs expected)
if len(sys.argv) - 1 != 7:
print("\nError: Missing required inputs.")
sys.exit(1)

# Unpack inputs (excluding the script name) into variables
(
db_type,
db_host,
db_port,
db_user,
db_password,
db_name,
migrations_path,
) = sys.argv[1:]

parser = argparse.ArgumentParser(description=SCRIPT_DESCRIPTION)
parser.add_argument("--db_url", type=str, help="Database URL")
parser.add_argument("--db_type", type=str, help="Database Type")
parser.add_argument("--db_host", type=str, help="Database Host")
parser.add_argument("--db_port", type=str, help="Database Port")
parser.add_argument("--db_user", type=str, help="Database User")
parser.add_argument("--db_password", type=str, help="Database Password")
parser.add_argument("--db_name", type=str, help="Database Name")
parser.add_argument(
"--migrations_path", type=str, help="Migrations Path", required=True
)
args = parser.parse_args()
# Initialize the AlembicMigrationChecker class with the unpacked inputs
checker = AlembicMigrationChecker(
db_type, db_host, db_port, db_user, db_password, db_name, migrations_path
args.db_url,
args.db_type,
args.db_host,
args.db_port,
args.db_user,
args.db_password,
args.db_name,
args.migrations_path,
)

# Assess the alignment between the database version and the latest migration script.
checker.evaluate_migration_alignment()

Expand Down
2 changes: 1 addition & 1 deletion action/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh -l

python /check_alembic_migration.py "${INPUT_DB_TYPE}" "${INPUT_DB_HOST}" "${INPUT_DB_PORT}" "${INPUT_DB_USER}" "${INPUT_DB_PASSWORD}" "${INPUT_DB_NAME}" "${INPUT_MIGRATIONS_PATH}"
python /check_alembic_migration.py --db_url="${INPUT_DB_URL}" --db_type="${INPUT_DB_TYPE}" --db_host="${INPUT_DB_HOST}" --db_port="${INPUT_DB_PORT}" --db_user="${INPUT_DB_USER}" --db_password="${INPUT_DB_PASSWORD}" --db_name="${INPUT_DB_NAME}" --migrations_path="${INPUT_MIGRATIONS_PATH}"

0 comments on commit 3554713

Please sign in to comment.