2
0
mirror of https://github.com/frappe/bench.git synced 2024-09-24 04:59:01 +00:00

Merge branch 'develop' into pull_specific_apps

This commit is contained in:
Chinmay Pai 2020-04-19 13:25:39 +05:30 committed by GitHub
commit 06380d83d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 387 additions and 194 deletions

View File

@ -1,13 +1,25 @@
# imports - standard imports
import atexit
import json
import logging
import os
import pwd
import sys
# imports - third party imports
import click
import os, sys, logging, json, pwd, subprocess
from bench.utils import is_root, PatchError, drop_privileges, get_env_cmd, get_cmd_output, get_frappe, log, find_parent_bench
# imports - module imports
from bench.app import get_apps
from bench.config.common_site_config import get_config
from bench.commands import bench_command
from bench.config.common_site_config import get_config
from bench.utils import PatchError, bench_cache_file, check_latest_version, drop_privileges, find_parent_bench, generate_command_cache, get_cmd_output, get_env_cmd, get_frappe, is_bench_directory, is_dist_editable, is_root, log
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
@ -18,42 +30,51 @@ def cli():
change_dir()
change_uid()
if is_dist_editable("bench") and len(sys.argv) > 1 and sys.argv[1] != "src":
log("bench is installed in editable mode!\n\nThis is not the recommended mode of installation for production. Instead, install the package from PyPI with: `pip install frappe-bench`\n", level=3)
if not is_bench_directory() and not cmd_requires_root() and len(sys.argv) > 1 and sys.argv[1] not in ("init", "find", "src"):
log("Command not being executed in bench directory", level=3)
if len(sys.argv) > 2 and sys.argv[1] == "frappe":
return old_frappe_cli()
elif len(sys.argv) > 1 and sys.argv[1] in get_frappe_commands():
return frappe_cmd()
elif len(sys.argv) > 1:
if sys.argv[1] in get_frappe_commands() + ["--site", "--verbose", "--force", "--profile"]:
return frappe_cmd()
elif len(sys.argv) > 1 and sys.argv[1] in ("--site", "--verbose", "--force", "--profile"):
return frappe_cmd()
elif sys.argv[1] == "--help":
print(click.Context(bench_command).get_help())
print(get_frappe_help())
return
elif len(sys.argv) > 1 and sys.argv[1]=="--help":
print(click.Context(bench_command).get_help())
print(get_frappe_help())
return
elif sys.argv[1] in get_apps():
return app_cmd()
elif len(sys.argv) > 1 and sys.argv[1] in get_apps():
return app_cmd()
if not (len(sys.argv) > 1 and sys.argv[1] == "src"):
atexit.register(check_latest_version)
try:
bench_command()
except PatchError:
sys.exit(1)
else:
try:
# NOTE: this is the main bench command
bench_command()
except PatchError:
sys.exit(1)
def check_uid():
if cmd_requires_root() and not is_root():
log('superuser privileges required for this command', level=3)
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',
'install'):
if len(sys.argv) >= 2 and sys.argv[1] in ('patch', 'renew-lets-encrypt', 'disable-production'):
return True
if len(sys.argv) > 2 and sys.argv[1] in ('install'):
return True
def change_dir():
if os.path.exists('config.json') or "init" in sys.argv:
@ -65,6 +86,7 @@ def change_dir():
if os.path.exists(dir_path):
os.chdir(dir_path)
def change_uid():
if is_root() and not cmd_requires_root():
frappe_user = get_config(".").get('frappe_user')
@ -72,38 +94,40 @@ 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='.'):
f = get_frappe(bench_path=bench_path)
os.chdir(os.path.join(bench_path, 'sites'))
os.execv(f, [f] + sys.argv[2:])
def app_cmd(bench_path='.'):
f = get_env_cmd('python', bench_path=bench_path)
os.chdir(os.path.join(bench_path, 'sites'))
os.execv(f, [f] + ['-m', 'frappe.utils.bench_helper'] + sys.argv[1:])
def frappe_cmd(bench_path='.'):
f = get_env_cmd('python', bench_path=bench_path)
os.chdir(os.path.join(bench_path, 'sites'))
os.execv(f, [f] + ['-m', 'frappe.utils.bench_helper', 'frappe'] + sys.argv[1:])
def get_frappe_commands(bench_path='.'):
python = get_env_cmd('python', bench_path=bench_path)
sites_path = os.path.join(bench_path, 'sites')
if not os.path.exists(sites_path):
log("Command not being executed in bench directory", level=3)
return []
try:
output = get_cmd_output("{python} -m frappe.utils.bench_helper get-frappe-commands".format(python=python), cwd=sites_path)
return json.loads(output)
except subprocess.CalledProcessError as e:
if hasattr(e, "stderr"):
print(e.stderr.decode('utf-8'))
def get_frappe_commands():
if not is_bench_directory():
return []
if os.path.exists(bench_cache_file):
command_dump = open(bench_cache_file, 'r').read() or '[]'
return json.loads(command_dump)
else:
return generate_command_cache()
def get_frappe_help(bench_path='.'):
python = get_env_cmd('python', bench_path=bench_path)
sites_path = os.path.join(bench_path, 'sites')
@ -113,6 +137,7 @@ def get_frappe_help(bench_path='.'):
except:
return ""
def change_working_directory():
"""Allows bench commands to be run from anywhere inside a bench directory"""
cur_dir = os.path.abspath(".")

