#!/usr/bin/env python
#
# This script contains the actual logic for upgrading from an old
# version of Zulip to the new version.  upgrade-zulip-stage-2 is
# always run from the new version of Zulip, so any bug fixes take
# effect on the very next upgrade.
from __future__ import print_function
import argparse
import subprocess
import os
import sys
import logging

os.environ["PYTHONUNBUFFERED"] = "y"

sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from scripts.lib.zulip_tools import DEPLOYMENTS_DIR, FAIL, WARNING, ENDC, su_to_zulip
from scripts.lib.node_cache import setup_node_modules

logging.basicConfig(format="%(asctime)s upgrade-zulip-stage-2: %(message)s",
                    level=logging.INFO)

if os.getuid() != 0:
    logging.error("Must be run as root.")
    sys.exit(1)

parser = argparse.ArgumentParser()
parser.add_argument("deploy_path", metavar="deploy_path",
                    help="Path to deployment directory")
parser.add_argument("--skip-puppet", dest="skip_puppet", action='store_true',
                    help="Skip doing puppet/apt upgrades.")
parser.add_argument("--skip-migrations", dest="skip_migrations", action='store_true',
                    help="Skip doing migrations.")
parser.add_argument("--from-git", dest="from_git", action='store_true',
                    help="Upgrading from git, so run update-prod-static.")
args = parser.parse_args()

deploy_path = args.deploy_path
os.chdir(deploy_path)

# Handle issues around upstart on Ubuntu Xenial
subprocess.check_call(["./scripts/lib/check-upstart"])

if not args.skip_puppet:
    logging.info("Upgrading system packages...")
    subprocess.check_call(["apt-get", "update"])
    subprocess.check_call(["apt-get", "-y", "upgrade"])

if not os.path.exists((os.path.join(deploy_path, "zproject/prod_settings"))):
    subprocess.check_call(["ln", "-nsf", "/etc/zulip/settings.py",
                           os.path.join(deploy_path, "zproject/prod_settings.py")])

# delete local_settings.py symlink if it exists, as it is now prod_settings.py
if os.path.islink((os.path.join(deploy_path, "zproject/local_settings.py"))):
    subprocess.check_call(["rm", os.path.join(deploy_path, "zproject/local_settings.py")])

# Now we should have an environment setup where we can run our tools;
# first, creating the production venv.
subprocess.check_call([os.path.join(deploy_path, "scripts", "lib", "create-production-venv"),
                       deploy_path])

# Make sure the right version of node is installed
subprocess.check_call([os.path.join(deploy_path, "scripts", "lib", "install-node"),
                       deploy_path])

# And then, building/installing the static assets.
if args.from_git:
    # Note: The fact that this is before we apply puppet changes means
    # that we don't support adding new puppet dependencies of
    # update-prod-static with the git upgrade process.  But it'll fail
    # safely; this seems like a worthwhile tradeoff to minimize downtime.
    logging.info("Building static assets...")
    subprocess.check_call(["./tools/update-prod-static", "--prev-deploy",
                           os.path.join(DEPLOYMENTS_DIR, 'current')],
                          preexec_fn=su_to_zulip)
else:
    # Since this doesn't do any actual work, it's likely safe to have
    # this run before we apply puppet changes (saving a bit of downtime).
    logging.info("Installing static assets...")
    subprocess.check_call(["cp", "-rT", os.path.join(deploy_path, 'prod-static/serve'),
                           '/home/zulip/prod-static'], preexec_fn=su_to_zulip)
    # Sets up npm cache
    setup_node_modules(npm_args=['--production'], copy_modules=True)

# Our next optimization is to check whether any migrations are needed
# before we start the critical section of the restart.  This saves
# about 1s of downtime in a no-op upgrade.
migrations_needed = False
if not args.skip_migrations:
    logging.info("Checking for needed migrations")
    migrations_output = subprocess.check_output(["./manage.py", "showmigrations"],
                                                preexec_fn=su_to_zulip)
    for ln in migrations_output.split(b"\n"):
        if ln.strip().startswith("[ ]"):
            migrations_needed = True

# Now we start shutting down services; we start with
# process-fts-updates, which isn't on the critical serving path.
if os.path.exists("/etc/supervisor/conf.d/zulip_db.conf"):
    subprocess.check_call(["supervisorctl", "stop", "process-fts-updates"], preexec_fn=su_to_zulip)

if not args.skip_puppet or migrations_needed:
    # By default, we shut down the service to apply migrations and
    # puppet changes, to minimize risk of issues due to inconsistent
    # state.
    logging.info("Stopping Zulip...")
    subprocess.check_call(["supervisorctl", "stop", "zulip-workers:*", "zulip-django",
                           "zulip-tornado", "zulip-senders:*"], preexec_fn=su_to_zulip)

if not args.skip_puppet:
    logging.info("Applying puppet changes...")
    subprocess.check_call(["./scripts/zulip-puppet-apply", "--force"])
    subprocess.check_call(["apt-get", "upgrade"])

if migrations_needed:
    logging.info("Applying database migrations...")
    subprocess.check_call(["./manage.py", "migrate", "--noinput"], preexec_fn=su_to_zulip)

logging.info("Restarting Zulip...")
subprocess.check_output(["./scripts/restart-server"], preexec_fn=su_to_zulip)

if os.path.exists("/etc/supervisor/conf.d/zulip_db.conf"):
    subprocess.check_call(["supervisorctl", "start", "process-fts-updates"], preexec_fn=su_to_zulip)

logging.info("Upgrade complete!")

subprocess.check_call(["./scripts/purge-old-deployments"])
