py3: Switch almost all shebang lines to use `python3`.
This causes `upgrade-zulip-from-git`, as well as a no-option run of
`tools/build-release-tarball`, to produce a Zulip install running
Python 3, rather than Python 2. In particular this means that the
virtualenv we create, in which all application code runs, is Python 3.
One shebang line, on `zulip-ec2-configure-interfaces`, explicitly
keeps Python 2, and at least one external ops script, `wal-e`, also
still runs on Python 2. See discussion on the respective previous
commits that made those explicit. There may also be some other
third-party scripts we use, outside of this source tree and running
outside our virtualenv, that still run on Python 2.
2017-08-02 23:15:16 +02:00
|
|
|
#!/usr/bin/env python3
|
2020-06-11 00:54:34 +02:00
|
|
|
import configparser
|
2012-08-28 18:45:35 +02:00
|
|
|
import os
|
|
|
|
import sys
|
2021-06-13 14:59:18 +02:00
|
|
|
from typing import Dict, List, Optional
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2018-11-29 22:22:01 +01:00
|
|
|
if sys.version_info <= (3, 0):
|
|
|
|
print("Error: Zulip is a Python 3 project, and cannot be run with Python 2.")
|
|
|
|
print("Use e.g. `/path/to/manage.py` not `python /path/to/manage.py`.")
|
|
|
|
sys.exit(1)
|
2012-08-28 18:45:35 +02:00
|
|
|
|
2016-06-25 17:07:13 +02:00
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
sys.path.append(BASE_DIR)
|
2020-01-08 00:06:39 +01:00
|
|
|
from scripts.lib.setup_path import setup_path
|
|
|
|
|
|
|
|
setup_path()
|
|
|
|
|
2021-06-13 14:59:18 +02:00
|
|
|
from collections import defaultdict
|
|
|
|
|
|
|
|
from django.core.management import ManagementUtility, get_commands
|
|
|
|
from django.core.management.color import color_style
|
|
|
|
|
2018-11-19 19:50:25 +01:00
|
|
|
from scripts.lib.zulip_tools import assert_not_running_as_root
|
2016-06-25 17:07:13 +02:00
|
|
|
|
2021-06-13 14:59:18 +02:00
|
|
|
|
|
|
|
def get_filtered_commands() -> Dict[str, str]:
|
|
|
|
"""Because Zulip uses management commands in production, `manage.py
|
|
|
|
help` is a form of documentation for users. Here we exclude from
|
|
|
|
that documentation built-in commands that are not constructive for
|
|
|
|
end users or even Zulip developers to run.
|
|
|
|
|
|
|
|
Ideally, we'd do further customization to display management
|
|
|
|
commands with more organization in the help text, and also hide
|
|
|
|
development-focused management commands in production.
|
|
|
|
"""
|
|
|
|
all_commands = get_commands()
|
|
|
|
documented_commands = dict()
|
|
|
|
documented_apps = [
|
|
|
|
# "auth" removed because its commands are not applicable to Zulip.
|
|
|
|
# "contenttypes" removed because we don't use that subsystem, and
|
|
|
|
# even if we did.
|
|
|
|
"django.core",
|
|
|
|
"analytics",
|
|
|
|
# "otp_static" removed because it's a 2FA internals detail.
|
|
|
|
# "sessions" removed because it's just a cron job with a misleading
|
|
|
|
# name, since all it does is delete expired sessions.
|
|
|
|
# "social_django" removed for similar reasons to sessions.
|
|
|
|
# "staticfiles" removed because its commands are only usefully run when
|
|
|
|
# wrapped by Zulip tooling.
|
|
|
|
# "two_factor" removed because it's a 2FA internals detail.
|
|
|
|
"zerver",
|
|
|
|
"zilencer",
|
|
|
|
]
|
|
|
|
documented_command_subsets = {
|
|
|
|
"django.core": {
|
|
|
|
"dbshell",
|
|
|
|
"makemigrations",
|
|
|
|
"migrate",
|
|
|
|
"shell",
|
|
|
|
"showmigrations",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for command, app in all_commands.items():
|
|
|
|
if app not in documented_apps:
|
|
|
|
continue
|
|
|
|
if app in documented_command_subsets:
|
|
|
|
if command not in documented_command_subsets[app]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
documented_commands[command] = app
|
|
|
|
return documented_commands
|
|
|
|
|
|
|
|
|
|
|
|
class FilteredManagementUtility(ManagementUtility):
|
|
|
|
"""Replaces the main_help_text function of ManagementUtility with one
|
|
|
|
that calls our get_filtered_commands(), rather than the default
|
|
|
|
get_commands() function.
|
|
|
|
|
|
|
|
All other change are just code style differences to pass the Zulip linter.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def main_help_text(self, commands_only: bool = False) -> str:
|
|
|
|
"""Return the script's main help text, as a string."""
|
|
|
|
if commands_only:
|
|
|
|
usage = sorted(get_filtered_commands())
|
|
|
|
else:
|
|
|
|
usage = [
|
|
|
|
"",
|
|
|
|
f"Type '{self.prog_name} help <subcommand>' for help on a specific subcommand.",
|
|
|
|
"",
|
|
|
|
"Available subcommands:",
|
|
|
|
]
|
|
|
|
commands_dict = defaultdict(lambda: [])
|
|
|
|
for name, app in get_filtered_commands().items():
|
|
|
|
if app == "django.core":
|
|
|
|
app = "django"
|
|
|
|
else:
|
|
|
|
app = app.rpartition(".")[-1]
|
|
|
|
commands_dict[app].append(name)
|
|
|
|
style = color_style()
|
|
|
|
for app in sorted(commands_dict):
|
|
|
|
usage.append("")
|
|
|
|
usage.append(style.NOTICE(f"[{app}]"))
|
|
|
|
for name in sorted(commands_dict[app]):
|
|
|
|
usage.append(f" {name}")
|
|
|
|
# Output an extra note if settings are not properly configured
|
|
|
|
if self.settings_exception is not None:
|
|
|
|
usage.append(
|
|
|
|
style.NOTICE(
|
|
|
|
"Note that only Django core commands are listed "
|
|
|
|
f"as settings are not properly configured (error: {self.settings_exception})."
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return "\n".join(usage)
|
|
|
|
|
|
|
|
|
|
|
|
def execute_from_command_line(argv: Optional[List[str]] = None) -> None:
|
|
|
|
"""Run a FilteredManagementUtility."""
|
|
|
|
utility = FilteredManagementUtility(argv)
|
|
|
|
utility.execute()
|
|
|
|
|
|
|
|
|
2012-08-28 18:45:35 +02:00
|
|
|
if __name__ == "__main__":
|
2018-11-19 19:50:25 +01:00
|
|
|
assert_not_running_as_root()
|
2018-12-01 17:23:17 +01:00
|
|
|
|
|
|
|
config_file = configparser.RawConfigParser()
|
|
|
|
config_file.read("/etc/zulip/zulip.conf")
|
2021-02-12 08:20:45 +01:00
|
|
|
PRODUCTION = config_file.has_option("machine", "deploy_type")
|
|
|
|
HAS_SECRETS = os.access("/etc/zulip/zulip-secrets.conf", os.R_OK)
|
2018-12-01 17:23:17 +01:00
|
|
|
|
|
|
|
if PRODUCTION and not HAS_SECRETS:
|
2018-07-30 21:09:24 +02:00
|
|
|
# The best way to detect running manage.py as another user in
|
|
|
|
# production before importing anything that would require that
|
|
|
|
# access is to check for access to /etc/zulip/zulip.conf (in
|
|
|
|
# which case it's a production server, not a dev environment)
|
|
|
|
# and lack of access for /etc/zulip/zulip-secrets.conf (which
|
|
|
|
# should be only readable by root and zulip)
|
2021-02-12 08:19:30 +01:00
|
|
|
print(
|
|
|
|
"Error accessing Zulip secrets; manage.py in production must be run as the zulip user."
|
|
|
|
)
|
2018-07-30 21:09:24 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
2013-11-19 00:04:45 +01:00
|
|
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zproject.settings")
|
|
|
|
from django.conf import settings
|
2017-07-07 20:50:50 +02:00
|
|
|
from django.core.management.base import CommandError
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2017-08-18 06:36:37 +02:00
|
|
|
from scripts.lib.zulip_tools import log_management_command
|
2013-11-19 00:04:45 +01:00
|
|
|
|
2021-01-26 20:49:37 +01:00
|
|
|
log_management_command(sys.argv, settings.MANAGEMENT_LOG_PATH)
|
2013-05-22 21:18:45 +02:00
|
|
|
|
2017-08-18 22:23:46 +02:00
|
|
|
os.environ.setdefault("PYTHONSTARTUP", os.path.join(BASE_DIR, "scripts/lib/pythonrc.py"))
|
2013-05-22 21:18:45 +02:00
|
|
|
if "--no-traceback" not in sys.argv and len(sys.argv) > 1:
|
|
|
|
sys.argv.append("--traceback")
|
2017-07-07 20:50:50 +02:00
|
|
|
try:
|
|
|
|
execute_from_command_line(sys.argv)
|
|
|
|
except CommandError as e:
|
|
|
|
print(e, file=sys.stderr)
|
|
|
|
sys.exit(1)
|