View File

@ -41,7 +41,8 @@ bench_command.add_command(switch_to_develop)
from bench.commands.utils import (start, restart, set_nginx_port, set_ssl_certificate, set_ssl_certificate_key, set_url_root,
set_mariadb_host, set_default_site, download_translations, backup_site, backup_all_sites, release, renew_lets_encrypt,
disable_production, bench_src, prepare_beta_release, set_redis_cache_host, set_redis_queue_host, set_redis_socketio_host, find_benches, migrate_env)
disable_production, bench_src, prepare_beta_release, set_redis_cache_host, set_redis_queue_host, set_redis_socketio_host, find_benches, migrate_env,
generate_command_cache, clear_command_cache)
bench_command.add_command(start)
bench_command.add_command(restart)
bench_command.add_command(set_nginx_port)
@ -63,7 +64,8 @@ bench_command.add_command(bench_src)
bench_command.add_command(prepare_beta_release)
bench_command.add_command(find_benches)
bench_command.add_command(migrate_env)
bench_command.add_command(generate_command_cache)
bench_command.add_command(clear_command_cache)
from bench.commands.setup import setup
bench_command.add_command(setup)

View File

@ -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)

View File

@ -180,3 +180,15 @@ def find_benches(location):
def migrate_env(python, backup=True):
from bench.utils import migrate_env
migrate_env(python=python, backup=backup)
@click.command('generate-command-cache', help="Caches Frappe Framework commands")
def generate_command_cache(bench_path='.'):
from bench.utils import generate_command_cache
return generate_command_cache(bench_path=bench_path)
@click.command('clear-command-cache', help="Clears Frappe Framework cached commands")
def clear_command_cache(bench_path='.'):
from bench.utils import clear_command_cache
return clear_command_cache(bench_path=bench_path)

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

View File

@ -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))

View File

@ -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",

View File

