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

Feat: reset command #213

Merged
merged 2 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 25 additions & 6 deletions frappe_manager/commands.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from pathlib import Path
from frappe_manager.site_manager import bench_operations
from frappe_manager.site_manager.site_exceptions import BenchNotRunning
from frappe_manager.utils.site import pull_docker_images
import typer
Expand Down Expand Up @@ -176,9 +175,7 @@ def create(
template: Annotated[bool, typer.Option(help="Create template bench.")] = False,
admin_pass: Annotated[
str,
typer.Option(
help="Default Password for the standard 'Administrator' User. This will be used as the password for the Administrator User for all new bench."
),
typer.Option(help="Password for the 'Administrator' User."),
] = "admin",
ssl: Annotated[
SUPPORTED_SSL_TYPES, typer.Option(help="Enable https", show_default=True)
Expand Down Expand Up @@ -523,12 +520,12 @@ def update(
if developer_mode == EnableDisableOptionsEnum.enable:
bench.bench_config.developer_mode = True
richprint.print("Enabling frappe developer mode.")
bench.common_bench_config_set({'developer_mode': bench.bench_config.developer_mode})
bench.set_common_bench_config({'developer_mode': bench.bench_config.developer_mode})
richprint.print("Enabled frappe developer mode.")
elif developer_mode == EnableDisableOptionsEnum.disable:
bench.bench_config.developer_mode = False
richprint.print("Disabling frappe developer mode.")
bench.common_bench_config_set({'developer_mode': bench.bench_config.developer_mode})
bench.set_common_bench_config({'developer_mode': bench.bench_config.developer_mode})
richprint.print("Enabled frappe developer mode.")

bench_config_save = True
Expand Down Expand Up @@ -622,3 +619,25 @@ def update(

if bench_config_save:
bench.save_bench_config()


@app.command()
def reset(
ctx: typer.Context,
benchname: Annotated[
Optional[str],
typer.Argument(
help="Name of the bench.", autocompletion=sites_autocompletion_callback, callback=sitename_callback
),
] = None,
admin_pass: Annotated[
Optional[str],
typer.Option(help="Password for the 'Administrator' User."),
] = None,
):
"""Reset bench site and reinstall all installed apps."""

services_manager = ctx.obj["services"]
verbose = ctx.obj['verbose']
bench = Bench.get_object(benchname, services_manager)
bench.reset(admin_pass)
30 changes: 27 additions & 3 deletions frappe_manager/site_manager/bench_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def create_fm_bench(self):
common_site_config_data = self.bench.bench_config.get_commmon_site_config_data(
self.bench.services.database_manager.database_server_info
)
self.bench.common_bench_config_set(common_site_config_data)
self.bench.set_common_bench_config(common_site_config_data)
richprint.print("Configured common_site_config.json")

richprint.change_head("Configuring frappe server")
Expand All @@ -58,6 +58,8 @@ def create_fm_bench(self):

self.bench_install_apps_site()

self.bench.set_bench_site_config({'admin_password': self.bench.bench_config.admin_pass})

def create_bench_site(self):
new_site_command = self.bench_cli_cmd + ["new-site"]
new_site_command += ["--db-root-password", self.bench.services.database_manager.database_server_info.password]
Expand Down Expand Up @@ -249,11 +251,15 @@ def bench_install_apps(self, apps_lists, already_installed_apps: Dict = STABLE_A

richprint.print(f"Builded and Installed app [blue]{app}{' -> ' + branch if branch else ''}[/blue] in env.")

def bench_install_apps_site(self):
def get_current_apps_list(self):
"""Return apps which are available in apps directory"""

apps_dir = self.frappe_bench_dir / 'apps'
apps_dirs: List[Path] = [item for item in apps_dir.iterdir() if item.is_dir()]
return apps_dirs

for app in apps_dirs:
def bench_install_apps_site(self):
for app in self.get_current_apps_list():
richprint.change_head(f"Installing app {app.name} in site.")
self.bench_install_app_site(app.name)
richprint.print(f"Installed app {app.name} in site.")
Expand Down Expand Up @@ -357,3 +363,21 @@ def check_required_docker_images_available(self):
for image in not_available_images:
richprint.error(f"Docker image '{image}' is not available locally")
raise BenchOperationRequiredDockerImagesNotAvailable(self.bench.name, 'fm self update images')

def reset_bench_site(self, admin_password: str):

global_db_info = self.bench.services.database_manager.database_server_info
reset_bench_site_command = self.bench_cli_cmd + ["--site", self.bench.name]
reset_bench_site_command += ['reinstall', '--admin-password', admin_password]
reset_bench_site_command += ['--db-root-username', global_db_info.user]
reset_bench_site_command += ['--db-root-password', global_db_info.password]
reset_bench_site_command += ['--yes']

reset_bench_site_command = " ".join(reset_bench_site_command)

self.frappe_container_run(
reset_bench_site_command,
raise_exception_obj=BenchOperationException(
bench_name=self.bench.name, message=f'Failed to reset bench site {self.bench.name}.'
),
)
77 changes: 60 additions & 17 deletions frappe_manager/site_manager/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
get_current_fm_version,
log_file,
get_container_name_prefix,
save_dict_to_file,
)
from frappe_manager.utils.docker import host_run_cp
from frappe_manager import (
Expand Down Expand Up @@ -126,7 +127,7 @@ def get_object(

def sync_bench_config_configuration(self):
# set developer_mode based on config
self.common_bench_config_set({'developer_mode': self.bench_config.developer_mode})
self.set_common_bench_config({'developer_mode': self.bench_config.developer_mode})

# dev or prod
self.switch_bench_env()
Expand Down Expand Up @@ -255,37 +256,43 @@ def create(self, is_template_bench: bool = False):
if not remove_status:
self.info()

def common_bench_config_set(self, config: dict):
def set_common_bench_config(self, config: dict):
"""
Sets the values in the common_site_config.json file.

Args:
config (dict): A dictionary containing the key-value pairs to be set in the common_site_config.json file.
config (dict): A dictionary containing the key-value pairs
"""
common_bench_config_path = self.path / "workspace/frappe-bench/sites/common_site_config.json"

if not common_bench_config_path.exists():
raise BenchException(self.name, message='common_site_config.json not found.')

common_site_config = {}
raise BenchException(self.name, message=f'File not found {common_bench_config_path.name}.')

with open(common_bench_config_path, "r") as f:
common_site_config = json.load(f)
save_dict_to_file(config, common_bench_config_path)

for key, value in config.items():
common_site_config[key] = value
def set_bench_site_config(self, config: dict):
"""
Sets the values in the bench's site site_config.json file.

with open(common_bench_config_path, "w") as f:
json.dump(common_site_config, f)
Args:
config (dict): A dictionary containing the key-value pairs
"""
site_config_path = self.path / "workspace/frappe-bench/sites" / self.name / "site_config.json"
if not site_config_path.exists():
raise BenchException(self.name, message=f'File not found {site_config_path.name}.')
save_dict_to_file(config, site_config_path)

def get_common_bench_config(self):
common_bench_config_path = self.path / "workspace/frappe-bench/sites/common_site_config.json"

if not common_bench_config_path.exists():
raise BenchException(self.name, message='common_site_config.json not found.')

return json.loads(common_bench_config_path.read_text())

def get_bench_site_config(self):
site_config_path = self.path / "workspace/frappe-bench/sites" / self.name / "site_config.json"
if not site_config_path.exists():
raise BenchException(self.name, message='site_config.json not found.')
return json.loads(site_config_path.read_text())

def generate_compose(self, inputs: dict) -> None:
"""
Generates the compose file for the site based on the given inputs.
Expand Down Expand Up @@ -337,7 +344,7 @@ def sync_bench_common_site_config(self, services_db_host: str, services_db_port:
"redis_queue": f"redis://{container_prefix}-redis-queue:6379",
"redis_socketio": f"redis://{container_prefix}-redis-cache:6379",
}
self.common_bench_config_set(common_site_config_data)
self.set_common_bench_config(common_site_config_data)

def create_compose_dirs(self) -> bool:
"""
Expand Down Expand Up @@ -664,6 +671,14 @@ def info(self):

protocol = 'https' if self.has_certificate() else 'http'

# get admin pass from site_config.json if available use that
admin_pass = self.bench_config.admin_pass + " (default)"

site_config = self.get_bench_site_config()

if 'admin_password' in site_config:
admin_pass = site_config['admin_password']

ssl_service_type = f'{self.bench_config.ssl.ssl_type.value}'

if self.bench_config.ssl.ssl_type == SUPPORTED_SSL_TYPES.le:
Expand All @@ -675,7 +690,7 @@ def info(self):
"Bench Url": f"{protocol}://{self.name}",
"Bench Root": f"[link=file://{self.path.absolute()}]{self.path.absolute()}[/link]",
"Frappe Username": "administrator",
"Frappe Password": self.bench_config.admin_pass,
"Frappe Password": admin_pass,
"Root DB User": services_db_info.user,
"Root DB Password": services_db_info.password,
"Root DB Host": services_db_info.host,
Expand Down Expand Up @@ -1167,3 +1182,31 @@ def is_supervisord_running(self, interval: int = 2, timeout: int = 30):
time.sleep(interval)
continue
return False

def reset(self, admin_password: Optional[str] = None):
admin_pass = None

if admin_password:
admin_pass = admin_password
else:
if not admin_pass:
site_config = self.get_bench_site_config()
if 'admin_password' in site_config:
admin_pass = site_config['admin_password']
richprint.print("Using admin_password defined in site_config.json")

if not admin_pass:
common_site_config = self.get_common_bench_config()
if 'admin_password' in common_site_config:
admin_pass = common_site_config['admin_password']
richprint.print("Using admin_password defined in common_site_config.json")

if not admin_pass:
admin_pass = richprint.prompt_ask(prompt=f"Please enter admin password for site {self.name}")

richprint.change_head(f"Resetting bench site {self.name}")

self.benchops.reset_bench_site(admin_pass)
self.set_bench_site_config({'admin_password': admin_pass})

richprint.print(f"Reset bench site {self.name}")
8 changes: 8 additions & 0 deletions frappe_manager/utils/examples.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,5 +263,13 @@
]
}
}
},
"reset": {
"examples": [
{
"desc": "Reset bench {benchname}",
"code": ""
}
]
}
}
20 changes: 20 additions & 0 deletions frappe_manager/utils/helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import importlib
import json
from cryptography.hazmat.backends import default_backend
from datetime import datetime
from cryptography import x509
from io import StringIO
import sys
from typing import Optional
from frappe_manager.site_manager.site_exceptions import BenchException
from frappe_manager.utils.docker import run_command_with_exit_code
import requests
import subprocess
Expand Down Expand Up @@ -121,6 +123,7 @@ def check_and_display_port_status(ports_to_check: list, exclude=[]):
f"Ports {', '.join(map(str, already_binded))} {'are' if len(already_binded) > 1 else 'is'} currently in use. Please free up these ports."
)


def generate_random_text(length=50):
"""
Generate a random text of specified length.
Expand Down Expand Up @@ -446,3 +449,20 @@ def get_certificate_expiry_date(fullchain_path: Path) -> datetime:
else:
expiry_date: datetime = cert.not_valid_after
return expiry_date


def save_dict_to_file(config: dict, json_file_path: Path):
"""
Sets the config value in the json_file_path file.

Args:
config (dict): A dictionary containing the key-value pairs.
"""

final_config = {}
with open(json_file_path, "r") as f:
final_config = json.load(f)
for key, value in config.items():
final_config[key] = value
with open(json_file_path, "w") as f:
json.dump(final_config, f)