2
0
mirror of https://github.com/frappe/bench.git synced 2024-11-12 00:06:36 +00:00

Merge pull request #925 from gavindsouza/bench-cmd-help

refactor: bench commands help
This commit is contained in:
gavin 2020-03-02 23:17:46 +05:30 committed by GitHub
commit 6ecfa006e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 477 additions and 506 deletions

View File

@ -1,6 +1,6 @@
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, is_bench_directory
from bench.utils import is_root, PatchError, drop_privileges, get_env_cmd, get_cmd_output, get_frappe, log, find_parent_bench
from bench.app import get_apps
from bench.config.common_site_config import get_config
from bench.commands import bench_command
@ -29,7 +29,6 @@ def cli():
elif len(sys.argv) > 1 and sys.argv[1]=="--help":
print(click.Context(bench_command).get_help())
print()
print(get_frappe_help())
return
@ -99,7 +98,6 @@ def get_frappe_commands(bench_path='.'):
return []
try:
output = get_cmd_output("{python} -m frappe.utils.bench_helper get-frappe-commands".format(python=python), cwd=sites_path)
# output = output.decode('utf-8')
return json.loads(output)
except subprocess.CalledProcessError as e:
if hasattr(e, "stderr"):
@ -109,27 +107,12 @@ def get_frappe_commands(bench_path='.'):
def get_frappe_help(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):
return []
try:
out = get_cmd_output("{python} -m frappe.utils.bench_helper get-frappe-help".format(python=python), cwd=sites_path)
return "Framework commands:\n" + out.split('Commands:')[1]
except subprocess.CalledProcessError:
return "\n\nFramework commands:\n" + out.split('Commands:')[1]
except:
return ""
def find_parent_bench(path):
"""Checks if parent directories are benches"""
if is_bench_directory(directory=path):
return path
home_path = os.path.expanduser("~")
root_path = os.path.abspath(os.sep)
if path not in {home_path, root_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 change_working_directory():
"""Allows bench commands to be run from anywhere inside a bench directory"""
cur_dir = os.path.abspath(".")

View File

@ -40,7 +40,7 @@ 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, shell, backup_site, backup_all_sites, release, renew_lets_encrypt,
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)
bench_command.add_command(start)
bench_command.add_command(restart)
@ -54,7 +54,6 @@ bench_command.add_command(set_redis_queue_host)
bench_command.add_command(set_redis_socketio_host)
bench_command.add_command(set_default_site)
bench_command.add_command(download_translations)
bench_command.add_command(shell)
bench_command.add_command(backup_site)
bench_command.add_command(backup_all_sites)
bench_command.add_command(release)

View File

@ -1,112 +1,80 @@
import click, json
from bench.config.common_site_config import update_config
# imports - standard imports
import ast
## Config
## Not DRY
@click.group()
# imports - module imports
from bench.config.common_site_config import update_config, get_config, put_config
# imports - third party imports
import click
@click.group(help='Change bench configuration')
def config():
"change bench configuration"
pass
@click.command('auto_update')
@click.argument('state', type=click.Choice(['on', 'off']))
def config_auto_update(state):
"Enable/Disable auto update for bench"
state = True if state == 'on' else False
update_config({'auto_update': state})
@click.command('restart_supervisor_on_update')
@click.command('restart_supervisor_on_update', help='Enable/Disable auto restart of supervisor processes')
@click.argument('state', type=click.Choice(['on', 'off']))
def config_restart_supervisor_on_update(state):
"Enable/Disable auto restart of supervisor processes"
state = True if state == 'on' else False
update_config({'restart_supervisor_on_update': state})
update_config({'restart_supervisor_on_update': state == 'on'})
@click.command('restart_systemd_on_update')
@click.command('restart_systemd_on_update', help='Enable/Disable auto restart of systemd units')
@click.argument('state', type=click.Choice(['on', 'off']))
def config_restart_systemd_on_update(state):
"Enable/Disable auto restart of systemd units"
state = True if state == 'on' else False
update_config({'restart_systemd_on_update': state})
update_config({'restart_systemd_on_update': state == 'on'})
@click.command('update_bench_on_update')
@click.command('update_bench_on_update', help='Enable/Disable bench updates on running bench update')
@click.argument('state', type=click.Choice(['on', 'off']))
def config_update_bench_on_update(state):
"Enable/Disable bench updates on running bench update"
state = True if state == 'on' else False
update_config({'update_bench_on_update': state})
update_config({'update_bench_on_update': state == 'on'})
@click.command('dns_multitenant')
@click.command('dns_multitenant', help='Enable/Disable bench multitenancy on running bench update')
@click.argument('state', type=click.Choice(['on', 'off']))
def config_dns_multitenant(state):
"Enable/Disable bench updates on running bench update"
state = True if state == 'on' else False
update_config({'dns_multitenant': state})
update_config({'dns_multitenant': state == 'on'})
@click.command('serve_default_site')
@click.command('serve_default_site', help='Configure nginx to serve the default site on port 80')
@click.argument('state', type=click.Choice(['on', 'off']))
def config_serve_default_site(state):
"Configure nginx to serve the default site on port 80"
state = True if state == 'on' else False
update_config({'serve_default_site': state})
update_config({'serve_default_site': state == 'on'})
@click.command('rebase_on_pull')
@click.command('rebase_on_pull', help='Rebase repositories on pulling')
@click.argument('state', type=click.Choice(['on', 'off']))
def config_rebase_on_pull(state):
"Rebase repositories on pulling"
state = True if state == 'on' else False
update_config({'rebase_on_pull': state})
update_config({'rebase_on_pull': state == 'on'})
@click.command('http_timeout')
@click.command('http_timeout', help='Set HTTP timeout')
@click.argument('seconds', type=int)
def config_http_timeout(seconds):
"set http timeout"
update_config({'http_timeout': seconds})
@click.command('set-common-config')
@click.command('set-common-config', help='Set value in common config')
@click.option('configs', '-c', '--config', multiple=True, type=(str, str))
def set_common_config(configs):
import ast
from bench.config.common_site_config import update_config
common_site_config = {}
for key, value in configs:
if value in ("False", "True"):
if value in ('true', 'false'):
value = value.title()
try:
value = ast.literal_eval(value)
elif "." in value:
try:
value = float(value)
except ValueError:
pass
elif "{" in value or "[" in value:
try:
value = json.loads(value)
except ValueError:
pass
else:
try:
value = int(value)
except ValueError:
pass
except ValueError:
pass
common_site_config[key] = value
update_config(common_site_config, bench_path='.')
@click.command('remove-common-config')
@click.command('remove-common-config', help='Remove specific keys from current bench\'s common config')
@click.argument('keys', nargs=-1)
def remove_common_config(keys):
from bench.config.common_site_config import get_config, put_config
common_site_config = get_config('.')
for key in keys:
if key in common_site_config:
@ -115,7 +83,6 @@ def remove_common_config(keys):
put_config(common_site_config)
config.add_command(config_auto_update)
config.add_command(config_update_bench_on_update)
config.add_command(config_restart_supervisor_on_update)
config.add_command(config_restart_systemd_on_update)

View File

@ -1,28 +1,30 @@
import click
import os, subprocess, re
# imports - standard imports
import os
import subprocess
# imports - module imports
from bench.app import get_repo_dir, get_apps, get_remote
from bench.utils import set_git_remote_url
# imports - third party imports
import click
@click.command('remote-set-url')
@click.command('remote-set-url', help="Set app remote url")
@click.argument('git-url')
def remote_set_url(git_url):
"Set app remote url"
set_git_remote_url(git_url)
@click.command('remote-reset-url')
@click.command('remote-reset-url', help="Reset app remote url to frappe official")
@click.argument('app')
def remote_reset_url(app):
"Reset app remote url to frappe official"
git_url = "https://github.com/frappe/{}.git".format(app)
set_git_remote_url(git_url)
@click.command('remote-urls')
@click.command('remote-urls', help="Show apps remote url")
def remote_urls():
"Show apps remote url"
for app in get_apps():
repo_dir = get_repo_dir(app)