@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# imports - standard imports
import errno
import glob
@ -20,7 +23,9 @@ from distutils.spawn import find_executable
# imports - third party imports
import click
from crontab import CronTab
import requests
from semantic_version import Version
from six import iteritems
from six.moves.urllib.parse import urlparse
@ -35,8 +40,9 @@ class CommandFailedError(Exception):
pass
logger = logging.getLogger(__name__)
bench_cache_file = '.bench.cmd'
folders_in_bench = ('apps', 'sites', 'config', 'logs', 'config/pids')
sudoers_file = '/etc/sudoers.d/frappe'
class color:
@ -60,15 +66,15 @@ def is_bench_directory(directory=os.path.curdir):
def log(message, level=0):
levels = {
0: color.blue + 'LOG', # normal
0: color.blue + 'INFO', # normal
1: color.green + 'SUCCESS', # success
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'):
@ -79,6 +85,23 @@ def safe_decode(string, encoding = 'utf-8'):
return string
def check_latest_version():
try:
pypi_request = requests.get("https://pypi.org/pypi/frappe-bench/json")
except Exception:
# Exceptions thrown are defined in requests.exceptions
# ignore checking on all Exceptions
return
if pypi_request.status_code == 200:
pypi_version_str = pypi_request.json().get('info').get('version')
pypi_version = Version(pypi_version_str)
local_version = Version(bench.__version__)
if pypi_version > local_version:
log("A newer version of bench is available: {0}{1}".format(local_version, pypi_version))
def get_frappe(bench_path='.'):
frappe = get_env_cmd('frappe', bench_path=bench_path)
if not os.path.exists(frappe):
@ -168,6 +191,8 @@ def update(pull=False, apps=apps, patch=False, build=False, requirements=False,
if apps and not pull:
apps = []
clear_command_cache(bench_path='.')
if conf.get('release_bench'):
print('Release bench detected, cannot update!')
sys.exit(1)
@ -224,7 +249,7 @@ def update(pull=False, apps=apps, patch=False, build=False, requirements=False,
conf.update({ "maintenance_mode": 0, "pause_scheduler": 0 })
update_config(conf, bench_path=bench_path)
print("_" * 80 + "\nBench: Deployment tool for Frappe and Frappe Applications (https://frappe.io/bench).\nOpen source depends on your contributions, so please contribute bug reports, patches, fixes or cash and be a part of the community")
print("_" * 80 + "\nBench: Deployment tool for Frappe and Frappe Applications (https://frappe.io/bench).\nOpen source depends on your contributions, so do give back by submitting bug reports, patches and fixes and be a part of the community :)")
def copy_patches_txt(bench_path):
@ -301,11 +326,13 @@ def get_venv_path():
def setup_env(bench_path='.', python='python3'):
frappe = os.path.join(bench_path, "apps", "frappe")
pip = os.path.join(".", "env", "bin", "pip")
pip = os.path.join(bench_path, "env", "bin", "pip")
virtualenv = get_venv_path()
exec_cmd('{} -q env -p {}'.format(virtualenv, python), cwd=bench_path)
exec_cmd('{} install -q -U -e {}'.format(pip, frappe), cwd=bench_path)
if os.path.exists(frappe):
exec_cmd('{} install -q -U -e {}'.format(pip, frappe), cwd=bench_path)
def setup_socketio(bench_path='.'):
@ -343,42 +370,27 @@ def get_sites(bench_path='.'):
return sites
def get_bench_dir(bench_path='.'):
return os.path.abspath(bench_path)
def setup_backups(bench_path='.'):
from bench.config.common_site_config import get_config
logger.info('setting up backups')
bench_dir = get_bench_dir(bench_path=bench_path)
bench_dir = os.path.abspath(bench_path)
user = get_config(bench_path=bench_dir).get('frappe_user')
logfile = os.path.join(bench_dir, 'logs', 'backup.log')
bench.set_frappe_version(bench_path=bench_path)
system_crontab = CronTab(user=user)
if bench.FRAPPE_VERSION == 4:
backup_command = "cd {sites_dir} && {frappe} --backup all".format(frappe=get_frappe(bench_path=bench_path),)
else:
backup_command = "cd {bench_dir} && {bench} --site all backup".format(bench_dir=bench_dir, bench=sys.argv[0])
backup_command = "cd {bench_dir} && {bench} --verbose --site all backup".format(bench_dir=bench_dir, bench=sys.argv[0])
add_to_crontab('0 */6 * * * {backup_command} >> {logfile} 2>&1'.format(backup_command=backup_command,
logfile=os.path.join(get_bench_dir(bench_path=bench_path), 'logs', 'backup.log')))
job_command = "{backup_command} >> {logfile} 2>&1".format(backup_command=backup_command, logfile=logfile)
def add_to_crontab(line):
current_crontab = read_crontab()
line = str.encode(line)
if not line in current_crontab:
cmd = ["crontab"]
if platform.system() == 'FreeBSD':
cmd = ["crontab", "-"]
s = subprocess.Popen(cmd, stdin=subprocess.PIPE)
s.stdin.write(current_crontab)
s.stdin.write(line + b'\n')
s.stdin.close()
def read_crontab():
s = subprocess.Popen(["crontab", "-l"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out = s.stdout.read()
s.stdout.close()
return out
if job_command not in str(system_crontab):
job = system_crontab.new(command=job_command, comment="bench auto backups set for every 6 hours")
job.hour.every(6)
system_crontab.write()
def setup_sudoers(user):
@ -397,16 +409,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)
@ -414,6 +422,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='.'):
@ -427,17 +436,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):
@ -492,15 +495,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'):
@ -522,7 +526,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:
@ -539,11 +543,10 @@ 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):
from .config.common_site_config import get_config
bench_name = get_bench_name(bench_path)
exec_cmd('sudo systemctl stop -- $(systemctl show -p Requires {bench_name}.target | cut -d= -f2)'.format(bench_name=bench_name))
exec_cmd('sudo systemctl start -- $(systemctl show -p Requires {bench_name}.target | cut -d= -f2)'.format(bench_name=bench_name))
@ -597,6 +600,7 @@ def update_yarn_packages(bench_path='.'):
for app in os.listdir(apps_dir):
app_path = os.path.join(apps_dir, app)
if os.path.exists(os.path.join(app_path, 'package.json')):
print('\n{0}Installing node dependencies for {1}{2}'.format(color.yellow, app, color.nc))
exec_cmd('yarn install', cwd=app_path)
@ -831,7 +835,7 @@ def update_translations_p(args):
def download_translations_p():
pool = multiprocessing.Pool(4)
pool = multiprocessing.Pool(multiprocessing.cpu_count())
langs = get_langs()
apps = ('frappe', 'erpnext')
@ -1051,8 +1055,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()
@ -1068,12 +1072,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:
@ -1084,7 +1088,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))
@ -1093,18 +1097,27 @@ 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
def is_dist_editable(dist):
"""Is distribution an editable install?"""
for path_item in sys.path:
egg_link = os.path.join(path_item, dist + '.egg-link')
if os.path.isfile(egg_link):
return True
return False
def find_parent_bench(path):
"""Checks if parent directories are benches"""
if is_bench_directory(directory=path):
@ -1117,3 +1130,36 @@ def find_parent_bench(path):
# NOTE: the os.path.split assumes that given path is absolute
parent_dir = os.path.split(path)[0]
return find_parent_bench(parent_dir)
def generate_command_cache(bench_path='.'):
"""Caches all available commands (even custom apps) via Frappe
Default caching behaviour: generated the first time any command (for a specific bench directory)
"""
python = get_env_cmd('python', bench_path=bench_path)
sites_path = os.path.join(bench_path, 'sites')
if os.path.exists(bench_cache_file):
os.remove(bench_cache_file)
try:
output = get_cmd_output("{0} -m frappe.utils.bench_helper get-frappe-commands".format(python), cwd=sites_path)
with open(bench_cache_file, 'w') as f:
json.dump(eval(output), f)
return json.loads(output)
except subprocess.CalledProcessError as e:
if hasattr(e, "stderr"):
print(e.stderr.decode('utf-8'))
def clear_command_cache(bench_path='.'):
"""Clears commands cached
Default invalidation behaviour: destroyed on each run of `bench update`
"""
if os.path.exists(bench_cache_file):
os.remove(bench_cache_file)
else:
print("Bench command cache doesn't exist in this folder!")

View File

@ -72,7 +72,7 @@
get_url:
url: https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_{{ "amd64" if ansible_architecture == "x86_64" else "i386"}}.deb
dest: /tmp/wkhtmltox.deb
checksum: '{{ 1140b0ab02aa6e17346af2f14ed0de807376de475ba90e1db3975f112fbd20bb if ansible_architecture == "x86_64" else 5b2d15e738ac479e7a8ca6fd765f406c3684a48091813520f87878278d6dd22a }}'
checksum: "sha256:{{ '1140b0ab02aa6e17346af2f14ed0de807376de475ba90e1db3975f112fbd20bb' if ansible_architecture == 'x86_64' else '5b2d15e738ac479e7a8ca6fd765f406c3684a48091813520f87878278d6dd22a' }}"
when: ansible_distribution == 'Debian' and ansible_distribution_major_version == '9'
- name: download wkthmltox Debian 10
@ -92,4 +92,4 @@
deb: /tmp/wkhtmltox.deb
state: present
when: ansible_os_family == 'Debian'
...
...