feat: vm create management command
parent
d84021915f
commit
0606c4ade0
|
@ -1,3 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
from .models import InstanceCreated
|
||||||
|
|
||||||
|
admin.site.register(InstanceCreated)
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Create your tests here.
|
||||||
|
|
||||||
|
# Copyright © 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
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)
|
|
@ -0,0 +1,103 @@
|
||||||
|
# Copyright © 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
|
@ -12,18 +12,26 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
from pathlib import Path
|
|
||||||
import shutil
|
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.test import TestCase, Client, override_settings
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.management import call_command
|
||||||
|
|
||||||
from git import Repo
|
from git import Repo
|
||||||
|
|
||||||
from dash.models import Instance
|
from dash.models import Instance, InstanceConfiguration
|
||||||
from .utils import Infra
|
|
||||||
|
|
||||||
from accounts.tests import register_util, login_util
|
from accounts.tests import register_util, login_util
|
||||||
from dash.tests import create_configurations, create_instance_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):
|
def custom_config(test_name: str):
|
||||||
|
@ -99,9 +107,7 @@ class InfraUtilTest(TestCase):
|
||||||
|
|
||||||
c = custom_config(test_name="test_add_vm--get-head")
|
c = custom_config(test_name="test_add_vm--get-head")
|
||||||
path = c["INFRA"]["HOSTEA_REPO"]["PATH"]
|
path = c["INFRA"]["HOSTEA_REPO"]["PATH"]
|
||||||
repo = Repo.clone_from(
|
repo = Repo.clone_from(conf["REMOTE"], path, env=infra.env)
|
||||||
conf["REMOTE"], path, env={"GIT_SSH_COMMAND": infra.ssh_cmd}
|
|
||||||
)
|
|
||||||
self.assertEqual(repo.head.commit.hexsha == after_add, True)
|
self.assertEqual(repo.head.commit.hexsha == after_add, True)
|
||||||
|
|
||||||
before_rm = infra.repo.head.commit.hexsha
|
before_rm = infra.repo.head.commit.hexsha
|
||||||
|
@ -111,3 +117,26 @@ class InfraUtilTest(TestCase):
|
||||||
|
|
||||||
repo.git.pull()
|
repo.git.pull()
|
||||||
self.assertEqual(repo.head.commit.hexsha == after_rm, True)
|
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)
|
||||||
|
|
|
@ -30,10 +30,10 @@ from .models import InstanceCreated
|
||||||
def create_vm_if_not_exists(instance: Instance):
|
def create_vm_if_not_exists(instance: Instance):
|
||||||
infra = Infra()
|
infra = Infra()
|
||||||
if not InstanceCreated.objects.filter(instance=instance).exists():
|
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)
|
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()
|
instance.save()
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,13 +48,12 @@ class Infra:
|
||||||
if not self.repo_path.exists():
|
if not self.repo_path.exists():
|
||||||
os.makedirs(self.repo_path)
|
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:
|
try:
|
||||||
self.repo = Repo(path=self.repo_path)
|
self.repo = Repo(path=self.repo_path)
|
||||||
except InvalidGitRepositoryError:
|
except InvalidGitRepositoryError:
|
||||||
self.repo = Repo.clone_from(
|
self.repo = Repo.clone_from(conf["REMOTE"], self.repo_path, env=self.env)
|
||||||
conf["REMOTE"], self.repo_path, env={"GIT_SSH_COMMAND": self.ssh_cmd}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _host_vars_dir(self, subdomain: str) -> Path:
|
def _host_vars_dir(self, subdomain: str) -> Path:
|
||||||
"""
|
"""
|
||||||
|
@ -120,13 +119,16 @@ class Infra:
|
||||||
author="Dashboard Bot <bot@dashboard.hostea.org>",
|
author="Dashboard Bot <bot@dashboard.hostea.org>",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _pull(self):
|
||||||
|
self.repo.git.pull(env=self.env, rebase="true")
|
||||||
|
|
||||||
def add_vm(self, instance: Instance) -> str:
|
def add_vm(self, instance: Instance) -> str:
|
||||||
"""
|
"""
|
||||||
Add new VM to infrastructure repository
|
Add new VM to infrastructure repository
|
||||||
|
|
||||||
The gitea user password is returned
|
The gitea user password is returned
|
||||||
"""
|
"""
|
||||||
self.repo.git.pull()
|
self._pull()
|
||||||
|
|
||||||
subdomain = instance.name
|
subdomain = instance.name
|
||||||
host_vars_dir = self._host_vars_dir(subdomain)
|
host_vars_dir = self._host_vars_dir(subdomain)
|
||||||
|
@ -205,14 +207,14 @@ class Infra:
|
||||||
)
|
)
|
||||||
|
|
||||||
self._commit(action="add", subdomain=subdomain)
|
self._commit(action="add", subdomain=subdomain)
|
||||||
self.repo.git.push()
|
self.repo.git.push(env=self.env)
|
||||||
return gitea_password
|
return gitea_password
|
||||||
|
|
||||||
def remove_vm(self, instance: Instance):
|
def remove_vm(self, instance: Instance):
|
||||||
"""
|
"""
|
||||||
Remove a VM from infrastructure repository
|
Remove a VM from infrastructure repository
|
||||||
"""
|
"""
|
||||||
self.repo.git.pull()
|
self._pull()
|
||||||
subdomain = instance.name
|
subdomain = instance.name
|
||||||
|
|
||||||
host_vars_dir = self._host_vars_dir(subdomain)
|
host_vars_dir = self._host_vars_dir(subdomain)
|
||||||
|
@ -229,4 +231,4 @@ class Infra:
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self._commit(action="rm", subdomain=subdomain)
|
self._commit(action="rm", subdomain=subdomain)
|
||||||
self.repo.git.push()
|
self.repo.git.push(env=self.env)
|
||||||
|
|
Loading…
Reference in New Issue