Removing a container can take some time to be performed. Listing containers when a container is currently being removed may cause errors. Because listing container and filtering them by name requires to fetch data from the container that is currently being removed.
240 lines
7.2 KiB
Python
240 lines
7.2 KiB
Python
import os
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import docker
|
|
from loguru import logger
|
|
|
|
from odoo_openupgrade_wizard.tools.tools_docker import (
|
|
exec_container,
|
|
get_docker_client,
|
|
run_container,
|
|
)
|
|
from odoo_openupgrade_wizard.tools.tools_system import get_script_folder
|
|
|
|
|
|
def get_postgres_container(ctx):
|
|
client = get_docker_client()
|
|
image_name = ctx.obj["config"]["postgres_image_name"]
|
|
container_name = ctx.obj["config"]["postgres_container_name"]
|
|
volume_name = ctx.obj["config"]["postgres_volume_name"]
|
|
|
|
# Check if container exists
|
|
containers = client.containers.list(
|
|
all=True,
|
|
filters={"name": container_name},
|
|
ignore_removed=True,
|
|
)
|
|
if containers:
|
|
container = containers[0]
|
|
if container.status == "exited":
|
|
logger.warning(
|
|
"Found container %s in a exited status. Removing it..."
|
|
% container_name
|
|
)
|
|
container.remove()
|
|
else:
|
|
return container
|
|
|
|
# Check if volume exists
|
|
try:
|
|
client.volumes.get(volume_name)
|
|
logger.debug("Recovering existing postgres volume: %s" % volume_name)
|
|
except docker.errors.NotFound:
|
|
logger.info("Creating Postgres volume: %s" % volume_name)
|
|
client.volumes.create(volume_name)
|
|
|
|
command = None
|
|
postgres_extra_settings = ctx.obj["config"].get("postgres_extra_settings")
|
|
if postgres_extra_settings:
|
|
command = "postgres"
|
|
for key, value in postgres_extra_settings.items():
|
|
command += f" -c {key}={value}"
|
|
|
|
logger.info("Launching Postgres Container. (Image %s)" % image_name)
|
|
container = run_container(
|
|
image_name,
|
|
container_name,
|
|
command=command,
|
|
environments={
|
|
"POSTGRES_USER": "odoo",
|
|
"POSTGRES_PASSWORD": "odoo",
|
|
"POSTGRES_DB": "postgres",
|
|
"PGDATA": "/var/lib/postgresql/data/pgdata",
|
|
},
|
|
volumes={
|
|
# Data volume
|
|
volume_name: "/var/lib/postgresql/data/pgdata/",
|
|
# main folder path (to pass files)
|
|
ctx.obj["env_folder_path"].absolute(): "/env/",
|
|
},
|
|
detach=True,
|
|
)
|
|
# TODO, improve me.
|
|
# Postgres container doesn't seems available immediately.
|
|
# check in odoo container, i remember that there is
|
|
# some script to do the job
|
|
time.sleep(5)
|
|
return container
|
|
|
|
|
|
def execute_sql_file(ctx, database, sql_file):
|
|
container = get_postgres_container(ctx)
|
|
|
|
# Recreate relative path to make posible to
|
|
# call psql in the container
|
|
if str(ctx.obj["env_folder_path"]) not in str(sql_file):
|
|
raise Exception(
|
|
"The SQL file %s is not in the"
|
|
" main folder %s available"
|
|
" in the postgres container."
|
|
% (sql_file, ctx.obj["env_folder_path"])
|
|
)
|
|
relative_path = Path(
|
|
str(sql_file).replace(str(ctx.obj["env_folder_path"]), ".")
|
|
)
|
|
|
|
container_path = Path("/env/") / relative_path
|
|
command = (
|
|
"psql --username=odoo --dbname={database} --file {file_path}"
|
|
).format(database=database, file_path=container_path)
|
|
logger.info(
|
|
"Executing the script '%s' in postgres container"
|
|
" on database %s" % (relative_path, database)
|
|
)
|
|
exec_container(container, command)
|
|
|
|
|
|
def execute_sql_request(ctx, request, database="postgres"):
|
|
psql_args = ("--tuples-only",)
|
|
output = execute_psql_command(ctx, request, database, psql_args)
|
|
lines = output.split("\n")
|
|
result = []
|
|
for line in lines:
|
|
if not line:
|
|
continue
|
|
result.append([x.strip() for x in line.split("|")])
|
|
return result
|
|
|
|
|
|
def execute_psql_command(
|
|
ctx, request: str, database: str = None, psql_args: tuple = ()
|
|
):
|
|
"""Execute psql request in postgres container with psql_args on database"""
|
|
container = get_postgres_container(ctx)
|
|
command = (
|
|
"psql"
|
|
" --username=odoo"
|
|
f" --dbname={database or 'postgres'}"
|
|
f' --command "{request}"'
|
|
f" {' '.join(psql_args)}"
|
|
)
|
|
logger.debug(
|
|
"Executing the following command in postgres container\n"
|
|
"%s" % (command)
|
|
)
|
|
docker_result = exec_container(container, command)
|
|
return docker_result.output.decode("utf-8")
|
|
|
|
|
|
def ensure_database(ctx, database: str, state="present"):
|
|
"""
|
|
- Connect to postgres container.
|
|
- Check if the database exist.
|
|
- if doesn't exists and state == 'present', create it.
|
|
- if exists and state == 'absent', drop it.
|
|
"""
|
|
request = "select datname FROM pg_database WHERE datistemplate = false;"
|
|
|
|
result = execute_sql_request(ctx, request)
|
|
|
|
if state == "present":
|
|
if [database] in result:
|
|
return
|
|
|
|
logger.info("Create database '%s' ..." % database)
|
|
request = "CREATE DATABASE {database} owner odoo;".format(
|
|
database=database
|
|
)
|
|
execute_sql_request(ctx, request)
|
|
else:
|
|
if [database] not in result:
|
|
return
|
|
|
|
logger.info("Drop database '%s' ..." % database)
|
|
request = "DROP DATABASE {database};".format(database=database)
|
|
execute_sql_request(ctx, request)
|
|
|
|
|
|
def execute_sql_files_pre_migration(
|
|
ctx, database: str, migration_step: dict, sql_files: list = []
|
|
):
|
|
ensure_database(ctx, database, state="present")
|
|
if not sql_files:
|
|
script_folder = get_script_folder(ctx, migration_step)
|
|
sql_files = [
|
|
script_folder / Path(f)
|
|
for f in os.listdir(script_folder)
|
|
if os.path.isfile(os.path.join(script_folder, f))
|
|
and f[-4:] == ".sql"
|
|
]
|
|
sql_files = sorted(sql_files)
|
|
|
|
for sql_file in sql_files:
|
|
execute_sql_file(ctx, database, sql_file)
|
|
|
|
|
|
def chown_to_local_user(ctx, filepath: os.PathLike):
|
|
"""Chown a filepath in the postgres container to the local user"""
|
|
container = get_postgres_container(ctx)
|
|
user_uid = os.getuid()
|
|
command = "chown -R {uid}:{uid} {filepath}".format(
|
|
uid=user_uid, filepath=filepath
|
|
)
|
|
logger.debug(
|
|
"Executing the following command in postgres container: %s"
|
|
% (command,)
|
|
)
|
|
chown_result = exec_container(container, command)
|
|
return chown_result.output.decode("utf8")
|
|
|
|
|
|
def execute_pg_dump(
|
|
ctx,
|
|
database: str,
|
|
dumpformat: str,
|
|
filename: str,
|
|
pg_dump_args="--no-owner",
|
|
):
|
|
"""Execute pg_dump command on the postgres container and dump the
|
|
result to dumpfile.
|
|
"""
|
|
if pg_dump_args and not isinstance(pg_dump_args, str):
|
|
pg_dump_args = " ".join(pg_dump_args)
|
|
container = get_postgres_container(ctx)
|
|
# Generate path for the output file
|
|
filepath = Path("/env") / Path(filename)
|
|
# Generate pg_dump command
|
|
command = (
|
|
"pg_dump"
|
|
" --username=odoo"
|
|
" --format {dumpformat}"
|
|
" --file {filepath}"
|
|
" {pg_dump_args}"
|
|
" {database}"
|
|
).format(
|
|
dumpformat=dumpformat,
|
|
filepath=filepath,
|
|
database=database,
|
|
pg_dump_args=pg_dump_args,
|
|
)
|
|
logger.debug(
|
|
"Executing the following command in postgres container: %s"
|
|
% (command,)
|
|
)
|
|
pg_dump_result = exec_container(container, command)
|
|
|
|
chown_to_local_user(ctx, filepath)
|
|
|
|
return pg_dump_result.output.decode("utf8")
|