planemo.database.postgres_singularity — Planemo 0.75.31.dev0 documentation (original) (raw)

"""Module describes a :class:DatabaseSource for managed, dockerized postgres databases."""

import os import subprocess import time from tempfile import mkdtemp

from galaxy.util.commands import execute

from planemo.io import info from .interface import DatabaseSource from .postgres import ExecutesPostgresSqlMixin

DEFAULT_CONTAINER_NAME = "planemopostgres" DEFAULT_POSTGRES_DATABASE_NAME = "galaxy" DEFAULT_POSTGRES_USER = "galaxy" DEFAULT_POSTGRES_PASSWORD = "mysecretpassword" DEFAULT_POSTGRES_PORT_EXPOSE = 5432 DEFAULT_DOCKERIMAGE = "postgres:14.2-alpine3.15" DEFAULT_SIF_NAME = "postgres_14_2-alpine3_15.sif"

DEFAULT_CONNECTION_STRING = f"postgresql://{DEFAULT_POSTGRES_USER}:{DEFAULT_POSTGRES_PASSWORD}@localhost:{DEFAULT_POSTGRES_PORT_EXPOSE}/{DEFAULT_POSTGRES_DATABASE_NAME}"

def start_postgres_singularity( singularity_path, container_instance_name, database_location, databasename=DEFAULT_POSTGRES_DATABASE_NAME, user=DEFAULT_POSTGRES_USER, password=DEFAULT_POSTGRES_PASSWORD, **kwds, ): info(f"Postgres database stored at: {database_location}") pgdata_path = os.path.join(database_location, "pgdata") pgrun_path = os.path.join(database_location, "pgrun")

if not os.path.exists(pgdata_path):
    os.makedirs(pgdata_path)
if not os.path.exists(pgrun_path):
    os.makedirs(pgrun_path)

version_file = os.path.join(pgdata_path, "PG_VERSION")
if not os.path.exists(version_file):
    # Run container for a short while to initialize the database
    # The database will not be initilizaed during a
    # "singularity instance start" command
    init_database_command = [
        singularity_path,
        "run",
        "-B",
        f"{pgdata_path}:/var/lib/postgresql/data",
        "-B",
        f"{pgrun_path}:/var/run/postgresql",
        "-e",
        "-C",
        "--env",
        f"POSTGRES_DB={databasename}",
        "--env",
        f"POSTGRES_USER={user}",
        "--env",
        f"POSTGRES_PASSWORD={password}",
        "--env",
        "POSTGRES_INITDB_ARGS='--encoding=UTF-8'",
        f"docker://{DEFAULT_DOCKERIMAGE}",
    ]
    info(f"Initilizing postgres database in folder: {pgdata_path}")
    process = subprocess.Popen(init_database_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    # Give the container time to initialize the database
    for _ in range(10):
        if os.path.exists(version_file):
            break
        time.sleep(5)
        info("Waiting for the postgres database to initialize.")
    else:
        raise Exception("Failed to initialize the postgres database.")
    time.sleep(10)
    process.terminate()

# Start the singularity instance, assumes the database is
# already initialized since the entrypoint will not be run
# when starting a instance of the container.
run_command = [
    singularity_path,
    "instance",
    "start",
    "-B",
    f"{pgdata_path}:/var/lib/postgresql/data",
    "-B",
    f"{pgrun_path}:/var/run/postgresql",
    "-e",
    "-C",
    f"docker://{DEFAULT_DOCKERIMAGE}",
    container_instance_name,
]
info(f"Starting singularity instance named: {container_instance_name}")
execute(run_command)

def stop_postgress_singularity(container_instance_name, **kwds): info(f"Stopping singularity instance named: {container_instance_name}") execute(["singularity", "instance", "stop", container_instance_name])

[docs] class SingularityPostgresDatabaseSource(ExecutesPostgresSqlMixin, DatabaseSource): """ Postgres database running inside a Singularity container. Should be used with "with" statements to automatically start and stop the container. """

def __init__(self, **kwds):
    """Construct a postgres database source from planemo configuration."""

    self.singularity_path = "singularity"
    self.database_user = DEFAULT_POSTGRES_USER
    self.database_password = DEFAULT_POSTGRES_PASSWORD
    self.database_host = "localhost"  # TODO: Make docker host
    self.database_port = DEFAULT_POSTGRES_PORT_EXPOSE
    if "postgres_storage_location" in kwds and kwds["postgres_storage_location"] is not None:
        self.database_location = kwds["postgres_storage_location"]
    else:
        self.database_location = os.path.join(mkdtemp(suffix="_planemo_postgres_db"))
    self.container_instance_name = f"{DEFAULT_CONTAINER_NAME}-{int(time.time() * 1000000)}"
    self._kwds = kwds

def __enter__(self):
    start_postgres_singularity(
        singularity_path=self.singularity_path,
        database_location=self.database_location,
        user=self.database_user,
        password=self.database_password,
        container_instance_name=self.container_instance_name,
        **self._kwds,
    )

def __exit__(self, exc_type, exc_value, traceback):
    stop_postgress_singularity(self.container_instance_name)

[docs] def sqlalchemy_url(self, identifier): """Return URL or form postgresql://username:password@localhost/mydatabase.""" return "postgresql://%s:%s@%s:%d/%s" % ( self.database_user, self.database_password, self.database_host, self.database_port, identifier, )

all = ("SingularityPostgresDatabaseSource",)