diff --git a/Makefile b/Makefile index ad56d80..c7757df 100644 --- a/Makefile +++ b/Makefile @@ -38,13 +38,7 @@ integration-test: ## run integration tests . ./venv/bin/activate && integration/tests.sh lint: ## Run linter - @./venv/bin/black ./dashboard/ - @./venv/bin/black ./accounts/ - @./venv/bin/black ./dash/ - @./venv/bin/black ./support/ - @./venv/bin/black ./billing/ - @./venv/bin/black ./infrastructure/ - @./venv/bin/black ./integration/ + @./venv/bin/black dashboard accounts dash support billing infrastructure integration migrate: ## Run migrations $(call run_migrations) diff --git a/dash/utils.py b/dash/utils.py index f54a96a..ac3d7b3 100644 --- a/dash/utils.py +++ b/dash/utils.py @@ -14,7 +14,6 @@ # along with this program. If not, see . from enum import Enum, unique -from git import Repo from django.contrib.auth.models import User from django.conf import settings diff --git a/infrastructure/tests.py b/infrastructure/tests.py index 71d576d..7ac8e08 100644 --- a/infrastructure/tests.py +++ b/infrastructure/tests.py @@ -12,19 +12,11 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import shutil import time -from io import StringIO -from urllib.parse import urlunparse -from pathlib import Path -import requests from django.test import TestCase, Client, override_settings -from django.conf import settings from django.core.management import call_command -from git import Repo - from dash.models import Instance, InstanceConfiguration from accounts.tests import register_util, login_util from dash.tests import create_configurations, create_instance_util, infra_custom_config @@ -77,59 +69,37 @@ class InfraUtilTest(TestCase): @override_settings(HOSTEA=infra_custom_config(test_name="test_add_vm")) def test_add_vm(self): - infra = Infra() c = Client() - conf = settings.HOSTEA["INFRA"]["HOSTEA_REPO"] login_util(self, c, "accounts.home") subdomain = "add_vm" - base = infra.repo_path - create_instance_util( t=self, c=c, instance_name=subdomain, config=self.instance_config[0] ) - before_add = infra.repo.head.commit.hexsha instance = Instance.objects.get(name=subdomain) - woodpecker_agent_secret = infra.add_vm(instance=instance) - after_add = infra.repo.head.commit.hexsha - self.assertEqual(before_add is not after_add, True) - # c = infra_custom_config(test_name="test_add_vm--get-head") - path = Path("/tmp/hostea/dashboard/check-test_add_vm") - if path.exists(): - shutil.rmtree(path) - repo = Repo.clone_from(conf["REMOTE"], path, env=infra.env) - repo.git.pull(env=infra.env) - self.assertEqual(repo.head.commit.hexsha == after_add, True) + infra = Infra() + before_add = infra._sha() + (password, after_add) = infra.add_vm(instance=instance) + self.assertNotEqual(before_add, after_add) - before_rm = infra.repo.head.commit.hexsha - infra.remove_vm(instance=instance) - after_rm = infra.repo.head.commit.hexsha - self.assertEqual(before_add is not after_add, True) - - repo.git.pull(env=infra.env) - self.assertEqual(repo.head.commit.hexsha == after_rm, True) + before_rm = after_add + after_rm = infra.remove_vm(instance=instance) + self.assertNotEqual(before_rm, after_rm) @override_settings(HOSTEA=infra_custom_config(test_name="test_cmd")) def test_cmd(self): subdomain = "cmd_vm" infra = Infra() c = Client() - conf = settings.HOSTEA["INFRA"]["HOSTEA_REPO"] login_util(self, c, "accounts.home") - base = infra.repo_path - - stdout = StringIO() - stderr = StringIO() - self.assertEqual(Instance.objects.filter(name=subdomain).exists(), False) # username exists call_command( "vm", "create", subdomain, f"--owner={self.username}", "--flavor=medium" ) - out = stdout.getvalue() instance = Instance.objects.get(name=subdomain) @@ -152,7 +122,6 @@ class InfraUtilTest(TestCase): # run create vm command again with different configuration but same name # to crudely check idempotency - old_size = instance.configuration_id call_command( "vm", "create", subdomain, f"--owner={self.username}", "--flavor=large" ) @@ -172,7 +141,6 @@ class InfraUtilTest(TestCase): ) call_command("vm", "delete", subdomain) - out = stdout.getvalue() self.assertEqual(Instance.objects.filter(name=subdomain).exists(), False) host_vars_dir = infra._host_vars_dir(subdomain) diff --git a/infrastructure/utils.py b/infrastructure/utils.py index 671b0d0..3cb098b 100644 --- a/infrastructure/utils.py +++ b/infrastructure/utils.py @@ -12,25 +12,28 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging import os +import sh import shutil import yaml import requests from pathlib import Path -from threading import Thread, Event +from threading import Thread from time import sleep from django.utils.crypto import get_random_string from django.template.loader import render_to_string from django.core.mail import send_mail from django.conf import settings -from git import Repo, Commit -from git.exc import InvalidGitRepositoryError from dash.models import Instance from infrastructure.models import InstanceCreated, JobType, Job +logging.basicConfig() +logger = logging.getLogger(__name__) + class Worker(Thread): def __init__(self, job: Job): @@ -75,7 +78,7 @@ class Worker(Thread): job.delete() -def create_vm_if_not_exists(instance: Instance) -> (str, Commit): +def create_vm_if_not_exists(instance: Instance) -> (str, str): """ Create VM utility. Gitea password is returned """ @@ -111,18 +114,17 @@ class Infra: def __init__(self): conf = settings.HOSTEA["INFRA"]["HOSTEA_REPO"] self.repo_path = Path(conf["PATH"]) - if not self.repo_path.exists(): - os.makedirs(self.repo_path) - - ssh_cmd = f"/usr/bin/ssh -oStrictHostKeyChecking=no -i {conf['SSH_KEY']}" - self.env = {"GIT_SSH_COMMAND": ssh_cmd} self._clone() def _clone(self): + conf = settings.HOSTEA["INFRA"]["HOSTEA_REPO"] + ssh_cmd = f"/usr/bin/ssh -oStrictHostKeyChecking=no -i {conf['SSH_KEY']}" + self.git = sh.git.bake(_env={"GIT_SSH_COMMAND": ssh_cmd}) conf = settings.HOSTEA["INFRA"]["HOSTEA_REPO"] if os.path.exists(self.repo_path): shutil.rmtree(self.repo_path) - self.repo = Repo.clone_from(conf["REMOTE"], self.repo_path, env=self.env) + self.git.clone(conf["REMOTE"], self.repo_path) + self.git = self.git.bake("-C", self.repo_path) def _host_vars_dir(self, subdomain: str) -> Path: """ @@ -210,25 +212,21 @@ class Infra: f.write(content) f.write("\n") - def _add_files(self, subdomain: str): - """ - Add all relevant files of a VM - """ - self.repo.git.add(str(self._host_vars_dir(subdomain=subdomain))) - self.repo.git.add(str(self._backup_path(subdomain=subdomain))) - self.repo.git.add(str(self._service_path(subdomain=subdomain))) - self.repo.git.add(str(self._hostscript_path(subdomain=subdomain))) + def _push(self, message): + self.git.add(".") + self.git.config("user.email", settings.HOSTEA["INSTANCE_MAINTAINER_CONTACT"]) + self.git.config("user.name", "Hostea dashboard") + try: + self.git.commit("-m", f"dashboard: {message}") + except sh.ErrorReturnCode_1: + logger.debug("no change") + else: + self.git.push("origin", "master") + return self._sha() - def _commit(self, action: str, subdomain: str) -> Commit: - """ - Commit changes to a VM configuration - """ - - self._add_files(subdomain=subdomain) - return self.repo.git.commit( - message=f"{action} VM {subdomain}", - author="Dashboard Bot ", - ) + def _sha(self): + sha = self.git("rev-parse", "origin/master") + return str(sha).strip() @staticmethod def translate_size(instance: Instance) -> str: @@ -244,13 +242,12 @@ class Infra: return "openstack_flavor_large" return instance.configuration_id.name - def add_vm(self, instance: Instance) -> (str, Commit): + def add_vm(self, instance: Instance) -> (str, str): """ Add new VM to infrastructure repository The gitea user password is returned """ - self._clone() subdomain = instance.name host_vars_dir = self._host_vars_dir(subdomain) @@ -321,29 +318,26 @@ class Infra: ), ) - commit = self._commit(action="add", subdomain=subdomain) - self.repo.git.push(env=self.env) + commit = self._push(f"add vm {subdomain}") return (gitea_password, commit) def remove_vm(self, instance: Instance): """ Remove a VM from infrastructure repository """ - self._clone() subdomain = instance.name - try: - - host_vars_dir = self._host_vars_dir(subdomain) + host_vars_dir = self._host_vars_dir(subdomain) + if os.path.exists(host_vars_dir): shutil.rmtree(host_vars_dir) - backup = self._backup_path(subdomain) + backup = self._backup_path(subdomain) + if os.path.exists(backup): os.remove(backup) - service = self._service_path(subdomain) + service = self._service_path(subdomain) + if os.path.exists(service): os.remove(service) - except FileNotFoundError: - pass hostscript = self._hostscript_path(subdomain) with open(hostscript, "w+", encoding="utf-8") as f: @@ -356,5 +350,4 @@ class Infra: context={"subdomain": subdomain}, ), ) - self._commit(action="rm", subdomain=subdomain) - self.repo.git.push(env=self.env) + return self._push(f"rm vm {subdomain}") diff --git a/integration/lib.sh b/integration/lib.sh index 079b9c2..7e79870 100755 --- a/integration/lib.sh +++ b/integration/lib.sh @@ -139,13 +139,13 @@ new_fleet_repo_init() { tmp_dir=$(mktemp -d) pushd $tmp_dir echo "init" >> README + git init if is_ci then - git config --global user.email "${CI_COMMIT_AUTHOR_EMAIL}" - git config --global user.name "${CI_COMMIT_AUTHOR}" + git config user.email "${CI_COMMIT_AUTHOR_EMAIL}" + git config user.name "${CI_COMMIT_AUTHOR}" chmod 600 $GITEA_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE fi - git init git add README git commit -m "init" REMOTE="$GITEA_SSH_URL/$GITEA_HOSTEA_USERNAME/$1.git" diff --git a/manage.py b/manage.py index 73d4f5b..f2be5c4 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dashboard.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/requirements.txt b/requirements.txt index 1aa7e5f..bb351ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,8 +15,6 @@ django-oauth-toolkit==2.0.0 django-payments==1.0.0 django-phonenumber-field==6.3.0 djangorestframework==3.13.1 -gitdb==4.0.9 -GitPython==3.1.27 greenlet==1.1.2 idna==3.3 install==1.3.5 @@ -40,6 +38,7 @@ pytz==2022.1 PyYAML==6.0 requests==2.27.1 six==1.16.0 +sh==1.14.2 smmap==5.0.0 sqlparse==0.4.2 stripe==3.4.0