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

Fix race condition #21

Merged
merged 4 commits into from
Jan 20, 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
13 changes: 7 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pyyaml
flask
pyjwt
Flask-HTTPAuth
python-dotenv
requests
pyyaml==6.0.1
flask==3.0.0
pyjwt==2.8.0
Flask-HTTPAuth==4.8.0
python-dotenv==1.0.0
requests==2.31.0
filelock==3.13.1
74 changes: 42 additions & 32 deletions server/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import subprocess
import typing

import filelock

from .utils import ComposeHelper, DeploymentConfig, NginxHelper, SecretsHelper

logger = logging.getLogger(__name__)
Expand All @@ -16,26 +18,32 @@ def __init__(self, config: DeploymentConfig):
"DEPLOYMENTS_MOUNT_DIR"
)
self._deployment_namespace = f"{self._config.project_name}_{self._config.branch_name}_{config.get_project_hash()}"
self._lock_file_path = os.path.join(
os.environ.get("LOCK_FILE_BASE_PATH") or "/tmp",
f"{self._deployment_namespace}.lock",
)
self._lock = filelock.FileLock(self._lock_file_path)
self._project_path: typing.Final[str] = os.path.join(
self._DEPLOYMENTS_MOUNT_DIR, self._deployment_namespace
)

if config.rest_action != "DELETE":
self._setup_project()
with self._lock:
if config.rest_action != "DELETE":
self._setup_project()

self._compose_helper = ComposeHelper(
os.path.join(self._project_path, config.compose_file_location),
config.rest_action != "DELETE",
)
self._secrets_helper = SecretsHelper(
self._config.project_name, self._config.branch_name, self._project_path
)
self._outer_proxy_conf_location = (
os.environ.get("NGINX_PROXY_CONF_LOCATION") or "/etc/nginx/conf.d"
)
self._nginx_helper = NginxHelper(
config, self._outer_proxy_conf_location, self._project_path
)
self._compose_helper = ComposeHelper(
os.path.join(self._project_path, config.compose_file_location),
config.rest_action != "DELETE",
)
self._secrets_helper = SecretsHelper(
self._config.project_name, self._config.branch_name, self._project_path
)
self._outer_proxy_conf_location = (
os.environ.get("NGINX_PROXY_CONF_LOCATION") or "/etc/nginx/conf.d"
)
self._nginx_helper = NginxHelper(
config, self._outer_proxy_conf_location, self._project_path
)

def _clone_project(self):
process = subprocess.Popen(
Expand All @@ -50,18 +58,18 @@ def _clone_project(self):
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

stdout, stderr = process.communicate()

try:
stdout, stderr = process.communicate()
except subprocess.TimeoutExpired as e:
logger.error(f"Error cloning the repo {self._config} with {e}")
raise
if process.returncode == 0:
logger.info("Git clone successful.")
else:
logger.error(f"Git clone failed. Return code: {process.returncode}")
logger.error("Standard Output:")
logger.error(stdout.decode())
logger.error("Standard Error:")
logger.error(stderr.decode())
raise Exception("Git clone failed")
logger.error(f"Standard Output: {stdout.decode()}")
logger.error(f"Standard Error: {stderr.decode()}")
raise Exception(f"Cloning the Git repo failed {self._config}")

def _setup_project(self):
if os.path.exists(self._project_path):
Expand Down Expand Up @@ -89,11 +97,6 @@ def _deploy_project(self):
)
return urls

def deploy_preview_environment(self):
urls = self._deploy_project()
self._configure_outer_proxy()
return urls

def _delete_deployment_files(self):
if not os.path.exists(self._project_path):
print(f"{self._project_path} already deleted!")
Expand All @@ -103,8 +106,15 @@ def _delete_deployment_files(self):
except Exception as e:
logger.debug(f"Error removing deployment files {e}")

def deploy_preview_environment(self):
with self._lock:
urls = self._deploy_project()
self._configure_outer_proxy()
return urls

def delete_preview_environment(self):
self._compose_helper.remove_services()
self._nginx_helper.remove_outer_proxy()
self._nginx_helper.reload_nginx()
self._delete_deployment_files()
with self._lock:
self._compose_helper.remove_services()
self._nginx_helper.remove_outer_proxy()
self._nginx_helper.reload_nginx()
self._delete_deployment_files()
6 changes: 6 additions & 0 deletions server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ class DeploymentConfig:
def get_project_hash(self):
return get_random_stub(f"{self.project_name}:{self.branch_name}")

def __repr__(self):
return (
f"DeploymentConfig({self.project_name!r}, {self.branch_name!r}, {self.project_git_url!r}, "
f"{self.compose_file_location!r}, {self.rest_action!r})"
)


class ComposeHelper:
NGINX_SERVICE_TEMPLATE: typing.Final[
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def deployment_config():
project_name="test-project-name",
branch_name="test-branch-name",
project_git_url="https://github.com/tushar5526/test-project-name.git",
rest_action="POST",
)


Expand Down
8 changes: 8 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,11 @@ def test_remove_outer_proxy_when_file_is_deleted_already(nginx_helper, mocker):
nginx_helper.remove_outer_proxy()

# Then No error should be raised


def test_deployment_config_repr(deployment_config):
expected_repr = (
"DeploymentConfig('test-project-name', 'test-branch-name', "
"'https://github.com/tushar5526/test-project-name.git', 'docker-compose.yml', 'POST')"
)
assert repr(deployment_config) == expected_repr
Loading