diff --git a/Jenkinsfile b/Jenkinsfile index c7b4c9d..e5692dd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -53,10 +53,10 @@ } post { - always { + //always { // Clean up the build directory in the workspace to prevent the "already exists" error - sh "rm -rf build/" - } + //sh "rm -rf build/" + //} success { echo "Deployment of ${env.TARGET_BRANCH} successful." } diff --git a/deployment/core/tasks.py b/deployment/core/tasks.py index 6dad71c..b54ef39 100644 --- a/deployment/core/tasks.py +++ b/deployment/core/tasks.py @@ -1,8 +1,6 @@ import os -import subprocess import time import tomllib -import socket from lupa import LuaRuntime from pathlib import Path from lib.task_types import SuiteTask @@ -149,7 +147,7 @@ self.sh("yarn combine:css", cwd=live_path) # 4. Restart to pick up Node.js changes - self.sh(f"sudo systemctl restart {cfg.service_name}") + self.sh(f"sudo systemctl restart {cfg.service_name}", shlex=True) raise PipelineSuccess("Hot fix applied successfully") @@ -169,6 +167,50 @@ timestamp = time.strftime(self.env.timestamp_format) self.env.release_dir = f"{Path(self.env.testing.deploy_link)}-{timestamp}" + build_git_path = os.path.join(self.env.build_dir, ".git") + + if os.path.exists(build_git_path): + try: + self.sh( + f"git pull origin {self.env.deploy_branch}", + cwd=self.env.build_dir, + handle_exception=False, + ) + except: + self.sh("git fetch origin", cwd=self.env.build_dir) + self.sh( + f"git reset --hard origin/{self.env.deploy_branch}", + cwd=self.env.build_dir, + ) + else: + self.sh( + f"git clone --branch {self.env.deploy_branch} {self.env.repo} {self.env.build_dir}" + ) + self.sh("git submodule update --init --recursive", cwd=self.env.build_dir) + self.sh("yarn config set enableGlobalCache true", cwd=self.env.build_dir) + self.sh( + f"yarn config set globalFolder {self.env.yarn_path}", cwd=self.env.build_dir + ) + self.sh("yarn config set nodeLinker pnp", cwd=self.env.build_dir) + self.sh("yarn install", cwd=self.env.build_dir) + self.sh("yarn combine:css", cwd=self.env.build_dir) + return True + +class YarnBuild(SuiteTask): + """Executes dependency installation and asset compilation""" + + _stage = Stage.BUILD + _deps = [GetDeploymentConfig, LoadServerConfig] + skip: bool = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.name = "Running Yarn build process" + + def _run(self): + timestamp = time.strftime(self.env.timestamp_format) + self.env.release_dir = f"{Path(self.env.testing.deploy_link)}-{timestamp}" + self.sh( f"git clone --branch {self.env.deploy_branch} {self.env.repo} {self.env.build_dir}" ) @@ -207,7 +249,7 @@ final_release_dir = Path(cfg.get_release_dir(timestamp)) # 1. Finalize the directory (Rename from -BUILDING to versioned path) - self.sh(f"mv {self.env.build_dir} {final_release_dir}") + self.sh(f"mv {self.env.build_dir} {final_release_dir}", shlex=True) # 2. Atomic Symlink Swap - ONLY if tests passed if test_success: @@ -216,13 +258,13 @@ temp_link = deploy_link.with_name(deploy_link.name + "_tmp") # Create temporary symlink pointing to the new version - self.sh(f"ln -sfn {final_release_dir} {temp_link}") + self.sh(f"ln -sfn {final_release_dir} {temp_link}", shlex=True) # Atomic rename of the symlink itself (overwrites the old link) - self.sh(f"mv -Tf {temp_link} {deploy_link}") + self.sh(f"mv -Tf {temp_link} {deploy_link}", shlex=True) # Restart service - self.sh(f"sudo systemctl restart {cfg.service_name}") + self.sh(f"sudo systemctl restart {cfg.service_name}", shlex=True) else: self.print(" [SKIP] Test failure detected. Symlink swap bypassed.") self.print(f" [INFO] Artifact preserved at: {final_release_dir}") diff --git a/deployment/core/tests.py b/deployment/core/tests.py index 69deab6..41776f5 100644 --- a/deployment/core/tests.py +++ b/deployment/core/tests.py @@ -1,4 +1,5 @@ import time +import shlex from lib.task_types import SuiteTask, SuiteSubTask from lib.types import Stage @@ -22,9 +23,14 @@ self.print(f" [EXEC] Starting app in {self.env.build_dir}") - cmd = f"sudo /usr/bin/systemctl restart {self.env.testing.service_name}" - - self.sh(cmd, cwd=self.env.build_dir) + cmd = f"nohup yarn run prod --config {self.env.testing.config_file} >> '{self.env.test_log}' 2>&1 & echo $! > '{self.env.pidfile}'" + # This doesn't work because systemd doesnt know where it is yet + # cmd=f"sudo systemctl restart {self.env.testing.service_name}", + self.sh( + cmd, + cwd=self.env.build_dir, + # shlex=True, + ) return True @@ -51,7 +57,7 @@ return if not status: # If the poll fails, we cat the log as requested before failing - self.sh(f"cat '{self.env.test_log}'") + self.sh(f"cat '{self.env.test_log}'", disabled=True) self.fail(f"Test service at {uri} failed to start.") return True @@ -89,7 +95,11 @@ def _run(self): self.sh(f"whoami") self.sh(f"id") - self.sh(f"sudo /usr/bin/systemctl stop {self.env.testing.service_name}") + self.sh(f"kill $(cat '{self.env.pidfile}') || true", shlex=False) + # self.sh( + # f"sudo systemctl stop {self.env.testing.service_name}", + # shlex=False, + # ) return True diff --git a/deployment/lib/task_types.py b/deployment/lib/task_types.py index 1d07574..5b33757 100755 --- a/deployment/lib/task_types.py +++ b/deployment/lib/task_types.py @@ -6,6 +6,7 @@ from pathlib import Path from abc import ABC, abstractmethod from typing import List, TYPE_CHECKING +from shlex import split as shlex_split from lib.printer import Printer from lib.errors import TaskError @@ -148,12 +149,26 @@ raise TaskError(self, critical=critical, *args, **kwargs) def sh( - self, cmd: str, cwd: Path | None = None, handle_exception=True, dry_run=None + self, + cmd: str, + cwd: Path | None = None, + handle_exception=True, + dry_run=None, + check=True, + shell=True, + shlex=False, + disabled=False, ): """Helper to run shell commands within the project context.""" + if shlex: + cmd = shlex_split(cmd) + shell = False if cwd is not None: self.print(f" [CWD] {cwd}") + if disabled: + self.msg(f"[DISABLED] [EXEC] {cmd}") + return cwd = str(cwd or os.getcwd()) self.msg(f" [EXEC] {cmd}") @@ -161,7 +176,7 @@ return try: - return subprocess.run(cmd, shell=True, check=True, cwd=cwd) + return subprocess.run(cmd, shell=shell, check=check, cwd=cwd) except subprocess.CalledProcessError as e: if handle_exception: self.fail(e)