View File

@ -1,21 +1,29 @@
import os, sys, json, click
from bench.utils import run_playbook, setup_sudoers, is_root
# imports - module imports
from bench.utils import run_playbook, setup_sudoers
extra_vars = {"production": True}
# imports - third party imports
import click
@click.group()
extra_vars = {
"production": True
}
@click.group(help="Install system dependencies for setting up Frappe environment")
def install():
"Install system dependancies"
pass
@click.command('prerequisites')
@click.command('prerequisites', help="Installs pre-requisite libraries, essential tools like b2zip, htop, screen, vim, x11-fonts, python libs, cups and Redis")
def install_prerequisites():
run_playbook('site.yml', tag='common, redis')
@click.command('mariadb')
@click.option('--mysql_root_password')
@click.command('mariadb', help="Install and setup MariaDB of specified version and root password")
@click.option('--mysql_root_password', '--mysql-root-password', default="")
@click.option('--version', default="10.3")
def install_maridb(mysql_root_password='', version=''):
def install_maridb(mysql_root_password, version):
if mysql_root_password:
extra_vars.update({
"mysql_root_password": mysql_root_password,
@ -27,41 +35,49 @@ def install_maridb(mysql_root_password='', version=''):
run_playbook('site.yml', extra_vars=extra_vars, tag='mariadb')
@click.command('wkhtmltopdf')
@click.command('wkhtmltopdf', help="Installs wkhtmltopdf v0.12.3 for linux")
def install_wkhtmltopdf():
run_playbook('site.yml', extra_vars=extra_vars, tag='wkhtmltopdf')
@click.command('nodejs')
@click.command('nodejs', help="Installs Node.js v8")
def install_nodejs():
run_playbook('site.yml', extra_vars=extra_vars, tag='nodejs')
@click.command('psutil')
@click.command('psutil', help="Installs psutil via pip")
def install_psutil():
run_playbook('site.yml', extra_vars=extra_vars, tag='psutil')
@click.command('supervisor')
@click.command('supervisor', help="Installs supervisor. If user is specified, sudoers is setup for that user")
@click.option('--user')
def install_supervisor(user=None):
run_playbook('site.yml', extra_vars=extra_vars, tag='supervisor')
if user:
setup_sudoers(user)
@click.command('nginx')
@click.command('nginx', help="Installs NGINX. If user is specified, sudoers is setup for that user")
@click.option('--user')
def install_nginx(user=None):
run_playbook('site.yml', extra_vars=extra_vars, tag='nginx')
if user:
setup_sudoers(user)
@click.command('virtualbox')
@click.command('virtualbox', help="Installs supervisor")
def install_virtualbox():
run_playbook('vm_build.yml', tag='virtualbox')
@click.command('packer')
@click.command('packer', help="Installs Oracle virtualbox and packer 1.2.1")
def install_packer():
run_playbook('vm_build.yml', tag='packer')
@click.command('fail2ban')
@click.command("fail2ban", help="Install fail2ban, an intrusion prevention software framework that protects computer servers from brute-force attacks")
@click.option('--maxretry', default=6, help="Number of matches (i.e. value of the counter) which triggers ban action on the IP.")
@click.option('--bantime', default=600, help="The counter is set to zero if no match is found within 'findtime' seconds.")
@click.option('--findtime', default=600, help='Duration (in seconds) for IP to be banned for. Negative number for "permanent" ban.')
@ -69,6 +85,7 @@ def install_failtoban(**kwargs):
extra_vars.update(kwargs)
run_playbook('site.yml', extra_vars=extra_vars, tag='fail2ban')
install.add_command(install_prerequisites)
install.add_command(install_maridb)
install.add_command(install_wkhtmltopdf)

View File

@ -1,6 +1,8 @@
# imports - third party imports
import click
@click.command()
@click.command('init', help='Initialize a new bench instance in the specified path')
@click.argument('path')
@click.option('--python', type = str, default = 'python3', help = 'Path to Python Executable.')
@click.option('--ignore-exist', is_flag = True, default = False, help = "Ignore if Bench instance exists.")
@ -11,14 +13,10 @@ import click
@click.option('--clone-without-update', is_flag=True, help="copy repos from path without update")
@click.option('--no-procfile', is_flag=True, help="Pull changes in all the apps in bench")
@click.option('--no-backups',is_flag=True, help="Run migrations for all sites in the bench")
@click.option('--no-auto-update',is_flag=True, help="Build JS and CSS artifacts for the bench")
@click.option('--skip-redis-config-generation', is_flag=True, help="Skip redis config generation if already specifying the common-site-config file")
@click.option('--skip-assets',is_flag=True, default=False, help="Do not build assets")
@click.option('--verbose',is_flag=True, help="Verbose output during install")
def init(path, apps_path, frappe_path, frappe_branch, no_procfile, no_backups, no_auto_update, clone_from, verbose, skip_redis_config_generation, clone_without_update, ignore_exist=False, skip_assets=False, python='python3'):
'''
Create a New Bench Instance.
'''
def init(path, apps_path, frappe_path, frappe_branch, no_procfile, no_backups, clone_from, verbose, skip_redis_config_generation, clone_without_update, ignore_exist=False, skip_assets=False, python='python3'):
from bench.utils import init, log
try:
@ -27,7 +25,6 @@ def init(path, apps_path, frappe_path, frappe_branch, no_procfile, no_backups, n
apps_path=apps_path,
no_procfile=no_procfile,
no_backups=no_backups,
no_auto_update=no_auto_update,
frappe_path=frappe_path,
frappe_branch=frappe_branch,
verbose=verbose,
@ -52,7 +49,7 @@ def init(path, apps_path, frappe_path, frappe_branch, no_procfile, no_backups, n
shutil.rmtree(path)
@click.command('get-app')
@click.command('get-app', help='Clone an app from the internet or filesystem and set it up in your bench')
@click.argument('name', nargs=-1) # Dummy argument for backward compatibility
@click.argument('git-url')
@click.option('--branch', default=None, help="branch to checkout")
@ -64,31 +61,28 @@ def get_app(git_url, branch, name=None, overwrite=False, skip_assets=False):
get_app(git_url, branch=branch, skip_assets=skip_assets, overwrite=overwrite)
@click.command('new-app')
@click.command('new-app', help='Create a new Frappe application under apps folder')
@click.argument('app-name')
def new_app(app_name):
"start a new app"
from bench.app import new_app
new_app(app_name)
@click.command('remove-app')
@click.command('remove-app', help='Completely remove app from bench and re-build assets if not installed on any site')
@click.argument('app-name')
def remove_app(app_name):
"completely remove app from bench"
from bench.app import remove_app
remove_app(app_name)
@click.command('exclude-app')
@click.command('exclude-app', help='Exclude app from updating')
@click.argument('app_name')
def exclude_app_for_update(app_name):
"Exclude app from updating"
from bench.app import add_to_excluded_apps_txt
add_to_excluded_apps_txt(app_name)
@click.command('include-app')
@click.command('include-app', help='Include app for updating')
@click.argument('app_name')
def include_app_for_update(app_name):
"Include app from updating"

View File

@ -1,233 +1,216 @@
from bench.utils import exec_cmd
from six import PY3
import click, sys, json
# imports - standard imports
import os
import sys
@click.group()
# imports - module imports
from bench.utils import exec_cmd
# imports - third party imports
from six import PY3
import click
@click.group(help="Setup command group for enabling setting up a Frappe environment")
def setup():
"Setup bench"
pass
@click.command('sudoers')
@click.argument('user')
@click.command("sudoers", help="Add commands to sudoers list for execution without password")
@click.argument("user")
def setup_sudoers(user):
"Add commands to sudoers list for execution without password"
from bench.utils import setup_sudoers
setup_sudoers(user)
@click.command('nginx')
@click.option('--yes', help='Yes to regeneration of nginx config file', default=False, is_flag=True)
@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):
"generate config for nginx"
from bench.config.nginx import make_nginx_conf
make_nginx_conf(bench_path=".", yes=yes)
@click.command('reload-nginx')
@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()
@click.command('supervisor')
@click.option('--user')
@click.option('--yes', help='Yes to regeneration of supervisor config', is_flag=True, default=False)
@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):
"generate config for supervisor with an optional user argument"
from bench.config.supervisor import generate_supervisor_config
generate_supervisor_config(bench_path=".", user=user, yes=yes)
@click.command('redis')
@click.command("redis", help="Generates configuration for Redis")
def setup_redis():
"generate config for redis cache"
from bench.config.redis import generate_config
generate_config('.')
generate_config(".")
@click.command('fonts')
@click.command("fonts", help="Add Frappe fonts to system")
def setup_fonts():
"Add frappe fonts to system"
from bench.utils import setup_fonts
setup_fonts()
@click.command('production')
@click.argument('user')
@click.option('--yes', help='Yes to regeneration config', is_flag=True, default=False)
@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):
"setup bench for production"
from bench.config.production_setup import setup_production
from bench.utils import run_playbook
# Install prereqs for production
from distutils.spawn import find_executable
if not find_executable('ansible'):
exec_cmd("sudo {0} install ansible".format("pip3" if PY3 else "pip2"))
if not find_executable('fail2ban-client'):
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'):
if not find_executable("nginx"):
exec_cmd("bench setup role nginx")
if not find_executable('supervisord'):
if not find_executable("supervisord"):
exec_cmd("bench setup role supervisor")
setup_production(user=user, yes=yes)
@click.command('auto-update')
def setup_auto_update():
"Add cronjob for bench auto update"
from bench.utils import setup_auto_update
setup_auto_update()
@click.command('backups')
@click.command("backups", help="Add cronjob for bench backups")
def setup_backups():
"Add cronjob for bench backups"
from bench.utils import setup_backups
setup_backups()
@click.command('env')
@click.option('--python', type = str, default = 'python3', help = 'Path to Python Executable.')
def setup_env(python='python3'):
"Setup virtualenv for bench"
@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)
@click.command('firewall')
@click.option('--ssh_port')
@click.option('--force')
@click.command("firewall", help="Setup firewall for system")
@click.option("--ssh_port")
@click.option("--force")
def setup_firewall(ssh_port=None, force=False):
"Setup firewall"
from bench.utils import run_playbook
if not force:
click.confirm('Setting up the firewall will block all ports except 80, 443 and 22\n'
'Do you want to continue?',
abort=True)
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)
if not ssh_port:
ssh_port = 22
run_playbook('roles/bench/tasks/setup_firewall.yml', {"ssh_port": ssh_port})
run_playbook("roles/bench/tasks/setup_firewall.yml", {"ssh_port": ssh_port})
@click.command('ssh-port')
@click.argument('port')
@click.option('--force')
@click.command("ssh-port", help="Set SSH Port for system")
@click.argument("port")
@click.option("--force")
def set_ssh_port(port, force=False):
"Set SSH Port"
from bench.utils import run_playbook
if not force:
click.confirm('This will change your SSH Port to {}\n'
'Do you want to continue?'.format(port),
abort=True)
click.confirm("This will change your SSH Port to {}\nDo you want to continue?".format(port), abort=True)
run_playbook('roles/bench/tasks/change_ssh_port.yml', {"ssh_port": port})
run_playbook("roles/bench/tasks/change_ssh_port.yml", {"ssh_port": port})
@click.command('lets-encrypt')
@click.argument('site')
@click.option('--custom-domain')
@click.command("lets-encrypt", help="Setup lets-encrypt SSL for site")
@click.argument("site")
@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):
"Setup lets-encrypt for site"
from bench.config.lets_encrypt import setup_letsencrypt
setup_letsencrypt(site, custom_domain, bench_path='.', interactive=not non_interactive)
setup_letsencrypt(site, custom_domain, bench_path=".", interactive=not non_interactive)
@click.command('wildcard-ssl')
@click.argument('domain')
@click.option('--email')
@click.option('--exclude-base-domain', default=False, is_flag=True, help="SSL Certificate not applicable for base domain")
@click.command("wildcard-ssl", help="Setup wildcard SSL certificate for multi-tenant bench")
@click.argument("domain")
@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):
''' Setup wildcard ssl certificate '''
from bench.config.lets_encrypt import setup_wildcard_ssl
setup_wildcard_ssl(domain, email, bench_path='.', exclude_base_domain=exclude_base_domain)
setup_wildcard_ssl(domain, email, bench_path=".", exclude_base_domain=exclude_base_domain)
@click.command('procfile')
@click.command("procfile", help="Generate Procfile for bench start")
def setup_procfile():
"Setup Procfile for bench start"
from bench.config.procfile import setup_procfile
setup_procfile('.')
setup_procfile(".")
@click.command('socketio')
@click.command("socketio", help="Setup node dependencies for socketio server")
def setup_socketio():
"Setup node deps for socketio server"
from bench.utils import setup_socketio
setup_socketio()
@click.command('requirements', help="Update Python and Node packages")
@click.option('--node', help="Update only Node packages", default=False, is_flag=True)
@click.option('--python', help="Update only Python packages", default=False, is_flag=True)
@click.command("requirements", help="Setup Python and Node dependencies")
@click.option("--node", help="Update only Node packages", default=False, is_flag=True)
@click.option("--python", help="Update only Python packages", default=False, is_flag=True)
def setup_requirements(node=False, python=False):
"Setup python and node requirements"
if not node:
setup_python_requirements()
from bench.utils import update_requirements as setup_python_packages
setup_python_packages()
if not python:
setup_node_requirements()
def setup_python_requirements():
from bench.utils import update_requirements
update_requirements()
def setup_node_requirements():
from bench.utils import update_node_packages
update_node_packages()
from bench.utils import update_node_packages as setup_node_packages
setup_node_packages()
@click.command('manager')
@click.option('--yes', help='Yes to regeneration of nginx config file', default=False, is_flag=True)
@click.option('--port', help='Port on which you want to run bench manager', default=23624)
@click.option('--domain', help='Domain on which you want to run bench manager')
@click.command("manager", help="Setup bench-manager.local site with the bench_manager app installed on it")
@click.option("--yes", help="Yes to regeneration of nginx config file", default=False, is_flag=True)
@click.option("--port", help="Port on which you want to run bench manager", default=23624)
@click.option("--domain", help="Domain on which you want to run bench manager")
def setup_manager(yes=False, port=23624, domain=None):
"Setup bench-manager.local site with the bench_manager app installed on it"
from six.moves import input
from bench.utils import get_sites
from bench.config.common_site_config import get_config
from bench.config.nginx import make_bench_manager_nginx_conf
create_new_site = True
if 'bench-manager.local' in os.listdir('sites'):
ans = input('Site already exists. Overwrite existing site? [Y/n]: ').lower()
while ans not in ('y', 'n', ''):
ans = input(
'Please enter "y" or "n". Site already exists. Overwrite existing site? [Y/n]: ').lower()
if ans == 'n':
if "bench-manager.local" in os.listdir("sites"):
ans = input("Site already exists. Overwrite existing site? [Y/n]: ").lower()
while ans not in ("y", "n", ""):
ans = input("Please enter 'y' or 'n'. Site already exists. Overwrite existing site? [Y/n]: ").lower()
if ans == "n":
create_new_site = False
if create_new_site:
exec_cmd("bench new-site --force bench-manager.local")
if 'bench_manager' in os.listdir('apps'):
print('App already exists. Skipping app download.')
if "bench_manager" in os.listdir("apps"):
print("App already exists. Skipping app download.")
else:
exec_cmd("bench get-app bench_manager")
exec_cmd("bench --site bench-manager.local install-app bench_manager")
from bench.config.common_site_config import get_config
bench_path = '.'
bench_path = "."
conf = get_config(bench_path)
if conf.get('restart_supervisor_on_update') or conf.get('restart_systemd_on_update'):
if conf.get("restart_supervisor_on_update") or conf.get("restart_systemd_on_update"):
# implicates a production setup or so I presume
if not domain:
print("Please specify the site name on which you want to host bench-manager using the 'domain' flag")
sys.exit(1)
from bench.utils import get_sites, get_bench_name
bench_name = get_bench_name(bench_path)
if domain not in get_sites(bench_path):
raise Exception("No such site")
from bench.config.nginx import make_bench_manager_nginx_conf
make_bench_manager_nginx_conf(bench_path, yes=yes, port=port, domain=domain)
@click.command('config')
@click.command("config", help="Generate or over-write sites/common_site_config.json")
def setup_config():
"overwrite or make config.json"
from bench.config.common_site_config import make_config
make_config('.')
make_config(".")
@click.command('add-domain')
@click.argument('domain')
@click.option('--site', prompt=True)
@click.option('--ssl-certificate', help="Absolute path to SSL Certificate")
@click.option('--ssl-certificate-key', help="Absolute path to SSL Certificate Key")
@click.command("add-domain", help="Add a custom domain to a particular site")
@click.argument("domain")
@click.option("--site", prompt=True)
@click.option("--ssl-certificate", help="Absolute path to SSL Certificate")
@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
@ -236,24 +219,25 @@ def add_domain(domain, site=None, ssl_certificate=None, ssl_certificate_key=None
print("Please specify site")
sys.exit(1)
add_domain(site, domain, ssl_certificate, ssl_certificate_key, bench_path='.')
add_domain(site, domain, ssl_certificate, ssl_certificate_key, bench_path=".")
@click.command('remove-domain')
@click.argument('domain')
@click.option('--site', prompt=True)
@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):
"""Remove custom domain from a site"""
from bench.config.site_config import remove_domain
if not site:
print("Please specify site")
sys.exit(1)
remove_domain(site, domain, bench_path='.')
remove_domain(site, domain, bench_path=".")
@click.command('sync-domains')
@click.option('--domain', multiple=True)
@click.option('--site', prompt=True)
@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
@ -262,53 +246,55 @@ def sync_domains(domain=None, site=None):
sys.exit(1)
try:
domains = list(map(str,domain))
domains = list(map(str, domain))
except Exception:
print("Domains should be a json list of strings or dictionaries")
sys.exit(1)
changed = sync_domains(site, domains, bench_path='.')
changed = sync_domains(site, domains, bench_path=".")
# if changed, success, else failure
sys.exit(0 if changed else 1)
@click.command('role')
@click.argument('role')
@click.option('--admin_emails', default='')
@click.option('--mysql_root_password')
@click.option('--container', is_flag=True, default=False)
@click.command("role", help="Install dependencies via ansible roles")
@click.argument("role")
@click.option("--admin_emails", default="")
@click.option("--mysql_root_password")
@click.option("--container", is_flag=True, default=False)
def setup_roles(role, **kwargs):
"Install dependancies via roles"
from bench.utils import run_playbook
extra_vars = {"production": True}
extra_vars.update(kwargs)
if role:
run_playbook('site.yml', extra_vars=extra_vars, tag=role)
run_playbook("site.yml", extra_vars=extra_vars, tag=role)
else:
run_playbook('site.yml', extra_vars=extra_vars)
run_playbook("site.yml", extra_vars=extra_vars)
@click.command('fail2ban')
@click.option('--maxretry', default=6, help="Number of matches (i.e. value of the counter) which triggers ban action on the IP. Default is 6 seconds" )
@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')
@click.command("fail2ban", help="Setup fail2ban, an intrusion prevention software framework that protects computer servers from brute-force attacks")
@click.option("--maxretry", default=6, help="Number of matches (i.e. value of the counter) which triggers ban action on the IP. Default is 6 seconds" )
@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)
run_playbook("roles/fail2ban/tasks/configure_nginx_jail.yml", extra_vars=kwargs)
@click.command('systemd')
@click.option('--user')
@click.option('--yes', help='Yes to regeneration of systemd config files', is_flag=True, default=False)
@click.option('--stop', help='Stop bench services', is_flag=True, default=False)
@click.option('--create-symlinks', help='Create Symlinks', is_flag=True, default=False)
@click.option('--delete-symlinks', help='Delete Symlinks', is_flag=True, default=False)
@click.command("systemd", help="Generate configuration for systemd")
@click.option("--user", help="Optional user argument")
@click.option("--yes", help="Yes to regeneration of systemd config files", is_flag=True, default=False)
@click.option("--stop", help="Stop bench services", is_flag=True, default=False)
@click.option("--create-symlinks", help="Create Symlinks", is_flag=True, default=False)
@click.option("--delete-symlinks", help="Delete Symlinks", is_flag=True, default=False)
def setup_systemd(user=None, yes=False, stop=False, create_symlinks=False, delete_symlinks=False):
"generate configs for systemd with an optional user argument"
from bench.config.systemd import generate_systemd_config
generate_systemd_config(bench_path=".", user=user, yes=yes,
stop=stop, create_symlinks=create_symlinks, delete_symlinks=delete_symlinks)
setup.add_command(setup_sudoers)
setup.add_command(setup_nginx)
setup.add_command(reload_nginx)
@ -317,7 +303,6 @@ setup.add_command(setup_redis)
setup.add_command(setup_letsencrypt)
setup.add_command(setup_wildcard_ssl)
setup.add_command(setup_production)
setup.add_command(setup_auto_update)
setup.add_command(setup_backups)
setup.add_command(setup_env)
setup.add_command(setup_procfile)

