Skip to content

Commit

Permalink
manage_extensions queues restart w/ migrate.py to install extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
dale-wahl committed Feb 18, 2025
1 parent a4b1a7d commit 8750a3b
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 9 deletions.
42 changes: 34 additions & 8 deletions backend/workers/manage_extension.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Manage a 4CAT extension
"""
import datetime
import subprocess
import requests
import logging
Expand Down Expand Up @@ -65,12 +66,31 @@ def work(self):
self.extension_log = logger

if task == "install":
self.install_extension(extension_reference)
success = self.install_extension(extension_reference)
if success:
# Add job to restart 4CAT; include upgrade to ensure migrate.py runs which will install any extension updates and new python packages
lock_file = config.get("PATH_CONFIG").joinpath("restart.lock")
if lock_file.exists():
# restart already in progress
self.extension_log.info("4CAT restart already in progress. Upgrade will be applied on next restart.")
else:
self.extension_log.info("Adding job to restart 4CAT and apply extension upgrade.")
# ensure lockfile exists - will be written to later by worker
lock_file.touch()
# this log file is used to keep track of the progress, and will also
# be viewable in the web interface
restart_log_file = config.get("PATH_CONFIG").joinpath("restart.log")
with restart_log_file.open("w") as outfile:
outfile.write(
f"Upgrade initiated at server timestamp {datetime.datetime.now().strftime('%c')}\n")
outfile.write(f"Telling 4CAT to upgrade via job queue...\n")

# add job to restart 4CAT
self.queue.add_job("restart-4cat", {}, "upgrade")

elif task == "uninstall":
self.uninstall_extension(extension_reference)

# TODO: 4CAT should restart here to load the new extension and run any installations or update the module loader on uninstall

self.job.finish()

def uninstall_extension(self, extension_name):
Expand Down Expand Up @@ -130,29 +150,33 @@ def install_extension(self, repository_reference, overwrite=False):
path.
:param bool overwrite: Overwrite extension if one exists? Set to
`true` to upgrade existing extensions (for example)
:return bool: `True` if installation was successful, `False` otherwise
"""
if self.job.details.get("source") == "remote":
extension_folder, extension_name = self.clone_from_url(repository_reference)
else:
extension_folder, extension_name = self.unpack_from_zip(repository_reference)

if not extension_name:
return self.extension_log.error("The 4CAT extension could not be installed.")
self.extension_log.error("The 4CAT extension could not be installed.")
return False

# read manifest file
manifest_file = extension_folder.joinpath("metadata.json")
if not manifest_file.exists():
shutil.rmtree(extension_folder)
return self.extension_log.error(f"Manifest file of newly cloned 4CAT extension {repository_reference} does "
self.extension_log.error(f"Manifest file of newly cloned 4CAT extension {repository_reference} does "
f"not exist. Cannot install as a 4CAT extension.")
return False
else:
try:
with manifest_file.open() as infile:
manifest_data = json.load(infile)
except json.JSONDecodeError:
shutil.rmtree(extension_folder)
return self.extension_log.error(f"Manifest file of newly cloned 4CAT extension {repository_reference} "
self.extension_log.error(f"Manifest file of newly cloned 4CAT extension {repository_reference} "
f"could not be parsed. Cannot install as a 4CAT extension.")
return False

canonical_name = manifest_data.get("name", extension_name)
canonical_id = manifest_data.get("id", extension_name)
Expand All @@ -171,19 +195,21 @@ def install_extension(self, repository_reference, overwrite=False):
except json.JSONDecodeError:
pass

shutil.rmtree(canonical_folder)
if overwrite:
self.extension_log.warning(f"Uninstalling existing 4CAT extension {existing_name} (version "
f"{existing_version}.")
shutil.rmtree(canonical_folder)
else:
return self.extension_log.error(f"An extension with ID {canonical_id} is already installed "
self.extension_log.error(f"An extension with ID {canonical_id} is already installed "
f"({extension_name}, version {existing_version}). Cannot install "
f"another one with the same ID - uninstall it first.")
return False

extension_folder.rename(canonical_folder)
version = f"version {manifest_data.get('version', 'unknown')}"
self.extension_log.info(f"Finished installing extension {canonical_name} (version {version}) with ID "
f"{canonical_id}.")
return True


def unpack_from_zip(self, archive_path):
Expand Down
7 changes: 6 additions & 1 deletion backend/workers/restart_4cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,14 @@ def work(self):
log_stream_restart.close() # close, because front-end will be writing to it
upgrade_ok = False
upgrade_timeout = False
upgrade_error_message = False
try:
upgrade_url = api_host + "/admin/trigger-frontend-upgrade/"
with lock_file.open() as infile:
frontend_upgrade = requests.post(upgrade_url, data={"token": infile.read()}, timeout=(10 * 60))
upgrade_ok = frontend_upgrade.json()["status"] == "OK"
upgrade_error_message = frontend_upgrade.json().get("message")
self.log.error("Front-end upgrade failed: %s" % upgrade_error_message)
except requests.RequestException:
pass
except TimeoutError:
Expand All @@ -176,8 +179,10 @@ def work(self):
if not upgrade_ok:
if upgrade_timeout:
log_stream_restart.write("Upgrade timed out.")
log_stream_restart.write("Error upgrading front-end container. You may need to upgrade and restart"
log_stream_restart.write("Error upgrading front-end container. You may need to upgrade and restart "
"containers manually.\n")
if upgrade_error_message:
log_stream_restart.write(f"Error message: {upgrade_error_message}\n")
lock_file.unlink()
return self.job.finish()

Expand Down

0 comments on commit 8750a3b

Please sign in to comment.