2022-06-24 15:05:32 +00:00
|
|
|
|
# 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 os
|
|
|
|
|
import shutil
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
2022-06-25 12:32:03 +00:00
|
|
|
|
from django.utils.crypto import get_random_string
|
2022-06-24 15:05:32 +00:00
|
|
|
|
from django.template.loader import render_to_string
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from git import Repo
|
|
|
|
|
from git.exc import InvalidGitRepositoryError
|
|
|
|
|
|
|
|
|
|
from dash.models import Instance
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Infra:
|
|
|
|
|
"""
|
|
|
|
|
Utility function to manage infrastructure repository
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
self.ssh_cmd = f"ssh -i {conf['SSH_KEY']}"
|
|
|
|
|
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}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _host_vars_dir(self, subdomain: str) -> Path:
|
|
|
|
|
"""
|
|
|
|
|
utility method: get host_vars directory for a subdomain
|
|
|
|
|
"""
|
|
|
|
|
return self.repo_path.joinpath(f"inventory/host_vars/{subdomain}-host/")
|
|
|
|
|
|
|
|
|
|
def _provision_path(self, subdomain: str) -> Path:
|
|
|
|
|
"""
|
|
|
|
|
utility method: get provision file pay for a subdomain
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self._host_vars_dir(subdomain=subdomain).joinpath("provision.yml")
|
|
|
|
|
|
|
|
|
|
def _gitea_path(self, subdomain: str) -> Path:
|
|
|
|
|
"""
|
|
|
|
|
utility method: get gitea file for a subdomain
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self._host_vars_dir(subdomain=subdomain).joinpath("gitea.yml")
|
|
|
|
|
|
|
|
|
|
def _backup_path(self, subdomain: str) -> Path:
|
|
|
|
|
"""
|
|
|
|
|
utility method: get backup file for a subdomain
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self.repo_path.joinpath(f"inventory/{subdomain}-backup.yml")
|
|
|
|
|
|
|
|
|
|
def _hostscript_path(self, subdomain: str) -> Path:
|
|
|
|
|
"""
|
|
|
|
|
utility method: hostscript file for a subdomain
|
|
|
|
|
"""
|
|
|
|
|
return self.repo_path.joinpath(f"inventory/hosts-scripts/{subdomain}-host.sh")
|
|
|
|
|
|
|
|
|
|
def write_hostscript(self, subdomain: str, content: str):
|
|
|
|
|
"""
|
|
|
|
|
Write contents to hostscript.
|
|
|
|
|
Hostscript will contain the history of all actions that have been
|
|
|
|
|
ordered on a particular VM. So content needs to be appended to it,
|
|
|
|
|
rather than overwritten.
|
|
|
|
|
"""
|
|
|
|
|
hostscript = self._hostscript_path(subdomain)
|
|
|
|
|
with open(hostscript, "a", encoding="utf-8") as f:
|
|
|
|
|
f.write(content)
|
|
|
|
|
f.write("\n")
|
|
|
|
|
|
|
|
|
|
def _add_files(self, subdomain: str):
|
|
|
|
|
"""
|
|
|
|
|
Add all relevant files of a VM
|
|
|
|
|
"""
|
2022-06-25 12:32:03 +00:00
|
|
|
|
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._hostscript_path(subdomain=subdomain)))
|
2022-06-24 15:05:32 +00:00
|
|
|
|
|
|
|
|
|
def _commit(self, action: str, subdomain: str):
|
|
|
|
|
"""
|
|
|
|
|
Commit changes to a VM configuration
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
self._add_files(subdomain=subdomain)
|
|
|
|
|
self.repo.git.commit(
|
2022-06-25 12:32:03 +00:00
|
|
|
|
message=f"{action} VM {subdomain}",
|
|
|
|
|
author="Dashboard Bot <bot@dashboard.hostea.org>",
|
2022-06-24 15:05:32 +00:00
|
|
|
|
)
|
|
|
|
|
|
2022-06-25 12:32:03 +00:00
|
|
|
|
def add_vm(self, instance: Instance) -> str:
|
2022-06-24 15:05:32 +00:00
|
|
|
|
"""
|
|
|
|
|
Add new VM to infrastructure repository
|
2022-06-25 12:32:03 +00:00
|
|
|
|
|
|
|
|
|
The gitea user password is returned
|
2022-06-24 15:05:32 +00:00
|
|
|
|
"""
|
2022-06-25 12:32:03 +00:00
|
|
|
|
self.repo.git.pull()
|
|
|
|
|
|
2022-06-24 15:05:32 +00:00
|
|
|
|
subdomain = instance.name
|
|
|
|
|
host_vars_dir = self._host_vars_dir(subdomain)
|
|
|
|
|
|
|
|
|
|
if not host_vars_dir.exists():
|
|
|
|
|
os.makedirs(host_vars_dir)
|
|
|
|
|
|
|
|
|
|
hostscript_path = self.repo_path.joinpath("inventory/hosts-scripts/")
|
|
|
|
|
if not hostscript_path.exists():
|
|
|
|
|
os.makedirs(hostscript_path)
|
|
|
|
|
|
2022-06-25 12:32:03 +00:00
|
|
|
|
woodpecker_agent_secret = get_random_string(64)
|
|
|
|
|
gitea_password = get_random_string(20)
|
|
|
|
|
|
|
|
|
|
ctx = {
|
|
|
|
|
"woodpecker_agent_secret": woodpecker_agent_secret,
|
|
|
|
|
"woodpecker_hostname": f"{subdomain}-ci",
|
|
|
|
|
"woodpecker_admins": f"{instance.owned_by.username}",
|
|
|
|
|
"gitea_email": instance.owned_by.email,
|
|
|
|
|
"gitea_password": gitea_password,
|
|
|
|
|
"subdomain": subdomain,
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 15:05:32 +00:00
|
|
|
|
gitea = self._gitea_path(subdomain)
|
2022-06-25 12:32:03 +00:00
|
|
|
|
with open(gitea, "w", encoding="utf-8") as f:
|
|
|
|
|
f.write(
|
|
|
|
|
render_to_string(
|
|
|
|
|
"infrastructure/yml/gitea.yml",
|
|
|
|
|
context=ctx,
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-06-24 15:05:32 +00:00
|
|
|
|
|
|
|
|
|
# provision_template = "./templates/infrastructure/yml/provision.yml"
|
|
|
|
|
provision = self._provision_path(subdomain)
|
|
|
|
|
# TODO: instance config names are different the flavours expected:
|
|
|
|
|
# ```
|
|
|
|
|
# openstack_flavor: ‘{{ openstack_flavor_medium }}’ * openstack_flavor: ‘{{ openstack_flavor_large }}’
|
|
|
|
|
# ```
|
|
|
|
|
# check with @dachary about this
|
2022-06-25 12:32:03 +00:00
|
|
|
|
|
|
|
|
|
size = None
|
|
|
|
|
if instance.configuration_id.name == "s1-2":
|
|
|
|
|
size = "openstack_flavor_small"
|
|
|
|
|
elif instance.configuration_id.name == "s1-4":
|
|
|
|
|
size = "openstack_flavor_medium"
|
|
|
|
|
elif instance.configuration_id.name == "s1-8":
|
|
|
|
|
size = "openstack_flavor_large"
|
|
|
|
|
else:
|
|
|
|
|
size = instance.configuration_id.name
|
|
|
|
|
|
2022-06-24 15:05:32 +00:00
|
|
|
|
with open(provision, "w", encoding="utf-8") as f:
|
|
|
|
|
f.write(
|
|
|
|
|
render_to_string(
|
2022-06-25 12:32:03 +00:00
|
|
|
|
"infrastructure/yml/provision.yml", context={"vm_size": size}
|
2022-06-24 15:05:32 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# backup = self.repo_path.joinpath(f"inventory/{instance.name}-backup.yml")
|
|
|
|
|
backup = self._backup_path(subdomain)
|
|
|
|
|
# backup_template = "./templates/infrastructure/yml/provision.yml"
|
|
|
|
|
with open(backup, "w", encoding="utf-8") as f:
|
|
|
|
|
f.write(
|
|
|
|
|
render_to_string(
|
|
|
|
|
"infrastructure/yml/backups.yml", context={"subdomain": subdomain}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# hostscript = self.repo_path.join("inventory/hosts-scripts/{instance.name}-host.sh")
|
|
|
|
|
|
|
|
|
|
self.write_hostscript(
|
|
|
|
|
subdomain=subdomain,
|
|
|
|
|
content=render_to_string(
|
2022-06-25 12:32:03 +00:00
|
|
|
|
"infrastructure/sh/hostscripts/create.sh",
|
2022-06-25 12:54:52 +00:00
|
|
|
|
context={"subdomain": subdomain},
|
2022-06-24 15:05:32 +00:00
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self._commit(action="add", subdomain=subdomain)
|
2022-06-25 12:32:03 +00:00
|
|
|
|
self.repo.git.push()
|
|
|
|
|
return gitea_password
|
2022-06-24 15:05:32 +00:00
|
|
|
|
|
|
|
|
|
def remove_vm(self, instance: Instance):
|
|
|
|
|
"""
|
|
|
|
|
Remove a VM from infrastructure repository
|
|
|
|
|
"""
|
2022-06-25 12:32:03 +00:00
|
|
|
|
self.repo.git.pull()
|
2022-06-24 15:05:32 +00:00
|
|
|
|
subdomain = instance.name
|
|
|
|
|
|
|
|
|
|
host_vars_dir = self._host_vars_dir(subdomain)
|
|
|
|
|
shutil.rmtree(host_vars_dir)
|
|
|
|
|
|
|
|
|
|
backup = self._backup_path(subdomain)
|
|
|
|
|
os.remove(backup)
|
|
|
|
|
|
|
|
|
|
self.write_hostscript(
|
|
|
|
|
subdomain=subdomain,
|
|
|
|
|
content=render_to_string(
|
2022-06-25 12:54:52 +00:00
|
|
|
|
"infrastructure/sh/hostscripts/rm.sh",
|
|
|
|
|
context={"subdomain": subdomain},
|
2022-06-24 15:05:32 +00:00
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
self._commit(action="rm", subdomain=subdomain)
|
2022-06-25 12:32:03 +00:00
|
|
|
|
self.repo.git.push()
|