Skip to main content

A schema isolated SQLAlchemy migrator for shared Postgres db micro services

Project description

MiGreat

A schema isolated Postgres migrator for shared database micro services

Philosophy

Several micro services may share the same database backend, but it's important to provide namespace isolation between said services. This is where schema level isolation is effective. The schema provides a namespace in which a given service can create tables, indices, types, etc. The default namespace in postgres is the public namespace. When writing queries, it's convenient to not have to qualify every item by namespace.

MiGreat provides the convenience of preparing schema isolation, bound to a non-privileged service account within the database. This means that migrations carried out by MiGreat for a particular service, do not require queries to be schema-qualified, as they are executed by the service user to which the schema is implicitly bound, via a combination of schema privileges, and the schema search order. Additionally, the micro service is locked by access grant to its own schema and cannot intentionally or otherwise clobber data that does not belong to it.

Installation

pip install MiGreat-cli

How to migrate (and be great)

Run the following:

migreat upgrade

This configures an unconfigured database, and executes unexecuted migrations. This should run standalone, outside of your application. In a kubernetes environment, you may have a dedicated pod spin up to execute the script as part of a rollout hook. In a compose, or local environment, you may want to execute it before your service starts. What you don't want to do however, is tightly couple migreat's execution to that of your microservice process. In an environment where your service runs in multiple processes or even on multiple hardware/virtual nodes, tight coupling will have undesired consequences (think everybody transacting on the same migration script at once).

TL;DR: Keep the migration execution and the application execution separate!

Usage

From a location of your choosing (example: the root of your microservice's directory structure), perform the basic intialization. NOTE, initialization does not communication with the databse, it merely provides the initial configuration. All database communication happens at migration time.

migreat init

This creates a configuration file MiGreat.yaml, which stores database connection information, as well as some basic operational parameters.

Basic example:

hostname: localhost
port: 5432
database: my_db

priv_db_username: postgres
priv_db_password: ${PRIVELEGED_DB_PASSWORD}

service_db_username: checkout_svc
service_db_password: ${SERVICE_DB_PASSWORD}

service_schema: checkout

This example illustrates a minimal configuration, in which a service user called checkout_svc, will be bound to a schema called checkout. The privileged user id and password are used to perform the initial configuration, but all subsequent operations are performed by the service user unless otherwise explicitly specified. The micro service which then consumes the database need only be aware of the checkout_svc user.

You'll notice as well that the passwords have some string interpolation syntax. While it's entirely possibly to pass strings directly into the config file, it is recommended to use this string interpolation syntax for sensitive data such as passwords. MiGreat will then insert values from the process environment.

In addition to the config file, a directory called versions is created. This will initially be empty, but is where migration scripts are stored.

Creating a first migration script

From the same directory where migreat init was invoked in the previous step, invoke the following command:

migreat create

This will create a new migration script in the form of XXXX_unnamed_migrator.py, where XXXX is the next version in the sequence of migrators (or 0001 if this is the first.) This operation does not communicate with the database, it only concerns itself with what scripts already exist.

Feel free to copy and paste migration scripts instead of executing migreat create. This is merely a convenience command which exposes a fresh migration script template.

The migration script template

CONFIG_OPTIONS = {
    "transact": True,           # Transact this migrator automatically
    "run_as_priv": False,       # Run as the privileged database user (instead of the service user)
}
"""
    These are all the available options on a per-migrator basis.  The options present have all
    been defaulted.  You don't need to include this dict, though if you do, the options you specify
    will be overlayed onto the defaults.
"""

def upgrade(conn):
    """
        Upgrades the database to the current defined migration version.
    """

def downgrade(conn):
    """
        *** DOWNGRADE SQL GOES HERE ***
    """

The template should be fairly self explanatory, but we'll re-iterate here. CONFIG_OPTIONS shown are the defaults, your migrator script does not need to include that variable unless your intent is to override a default. Overrides are mapped overtop the defaults. Not all options have to be specified when overriding, only the overridden ones.

The upgrade method uses an SQLAlchemy session for the conn argument, and the downgrade method does nothing but facilitate a convenient spot to specify downgrade SQL. Do what you like with this method.

Advanced options

There are a few advanced options exposed in the MiGreat.yaml config. They are as follows (defaults shown):

# Legacy sqlalchemy style (pre v2)
legacy_sqlalchemy: false

# Max connection retries
max_conn_retries: 10

# Connection retry interval (seconds)
conn_retry_interval: 5

# Migration table name
migration_table: migrate_version

# Service group name (optional) - All service users will gain membership to this group, and this
# group will be granted the USAGE permission on all schemas.  This basically allows the schema
# itself to be seen, but no other read or otherwise access is implicitly granted.
group:

# This indicates that the migrator series has been terminated.  Upon subsequent upgrade, schema
# and user will be deleted if existing.  (optional)
dead: false

# Wraps the entire upgrade process in a PG advisory lock, ensuring mutual exclusion on concurrent
# migrators.
use_advisory_lock: false

legacy_sqlalchemy assumes the pre v2 way of writing SQL queries (meaning, largely, they don't need to be wrapped in text()). The other options should be fairly self explanatory.

FAQ


Q) Why called migreat?

A) Because this functionality is great... why else :)


Q) What if I absolutely need to do something outside the scope of the schema, in a given migrator

A) Honestly, I'm not sure how this plays from a philosophical standpoint, but the ability exists for use cases I haven't conceived yet. This is what the run_as_priv option is for in the migrator's CONFIG_OPTIONS. Maybe a service decides that it needs to include a Postgres extension, after the fact... Maybe some cross-schema fix needs to happen. I don't know your use case, but it sounds dangerous.


Q) Can I grant some access across schemas?

A) Yes, and while it's not recommended to do so, the ability exists. In MiGreat.yaml, create a "group" entry:

group: <some_group_name>

This group will be granted the USAGE privilege to all schemas, and all service users will be given group membership. You then have the freedom of opening up database entities such as tables, etc. to the group.


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

MiGreat-cli-0.1.9.tar.gz (13.2 kB view details)

Uploaded Source

Built Distribution

MiGreat_cli-0.1.9-py3-none-any.whl (29.9 kB view details)

Uploaded Python 3

File details

Details for the file MiGreat-cli-0.1.9.tar.gz.

File metadata

  • Download URL: MiGreat-cli-0.1.9.tar.gz
  • Upload date:
  • Size: 13.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.22.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.8.10

File hashes

Hashes for MiGreat-cli-0.1.9.tar.gz
Algorithm Hash digest
SHA256 69a84dd94b3658d81d4d49637a306815581556b9643bb362e41a8f9904613b60
MD5 8d23a44b0d037e1177cfd1afb54b59fb
BLAKE2b-256 73a4662e0fd979f1e582d2a85699ddca7781ffcb7d0b641b52660b0ff5a7c144

See more details on using hashes here.

File details

Details for the file MiGreat_cli-0.1.9-py3-none-any.whl.

File metadata

  • Download URL: MiGreat_cli-0.1.9-py3-none-any.whl
  • Upload date:
  • Size: 29.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.22.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.8.10

File hashes

Hashes for MiGreat_cli-0.1.9-py3-none-any.whl
Algorithm Hash digest
SHA256 49caa48f5245a81e85ec634bb4cf00a3f96ffd08b944817165e7159344ca4cf0
MD5 ec35ad011241281e239e307898e88efb
BLAKE2b-256 605359a32cedb84248289e11b97e6ed5f12db3814f5d0bfded4c848e18990df7

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page