upgrade-zulip: Provide directories to run hooks before/after upgrade.

These hooks are run immediately around the critical section of the
upgrade.  If the upgrade fails for preparatory reasons, the pre-deploy
hook may not be run; if it fails during the upgrade, the post-deploy
hook will not be run.  Hooks are called from the CWD of the new
deploy, with arguments of the old version and the new version.  If
they exit with non-0 exit code, the deploy aborts.
This commit is contained in:
Alex Vandiver 2023-01-30 20:30:34 +00:00 committed by Tim Abbott
parent 74067f071f
commit 840884ec89
5 changed files with 59 additions and 1 deletions

View File

@ -85,6 +85,12 @@ running on the server; though installing alongside other applications
is not recommended, we do have [some notes on the is not recommended, we do have [some notes on the
process](install-existing-server.md). process](install-existing-server.md).
## Deployment hooks
Zulip's upgrades have a hook system which allows for arbitrary
user-configured actions to run before and after an upgrade; see the
[upgrading documentation](upgrade.md#deployment-hooks) for details.
## Running Zulip's service dependencies on different machines ## Running Zulip's service dependencies on different machines
Zulip has full support for each top-level service living on its own Zulip has full support for each top-level service living on its own

View File

@ -211,6 +211,18 @@ earlier previous version by running
`restart-server` script stops any running Zulip server, and starts `restart-server` script stops any running Zulip server, and starts
the version corresponding to the `restart-server` path you call. the version corresponding to the `restart-server` path you call.
## Deployment hooks
Zulip's upgrades have a hook system which allows for arbitrary
user-configured actions to run before and after an upgrade.
Files in the `/etc/zulip/pre-deploy.d` and `/etc/zulip/post-deploy.d`
directories are inspected for files ending with `.hook`, just before
and after the critical period when the server is restarted. Each file
is called, sorted in alphabetical order, from the working directory of
the new version, with arguments of the old and new Zulip versions. If
they exit with non-0 exit code, the upgrade will abort.
## Preserving local changes to service configuration files ## Preserving local changes to service configuration files
:::{warning} :::{warning}

View File

@ -5,6 +5,7 @@ class zulip::app_frontend_base {
include zulip::sasl_modules include zulip::sasl_modules
include zulip::supervisor include zulip::supervisor
include zulip::tornado_sharding include zulip::tornado_sharding
include zulip::hooks::base
if $::os['family'] == 'Debian' { if $::os['family'] == 'Debian' {
# Upgrade and other tooling wants to be able to get a database # Upgrade and other tooling wants to be able to get a database

View File

@ -0,0 +1,15 @@
# @summary Install sentry-cli binary and pre/post deploy hooks
#
class zulip::hooks::base {
file { [
'/etc/zulip/hooks',
'/etc/zulip/hooks/pre-deploy.d',
'/etc/zulip/hooks/post-deploy.d',
]:
ensure => directory,
owner => 'zulip',
group => 'zulip',
mode => '0755',
tag => ['hooks'],
}
}

View File

@ -14,7 +14,7 @@ import shutil
import subprocess import subprocess
import sys import sys
import time import time
from typing import NoReturn, Optional from typing import Literal, NoReturn, Optional
os.environ["PYTHONUNBUFFERED"] = "y" os.environ["PYTHONUNBUFFERED"] = "y"
@ -407,6 +407,13 @@ if not migrations_needed:
["./manage.py", "fill_memcached_caches", "--skip-checks"], preexec_fn=su_to_zulip ["./manage.py", "fill_memcached_caches", "--skip-checks"], preexec_fn=su_to_zulip
) )
# Install hooks before we check for puppet changes, so we skip an
# unnecessary stop/start cycle if hook changes are the only ones.
logging.info("Installing hooks")
subprocess.check_call(
["./scripts/zulip-puppet-apply", "--tags", "hooks", "--force"],
)
# If we are planning on running puppet, we can pre-run it in --noop # If we are planning on running puppet, we can pre-run it in --noop
# mode and see if it will actually make any changes; if not, we can # mode and see if it will actually make any changes; if not, we can
# skip it during the upgrade. We omit this check if the server is # skip it during the upgrade. We omit this check if the server is
@ -432,6 +439,19 @@ if not args.skip_puppet and IS_SERVER_UP:
sys.exit(1) sys.exit(1)
def run_hooks(kind: Literal["pre-deploy", "post-deploy"]) -> None:
# Updates to the above literal to add a new hook type should
# adjust puppet's `app_frontend_base.pp` as well.
path = f"/etc/zulip/hooks/{kind}.d"
if not os.path.exists(path):
return
for script_name in sorted(f for f in os.listdir(path) if f.endswith(".hook")):
subprocess.check_call(
[os.path.join(path, script_name), old_version, NEW_ZULIP_VERSION],
cwd=deploy_path,
)
if args.skip_restart: if args.skip_restart:
logging.info("Successfully configured in %s!", deploy_path) logging.info("Successfully configured in %s!", deploy_path)
else: else:
@ -440,6 +460,8 @@ else:
# steps that happen between here and the "Restarting Zulip" line # steps that happen between here and the "Restarting Zulip" line
# below. # below.
run_hooks("pre-deploy")
if rabbitmq_dist_listen: if rabbitmq_dist_listen:
shutdown_server() shutdown_server()
logging.info("Shutting down rabbitmq to adjust its ports...") logging.info("Shutting down rabbitmq to adjust its ports...")
@ -491,6 +513,8 @@ else:
logging.info("Upgrade complete!") logging.info("Upgrade complete!")
run_hooks("post-deploy")
if args.audit_fts_indexes: if args.audit_fts_indexes:
logging.info("Correcting full-text search indexes for updated dictionary files") logging.info("Correcting full-text search indexes for updated dictionary files")
logging.info("This may take a while but the server should work while it runs.") logging.info("This may take a while but the server should work while it runs.")