From 3554713ac617794888283ed386dfa6eba7c87a86 Mon Sep 17 00:00:00 2001 From: Luong Vo Date: Fri, 14 Jun 2024 04:23:03 +0700 Subject: [PATCH] feat: add support for database url (#4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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̶ <59583446+DevGlitch@users.noreply.github.com> --- README.md | 70 ++++++++++++++++----- action.yml | 5 ++ action/check_alembic_migration.py | 100 +++++++++++++++++++----------- action/entrypoint.sh | 2 +- 4 files changed, 126 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 44d4d9e..c88dda5 100644 --- a/README.md +++ b/README.md @@ -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`. @@ -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/alembic-migration-checker@v1.1 + 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 }} @@ -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/alembic-migration-checker@v1.1 + 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/alembic-migration-checker@v1.1 with: db_type: mysql db_host: ${{ secrets.DB_HOST }} @@ -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/alembic-migration-checker@v1.1 + 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/alembic-migration-checker@v1.1 with: db_type: sqlite - db_name: ${{ secrets.DB_NAME }} + db_name: ${{ secrets.DB_NAME }} # Path to the SQLite database file migrations_path: ./migrations/ ``` @@ -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 }} @@ -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 }} diff --git a/action.yml b/action.yml index 75cf3f2..408e077 100644 --- a/action.yml +++ b/action.yml @@ -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 @@ -36,6 +40,7 @@ runs: using: "docker" image: "Dockerfile" args: + - ${{ inputs.db_url }} - ${{ inputs.db_type }} - ${{ inputs.db_host }} - ${{ inputs.db_port }} diff --git a/action/check_alembic_migration.py b/action/check_alembic_migration.py index 5dd7586..dc7601c 100644 --- a/action/check_alembic_migration.py +++ b/action/check_alembic_migration.py @@ -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 @@ -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 @@ -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. @@ -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." @@ -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.""" @@ -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( @@ -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: @@ -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() diff --git a/action/entrypoint.sh b/action/entrypoint.sh index 2854175..2183219 100644 --- a/action/entrypoint.sh +++ b/action/entrypoint.sh @@ -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}"