diff --git a/bench/cli.py b/bench/cli.py index ad1c90fa..eea1acc7 100755 --- a/bench/cli.py +++ b/bench/cli.py @@ -8,6 +8,7 @@ from bench.commands import bench_command logger = logging.getLogger('bench') from_command_line = False +change_uid_msg = "You should not run this command as root" def cli(): global from_command_line @@ -48,7 +49,7 @@ def check_uid(): sys.exit(1) def cmd_requires_root(): - if len(sys.argv) > 2 and sys.argv[2] in ('production', 'sudoers', 'lets-encrypt', 'fonts', + if len(sys.argv) > 2 and sys.argv[2] in ('production', 'sudoers', 'supervisor', 'lets-encrypt', 'fonts', 'print', 'firewall', 'ssh-port', 'role', 'fail2ban', 'wildcard-ssl'): return True if len(sys.argv) >= 2 and sys.argv[1] in ('patch', 'renew-lets-encrypt', 'disable-production', @@ -72,7 +73,7 @@ def change_uid(): drop_privileges(uid_name=frappe_user, gid_name=frappe_user) os.environ['HOME'] = pwd.getpwnam(frappe_user).pw_dir else: - log('You should not run this command as root', level=3) + log(change_uid_msg, level=3) sys.exit(1) def old_frappe_cli(bench_path='.'): diff --git a/bench/commands/setup.py b/bench/commands/setup.py index 352a781b..96360bea 100755 --- a/bench/commands/setup.py +++ b/bench/commands/setup.py @@ -2,12 +2,21 @@ import os import sys -# imports - module imports -from bench.utils import exec_cmd - # imports - third party imports -from six import PY3 import click +from six import PY3 + +# imports - module imports +import bench.config.lets_encrypt +import bench.config.nginx +import bench.config.procfile +import bench.config.production_setup +import bench.config.redis +import bench.config.site_config +import bench.config.supervisor + +import bench.utils +from bench.utils import exec_cmd, run_playbook @click.group(help="Setup command group for enabling setting up a Frappe environment") @@ -18,80 +27,59 @@ def setup(): @click.command("sudoers", help="Add commands to sudoers list for execution without password") @click.argument("user") def setup_sudoers(user): - from bench.utils import setup_sudoers - setup_sudoers(user) + bench.utils.setup_sudoers(user) @click.command("nginx", help="Generate configuration files for NGINX") @click.option("--yes", help="Yes to regeneration of nginx config file", default=False, is_flag=True) def setup_nginx(yes=False): - from bench.config.nginx import make_nginx_conf - make_nginx_conf(bench_path=".", yes=yes) + bench.config.nginx.make_nginx_conf(bench_path=".", yes=yes) @click.command("reload-nginx", help="Checks NGINX config file and reloads service") def reload_nginx(): - from bench.config.production_setup import reload_nginx - reload_nginx() + bench.config.production_setup.reload_nginx() @click.command("supervisor", help="Generate configuration for supervisor") @click.option("--user", help="optional user argument") @click.option("--yes", help="Yes to regeneration of supervisor config", is_flag=True, default=False) def setup_supervisor(user=None, yes=False): - from bench.config.supervisor import generate_supervisor_config - generate_supervisor_config(bench_path=".", user=user, yes=yes) + bench.config.supervisor.generate_supervisor_config(bench_path=".", user=user, yes=yes) @click.command("redis", help="Generates configuration for Redis") def setup_redis(): - from bench.config.redis import generate_config - generate_config(".") + bench.config.redis.generate_config(".") @click.command("fonts", help="Add Frappe fonts to system") def setup_fonts(): - from bench.utils import setup_fonts - setup_fonts() + bench.utils.setup_fonts() @click.command("production", help="Setup Frappe production environment for specific user") @click.argument("user") @click.option("--yes", help="Yes to regeneration config", is_flag=True, default=False) def setup_production(user, yes=False): - from bench.config.production_setup import setup_production - # Install prereqs for production - from distutils.spawn import find_executable - if not find_executable("ansible"): - exec_cmd("sudo -H {0} -m pip install ansible".format(sys.executable)) - if not find_executable("fail2ban-client"): - exec_cmd("bench setup role fail2ban") - if not find_executable("nginx"): - exec_cmd("bench setup role nginx") - if not find_executable("supervisord"): - exec_cmd("bench setup role supervisor") - setup_production(user=user, yes=yes) + bench.config.production_setup.setup_production(user=user, yes=yes) @click.command("backups", help="Add cronjob for bench backups") def setup_backups(): - from bench.utils import setup_backups - setup_backups() + bench.utils.setup_backups() @click.command("env", help="Setup virtualenv for bench") @click.option("--python", type = str, default = "python3", help = "Path to Python Executable.") def setup_env(python="python3"): - from bench.utils import setup_env - setup_env(python=python) + bench.utils.setup_env(python=python) @click.command("firewall", help="Setup firewall for system") @click.option("--ssh_port") @click.option("--force") def setup_firewall(ssh_port=None, force=False): - from bench.utils import run_playbook - if not force: click.confirm("Setting up the firewall will block all ports except 80, 443 and {0}\nDo you want to continue?".format(ssh_port), abort=True) @@ -105,8 +93,6 @@ def setup_firewall(ssh_port=None, force=False): @click.argument("port") @click.option("--force") def set_ssh_port(port, force=False): - from bench.utils import run_playbook - if not force: click.confirm("This will change your SSH Port to {}\nDo you want to continue?".format(port), abort=True) @@ -118,8 +104,7 @@ def set_ssh_port(port, force=False): @click.option("--custom-domain") @click.option('-n', '--non-interactive', default=False, is_flag=True, help="Run command non-interactively. This flag restarts nginx and runs certbot non interactively. Shouldn't be used on 1'st attempt") def setup_letsencrypt(site, custom_domain, non_interactive): - from bench.config.lets_encrypt import setup_letsencrypt - setup_letsencrypt(site, custom_domain, bench_path=".", interactive=not non_interactive) + bench.config.lets_encrypt.setup_letsencrypt(site, custom_domain, bench_path=".", interactive=not non_interactive) @click.command("wildcard-ssl", help="Setup wildcard SSL certificate for multi-tenant bench") @@ -127,20 +112,17 @@ def setup_letsencrypt(site, custom_domain, non_interactive): @click.option("--email") @click.option("--exclude-base-domain", default=False, is_flag=True, help="SSL Certificate not applicable for base domain") def setup_wildcard_ssl(domain, email, exclude_base_domain): - from bench.config.lets_encrypt import setup_wildcard_ssl - setup_wildcard_ssl(domain, email, bench_path=".", exclude_base_domain=exclude_base_domain) + bench.config.lets_encrypt.setup_wildcard_ssl(domain, email, bench_path=".", exclude_base_domain=exclude_base_domain) @click.command("procfile", help="Generate Procfile for bench start") def setup_procfile(): - from bench.config.procfile import setup_procfile - setup_procfile(".") + bench.config.procfile.setup_procfile(".") @click.command("socketio", help="Setup node dependencies for socketio server") def setup_socketio(): - from bench.utils import setup_socketio - setup_socketio() + bench.utils.setup_socketio() @click.command("requirements", help="Setup Python and Node dependencies") @@ -213,34 +195,28 @@ def setup_config(): @click.option("--ssl-certificate-key", help="Absolute path to SSL Certificate Key") def add_domain(domain, site=None, ssl_certificate=None, ssl_certificate_key=None): """Add custom domain to site""" - from bench.config.site_config import add_domain - if not site: print("Please specify site") sys.exit(1) - add_domain(site, domain, ssl_certificate, ssl_certificate_key, bench_path=".") + bench.config.site_config.add_domain(site, domain, ssl_certificate, ssl_certificate_key, bench_path=".") @click.command("remove-domain", help="Remove custom domain from a site") @click.argument("domain") @click.option("--site", prompt=True) def remove_domain(domain, site=None): - from bench.config.site_config import remove_domain - if not site: print("Please specify site") sys.exit(1) - remove_domain(site, domain, bench_path=".") + bench.config.site_config.remove_domain(site, domain, bench_path=".") @click.command("sync-domains", help="Check if there is a change in domains. If yes, updates the domains list.") @click.option("--domain", multiple=True) @click.option("--site", prompt=True) def sync_domains(domain=None, site=None): - from bench.config.site_config import sync_domains - if not site: print("Please specify site") sys.exit(1) @@ -251,7 +227,7 @@ def sync_domains(domain=None, site=None): print("Domains should be a json list of strings or dictionaries") sys.exit(1) - changed = sync_domains(site, domains, bench_path=".") + changed = bench.config.site_config.sync_domains(site, domains, bench_path=".") # if changed, success, else failure sys.exit(0 if changed else 1) @@ -263,8 +239,6 @@ def sync_domains(domain=None, site=None): @click.option("--mysql_root_password") @click.option("--container", is_flag=True, default=False) def setup_roles(role, **kwargs): - from bench.utils import run_playbook - extra_vars = {"production": True} extra_vars.update(kwargs) @@ -279,7 +253,6 @@ def setup_roles(role, **kwargs): @click.option("--bantime", default=600, help="The counter is set to zero if no match is found within 'findtime' seconds. Default is 600 seconds") @click.option("--findtime", default=600, help="Duration (in seconds) for IP to be banned for. Negative number for 'permanent' ban. Default is 600 seconds") def setup_nginx_proxy_jail(**kwargs): - from bench.utils import run_playbook run_playbook("roles/fail2ban/tasks/configure_nginx_jail.yml", extra_vars=kwargs) diff --git a/bench/config/production_setup.py b/bench/config/production_setup.py index 8b2e7801..dff6a402 100755 --- a/bench/config/production_setup.py +++ b/bench/config/production_setup.py @@ -1,11 +1,30 @@ -from bench.utils import get_program, exec_cmd, get_cmd_output, fix_prod_setup_perms, get_bench_name, find_executable, CommandFailedError +# imports - standard imports +import os +import sys +from distutils.spawn import find_executable + +# imports - module imports +from bench.config.common_site_config import get_config +from bench.config.nginx import make_nginx_conf from bench.config.supervisor import generate_supervisor_config from bench.config.systemd import generate_systemd_config -from bench.config.nginx import make_nginx_conf -from bench.config.common_site_config import get_config -import os, subprocess +from bench.utils import CommandFailedError, exec_cmd, fix_prod_setup_perms, get_bench_name, get_cmd_output + + +def setup_production_prerequisites(): + """Installs ansible, fail2banc, NGINX and supervisor""" + if not find_executable("ansible"): + exec_cmd("sudo {0} -m pip install ansible".format(sys.executable)) + if not find_executable("fail2ban-client"): + exec_cmd("bench setup role fail2ban") + if not find_executable("nginx"): + exec_cmd("bench setup role nginx") + if not find_executable("supervisord"): + exec_cmd("bench setup role supervisor") + def setup_production(user, bench_path='.', yes=False): + setup_production_prerequisites() if get_config(bench_path).get('restart_supervisor_on_update') and get_config(bench_path).get('restart_systemd_on_update'): raise Exception("You cannot use supervisor and systemd at the same time. Modify your common_site_config accordingly." ) @@ -40,6 +59,7 @@ def setup_production(user, bench_path='.', yes=False): reload_nginx() + def disable_production(bench_path='.'): bench_name = get_bench_name(bench_path) @@ -62,28 +82,35 @@ def disable_production(bench_path='.'): reload_nginx() -def service(service, option): - if os.path.basename(get_program(['systemctl']) or '') == 'systemctl' and is_running_systemd(): - exec_cmd("sudo {service_manager} {option} {service}".format(service_manager='systemctl', option=option, service=service)) - elif os.path.basename(get_program(['service']) or '') == 'service': - exec_cmd("sudo {service_manager} {service} {option} ".format(service_manager='service', service=service, option=option)) + +def service(service_name, service_option): + if os.path.basename(find_executable('systemctl') or '') == 'systemctl' and is_running_systemd(): + systemctl_cmd = "sudo {service_manager} {service_option} {service_name}" + exec_cmd(systemctl_cmd.format(service_manager='systemctl', service_option=service_option, service_name=service_name)) + + elif os.path.basename(find_executable('service') or '') == 'service': + service_cmd = "sudo {service_manager} {service_name} {service_option}" + exec_cmd(service_cmd.format(service_manager='service', service_name=service_name, service_option=service_option)) + else: # look for 'service_manager' and 'service_manager_command' in environment service_manager = os.environ.get("BENCH_SERVICE_MANAGER") if service_manager: service_manager_command = (os.environ.get("BENCH_SERVICE_MANAGER_COMMAND") - or "{service_manager} {option} {service}").format(service_manager=service_manager, service=service, option=option) + or "{service_manager} {service_option} {service}").format(service_manager=service_manager, service=service, service_option=service_option) exec_cmd(service_manager_command) else: raise Exception('No service manager found') + def get_supervisor_confdir(): possiblities = ('/etc/supervisor/conf.d', '/etc/supervisor.d/', '/etc/supervisord/conf.d', '/etc/supervisord.d') for possiblity in possiblities: if os.path.exists(possiblity): return possiblity + def remove_default_nginx_configs(): default_nginx_configs = ['/etc/nginx/conf.d/default.conf', '/etc/nginx/sites-enabled/default'] @@ -95,6 +122,7 @@ def remove_default_nginx_configs(): def is_centos7(): return os.path.exists('/etc/redhat-release') and get_cmd_output("cat /etc/redhat-release | sed 's/Linux\ //g' | cut -d' ' -f3 | cut -d. -f1").strip() == '7' + def is_running_systemd(): with open('/proc/1/comm') as f: comm = f.read().strip() @@ -104,20 +132,21 @@ def is_running_systemd(): return True return False + def reload_supervisor(): supervisorctl = find_executable('supervisorctl') try: # first try reread/update - exec_cmd('sudo {0} reread'.format(supervisorctl)) - exec_cmd('sudo {0} update'.format(supervisorctl)) + exec_cmd('{0} reread'.format(supervisorctl)) + exec_cmd('{0} update'.format(supervisorctl)) return except CommandFailedError: pass try: # something is wrong, so try reloading - exec_cmd('sudo {0} reload'.format(supervisorctl)) + exec_cmd('{0} reload'.format(supervisorctl)) return except CommandFailedError: pass @@ -138,7 +167,7 @@ def reload_supervisor(): def reload_nginx(): try: - subprocess.check_output(['sudo', find_executable('nginx'), '-t']) + exec_cmd('sudo {0} -t'.format(find_executable('nginx'))) except: raise diff --git a/bench/config/supervisor.py b/bench/config/supervisor.py index 60db0018..43560cf0 100644 --- a/bench/config/supervisor.py +++ b/bench/config/supervisor.py @@ -1,17 +1,27 @@ -import os, getpass, click +# imports - standard imports +import getpass +import os + +# imports - module imports import bench +from bench.app import get_current_frappe_version, use_rq +from bench.utils import get_bench_name, find_executable +from bench.config.common_site_config import get_config, update_config, get_gunicorn_workers + +# imports - third party imports +import click +from six.moves import configparser + def generate_supervisor_config(bench_path, user=None, yes=False): - from bench.app import get_current_frappe_version, use_rq - from bench.utils import get_bench_name, find_executable - from bench.config.common_site_config import get_config, update_config, get_gunicorn_workers - - template = bench.env.get_template('supervisor.conf') + """Generate supervisor config for respective bench path""" if not user: user = getpass.getuser() - config = get_config(bench_path=bench_path) + update_supervisord_conf(user=user) + template = bench.env.get_template('supervisor.conf') + config = get_config(bench_path=bench_path) bench_dir = os.path.abspath(bench_path) config = template.render(**{ @@ -44,3 +54,37 @@ def generate_supervisor_config(bench_path, user=None, yes=False): update_config({'restart_supervisor_on_update': True}, bench_path=bench_path) update_config({'restart_systemd_on_update': False}, bench_path=bench_path) + +def get_supervisord_conf(): + """Returns path of supervisord config from possible paths""" + possibilities = ("supervisord.conf", "etc/supervisord.conf", "/etc/supervisord.conf", "/etc/supervisor/supervisord.conf", "/etc/supervisord.conf") + + for possibility in possibilities: + if os.path.exists(possibility): + return possibility + + +def update_supervisord_conf(user): + """From bench v5.0, we're moving to supervisor running as user""" + from bench.config.production_setup import service + + supervisord_conf = get_supervisord_conf() + section = "unix_http_server" + + if not supervisord_conf: + return + + config = configparser.ConfigParser() + config.read(supervisord_conf) + + if section not in config.sections(): + config.add_section(section) + + config.set(section, "chmod", "0760") + config.set(section, "chown", "{user}:{user}".format(user=user)) + + with open(supervisord_conf, "w") as f: + config.write(f) + + # restart supervisor to take new changes into effect + service('supervisor', 'restart') diff --git a/bench/config/templates/frappe_sudoers b/bench/config/templates/frappe_sudoers index 5394444f..567ccf06 100644 --- a/bench/config/templates/frappe_sudoers +++ b/bench/config/templates/frappe_sudoers @@ -1,20 +1,20 @@ +# This file is auto-generated by frappe/bench +# To re-generate this file, run "bench setup sudoers" + {% if service %} {{ user }} ALL = (root) {{ service }} {{ user }} ALL = (root) NOPASSWD: {{ service }} nginx * -{{ user }} ALL = (root) NOPASSWD: {{ service }} supervisord * {% endif %} + {% if systemctl %} {{ user }} ALL = (root) {{ systemctl }} {{ user }} ALL = (root) NOPASSWD: {{ systemctl }} * nginx -{{ user }} ALL = (root) NOPASSWD: {{ systemctl }} * supervisord -{% endif %} -{% if supervisorctl %} -{{ user }} ALL = (root) NOPASSWD: {{ supervisorctl }} {% endif %} + {% if nginx %} {{ user }} ALL = (root) NOPASSWD: {{ nginx }} {% endif %} + {{ user }} ALL = (root) NOPASSWD: /opt/certbot-auto -{{ user }} ALL = (root) NOPASSWD: {{ bench }} Defaults:{{ user }} !requiretty diff --git a/bench/patches/patches.txt b/bench/patches/patches.txt index 754068c5..7cb1a076 100644 --- a/bench/patches/patches.txt +++ b/bench/patches/patches.txt @@ -4,3 +4,4 @@ bench.patches.v3.redis_bind_ip bench.patches.v4.update_node bench.patches.v4.update_socketio bench.patches.v4.install_yarn #2 +bench.patches.v5.fix_user_permissions diff --git a/bench/patches/v5/__init__.py b/bench/patches/v5/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bench/patches/v5/fix_user_permissions.py b/bench/patches/v5/fix_user_permissions.py new file mode 100644 index 00000000..bdccf53c --- /dev/null +++ b/bench/patches/v5/fix_user_permissions.py @@ -0,0 +1,59 @@ +# imports - standard imports +import getpass +import os +import subprocess + +# imports - module imports +from bench.cli import change_uid_msg +from bench.config.production_setup import get_supervisor_confdir, is_centos7 +from bench.config.common_site_config import get_config +from bench.utils import exec_cmd, get_bench_name, get_cmd_output + + +def is_sudoers_set(): + """Check if bench sudoers is set""" + cmd = ["sudo", "-n", "bench"] + + with open(os.devnull, "wb") as f: + return_code_check = not subprocess.call(cmd, stdout=f) + + if return_code_check: + try: + bench_warn = change_uid_msg in get_cmd_output(cmd, _raise=False) + except subprocess.CalledProcessError: + bench_warn = False + finally: + return_code_check = return_code_check and bench_warn + + return return_code_check + + +def is_production_set(bench_path): + """Check if production is set for current bench""" + production_setup = False + bench_name = get_bench_name(bench_path) + + supervisor_conf_extn = "ini" if is_centos7() else "conf" + supervisor_conf_file_name = '{bench_name}.{extn}'.format(bench_name=bench_name, extn=supervisor_conf_extn) + supervisor_conf = os.path.join(get_supervisor_confdir(), supervisor_conf_file_name) + + if os.path.exists(supervisor_conf): + production_setup = production_setup or True + + nginx_conf = '/etc/nginx/conf.d/{bench_name}.conf'.format(bench_name=bench_name) + + if os.path.exists(nginx_conf): + production_setup = production_setup or True + + return production_setup + + +def execute(bench_path): + """This patch checks if bench sudoers is set and regenerate supervisor and sudoers files""" + user = get_config('.').get("frappe_user") or getpass.getuser() + + if is_sudoers_set(): + exec_cmd("sudo bench setup sudoers {user}".format(user=user)) + + if is_production_set(bench_path): + exec_cmd("sudo bench setup supervisor --yes --user {user}".format(user=user)) diff --git a/bench/tests/test_setup_production.py b/bench/tests/test_setup_production.py index ca5b4abc..e0a60b17 100644 --- a/bench/tests/test_setup_production.py +++ b/bench/tests/test_setup_production.py @@ -61,6 +61,9 @@ class TestSetupProduction(TestBenchBase): def assert_sudoers(self, user): sudoers_file = '/etc/sudoers.d/frappe' + service = bench.utils.which("service") + nginx = bench.utils.which("nginx") + self.assertTrue(self.file_exists(sudoers_file)) if os.environ.get("CI"): @@ -69,9 +72,8 @@ class TestSetupProduction(TestBenchBase): with open(sudoers_file, 'r') as f: sudoers = f.read() - self.assertTrue('{user} ALL = (root) NOPASSWD: /usr/sbin/service nginx *'.format(user=user) in sudoers) - self.assertTrue('{user} ALL = (root) NOPASSWD: /usr/bin/supervisorctl'.format(user=user) in sudoers) - self.assertTrue('{user} ALL = (root) NOPASSWD: /usr/sbin/nginx'.format(user=user) in sudoers) + self.assertTrue('{user} ALL = (root) NOPASSWD: {service} nginx *'.format(service=service, user=user) in sudoers) + self.assertTrue('{user} ALL = (root) NOPASSWD: {nginx}'.format(nginx=nginx, user=user) in sudoers) def assert_supervisor_config(self, bench_name, use_rq=True): @@ -126,12 +128,12 @@ class TestSetupProduction(TestBenchBase): def assert_supervisor_process(self, bench_name, use_rq=True, disable_production=False): - out = bench.utils.get_cmd_output("sudo supervisorctl status") + out = bench.utils.get_cmd_output("supervisorctl status") while "STARTING" in out: print ("Waiting for all processes to start...") time.sleep(10) - out = bench.utils.get_cmd_output("sudo supervisorctl status") + out = bench.utils.get_cmd_output("supervisorctl status") tests = [ "{bench_name}-web:{bench_name}-frappe-web[\s]+RUNNING", diff --git a/bench/utils.py b/bench/utils.py index d2c0f1a1..fc9ecbb4 100755 --- a/bench/utils.py +++ b/bench/utils.py @@ -37,6 +37,7 @@ class CommandFailedError(Exception): logger = logging.getLogger(__name__) folders_in_bench = ('apps', 'sites', 'config', 'logs', 'config/pids') +sudoers_file = '/etc/sudoers.d/frappe' class color: @@ -65,10 +66,10 @@ def log(message, level=0): 2: color.red + 'ERROR', # fail 3: color.yellow + 'WARN' # warn/suggest } - start = (levels.get(level) + ': ') if level in levels else '' - end = '\033[0m' + start_line = (levels.get(level) + ': ') if level in levels else '' + end_line = '\033[0m' - print(start + message + end) + print(start_line + message + end_line) def safe_decode(string, encoding = 'utf-8'): @@ -393,16 +394,12 @@ def setup_sudoers(user): if set_permissions: os.chmod('/etc/sudoers', 0o440) - sudoers_file = '/etc/sudoers.d/frappe' - template = env.get_template('frappe_sudoers') frappe_sudoers = template.render(**{ 'user': user, 'service': find_executable('service'), 'systemctl': find_executable('systemctl'), - 'supervisorctl': find_executable('supervisorctl'), 'nginx': find_executable('nginx'), - 'bench': find_executable('bench') }) frappe_sudoers = safe_decode(frappe_sudoers) @@ -410,6 +407,7 @@ def setup_sudoers(user): f.write(frappe_sudoers) os.chmod(sudoers_file, 0o440) + log("Sudoers was set up for user {}".format(user), level=1) def setup_logging(bench_path='.'): @@ -423,17 +421,11 @@ def setup_logging(bench_path='.'): logger.setLevel(logging.DEBUG) -def get_program(programs): - program = None - for p in programs: - program = find_executable(p) - if program: - break - return program - - def get_process_manager(): - return get_program(['foreman', 'forego', 'honcho']) + for proc_man in ['honcho', 'foreman', 'forego']: + proc_man_path = find_executable(proc_man) + if proc_man_path: + return proc_man_path def start(no_dev=False, concurrency=None, procfile=None): @@ -488,15 +480,16 @@ def check_git_for_shallow_clone(): return True -def get_cmd_output(cmd, cwd='.'): +def get_cmd_output(cmd, cwd='.', _raise=True): + output = "" try: output = subprocess.check_output(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE).strip() - output = output.decode('utf-8') - return output except subprocess.CalledProcessError as e: if e.output: - print(e.output) - raise + output = e.output + elif _raise: + raise + return safe_decode(output) def safe_encode(what, encoding = 'utf-8'): @@ -518,7 +511,7 @@ def restart_supervisor_processes(bench_path='.', web_workers=False): exec_cmd(cmd, cwd=bench_path) else: - supervisor_status = subprocess.check_output(['sudo', 'supervisorctl', 'status'], cwd=bench_path) + supervisor_status = get_cmd_output('supervisorctl status', cwd=bench_path) supervisor_status = safe_decode(supervisor_status) if web_workers and '{bench_name}-web:'.format(bench_name=bench_name) in supervisor_status: @@ -535,7 +528,7 @@ def restart_supervisor_processes(bench_path='.', web_workers=False): else: group = 'frappe:' - exec_cmd('sudo supervisorctl restart {group}'.format(group=group), cwd=bench_path) + exec_cmd('supervisorctl restart {group}'.format(group=group), cwd=bench_path) def restart_systemd_processes(bench_path='.', web_workers=False): @@ -1048,8 +1041,8 @@ def migrate_env(python, backup=False): from bench.config.common_site_config import get_config from bench.app import get_apps - log = logging.getLogger(__name__) - log.setLevel(logging.DEBUG) + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) nvenv = 'env' path = os.getcwd() @@ -1065,12 +1058,12 @@ def migrate_env(python, backup=False): redis = '{redis} -p {port}'.format(redis=which('redis-cli'), port=rredis.port) - log.debug('Clearing Redis Cache...') + logger.debug('Clearing Redis Cache...') exec_cmd('{redis} FLUSHALL'.format(redis = redis)) - log.debug('Clearing Redis DataBase...') + logger.debug('Clearing Redis DataBase...') exec_cmd('{redis} FLUSHDB'.format(redis = redis)) except: - log.warn('Please ensure Redis Connections are running or Daemonized.') + logger.warn('Please ensure Redis Connections are running or Daemonized.') # Backup venv: restore using `virtualenv --relocatable` if needed if backup: @@ -1081,7 +1074,7 @@ def migrate_env(python, backup=False): source = os.path.join(path, 'env') target = parch - log.debug('Backing up Virtual Environment') + logger.debug('Backing up Virtual Environment') stamp = datetime.now().strftime('%Y%m%d_%H%M%S') dest = os.path.join(path, str(stamp)) @@ -1090,15 +1083,15 @@ def migrate_env(python, backup=False): # Create virtualenv using specified python try: - log.debug('Setting up a New Virtual {} Environment'.format(python)) + logger.debug('Setting up a New Virtual {} Environment'.format(python)) exec_cmd('{virtualenv} --python {python} {pvenv}'.format(virtualenv=virtualenv, python=python, pvenv=pvenv)) apps = ' '.join(["-e {}".format(os.path.join("apps", app)) for app in get_apps()]) exec_cmd('{0} install -q -U {1}'.format(pip, apps)) - log.debug('Migration Successful to {}'.format(python)) + logger.debug('Migration Successful to {}'.format(python)) except: - log.debug('Migration Error') + logger.debug('Migration Error') raise