View File

@ -1,124 +1,32 @@
import click
import sys
# imports - standard imports
import os
from bench.config.common_site_config import get_config, update_config
from bench.app import pull_all_apps, is_version_upgrade, validate_branch
from bench.utils import (update_bench, validate_upgrade, pre_upgrade, post_upgrade, before_update,
update_requirements, update_node_packages, backup_all_sites, patch_sites, build_assets,
restart_supervisor_processes, restart_systemd_processes, is_bench_directory)
from bench import patches
# imports - third party imports
import click
from six.moves import reload_module
# imports - module imports
from bench.app import pull_all_apps
from bench.utils import post_upgrade, patch_sites, build_assets
@click.command('update')
@click.option('--pull', is_flag=True, help="Pull changes in all the apps in bench")
@click.command('update', help="Updates bench tool and if executed in a bench directory, without any flags will backup, pull, setup requirements, build, run patches and restart bench. Using specific flags will only do certain tasks instead of all")
@click.option('--pull', is_flag=True, help="Pull updates for all the apps in bench")
@click.option('--patch', is_flag=True, help="Run migrations for all sites in the bench")
@click.option('--build', is_flag=True, help="Build JS and CSS artifacts for the bench")
@click.option('--bench', is_flag=True, help="Update bench")
@click.option('--requirements', is_flag=True, help="Update requirements")
@click.option('--restart-supervisor', is_flag=True, help="restart supervisor processes after update")
@click.option('--restart-systemd', is_flag=True, help="restart systemd units after update")
@click.option('--auto', is_flag=True)
@click.option('--no-backup', is_flag=True)
@click.option('--force', is_flag=True)
@click.option('--build', is_flag=True, help="Build JS and CSS assets for the bench")
@click.option('--bench', is_flag=True, help="Update bench CLI tool")
@click.option('--requirements', is_flag=True, help="Update requirements. If run alone, equivalent to `bench setup requirements`")
@click.option('--restart-supervisor', is_flag=True, help="Restart supervisor processes after update")
@click.option('--restart-systemd', is_flag=True, help="Restart systemd units after update")
@click.option('--no-backup', is_flag=True, help="If this flag is set, sites won't be backed up prior to updates. Note: This is not recommended in production.")
@click.option('--force', is_flag=True, help="Forces major version upgrades")
@click.option('--reset', is_flag=True, help="Hard resets git branch's to their new states overriding any changes and overriding rebase on pull")
def update(pull=False, patch=False, build=False, bench=False, auto=False, restart_supervisor=False, restart_systemd=False, requirements=False, no_backup=False, force=False, reset=False):
"Update bench"
def update(pull, patch, build, bench, requirements, restart_supervisor, restart_systemd, no_backup, force, reset):
from bench.utils import update
update(pull=pull, patch=patch, build=build, bench=bench, requirements=requirements, restart_supervisor=restart_supervisor, restart_systemd=restart_systemd, backup= not no_backup, force=force, reset=reset)
if not is_bench_directory():
"""Update only bench if bench update called from outside a bench"""
update_bench(bench_repo=True, requirements=True)
sys.exit()
if not (pull or patch or build or bench or requirements):
pull, patch, build, bench, requirements = True, True, True, True, True
if auto:
sys.exit(1)
patches.run(bench_path='.')
conf = get_config(".")
if bench and conf.get('update_bench_on_update'):
update_bench(bench_repo=True, requirements=False)
restart_update({
'pull': pull,
'patch': patch,
'build': build,
'requirements': requirements,
'no-backup': no_backup,
'restart-supervisor': restart_supervisor,
'reset': reset
})
if conf.get('release_bench'):
print('Release bench, cannot update')
sys.exit(1)
validate_branch()
version_upgrade = is_version_upgrade()
if version_upgrade[0]:
print()
print()
print("This update will cause a major version change in Frappe/ERPNext from {0} to {1}.".format(*version_upgrade[1:]))
print("This would take significant time to migrate and might break custom apps.")
click.confirm('Do you want to continue?', abort=True)
_update(pull, patch, build, bench, auto, restart_supervisor, restart_systemd, requirements, no_backup, force=force, reset=reset)
def _update(pull=False, patch=False, build=False, update_bench=False, auto=False, restart_supervisor=False,
restart_systemd=False, requirements=False, no_backup=False, bench_path='.', force=False, reset=False):
conf = get_config(bench_path=bench_path)
version_upgrade = is_version_upgrade(bench_path=bench_path)
if version_upgrade[0] or (not version_upgrade[0] and force):
validate_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
before_update(bench_path=bench_path, requirements=requirements)
conf.update({ "maintenance_mode": 1, "pause_scheduler": 1 })
update_config(conf, bench_path=bench_path)
if not no_backup:
print('Backing up sites...')
backup_all_sites(bench_path=bench_path)
if pull:
pull_all_apps(bench_path=bench_path, reset=reset)
if requirements:
update_requirements(bench_path=bench_path)
update_node_packages(bench_path=bench_path)
if version_upgrade[0] or (not version_upgrade[0] and force):
pre_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
import bench.utils, bench.app
print('Reloading bench...')
reload_module(bench.utils)
reload_module(bench.app)
if patch:
print('Patching sites...')
patch_sites(bench_path=bench_path)
if build:
build_assets(bench_path=bench_path)
if version_upgrade[0] or (not version_upgrade[0] and force):
post_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
if restart_supervisor or conf.get('restart_supervisor_on_update'):
restart_supervisor_processes(bench_path=bench_path)
if restart_systemd or conf.get('restart_systemd_on_update'):
restart_systemd_processes(bench_path=bench_path)
conf.update({ "maintenance_mode": 0, "pause_scheduler": 0 })
update_config(conf, bench_path=bench_path)
print("_"*80)
print("Bench: Deployment tool for Frappe and ERPNext (https://erpnext.org).")
print("Open source depends on your contributions, so please contribute bug reports, patches, fixes or cash and be a part of the community")
print()
@click.command('retry-upgrade')
@click.command('retry-upgrade', help="Retry a failed upgrade")
@click.option('--version', default=5)
def retry_upgrade(version):
pull_all_apps()
@ -126,35 +34,27 @@ def retry_upgrade(version):
build_assets()
post_upgrade(version-1, version)
def restart_update(kwargs):
args = ['--'+k for k, v in list(kwargs.items()) if v]
os.execv(sys.argv[0], sys.argv[:2] + args)
@click.command('switch-to-branch')
@click.command('switch-to-branch', help="Switch all apps to specified branch, or specify apps separated by space")
@click.argument('branch')
@click.argument('apps', nargs=-1)
@click.option('--upgrade',is_flag=True)
def switch_to_branch(branch, apps, upgrade=False):
"Switch all apps to specified branch, or specify apps separated by space"
from bench.app import switch_to_branch
switch_to_branch(branch=branch, apps=list(apps), upgrade=upgrade)
print('Switched to ' + branch)
print('Please run `bench update --patch` to be safe from any differences in database schema')
@click.command('switch-to-master')
@click.command('switch-to-master', help="[DEPRECATED]: Switch frappe and erpnext to master branch")
def switch_to_master():
"Switch frappe and erpnext to master branch"
from bench.app import switch_to_master
switch_to_master(apps=['frappe', 'erpnext'])
print()
print('Switched to master')
print('Please run `bench update --patch` to be safe from any differences in database schema')
from bench.utils import log
log("`switch-to-master` has been deprecated as master branches were renamed to version-11")
@click.command('switch-to-develop')
def switch_to_develop(upgrade=False):
"Switch frappe and erpnext to develop branch"
from bench.app import switch_to_develop
switch_to_develop(apps=['frappe', 'erpnext'])
print()
print('Switched to develop')
print('Please run `bench update --patch` to be safe from any differences in database schema')
print('Switched to develop\nPlease run `bench update --patch` to be safe from any differences in database schema')

