From 1f8920ecb9cf070e3f3071a54e076086b0cd74fc Mon Sep 17 00:00:00 2001 From: Rudolf Cardinal Date: Tue, 16 Apr 2024 13:01:33 +0100 Subject: [PATCH] Suggestion: restore environment variable if found, and new exec_crate_command.sh (in contrast to run_crate_command.sh) --- installer/enter_crate_container.sh | 9 +++++-- installer/exec_crate_command.sh | 41 ++++++++++++++++++++++++++++ installer/installer.py | 37 ++++++++++++++++--------- installer/restore_crate_envvars.sh | 43 ++++++++++++++++++++++++++++++ installer/run_crate_command.sh | 11 +++++--- installer/start_crate.sh | 9 +++++-- installer/stop_crate.sh | 9 +++++-- 7 files changed, 137 insertions(+), 22 deletions(-) create mode 100755 installer/exec_crate_command.sh create mode 100644 installer/restore_crate_envvars.sh diff --git a/installer/enter_crate_container.sh b/installer/enter_crate_container.sh index d6d7a90b8..3c57f5916 100755 --- a/installer/enter_crate_container.sh +++ b/installer/enter_crate_container.sh @@ -26,11 +26,16 @@ # Run a bash shell in the CRATE container -set -euxo pipefail +set -euo pipefail +# Activate Python virtual environment CRATE_INSTALLER_VENV=${HOME}/.virtualenvs/crate-installer source "${CRATE_INSTALLER_VENV}/bin/activate" -INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" +# Restore user's environment variables, if found +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +source "${SCRIPT_DIR}/restore_crate_envvars.sh" +# Run Python installer script with a command +INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" python "${INSTALLER_HOME}/installer.py" shell "$@" diff --git a/installer/exec_crate_command.sh b/installer/exec_crate_command.sh new file mode 100755 index 000000000..b6f8ce56d --- /dev/null +++ b/installer/exec_crate_command.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# installer/exec_crate_command.sh + +# ============================================================================== +# +# Copyright (C) 2015, University of Cambridge, Department of Psychiatry. +# Created by Rudolf Cardinal (rnc1001@cam.ac.uk). +# +# This file is part of CRATE. +# +# CRATE is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# CRATE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with CRATE. If not, see . +# +# ============================================================================== + +# Runs a command in the CRATE container + +set -euo pipefail + +# Activate Python virtual environment +CRATE_INSTALLER_VENV=${HOME}/.virtualenvs/crate-installer +source "${CRATE_INSTALLER_VENV}/bin/activate" + +# Restore user's environment variables, if found +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +source "${SCRIPT_DIR}/restore_crate_envvars.sh" + +# Run Python installer script with a command +INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" +python "${INSTALLER_HOME}/installer.py" exec "$*" diff --git a/installer/installer.py b/installer/installer.py index ab2c3e801..a62594dc5 100755 --- a/installer/installer.py +++ b/installer/installer.py @@ -254,6 +254,8 @@ class InstallerEnvVar(EnvVar): # ============================================================================= # Ports # ============================================================================= + + class Ports: # In numeric order MYSQL = "3306" @@ -268,6 +270,8 @@ class Ports: # ============================================================================= # Database Engines # ============================================================================= + + class DatabaseEngine( collections.namedtuple( "DatabaseEngine", ["description", "sqlalchemy", "django"] @@ -326,6 +330,8 @@ def validate(self, document: Document) -> None: # ============================================================================= # Colours # ============================================================================= + + class Colours: # https://en.wikipedia.org/wiki/Solarized # Background tones (dark theme) @@ -538,7 +544,10 @@ def run_crate_command( ) -> Union[str, Container, Iterable[Tuple[str, bytes]]]: # Run a command in a new instance of the crate_workers container. # This goes through docker-entrypoint.sh so no need to source the - # virtualenv or call /bin/bash + # virtualenv or call /bin/bash. + # "Run" here means "without a terminal". + if not crate_command: + sys.exit("Error: no command specified") os.chdir(HostPath.DOCKERFILES_DIR) return self.docker.compose.run( DockerComposeServices.CRATE_WORKERS, @@ -550,9 +559,10 @@ def run_crate_command( def exec_crate_command( self, crate_command: str, as_root: bool = False ) -> None: - # Run a command in the existing instance of the crate_server - # container. This does not go through entrypoint.sh so we have to + # Execute a command in the existing instance of the crate_server + # container. This does not go through entrypoint.sh, so we have to # source the virtualenv and call /bin/bash + # "Execute" here means "with a terminal". venv_command = f'""source /crate/venv/bin/activate; {crate_command}""' user = "root" if as_root else None @@ -1789,16 +1799,14 @@ def get_installer_class() -> Type[Installer]: return MacOsInstaller if sys_info.system == "Windows": - print( + sys.exit( "The installer cannot be run under native Windows. Please " "install Windows Subsystem for Linux 2 (WSL2) and run the " "installer from there. Alternatively follow the instructions " "to install CRATE manually." ) - sys.exit(EXIT_FAILURE) - print(f"Sorry, the installer can't be run under {sys_info.system}.") - sys.exit(EXIT_FAILURE) + sys.exit(f"Sorry, the installer can't be run under {sys_info.system}.") # ============================================================================= @@ -1832,28 +1840,31 @@ def main() -> None: subparsers.required = True subparsers.add_parser( - Command.INSTALL, help="Install CRATE into a Docker Compose environment" + Command.INSTALL, + help="Install CRATE into a Docker Compose environment.", ) subparsers.add_parser( - Command.START, help="Start the Docker Compose application" + Command.START, help="Start the Docker Compose application." ) subparsers.add_parser( - Command.STOP, help="Stop the Docker Compose application" + Command.STOP, help="Stop the Docker Compose application." ) run_crate_command = subparsers.add_parser( Command.RUN_COMMAND, help=f"Run a command within the CRATE Docker environment, in the " - f"{DockerComposeServices.CRATE_WORKERS!r} service/container", + f"{DockerComposeServices.CRATE_WORKERS!r} service/container (without " + f"a terminal, so output will not be visible).", ) run_crate_command.add_argument("crate_command", type=str) exec_crate_command = subparsers.add_parser( Command.EXEC_COMMAND, help=f"Execute a command within the CRATE Docker environment, in the " - f"existing {DockerComposeServices.CRATE_SERVER!r} service/container", + f"existing {DockerComposeServices.CRATE_SERVER!r} service/container " + f"(with a terminal, so output is visible).", ) exec_crate_command.add_argument("crate_command", type=str) exec_crate_command.add_argument( @@ -1867,7 +1878,7 @@ def main() -> None: Command.SHELL, help=f"Start a shell (command prompt) within a already-running CRATE " f"Docker environment, in the " - f"{DockerComposeServices.CRATE_SERVER!r} container", + f"{DockerComposeServices.CRATE_SERVER!r} container.", ) shell.add_argument( "--as_root", diff --git a/installer/restore_crate_envvars.sh b/installer/restore_crate_envvars.sh new file mode 100644 index 000000000..6544fc303 --- /dev/null +++ b/installer/restore_crate_envvars.sh @@ -0,0 +1,43 @@ +# installer/restore_crate_envvars.sh + +# ============================================================================== +# +# Copyright (C) 2015, University of Cambridge, Department of Psychiatry. +# Created by Rudolf Cardinal (rnc1001@cam.ac.uk). +# +# This file is part of CRATE. +# +# CRATE is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# CRATE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with CRATE. If not, see . +# +# ============================================================================== + +# When the installer first runs, it stores a copy of the relevant environment +# variables. To run other Docker commands, it's helpful to "source" them back, +# if found. Likewise, this file itself should be "sourced", not executed. + +# The default is $HOME/crate/set_crate_docker_host_envvars (per HostPath in +# installer.py). We allow the user to pre-override this with environment +# variables. +CRATE_DIR=${CRATE_DIR:=$HOME/crate} +CRATE_CONFIG_DIR=${CRATE_CONFIG_DIR:=$CRATE_DIR/config} +CRATE_ENVVAR_FILE=${CRATE_CONFIG_DIR}/set_crate_docker_host_envvars +# ... filename itself not configurable, and written by installer.py + +if [ -f "${CRATE_ENVVAR_FILE}" ]; then + echo "- Restoring user-supplied environment variables from: ${CRATE_ENVVAR_FILE}" + # shellcheck disable=SC1090 + source "${CRATE_ENVVAR_FILE}" +else + echo "- No previous environment variable file found at: ${CRATE_ENVVAR_FILE}" +fi diff --git a/installer/run_crate_command.sh b/installer/run_crate_command.sh index 9ab3c97ad..43bfc970c 100755 --- a/installer/run_crate_command.sh +++ b/installer/run_crate_command.sh @@ -26,11 +26,16 @@ # Runs a command in the CRATE container -set -euxo pipefail +set -euo pipefail +# Activate Python virtual environment CRATE_INSTALLER_VENV=${HOME}/.virtualenvs/crate-installer source "${CRATE_INSTALLER_VENV}/bin/activate" -INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" +# Restore user's environment variables, if found +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +source "${SCRIPT_DIR}/restore_crate_envvars.sh" -python "${INSTALLER_HOME}/installer.py" run_crate_command "$*" +# Run Python installer script with a command +INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" +python "${INSTALLER_HOME}/installer.py" run "$*" diff --git a/installer/start_crate.sh b/installer/start_crate.sh index deb1d4f51..47ca49bae 100755 --- a/installer/start_crate.sh +++ b/installer/start_crate.sh @@ -26,11 +26,16 @@ # Starts CRATE -set -euxo pipefail +set -euo pipefail +# Activate Python virtual environment CRATE_INSTALLER_VENV=${HOME}/.virtualenvs/crate-installer source "${CRATE_INSTALLER_VENV}/bin/activate" -INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" +# Restore user's environment variables, if found +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +source "${SCRIPT_DIR}/restore_crate_envvars.sh" +# Run Python installer script with a command +INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" python "${INSTALLER_HOME}/installer.py" start diff --git a/installer/stop_crate.sh b/installer/stop_crate.sh index 05575ef34..60991ea5b 100755 --- a/installer/stop_crate.sh +++ b/installer/stop_crate.sh @@ -26,11 +26,16 @@ # Stops CRATE -set -euxo pipefail +set -euo pipefail +# Activate Python virtual environment CRATE_INSTALLER_VENV=${HOME}/.virtualenvs/crate-installer source "${CRATE_INSTALLER_VENV}/bin/activate" -INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" +# Restore user's environment variables, if found +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +source "${SCRIPT_DIR}/restore_crate_envvars.sh" +# Run Python installer script with a command +INSTALLER_HOME="$( cd "$( dirname "$0" )" && pwd )" python "${INSTALLER_HOME}/installer.py" stop