2021-11-12 12:21:20 +00:00
|
|
|
# imports - standard imports
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
from glob import glob
|
|
|
|
from shlex import split
|
2021-11-17 18:33:42 +00:00
|
|
|
from typing import List, Tuple
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
# imports - third party imports
|
|
|
|
import click
|
2022-02-04 15:20:35 +00:00
|
|
|
import requests
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
# imports - module imports
|
2021-11-12 20:18:30 +00:00
|
|
|
from bench import PROJECT_NAME, VERSION
|
|
|
|
|
2021-11-19 03:46:19 +00:00
|
|
|
from bench.exceptions import CommandFailedError, InvalidRemoteException, ValidationError
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
|
2021-11-12 20:18:30 +00:00
|
|
|
logger = logging.getLogger(PROJECT_NAME)
|
2021-11-12 21:29:01 +00:00
|
|
|
bench_cache_file = ".bench.cmd"
|
2021-11-15 07:08:18 +00:00
|
|
|
paths_in_app = ("hooks.py", "modules.txt", "patches.txt")
|
2021-11-12 21:29:01 +00:00
|
|
|
paths_in_bench = ("apps", "sites", "config", "logs", "config/pids")
|
|
|
|
sudoers_file = "/etc/sudoers.d/frappe"
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
def is_bench_directory(directory=os.path.curdir):
|
|
|
|
is_bench = True
|
|
|
|
|
|
|
|
for folder in paths_in_bench:
|
|
|
|
path = os.path.abspath(os.path.join(directory, folder))
|
|
|
|
is_bench = is_bench and os.path.exists(path)
|
|
|
|
|
|
|
|
return is_bench
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def is_frappe_app(directory: str) -> bool:
|
2021-11-12 12:21:20 +00:00
|
|
|
is_frappe_app = True
|
|
|
|
|
|
|
|
for folder in paths_in_app:
|
|
|
|
if not is_frappe_app:
|
|
|
|
break
|
|
|
|
|
|
|
|
path = glob(os.path.join(directory, "**", folder))
|
|
|
|
is_frappe_app = is_frappe_app and path
|
|
|
|
|
|
|
|
return bool(is_frappe_app)
|
|
|
|
|
|
|
|
|
2022-03-07 12:36:03 +00:00
|
|
|
def is_valid_frappe_branch(frappe_path, frappe_branch):
|
2022-02-07 04:43:15 +00:00
|
|
|
if "http" in frappe_path:
|
2022-03-07 12:36:03 +00:00
|
|
|
frappe_path = frappe_path.replace(".git", "")
|
|
|
|
try:
|
|
|
|
owner, repo = frappe_path.split("/")[3], frappe_path.split("/")[4]
|
|
|
|
except IndexError:
|
|
|
|
raise InvalidRemoteException
|
|
|
|
git_api = f"https://api.github.com/repos/{owner}/{repo}/branches"
|
|
|
|
res = requests.get(git_api).json()
|
|
|
|
if "message" in res or frappe_branch not in [x["name"] for x in res]:
|
|
|
|
raise InvalidRemoteException
|
2022-02-04 15:20:35 +00:00
|
|
|
|
|
|
|
|
2021-11-17 19:06:13 +00:00
|
|
|
def log(message, level=0, no_log=False):
|
|
|
|
import bench
|
|
|
|
import bench.cli
|
|
|
|
|
2021-11-12 12:21:20 +00:00
|
|
|
levels = {
|
2021-11-12 21:29:01 +00:00
|
|
|
0: ("blue", "INFO"), # normal
|
|
|
|
1: ("green", "SUCCESS"), # success
|
|
|
|
2: ("red", "ERROR"), # fail
|
|
|
|
3: ("yellow", "WARN"), # warn/suggest
|
2021-11-12 12:21:20 +00:00
|
|
|
}
|
2021-11-17 19:06:13 +00:00
|
|
|
|
2021-11-12 12:21:20 +00:00
|
|
|
color, prefix = levels.get(level, levels[0])
|
|
|
|
|
2021-11-20 05:46:19 +00:00
|
|
|
if bench.cli.from_command_line and bench.cli.dynamic_feed:
|
2021-11-17 19:06:13 +00:00
|
|
|
bench.LOG_BUFFER.append(
|
2021-11-26 10:54:56 +00:00
|
|
|
{"prefix": prefix, "message": message, "color": color}
|
2021-11-17 19:06:13 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if no_log:
|
|
|
|
click.secho(message, fg=color)
|
|
|
|
else:
|
|
|
|
loggers = {2: logger.error, 3: logger.warning}
|
|
|
|
level_logger = loggers.get(level, logger.info)
|
|
|
|
|
|
|
|
level_logger(message)
|
|
|
|
click.secho(f"{prefix}: {message}", fg=color)
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
def check_latest_version():
|
2021-11-12 20:18:30 +00:00
|
|
|
if VERSION.endswith("dev"):
|
2021-11-12 12:21:20 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
import requests
|
|
|
|
from semantic_version import Version
|
|
|
|
|
|
|
|
try:
|
|
|
|
pypi_request = requests.get("https://pypi.org/pypi/frappe-bench/json")
|
|
|
|
except Exception:
|
|
|
|
# Exceptions thrown are defined in requests.exceptions
|
|
|
|
# ignore checking on all Exceptions
|
|
|
|
return
|
|
|
|
|
|
|
|
if pypi_request.status_code == 200:
|
2021-11-12 21:29:01 +00:00
|
|
|
pypi_version_str = pypi_request.json().get("info").get("version")
|
2021-11-12 12:21:20 +00:00
|
|
|
pypi_version = Version(pypi_version_str)
|
2021-11-12 20:18:30 +00:00
|
|
|
local_version = Version(VERSION)
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
if pypi_version > local_version:
|
|
|
|
log(f"A newer version of bench is available: {local_version} → {pypi_version}")
|
|
|
|
|
|
|
|
|
|
|
|
def pause_exec(seconds=10):
|
|
|
|
from time import sleep
|
|
|
|
|
|
|
|
for i in range(seconds, 0, -1):
|
|
|
|
print(f"Will continue execution in {i} seconds...", end="\r")
|
|
|
|
sleep(1)
|
|
|
|
|
|
|
|
print(" " * 40, end="\r")
|
|
|
|
|
|
|
|
|
2021-11-19 03:46:19 +00:00
|
|
|
def exec_cmd(cmd, cwd=".", env=None, _raise=True):
|
2021-11-12 12:21:20 +00:00
|
|
|
if env:
|
|
|
|
env.update(os.environ.copy())
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
click.secho(f"$ {cmd}", fg="bright_black")
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
if return_code:
|
|
|
|
logger.warning(f"{cmd_log} executed with exit code {return_code}")
|
2021-11-19 03:46:19 +00:00
|
|
|
if _raise:
|
|
|
|
raise CommandFailedError
|
|
|
|
return return_code
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def which(executable: str, raise_err: bool = False) -> str:
|
2021-11-12 12:21:20 +00:00
|
|
|
from shutil import which
|
|
|
|
|
|
|
|
exec_ = which(executable)
|
|
|
|
|
|
|
|
if not exec_ and raise_err:
|
2021-11-12 21:29:01 +00:00
|
|
|
raise ValueError(f"{executable} not found.")
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
return exec_
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def setup_logging(bench_path=".") -> "logger":
|
2021-11-12 12:21:20 +00:00
|
|
|
LOG_LEVEL = 15
|
|
|
|
logging.addLevelName(LOG_LEVEL, "LOG")
|
2021-11-12 21:29:01 +00:00
|
|
|
|
2021-11-12 12:21:20 +00:00
|
|
|
def logv(self, message, *args, **kws):
|
|
|
|
if self.isEnabledFor(LOG_LEVEL):
|
|
|
|
self._log(LOG_LEVEL, message, args, **kws)
|
2021-11-12 21:29:01 +00:00
|
|
|
|
2021-11-12 12:21:20 +00:00
|
|
|
logging.Logger.log = logv
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
if os.path.exists(os.path.join(bench_path, "logs")):
|
|
|
|
log_file = os.path.join(bench_path, "logs", "bench.log")
|
2021-11-12 12:21:20 +00:00
|
|
|
hdlr = logging.FileHandler(log_file)
|
|
|
|
else:
|
|
|
|
hdlr = logging.NullHandler()
|
|
|
|
|
2021-11-12 20:18:30 +00:00
|
|
|
logger = logging.getLogger(PROJECT_NAME)
|
2021-11-12 21:29:01 +00:00
|
|
|
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
2021-11-12 12:21:20 +00:00
|
|
|
hdlr.setFormatter(formatter)
|
|
|
|
logger.addHandler(hdlr)
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
return logger
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def get_process_manager() -> str:
|
2021-11-12 21:29:01 +00:00
|
|
|
for proc_man in ["honcho", "foreman", "forego"]:
|
2021-11-12 12:21:20 +00:00
|
|
|
proc_man_path = which(proc_man)
|
|
|
|
if proc_man_path:
|
|
|
|
return proc_man_path
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def get_git_version() -> float:
|
2021-11-12 21:29:01 +00:00
|
|
|
"""returns git version from `git --version`
|
|
|
|
extracts version number from string `get version 1.9.1` etc"""
|
2021-11-12 12:21:20 +00:00
|
|
|
version = get_cmd_output("git --version")
|
|
|
|
version = version.strip().split()[2]
|
2021-11-12 21:29:01 +00:00
|
|
|
version = ".".join(version.split(".")[0:2])
|
2021-11-12 12:21:20 +00:00
|
|
|
return float(version)
|
|
|
|
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
def get_cmd_output(cmd, cwd=".", _raise=True):
|
2021-11-12 12:21:20 +00:00
|
|
|
output = ""
|
|
|
|
try:
|
2021-11-12 21:29:01 +00:00
|
|
|
output = subprocess.check_output(
|
|
|
|
cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, encoding="utf-8"
|
|
|
|
).strip()
|
2021-11-12 12:21:20 +00:00
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
if e.output:
|
|
|
|
output = e.output
|
|
|
|
elif _raise:
|
|
|
|
raise
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
|
|
def is_root():
|
|
|
|
return os.getuid() == 0
|
|
|
|
|
|
|
|
|
|
|
|
def run_frappe_cmd(*args, **kwargs):
|
|
|
|
from bench.cli import from_command_line
|
2021-11-12 20:18:30 +00:00
|
|
|
from bench.utils.bench import get_env_cmd
|
2021-11-12 12:21:20 +00:00
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
bench_path = kwargs.get("bench_path", ".")
|
|
|
|
f = get_env_cmd("python", bench_path=bench_path)
|
|
|
|
sites_dir = os.path.join(bench_path, "sites")
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
is_async = False if from_command_line else True
|
|
|
|
if is_async:
|
|
|
|
stderr = stdout = subprocess.PIPE
|
|
|
|
else:
|
|
|
|
stderr = stdout = None
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
p = subprocess.Popen(
|
|
|
|
(f, "-m", "frappe.utils.bench_helper", "frappe") + args,
|
|
|
|
cwd=sites_dir,
|
|
|
|
stdout=stdout,
|
|
|
|
stderr=stderr,
|
|
|
|
)
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
if is_async:
|
|
|
|
return_code = print_output(p)
|
|
|
|
else:
|
|
|
|
return_code = p.wait()
|
|
|
|
|
|
|
|
if return_code > 0:
|
|
|
|
sys.exit(return_code)
|
|
|
|
|
|
|
|
|
|
|
|
def print_output(p):
|
|
|
|
from select import select
|
|
|
|
|
|
|
|
while p.poll() is None:
|
|
|
|
readx = select([p.stdout.fileno(), p.stderr.fileno()], [], [])[0]
|
|
|
|
send_buffer = []
|
|
|
|
for fd in readx:
|
|
|
|
if fd == p.stdout.fileno():
|
|
|
|
while 1:
|
|
|
|
buf = p.stdout.read(1)
|
|
|
|
if not len(buf):
|
|
|
|
break
|
2021-11-12 21:29:01 +00:00
|
|
|
if buf == "\r" or buf == "\n":
|
2021-11-12 12:21:20 +00:00
|
|
|
send_buffer.append(buf)
|
2021-11-12 21:29:01 +00:00
|
|
|
log_line("".join(send_buffer), "stdout")
|
2021-11-12 12:21:20 +00:00
|
|
|
send_buffer = []
|
|
|
|
else:
|
|
|
|
send_buffer.append(buf)
|
|
|
|
|
|
|
|
if fd == p.stderr.fileno():
|
2021-11-12 21:29:01 +00:00
|
|
|
log_line(p.stderr.readline(), "stderr")
|
2021-11-12 12:21:20 +00:00
|
|
|
return p.poll()
|
|
|
|
|
|
|
|
|
|
|
|
def log_line(data, stream):
|
2021-11-12 21:29:01 +00:00
|
|
|
if stream == "stderr":
|
2021-11-12 12:21:20 +00:00
|
|
|
return sys.stderr.write(data)
|
|
|
|
return sys.stdout.write(data)
|
|
|
|
|
|
|
|
|
|
|
|
def get_bench_name(bench_path):
|
|
|
|
return os.path.basename(os.path.abspath(bench_path))
|
|
|
|
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
def set_git_remote_url(git_url, bench_path="."):
|
2021-11-12 12:21:20 +00:00
|
|
|
"Set app remote git url"
|
2021-11-12 18:50:05 +00:00
|
|
|
from bench.app import get_repo_dir
|
2021-11-12 12:21:20 +00:00
|
|
|
from bench.bench import Bench
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
app = git_url.rsplit("/", 1)[1].rsplit(".", 1)[0]
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
if app not in Bench(bench_path).apps:
|
2021-11-12 18:50:05 +00:00
|
|
|
raise ValidationError(f"No app named {app}")
|
|
|
|
|
|
|
|
app_dir = get_repo_dir(app, bench_path=bench_path)
|
2021-11-12 12:21:20 +00:00
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
if os.path.exists(os.path.join(app_dir, ".git")):
|
2021-11-12 12:21:20 +00:00
|
|
|
exec_cmd(f"git remote set-url upstream {git_url}", cwd=app_dir)
|
|
|
|
|
|
|
|
|
|
|
|
def run_playbook(playbook_name, extra_vars=None, tag=None):
|
2021-11-14 03:46:19 +00:00
|
|
|
import bench
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
if not which("ansible"):
|
|
|
|
print(
|
|
|
|
"Ansible is needed to run this command, please install it using 'pip"
|
|
|
|
" install ansible'"
|
|
|
|
)
|
2021-11-12 12:21:20 +00:00
|
|
|
sys.exit(1)
|
2021-11-12 21:29:01 +00:00
|
|
|
args = ["ansible-playbook", "-c", "local", playbook_name, "-vvvv"]
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
if extra_vars:
|
2021-11-12 21:29:01 +00:00
|
|
|
args.extend(["-e", json.dumps(extra_vars)])
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
if tag:
|
2021-11-12 21:29:01 +00:00
|
|
|
args.extend(["-t", tag])
|
2021-11-12 12:21:20 +00:00
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
subprocess.check_call(args, cwd=os.path.join(bench.__path__[0], "playbooks"))
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def find_benches(directory: str = None) -> List:
|
2021-11-12 12:21:20 +00:00
|
|
|
if not directory:
|
|
|
|
directory = os.path.expanduser("~")
|
|
|
|
elif os.path.exists(directory):
|
|
|
|
directory = os.path.abspath(directory)
|
|
|
|
else:
|
|
|
|
log("Directory doesn't exist", level=2)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if is_bench_directory(directory):
|
|
|
|
if os.path.curdir == directory:
|
|
|
|
print("You are in a bench directory!")
|
|
|
|
else:
|
|
|
|
print(f"{directory} is a bench directory!")
|
|
|
|
return
|
|
|
|
|
|
|
|
benches = []
|
|
|
|
for sub in os.listdir(directory):
|
|
|
|
sub = os.path.join(directory, sub)
|
|
|
|
if os.path.isdir(sub) and not os.path.islink(sub):
|
|
|
|
if is_bench_directory(sub):
|
|
|
|
print(f"{sub} found!")
|
|
|
|
benches.append(sub)
|
|
|
|
else:
|
|
|
|
benches.extend(find_benches(sub))
|
|
|
|
|
|
|
|
return benches
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def is_dist_editable(dist: str) -> bool:
|
2021-11-12 12:21:20 +00:00
|
|
|
"""Is distribution an editable install?"""
|
|
|
|
for path_item in sys.path:
|
2021-11-17 18:33:42 +00:00
|
|
|
egg_link = os.path.join(path_item, f"{dist}.egg-link")
|
2021-11-12 12:21:20 +00:00
|
|
|
if os.path.isfile(egg_link):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def find_parent_bench(path: str) -> str:
|
2021-11-12 12:21:20 +00:00
|
|
|
"""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)
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def generate_command_cache(bench_path=".") -> List:
|
2021-11-12 12:21:20 +00:00
|
|
|
"""Caches all available commands (even custom apps) via Frappe
|
|
|
|
Default caching behaviour: generated the first time any command (for a specific bench directory)
|
|
|
|
"""
|
2021-11-14 03:46:19 +00:00
|
|
|
from bench.utils.bench import get_env_cmd
|
2021-11-12 12:21:20 +00:00
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
python = get_env_cmd("python", bench_path=bench_path)
|
|
|
|
sites_path = os.path.join(bench_path, "sites")
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
if os.path.exists(bench_cache_file):
|
|
|
|
os.remove(bench_cache_file)
|
|
|
|
|
|
|
|
try:
|
2021-11-12 21:29:01 +00:00
|
|
|
output = get_cmd_output(
|
|
|
|
f"{python} -m frappe.utils.bench_helper get-frappe-commands", cwd=sites_path
|
|
|
|
)
|
|
|
|
with open(bench_cache_file, "w") as f:
|
2021-11-12 12:21:20 +00:00
|
|
|
json.dump(eval(output), f)
|
|
|
|
return json.loads(output)
|
|
|
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
if hasattr(e, "stderr"):
|
2021-11-12 13:03:23 +00:00
|
|
|
print(e.stderr)
|
2021-11-12 12:21:20 +00:00
|
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
def clear_command_cache(bench_path="."):
|
2021-11-12 12:21:20 +00:00
|
|
|
"""Clears commands cached
|
|
|
|
Default invalidation behaviour: destroyed on each run of `bench update`
|
|
|
|
"""
|
|
|
|
|
|
|
|
if os.path.exists(bench_cache_file):
|
|
|
|
os.remove(bench_cache_file)
|
|
|
|
else:
|
|
|
|
print("Bench command cache doesn't exist in this folder!")
|
|
|
|
|
|
|
|
|
|
|
|
def find_org(org_repo):
|
|
|
|
import requests
|
|
|
|
|
|
|
|
org_repo = org_repo[0]
|
|
|
|
|
|
|
|
for org in ["frappe", "erpnext"]:
|
2021-11-12 21:29:01 +00:00
|
|
|
res = requests.head(f"https://api.github.com/repos/{org}/{org_repo}")
|
2021-11-12 12:21:20 +00:00
|
|
|
if res.ok:
|
|
|
|
return org, org_repo
|
|
|
|
|
|
|
|
raise InvalidRemoteException
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def fetch_details_from_tag(_tag: str) -> Tuple[str, str, str]:
|
2021-11-12 12:21:20 +00:00
|
|
|
if not _tag:
|
|
|
|
raise Exception("Tag is not provided")
|
|
|
|
|
|
|
|
app_tag = _tag.split("@")
|
|
|
|
org_repo = app_tag[0].split("/")
|
|
|
|
|
|
|
|
try:
|
|
|
|
repo, tag = app_tag
|
|
|
|
except ValueError:
|
|
|
|
repo, tag = app_tag + [None]
|
|
|
|
|
|
|
|
try:
|
|
|
|
org, repo = org_repo
|
2021-11-13 08:53:08 +00:00
|
|
|
except Exception:
|
2021-11-12 12:21:20 +00:00
|
|
|
org, repo = find_org(org_repo)
|
|
|
|
|
|
|
|
return org, repo, tag
|
|
|
|
|
|
|
|
|
2021-11-17 18:33:42 +00:00
|
|
|
def is_git_url(url: str) -> bool:
|
2021-11-12 12:21:20 +00:00
|
|
|
# modified to allow without the tailing .git from https://github.com/jonschlinkert/is-git-url.git
|
|
|
|
pattern = r"(?:git|ssh|https?|\w*@[-\w.]+):(\/\/)?(.*?)(\.git)?(\/?|\#[-\d\w._]+?)$"
|
|
|
|
return bool(re.match(pattern, url))
|
|
|
|
|
|
|
|
|
2021-11-12 21:29:01 +00:00
|
|
|
def drop_privileges(uid_name="nobody", gid_name="nogroup"):
|
2021-11-12 20:18:30 +00:00
|
|
|
import grp
|
|
|
|
import pwd
|
|
|
|
|
2021-11-12 18:49:12 +00:00
|
|
|
# from http://stackoverflow.com/a/2699996
|
|
|
|
if os.getuid() != 0:
|
|
|
|
# We're not root so, like, whatever dude
|
|
|
|
return
|
|
|
|
|
|
|
|
# Get the uid/gid from the name
|
|
|
|
running_uid = pwd.getpwnam(uid_name).pw_uid
|
|
|
|
running_gid = grp.getgrnam(gid_name).gr_gid
|
|
|
|
|
|
|
|
# Remove group privileges
|
|
|
|
os.setgroups([])
|
|
|
|
|
|
|
|
# Try setting the new uid/gid
|
|
|
|
os.setgid(running_gid)
|
|
|
|
os.setuid(running_uid)
|
|
|
|
|
|
|
|
# Ensure a very conservative umask
|
|
|
|
os.umask(0o22)
|
2021-11-17 18:15:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_available_folder_name(name: str, path: str) -> str:
|
2021-11-17 18:33:42 +00:00
|
|
|
"""Subfixes the passed name with -1 uptil -100 whatever's available"""
|
2021-11-17 18:15:30 +00:00
|
|
|
if os.path.exists(os.path.join(path, name)):
|
|
|
|
for num in range(1, 100):
|
|
|
|
_dt = f"{name}_{num}"
|
|
|
|
if not os.path.exists(os.path.join(path, _dt)):
|
|
|
|
return _dt
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
|
|
|
def get_traceback() -> str:
|
2021-11-17 18:33:42 +00:00
|
|
|
"""Returns the traceback of the Exception"""
|
2021-11-17 18:15:30 +00:00
|
|
|
from traceback import format_exception
|
2021-11-17 18:33:42 +00:00
|
|
|
|
2021-11-17 18:15:30 +00:00
|
|
|
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
|
|
|
|
|
|
if not any([exc_type, exc_value, exc_tb]):
|
|
|
|
return ""
|
|
|
|
|
|
|
|
trace_list = format_exception(exc_type, exc_value, exc_tb)
|
|
|
|
return "".join(trace_list)
|
2021-12-28 08:06:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
class _dict(dict):
|
|
|
|
"""dict like object that exposes keys as attributes"""
|
|
|
|
# bench port of frappe._dict
|
|
|
|
def __getattr__(self, key):
|
|
|
|
ret = self.get(key)
|
|
|
|
# "__deepcopy__" exception added to fix frappe#14833 via DFP
|
|
|
|
if not ret and key.startswith("__") and key != "__deepcopy__":
|
|
|
|
raise AttributeError()
|
|
|
|
return ret
|
|
|
|
def __setattr__(self, key, value):
|
|
|
|
self[key] = value
|
|
|
|
def __getstate__(self):
|
|
|
|
return self
|
|
|
|
def __setstate__(self, d):
|
|
|
|
self.update(d)
|
|
|
|
def update(self, d):
|
|
|
|
"""update and return self -- the missing dict feature in python"""
|
|
|
|
super(_dict, self).update(d)
|
|
|
|
return self
|
|
|
|
def copy(self):
|
|
|
|
return _dict(dict(self).copy())
|
|
|
|
|
|
|
|
|
|
|
|
def parse_sys_argv():
|
|
|
|
sys_argv = _dict(options=set(), commands=set())
|
|
|
|
|
|
|
|
for c in sys.argv[1:]:
|
|
|
|
if c.startswith("-"):
|
|
|
|
sys_argv.options.add(c)
|
|
|
|
else:
|
|
|
|
sys_argv.commands.add(c)
|
|
|
|
|
|
|
|
return sys_argv
|