Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: merge develop into staging #1459

Merged
merged 19 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/easy-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 9 additions & 9 deletions bench/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def get(self):

@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):
Expand All @@ -209,7 +209,7 @@ def remove(self, no_backup: bool = False):
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)

Expand All @@ -233,7 +233,7 @@ def install(

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",
Expand Down Expand Up @@ -262,7 +262,7 @@ def _get_dependencies(self):
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:
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions bench/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def uninstall(self, app, no_backup=False, force=False):
except InvalidRemoteException:
if not force:
raise

self.apps.sync()
# self.build() - removed because it seems unnecessary
self.reload(_raise=False)
Expand Down Expand Up @@ -309,13 +310,13 @@ def insert(self, key, value):
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)
Expand Down
28 changes: 28 additions & 0 deletions bench/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
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
Expand All @@ -38,6 +40,7 @@

change_uid_msg = "You should not run this command as root"
src = os.path.dirname(__file__)
SKIP_MODULE_TRACEBACK = ("click",)


@contextmanager
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -240,3 +245,26 @@ def _chdir(*args, **kwargs):
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
4 changes: 2 additions & 2 deletions bench/config/templates/supervisor.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}

Expand Down
4 changes: 2 additions & 2 deletions bench/tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def test_init(self, bench_name="test-bench", **kwargs):
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)
Expand Down
4 changes: 3 additions & 1 deletion bench/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,6 @@ def test_app_states(self):

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")
)
6 changes: 3 additions & 3 deletions bench/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ def exec_cmd(cmd, cwd=".", env=None, _raise=True):
cwd_info = f"cd {cwd} && " if cwd != "." else ""
cmd_log = f"{cwd_info}{cmd}"
logger.debug(cmd_log)
cmd = split(cmd)
return_code = subprocess.call(cmd, cwd=cwd, universal_newlines=True, env=env)
spl_cmd = split(cmd)
return_code = subprocess.call(spl_cmd, cwd=cwd, universal_newlines=True, env=env)
if return_code:
logger.warning(f"{cmd_log} executed with exit code {return_code}")
if _raise:
raise CommandFailedError
raise CommandFailedError(cmd) from subprocess.CalledProcessError(return_code, cmd)
return return_code


Expand Down
8 changes: 7 additions & 1 deletion bench/utils/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down Expand Up @@ -298,6 +298,12 @@ def restart_supervisor_processes(bench_path=".", web_workers=False, _raise=False
sudo = "sudo "
supervisor_status = get_cmd_output("sudo supervisorctl status", cwd=bench_path)

if not sudo and (
"error: <class 'PermissionError'>, [Errno 13] Permission denied" in supervisor_status
):
sudo = "sudo "
supervisor_status = get_cmd_output("sudo supervisorctl status", cwd=bench_path)

if web_workers and f"{bench_name}-web:" in supervisor_status:
group = f"{bench_name}-web:\t"

Expand Down
90 changes: 51 additions & 39 deletions easy-install.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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",
]
)

Expand All @@ -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")
Expand All @@ -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,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand All @@ -305,8 +315,10 @@ def install_docker():
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(
Expand All @@ -326,6 +338,6 @@ def install_docker():
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()