2
0
mirror of https://github.com/frappe/bench.git synced 2025-01-10 00:37:51 +00:00

Merge branch 'staging' into v5.x

This commit is contained in:
Gavin D'souza 2023-06-09 17:24:52 +05:30
commit 93eedbd876
No known key found for this signature in database
GPG Key ID: 3A7BF4D4340DE6F7
10 changed files with 107 additions and 59 deletions

View File

@ -21,12 +21,17 @@ jobs:
name: Easy Install Test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Perform production easy install
run: |
python3 ${GITHUB_WORKSPACE}/easy-install.py -p -n actions_test --email test@frappe.io
docker compose -p actions_test exec backend bench version --format json
docker compose -p actions_test exec backend bench --site site1.local list-apps --format json
result=$(curl -sk https://127.0.0.1/api/method/ping | jq -r ."message")
docker compose -p actions_test exec backend bench --site site1.localhost list-apps --format json
result=$(curl -H "Host: site1.localhost" -sk https://127.0.0.1/api/method/ping | jq -r ."message")
if [[ "$result" == "pong" ]]; then echo "New instance works fine"; else exit 1; fi
docker compose -p actions_test down
docker volume prune -f

View File

@ -198,7 +198,7 @@ class App(AppMeta):
@step(title="Archiving App {repo}", success="App {repo} Archived")
def remove(self, no_backup: bool = False):
active_app_path = os.path.join("apps", self.repo)
active_app_path = os.path.join("apps", self.app_name)
if no_backup:
if not os.path.islink(active_app_path):
@ -209,7 +209,7 @@ class App(AppMeta):
else:
archived_path = os.path.join("archived", "apps")
archived_name = get_available_folder_name(
f"{self.repo}-{date.today()}", archived_path
f"{self.app_name}-{date.today()}", archived_path
)
archived_app_path = os.path.join(archived_path, archived_name)
@ -233,7 +233,7 @@ class App(AppMeta):
verbose = bench.cli.verbose or verbose
app_name = get_app_name(self.bench.name, self.app_name)
if not resolved and self.repo != "frappe" and not ignore_resolution:
if not resolved and self.app_name != "frappe" and not ignore_resolution:
click.secho(
f"Ignoring dependencies of {self.name}. To install dependencies use --resolve-deps",
fg="yellow",
@ -262,7 +262,7 @@ class App(AppMeta):
from bench.utils.app import get_required_deps, required_apps_from_hooks
if self.on_disk:
required_deps = os.path.join(self.mount_path, self.repo, "hooks.py")
required_deps = os.path.join(self.mount_path, self.app_name, "hooks.py")
try:
return required_apps_from_hooks(required_deps, local=True)
except IndexError:
@ -290,16 +290,16 @@ def make_resolution_plan(app: App, bench: "Bench"):
decide what apps and versions to install and in what order
"""
resolution = OrderedDict()
resolution[app.repo] = app
resolution[app.app_name] = app
for app_name in app._get_dependencies():
dep_app = App(app_name, bench=bench)
is_valid_frappe_branch(dep_app.url, dep_app.branch)
dep_app.required_by = app.name
if dep_app.repo in resolution:
click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow")
if dep_app.app_name in resolution:
click.secho(f"{dep_app.app_name} is already resolved skipping", fg="yellow")
continue
resolution[dep_app.repo] = dep_app
resolution[dep_app.app_name] = dep_app
resolution.update(make_resolution_plan(dep_app, bench))
app.local_resolution = [repo_name for repo_name, _ in reversed(resolution.items())]
return resolution
@ -315,7 +315,7 @@ def get_excluded_apps(bench_path="."):
def add_to_excluded_apps_txt(app, bench_path="."):
if app == "frappe":
raise ValueError("Frappe app cannot be excludeed from update")
raise ValueError("Frappe app cannot be excluded from update")
if app not in os.listdir("apps"):
raise ValueError(f"The app {app} does not exist")
apps = get_excluded_apps(bench_path=bench_path)

View File

@ -131,6 +131,7 @@ class Bench(Base, Validator):
except InvalidRemoteException:
if not force:
raise
self.apps.sync()
# self.build() - removed because it seems unnecessary
self.reload(_raise=False)
@ -309,13 +310,13 @@ class BenchApps(MutableSequence):
def add(self, app: "App"):
app.get()
app.install()
super().append(app.repo)
super().append(app.app_name)
self.apps.sort()
def remove(self, app: "App", no_backup: bool = False):
app.uninstall()
app.remove(no_backup=no_backup)
super().remove(app.repo)
super().remove(app.app_name)
def append(self, app: "App"):
return self.add(app)

View File

@ -28,6 +28,8 @@ from bench.utils import (
get_cmd_from_sysargv,
)
from bench.utils.bench import get_env_cmd
from importlib.util import find_spec
# these variables are used to show dynamic outputs on the terminal
dynamic_feed = False
@ -38,6 +40,7 @@ bench.LOG_BUFFER = []
change_uid_msg = "You should not run this command as root"
src = os.path.dirname(__file__)
SKIP_MODULE_TRACEBACK = ("click",)
@contextmanager
@ -118,6 +121,8 @@ def cli():
_opts = [x.opts + x.secondary_opts for x in bench_command.params]
opts = {item for sublist in _opts for item in sublist}
setup_exception_handler()
# handle usages like `--use-feature='feat-x'` and `--use-feature 'feat-x'`
if cmd_from_sys and cmd_from_sys.split("=", 1)[0].strip() in opts:
bench_command()
@ -240,3 +245,26 @@ def setup_clear_cache():
return f(*args, **kwargs)
os.chdir = _chdir
def setup_exception_handler():
from traceback import format_exception
from bench.exceptions import CommandFailedError
def handle_exception(exc_type, exc_info, tb):
if exc_type == CommandFailedError:
print("".join(generate_exc(exc_type, exc_info, tb)))
else:
sys.__excepthook__(exc_type, exc_info, tb)
def generate_exc(exc_type, exc_info, tb):
TB_SKIP = [
os.path.dirname(find_spec(module).origin) for module in SKIP_MODULE_TRACEBACK
]
for tb_line in format_exception(exc_type, exc_info, tb):
for skip_module in TB_SKIP:
if skip_module not in tb_line:
yield tb_line
sys.excepthook = handle_exception

View File

@ -182,12 +182,12 @@ programs={{ bench_name }}-frappe-web {%- if node -%} ,{{ bench_name }}-node-sock
{% if use_rq %}
[group:{{ bench_name }}-workers]
programs={{ bench_name }}-frappe-schedule,{{ bench_name }}-frappe-default-worker,{{ bench_name }}-frappe-short-worker,{{ bench_name }}-frappe-long-worker
programs={{ bench_name }}-frappe-schedule,{{ bench_name }}-frappe-default-worker,{{ bench_name }}-frappe-short-worker,{{ bench_name }}-frappe-long-worker{%- for worker_name in workers -%},{{ bench_name }}-frappe-{{ worker_name }}-worker{%- endfor %}
{% else %}
[group:{{ bench_name }}-workers]
programs={{ bench_name }}-frappe-workerbeat,{{ bench_name }}-frappe-worker,{{ bench_name }}-frappe-longjob-worker,{{ bench_name }}-frappe-async-worker
programs={{ bench_name }}-frappe-workerbeat,{{ bench_name }}-frappe-worker,{{ bench_name }}-frappe-longjob-worker,{{ bench_name }}-frappe-async-worker{%- for worker_name in workers -%},{{ bench_name }}-frappe-{{ worker_name }}-worker{%- endfor %}
{% endif %}

View File

@ -28,8 +28,8 @@ class TestBenchInit(TestBenchBase):
self.init_bench(bench_name, **kwargs)
app = App("file:///tmp/frappe")
self.assertTupleEqual(
(app.mount_path, app.url, app.repo, app.org),
("/tmp/frappe", "file:///tmp/frappe", "frappe", "frappe"),
(app.mount_path, app.url, app.repo, app.app_name, app.org),
("/tmp/frappe", "file:///tmp/frappe", "frappe", "frappe", "frappe"),
)
self.assert_folders(bench_name)
self.assert_virtual_env(bench_name)

View File

@ -101,4 +101,6 @@ class TestUtils(unittest.TestCase):
def test_ssh_ports(self):
app = App("git@github.com:22:frappe/frappe")
self.assertEqual((app.use_ssh, app.org, app.repo), (True, "frappe", "frappe"))
self.assertEqual(
(app.use_ssh, app.org, app.repo, app.app_name), (True, "frappe", "frappe", "frappe")
)

View File

@ -155,7 +155,7 @@ def exec_cmd(cmd, cwd=".", env=None, _raise=True):
if return_code:
logger.warning(f"{cmd_log} executed with exit code {return_code}")
if _raise:
raise CommandFailedError from subprocess.CalledProcessError(return_code, cmd)
raise CommandFailedError(cmd) from subprocess.CalledProcessError(return_code, cmd)
return return_code

View File

@ -155,7 +155,7 @@ def update_npm_packages(bench_path=".", apps=None):
else:
package_json[key] = value
if package_json is {}:
if package_json == {}:
with open(os.path.join(os.path.dirname(__file__), "package.json")) as f:
package_json = json.loads(f.read())

View File

@ -73,13 +73,13 @@ def get_from_env(dir, file) -> Dict:
def write_to_env(
wd: str,
site: str,
sites,
db_pass: str,
admin_pass: str,
email: str,
erpnext_version: str = None,
) -> None:
site_name = site or ""
quoted_sites = ",".join([f"`{site}`" for site in sites]).strip(",")
example_env = get_from_env(wd, "example.env")
erpnext_version = erpnext_version or example_env["ERPNEXT_VERSION"]
with open(os.path.join(wd, ".env"), "w") as f:
@ -93,8 +93,8 @@ def write_to_env(
"REDIS_QUEUE=redis-queue:6379\n",
"REDIS_SOCKETIO=redis-socketio:6379\n",
f"LETSENCRYPT_EMAIL={email}\n",
f"FRAPPE_SITE_NAME_HEADER={site_name}\n",
f"SITE_ADMIN_PASS={admin_pass}",
f"SITE_ADMIN_PASS={admin_pass}\n",
f"SITES={quoted_sites}\n",
]
)
@ -114,7 +114,7 @@ def check_repo_exists() -> bool:
return os.path.exists(os.path.join(os.getcwd(), "frappe_docker"))
def setup_prod(project: str, sitename: str, email: str, version: str = None) -> None:
def setup_prod(project: str, sites, email: str, version: str = None) -> None:
if check_repo_exists():
compose_file_name = os.path.join(os.path.expanduser("~"), f"{project}-compose.yml")
docker_repo_path = os.path.join(os.getcwd(), "frappe_docker")
@ -129,7 +129,7 @@ def setup_prod(project: str, sitename: str, email: str, version: str = None) ->
if not os.path.exists(os.path.join(docker_repo_path, ".env")):
admin_pass = generate_pass()
db_pass = generate_pass(9)
write_to_env(docker_repo_path, sitename, db_pass, admin_pass, email, version)
write_to_env(docker_repo_path, sites, db_pass, admin_pass, email, version)
cprint(
"\nA .env file is generated with basic configs. Please edit it to fit to your needs \n",
level=3,
@ -193,40 +193,13 @@ def setup_prod(project: str, sitename: str, email: str, version: str = None) ->
cprint(" Docker Compose failed, please check the container logs\n", e)
sys.exit(1)
cprint(f"\nCreating site: {sitename} \n", level=3)
for sitename in sites:
create_site(sitename, project, db_pass, admin_pass)
try:
subprocess.run(
[
which("docker"),
"compose",
"-p",
project,
"exec",
"backend",
"bench",
"new-site",
sitename,
"--no-mariadb-socket",
"--db-root-password",
db_pass,
"--admin-password",
admin_pass,
"--install-app",
"erpnext",
"--set-default",
],
check=True,
)
logging.info("New site creation completed")
except Exception as e:
logging.error("Bench site creation failed", exc_info=True)
cprint("Bench Site creation failed\n", e)
sys.exit(1)
else:
install_docker()
clone_frappe_docker_repo()
setup_prod(project, sitename, email, version) # Recursive
setup_prod(project, sites, email, version) # Recursive
def setup_dev_instance(project: str):
@ -294,6 +267,43 @@ def install_docker():
sys.exit(1)
def create_site(
sitename: str,
project: str,
db_pass: str,
admin_pass: str,
):
cprint(f"\nCreating site: {sitename} \n", level=3)
try:
subprocess.run(
[
which("docker"),
"compose",
"-p",
project,
"exec",
"backend",
"bench",
"new-site",
sitename,
"--no-mariadb-socket",
"--db-root-password",
db_pass,
"--admin-password",
admin_pass,
"--install-app",
"erpnext",
"--set-default",
],
check=True,
)
logging.info("New site creation completed")
except Exception as e:
logging.error(f"Bench site creation failed for {sitename}", exc_info=True)
cprint(f"Bench Site creation failed for {sitename}\n", e)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Install Frappe with Docker")
parser.add_argument(
@ -305,8 +315,10 @@ if __name__ == "__main__":
parser.add_argument(
"-s",
"--sitename",
help="The Site Name for your production site",
default="site1.local",
help="Site Name(s) for your production bench",
default=["site1.localhost"],
action="append",
dest="sites",
)
parser.add_argument("-n", "--project", help="Project Name", default="frappe")
parser.add_argument(
@ -326,6 +338,6 @@ if __name__ == "__main__":
if "example.com" in args.email:
cprint("Emails with example.com not acceptable", level=1)
sys.exit(1)
setup_prod(args.project, args.sitename, args.email, args.version)
setup_prod(args.project, args.sites, args.email, args.version)
else:
parser.print_help()