From 0606c4ade006ee5239242a98ba510ab2d397bbfd Mon Sep 17 00:00:00 2001 From: realaravinth Date: Tue, 28 Jun 2022 00:57:25 +0530 Subject: [PATCH] feat: vm create management command --- infrastructure/admin.py | 4 +- infrastructure/management/commands/tests.py | 77 +++++++++++++++ infrastructure/management/commands/vm.py | 103 ++++++++++++++++++++ infrastructure/tests.py | 43 ++++++-- infrastructure/utils.py | 24 ++--- 5 files changed, 232 insertions(+), 19 deletions(-) create mode 100644 infrastructure/management/commands/tests.py create mode 100644 infrastructure/management/commands/vm.py diff --git a/infrastructure/admin.py b/infrastructure/admin.py index 8c38f3f..1f98286 100644 --- a/infrastructure/admin.py +++ b/infrastructure/admin.py @@ -1,3 +1,5 @@ from django.contrib import admin -# Register your models here. +from .models import InstanceCreated + +admin.site.register(InstanceCreated) diff --git a/infrastructure/management/commands/tests.py b/infrastructure/management/commands/tests.py new file mode 100644 index 0000000..3dac69b --- /dev/null +++ b/infrastructure/management/commands/tests.py @@ -0,0 +1,77 @@ +# Create your tests here. + +# Copyright © 2022 Aravinth Manivannan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +import time +import os +from io import StringIO +from urllib.parse import urlparse, urlunparse + +import requests + +from django.core import mail +from django.contrib.auth import get_user_model +from django.core.management import call_command +from django.urls import reverse +from django.test import TestCase, Client, override_settings +from django.utils.http import urlencode +from django.contrib.auth import authenticate +from django.conf import settings + + +from accounts.tests import register_util +from dash.models import Instance +from infrastructure.models import InstanceCreated, InstanceConfiguration + + +class VMCommands(TestCase): + """ + Test command: manage.py create_oidc + """ + + def setUp(self): + self.username = "hosteacustomer" + register_util(t=self, username=self.username) + self.vm_name = "MyHosteaVM" + + def test_cmd(self): + + Application = get_application_model() + + stdout = StringIO() + stderr = StringIO() + + redirect_uri = "http://example.org" + app_name = "test_cmd_oidc" + + self.assertEqual(Instance.objects.filter(name=vm_name).exists(), False) + # username exists + call_command( + "vm", "create", self.vm_name, f"--owner={self.username}", "==flavor=medium" + ) + out = stdout.getvalue() + + self.assertIn(f"Instance created", out) + + instance = Instance.objects.get(name=self.vm_name) + self.assertEqual(instance.owned_by, self.user) + self.assertEqual( + instance.configuration_id, InstanceConfiguration.objects.get(name="s1-4") + ) + + instance_created = InstanceCreated.objects.get(name=self.vm_name) + self.assertEqual(instance_created.instance, instance) + + self.assertEqual(instance_created.created, True) diff --git a/infrastructure/management/commands/vm.py b/infrastructure/management/commands/vm.py new file mode 100644 index 0000000..34cf258 --- /dev/null +++ b/infrastructure/management/commands/vm.py @@ -0,0 +1,103 @@ +# Copyright © 2022 Aravinth Manivannan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.core.management.base import BaseCommand +from django.core.exceptions import ValidationError +from django.conf import settings +from django.contrib.auth import get_user_model + +from oauth2_provider.models import get_application_model +from oauth2_provider.generators import generate_client_id, generate_client_secret + +from dash.models import InstanceConfiguration, Instance +from dash.utils import create_instance +from infrastructure.utils import create_vm_if_not_exists + + +class Command(BaseCommand): + help = "Get user ID from username" + action_key = "action" + vm_name_key = "vm_name" + flavor_key = "flavor" + owner_key = "owner" + + def add_arguments(self, parser): + parser.add_argument( + self.action_key, + type=str, + help="VM action: create/delete", + ) + + parser.add_argument( + self.vm_name_key, + type=str, + help="Name of the VM", + ) + + parser.add_argument( + f"--{self.owner_key}", + type=str, + help="Owner username", + ) + + parser.add_argument( + f"--{self.flavor_key}", + type=str, + help="Name of the VM flavor: small, medium, large", + ) + + def create_vm(self, *args, **options): + owner = options[self.owner_key] + flavor = options[self.flavor_key] + vm_name = options[self.vm_name_key] + + if flavor == "small": + size = "s1-2" + elif flavor == "medium": + size = "s1-4" + elif flavor == "large": + size = "s1-8" + else: + self.stdout.write(self.style.WARN("flavour no match")) + size = flavor + user = get_user_model().objects.get(username=owner) + instance = create_instance(vm_name=vm_name, configuration_name=size, user=user) + create_instance + create_vm_if_not_exists(instance) + print("Instance created") + + def delete_vm(self, *args, **options): + create_vm + + def handle(self, *args, **options): + for i in [self.action_key, self.vm_name_key]: + if i not in options: + self.stdout.write(self.style.ERROR(f"Please provide {i}")) + return + if options[self.action_key] == "create": + for i in [self.flavor_key, self.owner_key]: + if i not in options: + self.stdout.write(self.style.ERROR(f"Please provide {i}")) + return + + self.create_vm(*args, **options) + elif options[self.action_key] == "delete": + self.delete_vm(*args, **options) + + else: + self.stdout.write( + self.style.ERROR("Unknown action: {options[self.action_key]}") + ) + return diff --git a/infrastructure/tests.py b/infrastructure/tests.py index d549ffe..3105d1b 100644 --- a/infrastructure/tests.py +++ b/infrastructure/tests.py @@ -12,18 +12,26 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from pathlib import Path import shutil +import time +import os +import requests +from io import StringIO +from urllib.parse import urlparse, urlunparse +from pathlib import Path 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 -from .utils import Infra - +from dash.models import Instance, InstanceConfiguration from accounts.tests import register_util, login_util from dash.tests import create_configurations, create_instance_util +from infrastructure.models import InstanceCreated + +from .utils import Infra def custom_config(test_name: str): @@ -99,9 +107,7 @@ class InfraUtilTest(TestCase): c = custom_config(test_name="test_add_vm--get-head") path = c["INFRA"]["HOSTEA_REPO"]["PATH"] - repo = Repo.clone_from( - conf["REMOTE"], path, env={"GIT_SSH_COMMAND": infra.ssh_cmd} - ) + repo = Repo.clone_from(conf["REMOTE"], path, env=infra.env) self.assertEqual(repo.head.commit.hexsha == after_add, True) before_rm = infra.repo.head.commit.hexsha @@ -111,3 +117,26 @@ class InfraUtilTest(TestCase): repo.git.pull() self.assertEqual(repo.head.commit.hexsha == after_rm, True) + + vm_name = "cmd_vm" + + stdout = StringIO() + stderr = StringIO() + + self.assertEqual(Instance.objects.filter(name=vm_name).exists(), False) + # username exists + call_command( + "vm", "create", vm_name, f"--owner={self.username}", "--flavor=medium" + ) + out = stdout.getvalue() + + instance = Instance.objects.get(name=vm_name) + self.assertEqual(instance.owned_by, self.user) + self.assertEqual( + instance.configuration_id, InstanceConfiguration.objects.get(name="s1-4") + ) + + instance_created = InstanceCreated.objects.get(instance=instance) + self.assertEqual(instance_created.instance, instance) + + self.assertEqual(instance_created.created, True) diff --git a/infrastructure/utils.py b/infrastructure/utils.py index 9e3d1c1..601ad1f 100644 --- a/infrastructure/utils.py +++ b/infrastructure/utils.py @@ -30,10 +30,10 @@ from .models import InstanceCreated def create_vm_if_not_exists(instance: Instance): infra = Infra() if not InstanceCreated.objects.filter(instance=instance).exists(): - instance = InstanceCreated.objects.create(instance=instance, created=True) - instance.save() gitea_password = infra.add_vm(instance=instance) - instance.gitea_password = gitea_password + instance = InstanceCreated.objects.create( + instance=instance, gitea_password=gitea_password, created=True + ) instance.save() @@ -48,13 +48,12 @@ class Infra: if not self.repo_path.exists(): os.makedirs(self.repo_path) - self.ssh_cmd = f"/usr/bin/ssh -oStrictHostKeyChecking=no -i {conf['SSH_KEY']}" + ssh_cmd = f"/usr/bin/ssh -oStrictHostKeyChecking=no -i {conf['SSH_KEY']}" + self.env = {"GIT_SSH_COMMAND": ssh_cmd} try: self.repo = Repo(path=self.repo_path) except InvalidGitRepositoryError: - self.repo = Repo.clone_from( - conf["REMOTE"], self.repo_path, env={"GIT_SSH_COMMAND": self.ssh_cmd} - ) + self.repo = Repo.clone_from(conf["REMOTE"], self.repo_path, env=self.env) def _host_vars_dir(self, subdomain: str) -> Path: """ @@ -120,13 +119,16 @@ class Infra: author="Dashboard Bot ", ) + def _pull(self): + self.repo.git.pull(env=self.env, rebase="true") + def add_vm(self, instance: Instance) -> str: """ Add new VM to infrastructure repository The gitea user password is returned """ - self.repo.git.pull() + self._pull() subdomain = instance.name host_vars_dir = self._host_vars_dir(subdomain) @@ -205,14 +207,14 @@ class Infra: ) self._commit(action="add", subdomain=subdomain) - self.repo.git.push() + self.repo.git.push(env=self.env) return gitea_password def remove_vm(self, instance: Instance): """ Remove a VM from infrastructure repository """ - self.repo.git.pull() + self._pull() subdomain = instance.name host_vars_dir = self._host_vars_dir(subdomain) @@ -229,4 +231,4 @@ class Infra: ), ) self._commit(action="rm", subdomain=subdomain) - self.repo.git.push() + self.repo.git.push(env=self.env)