2
0
mirror of https://github.com/frappe/frappe_docker.git synced 2025-01-27 09:08:31 +00:00

501 lines
11 KiB
Python
Raw Normal View History

2021-12-13 19:37:03 +03:00
import os
2022-02-08 15:49:14 +03:00
import shlex
2021-12-13 20:03:40 +03:00
import shutil
2021-12-13 19:37:03 +03:00
import ssl
import subprocess
import sys
2021-12-13 19:37:03 +03:00
from enum import Enum
from functools import wraps
2022-02-08 15:49:14 +03:00
from textwrap import dedent
2021-12-13 19:37:03 +03:00
from time import sleep
from typing import Any, Callable, Optional
2021-12-15 09:03:32 +03:00
from urllib.error import HTTPError, URLError
2021-12-13 19:37:03 +03:00
from urllib.request import Request, urlopen
CI = os.getenv("CI")
SITE_NAME = "tests"
BACKEND_SERVICES = (
"backend",
"queue-short",
"queue-default",
"queue-long",
"scheduler",
)
S3_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
S3_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
TTY = sys.stdout.isatty()
2021-12-13 19:37:03 +03:00
def patch_print():
# Patch `print()` builtin to have nice logs when running GitHub Actions
if not CI:
return
global print
_old_print = print
def print(
*values: Any,
sep: Optional[str] = None,
end: Optional[str] = None,
file: Any = None,
flush: bool = False,
):
return _old_print(*values, sep=sep, end=end, file=file, flush=True)
class Color(Enum):
GREY = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
MAGENTA = 35
CYAN = 36
WHITE = 37
def colored(text: str, color: Color):
return f"\033[{color.value}m{text}\033[0m"
def log(text: str):
def decorator(f: Callable[..., Any]):
@wraps(f)
def wrapper(*args: Any, **kwargs: Any):
if CI:
print(f"::group::{text}")
else:
2022-02-08 15:49:14 +03:00
output = (
f"\n{f' {text} '.center(os.get_terminal_size().columns, '=')}\n"
)
print(colored(output, Color.YELLOW))
2021-12-13 19:37:03 +03:00
ret = f(*args, **kwargs)
2022-02-08 15:49:14 +03:00
2021-12-13 19:37:03 +03:00
if CI:
print("::endgroup::")
return ret
return wrapper
return decorator
def run(*cmd: str):
2022-02-08 15:49:14 +03:00
print(colored(f"> {shlex.join(cmd)}", Color.GREEN))
2021-12-13 19:37:03 +03:00
return subprocess.check_call(cmd)
def docker_compose(*cmd: str):
args = [
"docker",
"compose",
"-p",
"test",
"--env-file",
2021-12-13 20:03:40 +03:00
"tests/.env",
2021-12-13 19:37:03 +03:00
"-f",
"compose.yaml",
"-f",
"overrides/compose.proxy.yaml",
"-f",
"overrides/compose.mariadb.yaml",
"-f",
"overrides/compose.redis.yaml",
2021-12-13 19:37:03 +03:00
]
if CI:
args.extend(("-f", "tests/compose.ci.yaml"))
2021-12-13 19:37:03 +03:00
return run(*args, *cmd)
2022-02-08 15:49:14 +03:00
def docker_compose_exec(*cmd: str):
if TTY:
return docker_compose("exec", *cmd)
else:
return docker_compose("exec", "-T", *cmd)
2021-12-13 20:03:40 +03:00
@log("Setup .env")
def setup_env():
shutil.copy("example.env", "tests/.env")
2022-02-08 15:49:14 +03:00
if not CI:
return
2022-02-08 15:49:14 +03:00
for var in ("FRAPPE_VERSION", "ERPNEXT_VERSION"):
if os.environ[var] == "develop":
2021-12-17 12:17:26 +03:00
with open(os.environ["GITHUB_ENV"], "a") as f:
2022-02-08 15:49:14 +03:00
f.write(f"\n{var}=latest")
os.environ[var] = "latest"
with open("tests/.env", "a") as f:
f.write(
2022-02-08 15:49:14 +03:00
dedent(
f"""
FRAPPE_VERSION={os.environ['FRAPPE_VERSION']}
ERPNEXT_VERSION={os.environ['ERPNEXT_VERSION']}
"""
)
)
2022-02-08 15:49:14 +03:00
with open("tests/.env") as f:
print(f.read())
2021-12-13 20:03:40 +03:00
2022-02-08 15:49:14 +03:00
@log("Print configuration")
def print_compose_configuration():
docker_compose("config")
2021-12-13 19:37:03 +03:00
@log("Create containers")
def create_containers():
2021-12-15 08:30:00 +03:00
docker_compose("up", "-d", "--quiet-pull")
2021-12-13 19:37:03 +03:00
2022-02-08 15:49:14 +03:00
@log("Check if Python services have connections")
2021-12-13 19:37:03 +03:00
def ping_links_in_backends():
for service in BACKEND_SERVICES:
docker_compose("cp", "tests/healthcheck.sh", f"{service}:/tmp/")
2021-12-13 19:37:03 +03:00
for _ in range(10):
try:
docker_compose_exec(service, "bash", "/tmp/healthcheck.sh")
2021-12-13 19:37:03 +03:00
break
except subprocess.CalledProcessError:
sleep(1)
else:
2022-02-08 15:49:14 +03:00
raise RuntimeError(f"Connections healthcheck failed for service {service}")
2021-12-13 19:37:03 +03:00
2022-02-08 15:49:14 +03:00
@log("Create site")
2021-12-13 19:37:03 +03:00
def create_site():
2022-02-08 15:49:14 +03:00
docker_compose_exec(
2021-12-13 19:37:03 +03:00
"backend",
"bench",
"new-site",
SITE_NAME,
"--mariadb-root-password",
"123",
"--admin-password",
"admin",
)
docker_compose("restart", "backend")
# This is needed to check https override
_ssl_ctx = ssl.create_default_context()
_ssl_ctx.check_hostname = False
_ssl_ctx.verify_mode = ssl.CERT_NONE
def ping_and_check_content(url: str, callback: Callable[[str], Optional[str]]):
request = Request(url, headers={"Host": SITE_NAME})
print(f"Checking {url}")
2022-02-08 15:49:14 +03:00
2021-12-13 19:37:03 +03:00
for _ in range(100):
try:
response = urlopen(request, context=_ssl_ctx)
2022-02-08 15:49:14 +03:00
2021-12-13 19:37:03 +03:00
except HTTPError as exc:
if exc.code not in (404, 502):
raise
2022-02-08 15:49:14 +03:00
2021-12-15 09:03:32 +03:00
except URLError:
pass
2022-02-08 15:49:14 +03:00
2021-12-13 19:37:03 +03:00
else:
text: str = response.read().decode()
ret = callback(text)
if ret:
print(ret)
return
sleep(0.1)
2022-02-08 15:49:14 +03:00
raise RuntimeError(f"Couldn't ping {url}")
2021-12-13 19:37:03 +03:00
2022-02-08 15:49:14 +03:00
def check_index_callback(text: str):
2021-12-13 19:37:03 +03:00
if "404 page not found" not in text:
return text[:200]
@log("Check /")
def check_index():
2022-02-08 15:49:14 +03:00
ping_and_check_content("http://127.0.0.1", check_index_callback)
2021-12-13 19:37:03 +03:00
@log("Check /api/method/version")
def check_api():
ping_and_check_content(
2022-02-08 15:49:14 +03:00
"http://127.0.0.1/api/method/version",
lambda text: text if '"message"' in text else None,
2021-12-13 19:37:03 +03:00
)
2022-02-08 15:49:14 +03:00
@log("Check if Frappe can connect to services in Python services")
2021-12-13 19:37:03 +03:00
def ping_frappe_connections_in_backends():
for service in BACKEND_SERVICES:
docker_compose("cp", "tests/_ping_frappe_connections.py", f"{service}:/tmp/")
2022-02-08 15:49:14 +03:00
docker_compose_exec(
2021-12-13 19:37:03 +03:00
service,
"/home/frappe/frappe-bench/env/bin/python",
f"/tmp/_ping_frappe_connections.py",
2021-12-13 19:37:03 +03:00
)
@log("Check /assets")
def check_assets():
ping_and_check_content(
2022-02-08 15:49:14 +03:00
"http://127.0.0.1/assets/frappe/images/frappe-framework-logo.svg",
lambda text: text[:200] if text else None,
2021-12-13 19:37:03 +03:00
)
@log("Check /files")
def check_files():
file_name = "testfile.txt"
docker_compose(
"cp",
f"tests/{file_name}",
f"backend:/home/frappe/frappe-bench/sites/{SITE_NAME}/public/files/",
)
ping_and_check_content(
2022-02-08 15:49:14 +03:00
f"http://127.0.0.1/files/{file_name}",
lambda text: text if text == "lalala\n" else None,
2021-12-13 19:37:03 +03:00
)
2021-12-15 08:30:00 +03:00
@log("Prepare S3 server")
def prepare_s3_server():
run(
"docker",
"run",
"--name",
"minio",
"-d",
"-e",
2021-12-19 16:50:56 +03:00
f"MINIO_ACCESS_KEY={S3_ACCESS_KEY}",
2021-12-15 08:30:00 +03:00
"-e",
2021-12-19 16:50:56 +03:00
f"MINIO_SECRET_KEY={S3_SECRET_KEY}",
2021-12-15 08:30:00 +03:00
"--network",
"test_default",
"minio/minio",
"server",
"/data",
)
docker_compose("cp", "tests/_create_bucket.py", "backend:/tmp")
2022-02-08 15:49:14 +03:00
docker_compose_exec(
"-e",
f"S3_ACCESS_KEY={S3_ACCESS_KEY}",
"-e",
f"S3_SECRET_KEY={S3_SECRET_KEY}",
"backend",
"/home/frappe/frappe-bench/env/bin/python",
"/tmp/_create_bucket.py",
)
2021-12-15 08:30:00 +03:00
@log("Push backup to S3")
def push_backup_to_s3():
2022-02-08 15:49:14 +03:00
docker_compose_exec(
"backend", "bench", "--site", SITE_NAME, "backup", "--with-files"
)
docker_compose_exec(
2021-12-15 08:30:00 +03:00
"backend",
"push-backup",
"--site",
SITE_NAME,
"--bucket",
"frappe",
"--region-name",
"us-east-1",
"--endpoint-url",
"http://minio:9000",
"--aws-access-key-id",
S3_ACCESS_KEY,
2021-12-15 08:30:00 +03:00
"--aws-secret-access-key",
S3_SECRET_KEY,
2021-12-15 08:30:00 +03:00
)
2022-02-08 15:49:14 +03:00
@log("Check backup files in S3")
2021-12-15 08:30:00 +03:00
def check_backup_in_s3():
docker_compose("cp", "tests/_check_backup_files.py", "backend:/tmp")
2022-02-08 15:49:14 +03:00
docker_compose_exec(
"-e",
f"S3_ACCESS_KEY={S3_ACCESS_KEY}",
"-e",
f"S3_SECRET_KEY={S3_SECRET_KEY}",
"backend",
"/home/frappe/frappe-bench/env/bin/python",
"/tmp/_check_backup_files.py",
)
2021-12-15 08:30:00 +03:00
@log("Stop S3 container")
def stop_s3_container():
run("docker", "rm", "minio", "-f")
2022-02-08 15:49:14 +03:00
@log("Recreate with HTTPS override")
2021-12-13 19:37:03 +03:00
def recreate_with_https_override():
2021-12-15 12:37:47 +03:00
docker_compose("-f", "overrides/compose.https.yaml", "up", "-d")
2021-12-13 19:37:03 +03:00
2022-02-08 15:49:14 +03:00
@log("Check / (HTTPS)")
2021-12-13 19:37:03 +03:00
def check_index_https():
2022-02-08 15:49:14 +03:00
ping_and_check_content("https://127.0.0.1", check_index_callback)
2021-12-13 19:37:03 +03:00
@log("Stop containers")
def stop_containers():
docker_compose("down", "-v", "--remove-orphans")
@log("Recreate with ERPNext override")
def create_containers_with_erpnext_override():
args = ["-f", "overrides/compose.erpnext.yaml"]
2021-12-13 19:37:03 +03:00
if CI:
2022-02-08 15:49:14 +03:00
args += ("-f", "tests/compose.ci-erpnext.yaml")
2021-12-15 08:30:00 +03:00
docker_compose(*args, "up", "-d", "--quiet-pull")
2021-12-13 19:37:03 +03:00
@log("Create ERPNext site")
def create_erpnext_site():
2022-02-08 15:49:14 +03:00
docker_compose_exec(
2021-12-13 19:37:03 +03:00
"backend",
"bench",
"new-site",
SITE_NAME,
"--mariadb-root-password",
"123",
"--admin-password",
"admin",
"--install-app",
"erpnext",
)
docker_compose("restart", "backend")
2022-02-08 15:49:14 +03:00
@log("Check /api/method")
2021-12-13 19:37:03 +03:00
def check_erpnext_api():
ping_and_check_content(
2022-02-08 15:49:14 +03:00
"http://127.0.0.1/api/method/erpnext.templates.pages.product_search.get_product_list",
lambda text: text if '"message"' in text else None,
2021-12-13 19:37:03 +03:00
)
2022-02-08 15:49:14 +03:00
@log("Check /assets")
2021-12-13 19:37:03 +03:00
def check_erpnext_assets():
ping_and_check_content(
2022-02-08 15:49:14 +03:00
"http://127.0.0.1/assets/erpnext/js/setup_wizard.js",
lambda text: text[:200] if text else None,
2021-12-13 19:37:03 +03:00
)
@log("Create containers with Postgres override")
def create_containers_with_postgres_override():
2021-12-15 12:41:47 +03:00
docker_compose("-f", "overrides/compose.postgres.yaml", "up", "-d", "--quiet-pull")
2021-12-13 19:37:03 +03:00
@log("Create Postgres site")
def create_postgres_site():
2022-02-08 15:49:14 +03:00
docker_compose_exec(
"backend", "bench", "set-config", "-g", "root_login", "postgres"
)
docker_compose_exec("backend", "bench", "set-config", "-g", "root_password", "123")
docker_compose_exec(
2021-12-13 19:37:03 +03:00
"backend",
"bench",
"new-site",
SITE_NAME,
"--db-type",
"postgres",
"--admin-password",
"admin",
)
docker_compose("restart", "backend")
2021-12-13 20:03:40 +03:00
@log("Delete .env")
def delete_env():
os.remove("tests/.env")
2022-02-08 15:49:14 +03:00
@log("Show logs")
2021-12-13 19:37:03 +03:00
def show_docker_compose_logs():
docker_compose("logs")
2022-02-08 15:49:14 +03:00
def start():
setup_env()
print_compose_configuration()
create_containers()
ping_links_in_backends()
def create_frappe_site_and_check_availability():
create_site()
check_index()
check_api()
ping_frappe_connections_in_backends()
check_assets()
check_files()
2021-12-13 19:37:03 +03:00
2021-12-17 12:24:38 +03:00
2022-02-08 15:49:14 +03:00
def check_s3():
prepare_s3_server()
2021-12-17 12:24:38 +03:00
2022-02-08 15:49:14 +03:00
try:
2021-12-17 12:24:38 +03:00
push_backup_to_s3()
check_backup_in_s3()
2022-02-08 15:49:14 +03:00
finally:
2021-12-17 12:24:38 +03:00
stop_s3_container()
2022-02-08 15:49:14 +03:00
def check_https():
print_compose_configuration()
recreate_with_https_override()
check_index_https()
stop_containers()
2021-12-17 12:24:38 +03:00
2022-02-08 15:49:14 +03:00
def check_erpnext():
print_compose_configuration()
create_containers_with_erpnext_override()
create_erpnext_site()
check_erpnext_api()
check_erpnext_assets()
stop_containers()
def check_postgres():
print_compose_configuration()
create_containers_with_postgres_override()
create_postgres_site()
ping_links_in_backends()
def main() -> int:
try:
patch_print()
start()
create_frappe_site_and_check_availability()
check_s3()
check_https()
check_erpnext()
check_postgres()
2021-12-13 19:37:03 +03:00
finally:
2021-12-13 20:03:40 +03:00
delete_env()
2021-12-13 19:37:03 +03:00
show_docker_compose_logs()
stop_containers()
2022-02-08 15:49:14 +03:00
print(colored("\nTests successfully passed!", Color.YELLOW))
2021-12-13 19:37:03 +03:00
return 0
if __name__ == "__main__":
raise SystemExit(main())