View File

@ -1,23 +1,25 @@
# imports - standard imports
import os
import sys
# imports - third party imports
import click
import sys, os, copy
@click.command('start')
@click.command('start', help="Start Frappe development processes")
@click.option('--no-dev', is_flag=True, default=False)
@click.option('--concurrency', '-c', type=str)
@click.option('--procfile', '-p', type=str)
def start(no_dev, concurrency, procfile):
"Start Frappe development processes"
from bench.utils import start
start(no_dev=no_dev, concurrency=concurrency, procfile=procfile)
@click.command('restart')
@click.command('restart', help="Restart supervisor processes or systemd units")
@click.option('--web', is_flag=True, default=False)
@click.option('--supervisor', is_flag=True, default=False)
@click.option('--systemd', is_flag=True, default=False)
def restart(web, supervisor, systemd):
"Restart supervisor processes or systemd units"
from bench.utils import restart_supervisor_processes, restart_systemd_processes
from bench.config.common_site_config import get_config
if get_config('.').get('restart_supervisor_on_update') or supervisor:
@ -25,134 +27,112 @@ def restart(web, supervisor, systemd):
if get_config('.').get('restart_systemd_on_update') or systemd:
restart_systemd_processes(bench_path='.', web_workers=web)
@click.command('set-nginx-port')
@click.command('set-nginx-port', help="Set NGINX port for site")
@click.argument('site')
@click.argument('port', type=int)
def set_nginx_port(site, port):
"Set nginx port for site"
from bench.config.site_config import set_nginx_port
set_nginx_port(site, port)
@click.command('set-ssl-certificate')
@click.command('set-ssl-certificate', help="Set SSL certificate path for site")
@click.argument('site')
@click.argument('ssl-certificate-path')
def set_ssl_certificate(site, ssl_certificate_path):
"Set ssl certificate path for site"
from bench.config.site_config import set_ssl_certificate
set_ssl_certificate(site, ssl_certificate_path)
@click.command('set-ssl-key')
@click.command('set-ssl-key', help="Set SSL certificate private key path for site")
@click.argument('site')
@click.argument('ssl-certificate-key-path')
def set_ssl_certificate_key(site, ssl_certificate_key_path):
"Set ssl certificate private key path for site"
from bench.config.site_config import set_ssl_certificate_key
set_ssl_certificate_key(site, ssl_certificate_key_path)
@click.command('set-url-root')
@click.command('set-url-root', help="Set URL root for site")
@click.argument('site')
@click.argument('url-root')
def set_url_root(site, url_root):
"Set url root for site"
from bench.config.site_config import set_url_root
set_url_root(site, url_root)
@click.command('set-mariadb-host')
@click.command('set-mariadb-host', help="Set MariaDB host for bench")
@click.argument('host')
def set_mariadb_host(host):
"Set MariaDB host for bench"
from bench.utils import set_mariadb_host
set_mariadb_host(host)
@click.command('set-redis-cache-host')
@click.command('set-redis-cache-host', help="Set Redis cache host for bench")
@click.argument('host')
def set_redis_cache_host(host):
"""
Set Redis cache host for bench
Eg: bench set-redis-cache-host localhost:6379/1
Usage: bench set-redis-cache-host localhost:6379/1
"""
from bench.utils import set_redis_cache_host
set_redis_cache_host(host)
@click.command('set-redis-queue-host')
@click.command('set-redis-queue-host', help="Set Redis queue host for bench")
@click.argument('host')
def set_redis_queue_host(host):
"""
Set Redis queue host for bench
Eg: bench set-redis-queue-host localhost:6379/2
Usage: bench set-redis-queue-host localhost:6379/2
"""
from bench.utils import set_redis_queue_host
set_redis_queue_host(host)
@click.command('set-redis-socketio-host')
@click.command('set-redis-socketio-host', help="Set Redis socketio host for bench")
@click.argument('host')
def set_redis_socketio_host(host):
"""
Set Redis socketio host for bench
Eg: bench set-redis-socketio-host localhost:6379/3
Usage: bench set-redis-socketio-host localhost:6379/3
"""
from bench.utils import set_redis_socketio_host
set_redis_socketio_host(host)
@click.command('set-default-site')
@click.command('set-default-site', help="Set default site for bench")
@click.argument('site')
def set_default_site(site):
"Set default site for bench"
from bench.utils import set_default_site
set_default_site(site)
@click.command('download-translations')
@click.command('download-translations', help="Download latest translations")
def download_translations():
"Download latest translations"
from bench.utils import download_translations_p
download_translations_p()
@click.command('renew-lets-encrypt')
@click.command('renew-lets-encrypt', help="Renew Let's Encrypt certificate")
def renew_lets_encrypt():
"Renew Let's Encrypt certificate"
from bench.config.lets_encrypt import renew_certs
renew_certs()
@click.command()
def shell(bench_path='.'):
if not os.environ.get('SHELL'):
print("Cannot get shell")
sys.exit(1)
if not os.path.exists('sites'):
print("sites dir doesn't exist")
sys.exit(1)
env = copy.copy(os.environ)
env['PS1'] = '(' + os.path.basename(os.path.dirname(os.path.abspath(__file__))) + ')' + env.get('PS1', '')
env['PATH'] = os.path.dirname(os.path.abspath(os.path.join('env','bin')) + ':' + env['PATH'])
os.chdir('sites')
os.execve(env['SHELL'], [env['SHELL']], env)
@click.command('backup')
@click.command('backup', help="Backup single site")
@click.argument('site')
def backup_site(site):
"backup site"
from bench.utils import get_sites, backup_site
if site not in get_sites(bench_path='.'):
print('site not found')
print('Site `{0}` not found'.format(site))
sys.exit(1)
backup_site(site, bench_path='.')
@click.command('backup-all-sites')
@click.command('backup-all-sites', help="Backup all sites in current bench")
def backup_all_sites():
"backup all sites"
from bench.utils import backup_all_sites
backup_all_sites(bench_path='.')
@click.command('release')
@click.command('release', help="Release a Frappe app (internal to the Frappe team)")
@click.argument('app')
@click.argument('bump-type', type=click.Choice(['major', 'minor', 'patch', 'stable', 'prerelease']))
@click.option('--from-branch', default='develop')
@ -162,48 +142,41 @@ def backup_all_sites():
@click.option('--repo-name')
@click.option('--dont-frontport', is_flag=True, default=False, help='Front port fixes to new branches, example merging hotfix(v10) into staging-fixes(v11)')
def release(app, bump_type, from_branch, to_branch, owner, repo_name, remote, dont_frontport):
"Release app (internal to the Frappe team)"
from bench.release import release
frontport = not dont_frontport
release(bench_path='.', app=app, bump_type=bump_type, from_branch=from_branch, to_branch=to_branch,
remote=remote, owner=owner, repo_name=repo_name, frontport=frontport)
release(bench_path='.', app=app, bump_type=bump_type, from_branch=from_branch, to_branch=to_branch, remote=remote, owner=owner, repo_name=repo_name, frontport=frontport)
@click.command('prepare-beta-release')
@click.command('prepare-beta-release', help="Prepare major beta release from develop branch")
@click.argument('app')
@click.option('--owner', default='frappe')
def prepare_beta_release(app, owner):
"""Prepare major beta release from develop branch"""
from bench.prepare_beta_release import prepare_beta_release
prepare_beta_release(bench_path='.', app=app, owner=owner)
@click.command('disable-production')
@click.command('disable-production', help="Disables production environment for the bench.")
def disable_production():
"""Disables production environment for the bench."""
from bench.config.production_setup import disable_production
disable_production(bench_path='.')
@click.command('src')
@click.command('src', help="Prints bench source folder path, which can be used as: cd `bench src`")
def bench_src():
"""Prints bench source folder path, which can be used as: cd `bench src` """
import bench
print(os.path.dirname(bench.__path__[0]))
@click.command('find')
@click.command('find', help="Finds benches recursively from location")
@click.argument('location', default='')
def find_benches(location):
"""Finds benches recursively from location"""
from bench.utils import find_benches
find_benches(directory=location)
@click.command('migrate-env')
@click.command('migrate-env', help="Migrate Virtual Environment to desired Python Version")
@click.argument('python', type=str)
@click.option('--no-backup', 'backup', is_flag=True, default=True)
def migrate_env(python, backup=True):
"""Migrate Virtual Environment to desired Python Version"""
from bench.utils import migrate_env
migrate_env(python=python, backup=backup)

