2
0
mirror of https://github.com/frappe/bench.git synced 2025-01-08 00:04:38 +00:00

Merge pull request #1592 from revant/easy-build

This commit is contained in:
Md Hussain Nagaria 2024-12-03 15:00:33 +05:30 committed by GitHub
commit 1bf53b4e7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 865 additions and 304 deletions

View File

@ -28,7 +28,7 @@ jobs:
- name: Perform production easy install - name: Perform production easy install
run: | run: |
python3 ${GITHUB_WORKSPACE}/easy-install.py -p -n actions_test --email test@frappe.io python3 ${GITHUB_WORKSPACE}/easy-install.py build --deploy --tag=custom-apps:latest --project=actions_test --email=test@frappe.io --image=custom-apps --version=latest --app=erpnext
docker compose -p actions_test exec backend bench version --format json docker compose -p actions_test exec backend bench version --format json
docker compose -p actions_test exec backend bench --site site1.localhost list-apps --format json 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") result=$(curl -H "Host: site1.localhost" -sk https://127.0.0.1/api/method/ping | jq -r ."message")

148
README.md
View File

@ -87,7 +87,7 @@ Download the Easy Install script and execute it:
```sh ```sh
$ wget https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py $ wget https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py
$ python3 easy-install.py --prod --email your@email.tld $ python3 easy-install.py deploy --email=user@domain.tld --sitename=subdomain.domain.tld --app=erpnext
``` ```
This script will install docker on your system and will fetch the required containers, setup bench and a default ERPNext instance. This script will install docker on your system and will fetch the required containers, setup bench and a default ERPNext instance.
@ -101,20 +101,150 @@ When the setup is complete, you will be able to access the system at `http://<yo
Here are the arguments for the easy-install script Here are the arguments for the easy-install script
```txt **Build custom images**
usage: easy-install.py [-h] [-p] [-d] [-s SITENAME] [-n PROJECT] [--email EMAIL]
Install Frappe with Docker ```txt
usage: easy-install.py build [-h] [-n PROJECT] [-i IMAGE] [-q] [-m HTTP_PORT] [-v VERSION] [-a APPS] [-s SITES] [-e EMAIL]
[-p] [-r FRAPPE_PATH] [-b FRAPPE_BRANCH] [-j APPS_JSON] [-t TAGS] [-c CONTAINERFILE]
[-y PYTHON_VERSION] [-d NODE_VERSION] [-x] [-u]
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-p, --prod Setup Production System -n PROJECT, --project PROJECT
-d, --dev Setup Development System Project Name
-s SITENAME, --sitename SITENAME The Site Name for your production site -i IMAGE, --image IMAGE
-n PROJECT, --project PROJECT Project Name Full Image Name
--email EMAIL Add email for the SSL. -q, --no-ssl No https
-m HTTP_PORT, --http-port HTTP_PORT
Http port in case of no-ssl
-v VERSION, --version VERSION
ERPNext version to install, defaults to latest stable
-a APPS, --app APPS list of app(s) to be installed
-s SITES, --sitename SITES
Site Name(s) for your production bench
-e EMAIL, --email EMAIL
Add email for the SSL.
-p, --push Push the built image to registry
-r FRAPPE_PATH, --frappe-path FRAPPE_PATH
Frappe Repository to use, default: https://github.com/frappe/frappe
-b FRAPPE_BRANCH, --frappe-branch FRAPPE_BRANCH
Frappe branch to use, default: version-15
-j APPS_JSON, --apps-json APPS_JSON
Path to apps json, default: frappe_docker/development/apps-example.json
-t TAGS, --tag TAGS Full Image Name(s), default: custom-apps:latest
-c CONTAINERFILE, --containerfile CONTAINERFILE
Path to Containerfile: images/layered/Containerfile
-y PYTHON_VERSION, --python-version PYTHON_VERSION
Python Version, default: 3.11.6
-d NODE_VERSION, --node-version NODE_VERSION
NodeJS Version, default: 18.18.2
-x, --deploy Deploy after build
-u, --upgrade Upgrade after build
``` ```
**Deploy using compose**
```txt
usage: easy-install.py deploy [-h] [-n PROJECT] [-i IMAGE] [-q] [-m HTTP_PORT] [-v VERSION] [-a APPS] [-s SITES] [-e EMAIL]
options:
-h, --help show this help message and exit
-n PROJECT, --project PROJECT
Project Name
-i IMAGE, --image IMAGE
Full Image Name
-q, --no-ssl No https
-m HTTP_PORT, --http-port HTTP_PORT
Http port in case of no-ssl
-v VERSION, --version VERSION
ERPNext version to install, defaults to latest stable
-a APPS, --app APPS list of app(s) to be installed
-s SITES, --sitename SITES
Site Name(s) for your production bench
-e EMAIL, --email EMAIL
Add email for the SSL.
```
**Upgrade existing project**
```txt
usage: easy-install.py upgrade [-h] [-n PROJECT] [-i IMAGE] [-q] [-m HTTP_PORT] [-v VERSION]
options:
-h, --help show this help message and exit
-n PROJECT, --project PROJECT
Project Name
-i IMAGE, --image IMAGE
Full Image Name
-q, --no-ssl No https
-m HTTP_PORT, --http-port HTTP_PORT
Http port in case of no-ssl
-v VERSION, --version VERSION
ERPNext or image version to install, defaults to latest stable
```
**Development setup using compose**
```txt
usage: easy-install.py develop [-h] [-n PROJECT]
options:
-h, --help show this help message and exit
-n PROJECT, --project PROJECT
Compose project name
```
**Exec into existing project**
```txt
usage: easy-install.py exec [-h] [-n PROJECT]
options:
-h, --help show this help message and exit
-n PROJECT, --project PROJECT
Project Name
```
To use custom apps, you need to create a json file with list of apps and pass it to build command.
Example apps.json
```json
[
{
"url": "https://github.com/frappe/wiki.git",
"branch": "master"
}
]
```
Execute following command to build and deploy above apps:
```sh
$ python3 easy-install.py build \
--tag=ghcr.io/org/repo/custom-apps:latest \
--push \
--image=ghcr.io/org/repo/custom-apps \
--version=latest \
--deploy \
--project=actions_test \
--email=test@frappe.io \
--apps-json=apps.json \
--app=wiki
```
Note:
- `--tag`, tag to set for built image, can be multiple.
- `--push`, push the built image.
- `--image`, the image to use when starting docker compose project.
- `--version`, the version to use when starting docker compose project.
- `--app`, app to install on site creation, can be multiple.
- `--deploy`, flag to deploy after build/push is complete
- `--project=actions_test`, name of the project, compose file with project name will be stored in user home directory.
- `--email=test@frappe.io`, valid email for letsencrypt certificate expiry notification.
- `--apps-json`, path to json file with list of apps to be added to bench.
#### Troubleshooting #### Troubleshooting
In case the setup fails, the log file is saved under `$HOME/easy-install.log`. You may then In case the setup fails, the log file is saved under `$HOME/easy-install.log`. You may then

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import base64
import fileinput import fileinput
import logging import logging
import os import os
@ -10,7 +11,7 @@ import sys
import time import time
import urllib.request import urllib.request
from shutil import move, unpack_archive, which from shutil import move, unpack_archive, which
from typing import Dict from typing import Dict, List
logging.basicConfig( logging.basicConfig(
filename="easy-install.log", filename="easy-install.log",
@ -49,9 +50,8 @@ def clone_frappe_docker_repo() -> None:
"frappe_docker.zip", "frappe_docker.zip",
) )
logging.info("Downloaded frappe_docker zip file from GitHub") logging.info("Downloaded frappe_docker zip file from GitHub")
unpack_archive( unpack_archive("frappe_docker.zip", ".")
"frappe_docker.zip", "." # Unzipping the frappe_docker.zip creates a folder "frappe_docker-main"
) # Unzipping the frappe_docker.zip creates a folder "frappe_docker-main"
move("frappe_docker-main", "frappe_docker") move("frappe_docker-main", "frappe_docker")
logging.info("Unzipped and Renamed frappe_docker") logging.info("Unzipped and Renamed frappe_docker")
os.remove("frappe_docker.zip") os.remove("frappe_docker.zip")
@ -74,19 +74,19 @@ def get_from_env(dir, file) -> Dict:
def write_to_env( def write_to_env(
wd: str, wd: str,
sites, sites: List[str],
db_pass: str, db_pass: str,
admin_pass: str, admin_pass: str,
email: str, email: str,
erpnext_version: str = None, erpnext_version: str = None,
http_port: str = None,
) -> None: ) -> None:
quoted_sites = ",".join([f"`{site}`" for site in sites]).strip(",") quoted_sites = ",".join([f"`{site}`" for site in sites]).strip(",")
example_env = get_from_env(wd, "example.env") example_env = get_from_env(wd, "example.env")
erpnext_version = erpnext_version or example_env["ERPNEXT_VERSION"] erpnext_version = erpnext_version or example_env["ERPNEXT_VERSION"]
with open(os.path.join(wd, ".env"), "w") as f: env_file_lines = [
f.writelines( # defaults to latest version of ERPNext
[ f"ERPNEXT_VERSION={erpnext_version}\n",
f"ERPNEXT_VERSION={erpnext_version}\n", # defaults to latest version of ERPNext
f"DB_PASSWORD={db_pass}\n", f"DB_PASSWORD={db_pass}\n",
"DB_HOST=db\n", "DB_HOST=db\n",
"DB_PORT=3306\n", "DB_PORT=3306\n",
@ -96,8 +96,14 @@ def write_to_env(
f"LETSENCRYPT_EMAIL={email}\n", f"LETSENCRYPT_EMAIL={email}\n",
f"SITE_ADMIN_PASS={admin_pass}\n", f"SITE_ADMIN_PASS={admin_pass}\n",
f"SITES={quoted_sites}\n", f"SITES={quoted_sites}\n",
"PULL_POLICY=missing\n",
] ]
)
if http_port:
env_file_lines.append(f"HTTP_PUBLISH_PORT={http_port}\n")
with open(os.path.join(wd, ".env"), "w") as f:
f.writelines(env_file_lines)
def generate_pass(length: int = 12) -> str: def generate_pass(length: int = 12) -> str:
@ -115,12 +121,23 @@ def check_repo_exists() -> bool:
return os.path.exists(os.path.join(os.getcwd(), "frappe_docker")) return os.path.exists(os.path.join(os.getcwd(), "frappe_docker"))
def setup_prod(project: str, sites, email: str, version: str = None, image = None) -> None: def start_prod(
if len(sites) == 0: project: str,
sites = ["site1.localhost"] sites: List[str] = [],
email: str = None,
version: str = None,
image: str = None,
is_https: bool = True,
http_port: str = None,
):
if not check_repo_exists():
clone_frappe_docker_repo()
install_container_runtime()
if check_repo_exists(): compose_file_name = os.path.join(
compose_file_name = os.path.join(os.path.expanduser("~"), f"{project}-compose.yml") os.path.expanduser("~"),
f"{project}-compose.yml",
)
docker_repo_path = os.path.join(os.getcwd(), "frappe_docker") docker_repo_path = os.path.join(os.getcwd(), "frappe_docker")
cprint( cprint(
"\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n", "\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n",
@ -133,23 +150,43 @@ def setup_prod(project: str, sites, email: str, version: str = None, image = Non
if not os.path.exists(os.path.join(docker_repo_path, ".env")): if not os.path.exists(os.path.join(docker_repo_path, ".env")):
admin_pass = generate_pass() admin_pass = generate_pass()
db_pass = generate_pass(9) db_pass = generate_pass(9)
write_to_env(docker_repo_path, sites, db_pass, admin_pass, email, version) write_to_env(
wd=docker_repo_path,
sites=sites,
db_pass=db_pass,
admin_pass=admin_pass,
email=email,
erpnext_version=version,
http_port=http_port if not is_https and http_port else None,
)
cprint( cprint(
"\nA .env file is generated with basic configs. Please edit it to fit to your needs \n", "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n",
level=3, level=3,
) )
with open(os.path.join(os.path.expanduser("~"), "passwords.txt"), "w") as en: with open(
os.path.join(os.path.expanduser("~"), "passwords.txt"), "w"
) as en:
en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n") en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n")
en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n") en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n")
else: else:
env = get_from_env(docker_repo_path, ".env") env = get_from_env(docker_repo_path, ".env")
admin_pass = env["SITE_ADMIN_PASS"] sites = env["SITES"].replace("`", "").split(",") if env["SITES"] else []
db_pass = env["DB_PASSWORD"] db_pass = env["DB_PASSWORD"]
admin_pass = env["SITE_ADMIN_PASS"]
email = env["LETSENCRYPT_EMAIL"]
write_to_env(
wd=docker_repo_path,
sites=sites,
db_pass=db_pass,
admin_pass=admin_pass,
email=email,
erpnext_version=version,
http_port=http_port if not is_https and http_port else None,
)
try: try:
# TODO: Include flags for non-https and non-erpnext installation command = [
subprocess.run( "docker",
[
which("docker"),
"compose", "compose",
"--project-name", "--project-name",
project, project,
@ -159,13 +196,19 @@ def setup_prod(project: str, sites, email: str, version: str = None, image = Non
"overrides/compose.mariadb.yaml", "overrides/compose.mariadb.yaml",
"-f", "-f",
"overrides/compose.redis.yaml", "overrides/compose.redis.yaml",
# "-f", "overrides/compose.noproxy.yaml", TODO: Add support for local proxying without HTTPs
"-f", "-f",
"overrides/compose.https.yaml", (
"overrides/compose.https.yaml"
if is_https
else "overrides/compose.noproxy.yaml"
),
"--env-file", "--env-file",
".env", ".env",
"config", "config",
], ]
subprocess.run(
command,
cwd=docker_repo_path, cwd=docker_repo_path,
stdout=f, stdout=f,
check=True, check=True,
@ -185,17 +228,20 @@ def setup_prod(project: str, sites, email: str, version: str = None, image = Non
try: try:
# Starting with generated compose file # Starting with generated compose file
subprocess.run( command = [
[ "docker",
which("docker"),
"compose", "compose",
"-p", "-p",
project, project,
"-f", "-f",
compose_file_name, compose_file_name,
"up", "up",
"--force-recreate",
"--remove-orphans",
"-d", "-d",
], ]
subprocess.run(
command,
check=True, check=True,
) )
logging.info(f"Docker Compose file generated at ~/{project}-compose.yml") logging.info(f"Docker Compose file generated at ~/{project}-compose.yml")
@ -205,20 +251,74 @@ def setup_prod(project: str, sites, email: str, version: str = None, image = Non
cprint(" Docker Compose failed, please check the container logs\n", e) cprint(" Docker Compose failed, please check the container logs\n", e)
sys.exit(1) sys.exit(1)
for sitename in sites: return db_pass, admin_pass
create_site(sitename, project, db_pass, admin_pass)
else:
install_docker() def setup_prod(
clone_frappe_docker_repo() project: str,
setup_prod(project, sites, email, version, image) # Recursive sites: List[str],
email: str,
version: str = None,
image: str = None,
apps: List[str] = [],
is_https: bool = False,
http_port: str = None,
) -> None:
if len(sites) == 0:
sites = ["site1.localhost"]
db_pass, admin_pass = start_prod(
project=project,
sites=sites,
email=email,
version=version,
image=image,
is_https=is_https,
http_port=http_port,
)
for sitename in sites:
create_site(sitename, project, db_pass, admin_pass, apps)
cprint(
f"MariaDB root password is {db_pass}",
level=2,
)
cprint(
f"Site administrator password is {admin_pass}",
level=2,
)
passwords_file_path = os.path.join(
os.path.expanduser("~"),
"passwords.txt",
)
cprint(f"Passwords are stored in {passwords_file_path}", level=3)
def update_prod(
project: str,
version: str = None,
image=None,
is_https: bool = False,
http_port: str = None,
) -> None:
start_prod(
project=project,
version=version,
image=image,
is_https=is_https,
http_port=http_port,
)
migrate_site(project=project)
def setup_dev_instance(project: str): def setup_dev_instance(project: str):
if check_repo_exists(): if not check_repo_exists():
clone_frappe_docker_repo()
install_container_runtime()
try: try:
subprocess.run( command = [
[
"docker", "docker",
"compose", "compose",
"-f", "-f",
@ -227,7 +327,9 @@ def setup_dev_instance(project: str):
project, project,
"up", "up",
"-d", "-d",
], ]
subprocess.run(
command,
cwd=os.path.join(os.getcwd(), "frappe_docker"), cwd=os.path.join(os.getcwd(), "frappe_docker"),
check=True, check=True,
) )
@ -239,19 +341,13 @@ def setup_dev_instance(project: str):
except Exception as e: except Exception as e:
logging.error("Dev Environment setup failed", exc_info=True) logging.error("Dev Environment setup failed", exc_info=True)
cprint("Setting Up Development Environment Failed\n", e) cprint("Setting Up Development Environment Failed\n", e)
else:
install_docker()
clone_frappe_docker_repo()
setup_dev_instance(project) # Recursion on goes brrrr
def install_docker(): def install_docker():
if which("docker") is not None:
return
cprint("Docker is not installed, Installing Docker...", level=3) cprint("Docker is not installed, Installing Docker...", level=3)
logging.info("Docker not found, installing Docker") logging.info("Docker not found, installing Docker")
if platform.system() == "Darwin" or platform.system() == "Windows": if platform.system() == "Darwin" or platform.system() == "Windows":
print( cprint(
f""" f"""
This script doesn't install Docker on {"Mac" if platform.system()=="Darwin" else "Windows"}. This script doesn't install Docker on {"Mac" if platform.system()=="Darwin" else "Windows"}.
@ -267,11 +363,26 @@ def install_docker():
) )
subprocess.run(["/bin/bash"], input=ps.stdout, capture_output=True) subprocess.run(["/bin/bash"], input=ps.stdout, capture_output=True)
subprocess.run( subprocess.run(
["sudo", "usermod", "-aG", "docker", str(os.getenv("USER"))], check=True [
"sudo",
"usermod",
"-aG",
"docker",
str(os.getenv("USER")),
],
check=True,
) )
cprint("Waiting Docker to start", level=3) cprint("Waiting Docker to start", level=3)
time.sleep(10) time.sleep(10)
subprocess.run(["sudo", "systemctl", "restart", "docker.service"], check=True) subprocess.run(
[
"sudo",
"systemctl",
"restart",
"docker.service",
],
check=True,
)
except Exception as e: except Exception as e:
logging.error("Installing Docker failed", exc_info=True) logging.error("Installing Docker failed", exc_info=True)
cprint("Failed to Install Docker\n", e) cprint("Failed to Install Docker\n", e)
@ -279,18 +390,25 @@ def install_docker():
sys.exit(1) sys.exit(1)
def install_container_runtime(runtime="docker"):
if which(runtime) is not None:
cprint(runtime.title() + " is already installed", level=2)
return
if runtime == "docker":
install_docker()
def create_site( def create_site(
sitename: str, sitename: str,
project: str, project: str,
db_pass: str, db_pass: str,
admin_pass: str, admin_pass: str,
apps: List[str] = [],
): ):
apps = apps or []
cprint(f"\nCreating site: {sitename} \n", level=3) cprint(f"\nCreating site: {sitename} \n", level=3)
command = [
try: "docker",
subprocess.run(
[
which("docker"),
"compose", "compose",
"-p", "-p",
project, project,
@ -298,16 +416,20 @@ def create_site(
"backend", "backend",
"bench", "bench",
"new-site", "new-site",
sitename,
"--no-mariadb-socket", "--no-mariadb-socket",
"--db-root-password", f"--db-root-password={db_pass}",
db_pass, f"--admin-password={admin_pass}",
"--admin-password", ]
admin_pass,
"--install-app", for app in apps:
"erpnext", command.append("--install-app")
"--set-default", command.append(app)
],
command.append(sitename)
try:
subprocess.run(
command,
check=True, check=True,
) )
logging.info("New site creation completed") logging.info("New site creation completed")
@ -316,13 +438,68 @@ def create_site(
cprint(f"Bench Site creation failed for {sitename}\n", e) cprint(f"Bench Site creation failed for {sitename}\n", e)
if __name__ == "__main__": def migrate_site(project: str):
parser = argparse.ArgumentParser(description="Install Frappe with Docker") cprint(f"\nMigrating sites for {project}", level=3)
parser.add_argument(
"-p", "--prod", help="Setup Production System", action="store_true" exec_command(
project=project,
command=[
"bench",
"--site",
"all",
"migrate",
],
) )
def exec_command(project: str, command: List[str] = [], interactive_terminal=False):
if not command:
command = ["echo", '"Please execute a command"']
cprint(f"\nExecuting Command:\n{' '.join(command)}", level=3)
exec_command = [
"docker",
"compose",
"-p",
project,
"exec",
]
if interactive_terminal:
exec_command.append("-it")
exec_command.append("backend")
exec_command += command
try:
subprocess.run(
exec_command,
check=True,
)
logging.info("New site creation completed")
except Exception as e:
logging.error(f"Exec command failed for {project}", exc_info=True)
cprint(f"Exec command failed for {project}\n", e)
def add_project_option(parser: argparse.ArgumentParser):
parser.add_argument( parser.add_argument(
"-d", "--dev", help="Setup Development System", action="store_true" "-n",
"--project",
help="Project Name",
default="frappe",
)
return parser
def add_setup_options(parser: argparse.ArgumentParser):
parser.add_argument(
"-a",
"--app",
dest="apps",
default=[],
help="list of app(s) to be installed",
action="append",
) )
parser.add_argument( parser.add_argument(
"-s", "-s",
@ -332,25 +509,279 @@ if __name__ == "__main__":
action="append", action="append",
dest="sites", dest="sites",
) )
parser.add_argument("-n", "--project", help="Project Name", default="frappe") parser.add_argument("-e", "--email", help="Add email for the SSL.")
return parser
def add_common_parser(parser: argparse.ArgumentParser):
parser = add_project_option(parser)
parser.add_argument("-i", "--image", help="Full Image Name") parser.add_argument("-i", "--image", help="Full Image Name")
parser.add_argument("-q", "--no-ssl", action="store_true", help="No https")
parser.add_argument( parser.add_argument(
"--email", help="Add email for the SSL.", required="--prod" in sys.argv "-m", "--http-port", help="Http port in case of no-ssl", default="8080"
) )
parser.add_argument( parser.add_argument(
"-v", "--version", help="ERPNext version to install, defaults to latest stable" "-v",
"--version",
help="ERPNext or image version to install, defaults to latest stable",
) )
return parser
def add_build_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("build", help="Build custom images")
parser = add_common_parser(parser)
parser = add_setup_options(parser)
parser.add_argument(
"-p",
"--push",
help="Push the built image to registry",
action="store_true",
)
parser.add_argument(
"-r",
"--frappe-path",
help="Frappe Repository to use, default: https://github.com/frappe/frappe",
default="https://github.com/frappe/frappe",
)
parser.add_argument(
"-b",
"--frappe-branch",
help="Frappe branch to use, default: version-15",
default="version-15",
)
parser.add_argument(
"-j",
"--apps-json",
help="Path to apps json, default: frappe_docker/development/apps-example.json",
default="frappe_docker/development/apps-example.json",
)
parser.add_argument(
"-t",
"--tag",
dest="tags",
help="Full Image Name(s), default: custom-apps:latest",
action="append",
)
parser.add_argument(
"-c",
"--containerfile",
help="Path to Containerfile: images/layered/Containerfile",
default="images/layered/Containerfile",
)
parser.add_argument(
"-y",
"--python-version",
help="Python Version, default: 3.11.6",
default="3.11.6",
)
parser.add_argument(
"-d",
"--node-version",
help="NodeJS Version, default: 18.18.2",
default="18.18.2",
)
parser.add_argument(
"-x",
"--deploy",
help="Deploy after build",
action="store_true",
)
parser.add_argument(
"-u",
"--upgrade",
help="Upgrade after build",
action="store_true",
)
def add_deploy_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("deploy", help="Deploy using compose")
parser = add_common_parser(parser)
parser = add_setup_options(parser)
def add_develop_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("develop", help="Development setup using compose")
parser.add_argument(
"-n", "--project", default="frappe", help="Compose project name"
)
def add_upgrade_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("upgrade", help="Upgrade existing project")
parser = add_common_parser(parser)
def add_exec_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("exec", help="Exec into existing project")
parser = add_project_option(parser)
def build_image(
push: bool,
frappe_path: str,
frappe_branch: str,
containerfile_path: str,
apps_json_path: str,
tags: List[str],
python_version: str,
node_version: str,
):
if not check_repo_exists():
clone_frappe_docker_repo()
install_container_runtime()
if not tags:
tags = ["custom-apps:latest"]
apps_json_base64 = None
try:
with open(apps_json_path, "rb") as file_text:
file_read = file_text.read()
apps_json_base64 = (
base64.encodebytes(file_read).decode("utf-8").replace("\n", "")
)
except Exception as e:
logging.error("Unable to base64 encode apps.json", exc_info=True)
cprint("\nUnable to base64 encode apps.json\n\n", "[ERROR]: ", e, level=1)
command = [
which("docker"),
"build",
"--progress=plain",
]
for tag in tags:
command.append(f"--tag={tag}")
command += [
f"--file={containerfile_path}",
f"--build-arg=FRAPPE_PATH={frappe_path}",
f"--build-arg=FRAPPE_BRANCH={frappe_branch}",
f"--build-arg=PYTHON_VERSION={python_version}",
f"--build-arg=NODE_VERSION={node_version}",
f"--build-arg=APPS_JSON_BASE64={apps_json_base64}",
".",
]
try:
subprocess.run(
command,
check=True,
cwd="frappe_docker",
)
except Exception as e:
logging.error("Image build failed", exc_info=True)
cprint("\nImage build failed\n\n", "[ERROR]: ", e, level=1)
if push:
try:
for tag in tags:
subprocess.run(
[which("docker"), "push", tag],
check=True,
)
except Exception as e:
logging.error("Image push failed", exc_info=True)
cprint("\nImage push failed\n\n", "[ERROR]: ", e, level=1)
def get_args_parser():
parser = argparse.ArgumentParser(
description="Easy install script for Frappe Framework"
)
# Setup sub-commands
subparsers = parser.add_subparsers(dest="subcommand")
# Build command
add_build_parser(subparsers)
# Deploy command
add_deploy_parser(subparsers)
# Upgrade command
add_upgrade_parser(subparsers)
# Develop command
add_develop_parser(subparsers)
# Exec command
add_exec_parser(subparsers)
return parser
if __name__ == "__main__":
parser = get_args_parser()
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)
args = parser.parse_args() args = parser.parse_args()
if args.dev:
cprint("\nSetting Up Development Instance\n", level=2) if args.subcommand == "build":
logging.info("Running Development Setup") build_image(
setup_dev_instance(args.project) push=args.push,
elif args.prod: frappe_path=args.frappe_path,
frappe_branch=args.frappe_branch,
apps_json_path=args.apps_json,
tags=args.tags,
containerfile_path=args.containerfile,
python_version=args.python_version,
node_version=args.node_version,
)
if args.deploy:
setup_prod(
project=args.project,
sites=args.sites,
email=args.email,
version=args.version,
image=args.image,
apps=args.apps,
is_https=not args.no_ssl,
http_port=args.http_port,
)
elif args.upgrade:
update_prod(
project=args.project,
version=args.version,
image=args.image,
is_https=not args.no_ssl,
http_port=args.http_port,
)
elif args.subcommand == "deploy":
cprint("\nSetting Up Production Instance\n", level=2) cprint("\nSetting Up Production Instance\n", level=2)
logging.info("Running Production Setup") logging.info("Running Production Setup")
if "example.com" in args.email: if "example.com" in args.email:
cprint("Emails with example.com not acceptable", level=1) cprint("Emails with example.com not acceptable", level=1)
sys.exit(1) sys.exit(1)
setup_prod(args.project, args.sites, args.email, args.version, args.image) setup_prod(
else: project=args.project,
parser.print_help() sites=args.sites,
email=args.email,
version=args.version,
image=args.image,
apps=args.apps,
is_https=not args.no_ssl,
http_port=args.http_port,
)
elif args.subcommand == "develop":
cprint("\nSetting Up Development Instance\n", level=2)
logging.info("Running Development Setup")
setup_dev_instance(args.project)
elif args.subcommand == "upgrade":
cprint("\nUpgrading Production Instance\n", level=2)
logging.info("Upgrading Development Setup")
update_prod(
project=args.project,
version=args.version,
image=args.image,
is_https=not args.no_ssl,
http_port=args.http_port,
)
elif args.subcommand == "exec":
cprint(f"\nExec into {args.project} backend\n", level=2)
logging.info(f"Exec into {args.project} backend")
exec_command(
project=args.project,
command=["bash"],
interactive_terminal=True,
)