View File

@ -19,9 +19,7 @@ def prepare_staging(bench_path, app, remote='upstream'):
print('No commits to release')
return
print()
print(message)
print()
click.confirm('Do you want to continue?', abort=True)
@ -52,13 +50,13 @@ def create_staging(repo_path, from_branch='develop'):
g.merge(from_branch, '--no-ff')
except git.exc.GitCommandError as e:
handle_merge_error(e, source=from_branch, target='staging')
g.checkout(from_branch)
try:
g.merge('staging')
except git.exc.GitCommandError as e:
handle_merge_error(e, source='staging', target=from_branch)
def push_commits(repo_path, remote='upstream'):
print('pushing staging branch of', repo_path)

View File

@ -83,9 +83,7 @@ def bump(bench_path, app, bump_type, from_branch, to_branch, remote, owner, repo
print('No commits to release')
return
print()
print(message)
print()
click.confirm('Do you want to continue?', abort=True)

View File

@ -1,14 +1,31 @@
import errno, glob, grp, itertools, json, logging, multiprocessing, os, platform, pwd, re, select, shutil, site, subprocess, sys
# imports - standard imports
import errno
import glob
import grp
import itertools
import json
import logging
import multiprocessing
import os
import platform
import pwd
import re
import select
import shutil
import site
import subprocess
import sys
from datetime import datetime
from distutils.spawn import find_executable
# imports - third party imports
import click
import requests
import semantic_version
from six import iteritems
from six.moves.urllib.parse import urlparse
# imports - module imports
import bench
from bench import env
class PatchError(Exception):
@ -61,6 +78,7 @@ def safe_decode(string, encoding = 'utf-8'):
pass
return string
def get_frappe(bench_path='.'):
frappe = get_env_cmd('frappe', bench_path=bench_path)
if not os.path.exists(frappe):
@ -68,12 +86,16 @@ def get_frappe(bench_path='.'):
print('bench get-app https://github.com/frappe/frappe.git')
return frappe
def get_env_cmd(cmd, bench_path='.'):
return os.path.abspath(os.path.join(bench_path, 'env', 'bin', cmd))
def init(path, apps_path=None, no_procfile=False, no_backups=False, no_auto_update=False,
frappe_path=None, frappe_branch=None, wheel_cache_dir=None, verbose=False, clone_from=None,
skip_redis_config_generation=False, clone_without_update=False, ignore_exist = False, skip_assets=False, python='python3'):
def init(path, apps_path=None, no_procfile=False, no_backups=False,
frappe_path=None, frappe_branch=None, verbose=False, clone_from=None,
skip_redis_config_generation=False, clone_without_update=False, ignore_exist=False, skip_assets=False,
python='python3'):
"""Initialize a new bench directory"""
from bench.app import get_app, install_apps_from_path
from bench.config import redis
from bench.config.common_site_config import make_config
@ -128,14 +150,107 @@ def init(path, apps_path=None, no_procfile=False, no_backups=False, no_auto_upda
setup_procfile(path)
if not no_backups:
setup_backups(bench_path=path)
if not no_auto_update:
setup_auto_update(bench_path=path)
copy_patches_txt(path)
def restart_update(kwargs):
args = ['--'+k for k, v in list(kwargs.items()) if v]
os.execv(sys.argv[0], sys.argv[:2] + args)
def update(pull=False, patch=False, build=False, bench=False, restart_supervisor=False,
restart_systemd=False, requirements=False, backup=True, force=False, reset=False):
"""command: bench update"""
if not is_bench_directory():
"""Update only bench CLI if bench update called from outside a bench"""
update_bench(bench_repo=True, requirements=True)
sys.exit(0)
from bench import patches
from bench.app import is_version_upgrade, pull_all_apps, validate_branch
from bench.config.common_site_config import get_config, update_config
bench_path = os.path.abspath(".")
patches.run(bench_path=bench_path)
conf = get_config(bench_path)
if conf.get('release_bench'):
print('Release bench detected, cannot update!')
sys.exit(1)
if not (pull or patch or build or bench or requirements):
pull, patch, build, bench, requirements = True, True, True, True, True
if bench and conf.get('update_bench_on_update'):
update_bench(bench_repo=True, requirements=False)
restart_update({
'pull': pull,
'patch': patch,
'build': build,
'requirements': requirements,
'backup': backup,
'restart-supervisor': restart_supervisor,
'reset': reset
})
validate_branch()
version_upgrade = is_version_upgrade()
if version_upgrade[0]:
if force:
print("Force flag has been used for a major version change in Frappe and it's apps. \nThis will take significant time to migrate and might break custom apps.")
else:
print("This update will cause a major version change in Frappe/ERPNext from {0} to {1}. \nThis would take significant time to migrate and might break custom apps.".format(*version_upgrade[1:]))
click.confirm('Do you want to continue?', abort=True)
if version_upgrade[0] or (not version_upgrade[0] and force):
validate_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
before_update(bench_path=bench_path, requirements=requirements)
conf.update({ "maintenance_mode": 1, "pause_scheduler": 1 })
update_config(conf, bench_path=bench_path)
if backup:
print('Backing up sites...')
backup_all_sites(bench_path=bench_path)
if pull:
pull_all_apps(bench_path=bench_path, reset=reset)
if requirements:
update_requirements(bench_path=bench_path)
update_node_packages(bench_path=bench_path)
if patch:
print('Patching sites...')
patch_sites(bench_path=bench_path)
if build:
build_assets(bench_path=bench_path)
if version_upgrade[0] or (not version_upgrade[0] and force):
post_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
if restart_supervisor or conf.get('restart_supervisor_on_update'):
restart_supervisor_processes(bench_path=bench_path)
if restart_systemd or conf.get('restart_systemd_on_update'):
restart_systemd_processes(bench_path=bench_path)
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")
def copy_patches_txt(bench_path):
shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'patches', 'patches.txt'),
os.path.join(bench_path, 'patches.txt'))
def clone_apps_from(bench_path, clone_from, update_app=True):
from .app import install_app
print('Copying apps from {0}...'.format(clone_from))
@ -172,14 +287,15 @@ def clone_apps_from(bench_path, clone_from, update_app=True):
for app in apps:
setup_app(app)
def exec_cmd(cmd, cwd='.'):
import shlex
print("{0}$ {1}{2}".format(color.silver, cmd, color.nc))
cmd = shlex.split(cmd)
subprocess.call(cmd, cwd=cwd, universal_newlines=True)
def which(executable, raise_err = False):
from distutils.spawn import find_executable
exec_ = find_executable(executable)
if not exec_ and raise_err:
@ -189,6 +305,7 @@ def which(executable, raise_err = False):
return exec_
def setup_env(bench_path='.', python = 'python3'):
python = which(python, raise_err = True)
pip = os.path.join('env', 'bin', 'pip')
@ -197,10 +314,12 @@ def setup_env(bench_path='.', python = 'python3'):
exec_cmd('{} -q install -U pip wheel six'.format(pip), cwd=bench_path)
exec_cmd('{} -q install -e git+https://github.com/frappe/python-pdfkit.git#egg=pdfkit'.format(pip), cwd=bench_path)
def setup_socketio(bench_path='.'):
exec_cmd("npm install socket.io redis express superagent cookie babel-core less chokidar \
babel-cli babel-preset-es2015 babel-preset-es2016 babel-preset-es2017 babel-preset-babili", cwd=bench_path)
def patch_sites(bench_path='.'):
bench.set_frappe_version(bench_path=bench_path)
@ -212,6 +331,7 @@ def patch_sites(bench_path='.'):
except subprocess.CalledProcessError:
raise PatchError
def build_assets(bench_path='.', app=None):
bench.set_frappe_version(bench_path=bench_path)
@ -223,19 +343,16 @@ def build_assets(bench_path='.', app=None):
command += ' --app {}'.format(app)
exec_cmd(command, cwd=bench_path)
def get_sites(bench_path='.'):
sites_path = os.path.join(bench_path, 'sites')
sites = (site for site in os.listdir(sites_path) if os.path.exists(os.path.join(sites_path, site, 'site_config.json')))
return sites
def get_bench_dir(bench_path='.'):
return os.path.abspath(bench_path)
def setup_auto_update(bench_path='.'):
logger.info('setting up auto update')
add_to_crontab('0 10 * * * cd {bench_dir} && {bench} update --auto >> {logfile} 2>&1'.format(bench_dir=get_bench_dir(bench_path=bench_path),
bench=os.path.join(get_bench_dir(bench_path=bench_path), 'env', 'bin', 'bench'),
logfile=os.path.join(get_bench_dir(bench_path=bench_path), 'logs', 'auto_update_log.log')))
def setup_backups(bench_path='.'):
logger.info('setting up backups')
@ -250,6 +367,7 @@ def setup_backups(bench_path='.'):
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')))
def add_to_crontab(line):
current_crontab = read_crontab()
line = str.encode(line)
@ -262,12 +380,14 @@ def add_to_crontab(line):
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
def update_bench(bench_repo=True, requirements=True):
logger.info("Updating bench")
@ -286,7 +406,10 @@ def update_bench(bench_repo=True, requirements=True):
logger.info("Bench Updated!")
def setup_sudoers(user):
from bench import env
if not os.path.exists('/etc/sudoers.d'):
os.makedirs('/etc/sudoers.d')
@ -318,6 +441,7 @@ def setup_sudoers(user):
os.chmod(sudoers_file, 0o440)
def setup_logging(bench_path='.'):
if os.path.exists(os.path.join(bench_path, 'logs')):
logger = logging.getLogger('bench')
@ -328,6 +452,7 @@ def setup_logging(bench_path='.'):
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
def get_program(programs):
program = None
for p in programs:
@ -336,9 +461,11 @@ def get_program(programs):
break
return program
def get_process_manager():
return get_program(['foreman', 'forego', 'honcho'])
def start(no_dev=False, concurrency=None, procfile=None):
program = get_process_manager()
if not program:
@ -356,6 +483,7 @@ def start(no_dev=False, concurrency=None, procfile=None):
os.execv(program, command)
def check_cmd(cmd, cwd='.'):
try:
subprocess.check_call(cmd, cwd=cwd, shell=True)
@ -363,6 +491,7 @@ def check_cmd(cmd, cwd='.'):
except subprocess.CalledProcessError:
return False
def get_git_version():
'''returns git version from `git --version`
extracts version number from string `get version 1.9.1` etc'''
@ -372,6 +501,7 @@ def get_git_version():
version = '.'.join(version.split('.')[0:2])
return float(version)
def check_git_for_shallow_clone():
from .config.common_site_config import get_config
config = get_config('.')
@ -387,6 +517,7 @@ def check_git_for_shallow_clone():
if git_version > 1.9:
return True
def get_cmd_output(cmd, cwd='.'):
try:
output = subprocess.check_output(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE).strip()
@ -397,6 +528,7 @@ def get_cmd_output(cmd, cwd='.'):
print(e.output)
raise
def safe_encode(what, encoding = 'utf-8'):
try:
what = what.encode(encoding)
@ -405,6 +537,7 @@ def safe_encode(what, encoding = 'utf-8'):
return what
def restart_supervisor_processes(bench_path='.', web_workers=False):
from .config.common_site_config import get_config
conf = get_config(bench_path=bench_path)
@ -434,26 +567,31 @@ def restart_supervisor_processes(bench_path='.', web_workers=False):
exec_cmd('sudo 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))
def set_default_site(site, bench_path='.'):
if site not in get_sites(bench_path=bench_path):
raise Exception("Site not in bench")
exec_cmd("{frappe} --use {site}".format(frappe=get_frappe(bench_path=bench_path), site=site),
cwd=os.path.join(bench_path, 'sites'))
def update_bench_requirements():
bench_req_file = os.path.join(os.path.dirname(bench.__path__[0]), 'requirements.txt')
install_requirements(bench_req_file, user=True)
def update_env_pip(bench_path):
env_pip = os.path.join(bench_path, 'env', 'bin', 'pip')
exec_cmd("{pip} install -q -U pip".format(pip=env_pip))
def update_requirements(bench_path='.'):
from bench.app import get_apps, install_app
print('Updating Python libraries...')
@ -467,13 +605,13 @@ def update_requirements(bench_path='.'):
for app in get_apps():
install_app(app, bench_path=bench_path)
def update_node_packages(bench_path='.'):
print('Updating node packages...')
from bench.app import get_develop_version
from distutils.version import LooseVersion
v = LooseVersion(get_develop_version('frappe', bench_path = bench_path))
# After rollup was merged, frappe_version = 10.1
# if develop_verion is 11 and up, only then install yarn
if v < LooseVersion('11.x.x-develop'):
@ -481,6 +619,7 @@ def update_node_packages(bench_path='.'):
else:
update_yarn_packages(bench_path)
def update_yarn_packages(bench_path='.'):
apps_dir = os.path.join(bench_path, 'apps')
@ -541,6 +680,7 @@ def install_requirements(req_file, user=False):
exec_cmd("{python} -m pip install {user_flag} -q -U -r {req_file}".format(python=python, user_flag=user_flag, req_file=req_file))
def backup_site(site, bench_path='.'):
bench.set_frappe_version(bench_path=bench_path)
@ -550,30 +690,38 @@ def backup_site(site, bench_path='.'):
else:
run_frappe_cmd('--site', site, 'backup', bench_path=bench_path)
def backup_all_sites(bench_path='.'):
for site in get_sites(bench_path=bench_path):
backup_site(site, bench_path=bench_path)
def is_root():
if os.getuid() == 0:
return True
return False
def set_mariadb_host(host, bench_path='.'):
update_common_site_config({'db_host': host}, bench_path=bench_path)
def set_redis_cache_host(host, bench_path='.'):
update_common_site_config({'redis_cache': "redis://{}".format(host)}, bench_path=bench_path)
def set_redis_queue_host(host, bench_path='.'):
update_common_site_config({'redis_queue': "redis://{}".format(host)}, bench_path=bench_path)
def set_redis_socketio_host(host, bench_path='.'):
update_common_site_config({'redis_socketio': "redis://{}".format(host)}, bench_path=bench_path)
def update_common_site_config(ddict, bench_path='.'):
update_json_file(os.path.join(bench_path, 'sites', 'common_site_config.json'), ddict)
def update_json_file(filename, ddict):
if os.path.exists(filename):
with open(filename, 'r') as f:
@ -586,6 +734,7 @@ def update_json_file(filename, ddict):
with open(filename, 'w') as f:
json.dump(content, f, indent=1, sort_keys=True)
def drop_privileges(uid_name='nobody', gid_name='nogroup'):
# from http://stackoverflow.com/a/2699996
if os.getuid() != 0:
@ -606,6 +755,7 @@ def drop_privileges(uid_name='nobody', gid_name='nogroup'):
# Ensure a very conservative umask
os.umask(0o22)
def fix_prod_setup_perms(bench_path='.', frappe_user=None):
from .config.common_site_config import get_config
@ -623,6 +773,7 @@ def fix_prod_setup_perms(bench_path='.', frappe_user=None):
gid = grp.getgrnam(frappe_user).gr_gid
os.chown(path, uid, gid)
def fix_file_perms():
for dir_path, dirs, files in os.walk('.'):
for _dir in dirs:
@ -635,10 +786,12 @@ def fix_file_perms():
if not _file.startswith('activate'):
os.chmod(os.path.join(bin_dir, _file), 0o755)
def get_current_frappe_version(bench_path='.'):
from .app import get_current_frappe_version as fv
return fv(bench_path=bench_path)
def run_frappe_cmd(*args, **kwargs):
from .cli import from_command_line
@ -662,7 +815,7 @@ def run_frappe_cmd(*args, **kwargs):
if return_code > 0:
sys.exit(return_code)
#raise CommandFailedError(args)
def get_frappe_cmd_output(*args, **kwargs):
bench_path = kwargs.get('bench_path', '.')
@ -670,24 +823,12 @@ def get_frappe_cmd_output(*args, **kwargs):
sites_dir = os.path.join(bench_path, 'sites')
return subprocess.check_output((f, '-m', 'frappe.utils.bench_helper', 'frappe') + args, cwd=sites_dir)
def validate_upgrade(from_ver, to_ver, bench_path='.'):
if to_ver >= 6:
if not find_executable('npm') and not (find_executable('node') or find_executable('nodejs')):
raise Exception("Please install nodejs and npm")
def pre_upgrade(from_ver, to_ver, bench_path='.'):
pip = os.path.join(bench_path, 'env', 'bin', 'pip')
if from_ver <= 4 and to_ver >= 5:
from .migrate_to_v5 import remove_shopping_cart
apps = ('frappe', 'erpnext')
remove_shopping_cart(bench_path=bench_path)
for app in apps:
cwd = os.path.abspath(os.path.join(bench_path, 'apps', app))
if os.path.exists(cwd):
exec_cmd("git clean -dxf", cwd=cwd)
exec_cmd("{pip} install --upgrade -e {app}".format(pip=pip, app=cwd))
def post_upgrade(from_ver, to_ver, bench_path='.'):
from .config.common_site_config import get_config
@ -695,8 +836,7 @@ def post_upgrade(from_ver, to_ver, bench_path='.'):
from .config.supervisor import generate_supervisor_config
from .config.nginx import make_nginx_conf
conf = get_config(bench_path=bench_path)
print("-"*80)
print("Your bench was upgraded to version {0}".format(to_ver))
print("-" * 80 + "Your bench was upgraded to version {0}".format(to_ver))
if conf.get('restart_supervisor_on_update'):
redis.generate_config(bench_path=bench_path)
@ -972,6 +1112,7 @@ def in_virtual_env():
return False
def migrate_env(python, backup=False):
from bench.config.common_site_config import get_config
from bench.app import get_apps
@ -1028,3 +1169,17 @@ def migrate_env(python, backup=False):
except:
log.debug('Migration Error')
raise
def find_parent_bench(path):
"""Checks if parent directories are benches"""
if is_bench_directory(directory=path):
return path
home_path = os.path.expanduser("~")
root_path = os.path.abspath(os.sep)
if path not in {home_path, root_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)