draft of installing apps and getting app status
This commit is contained in:
parent
2baae3e61e
commit
cea3f5a3cb
13 changed files with 1148 additions and 7 deletions
|
|
@ -24,6 +24,14 @@ APPS_DATA = [
|
|||
|
||||
APP_DATA = {"id": 1, "name": "Nextcloud", "selected": True, "status": "ON for everyone", "config": CONFIG_DATA},
|
||||
|
||||
# Apps that should not get oauth variables when they are installed
|
||||
APPS_WITHOUT_OAUTH = [
|
||||
"single-sign-on",
|
||||
"prometheus",
|
||||
"alertmanager",
|
||||
]
|
||||
|
||||
APP_NOT_INSTALLED_STATUS = "Not installed"
|
||||
|
||||
@api_v1.route('/apps', methods=['GET'])
|
||||
@jwt_required()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
"""Everything to do with Apps"""
|
||||
|
||||
import os
|
||||
|
||||
from sqlalchemy import ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from database import db
|
||||
import helpers.kubernetes as k8s
|
||||
from .apps import APPS_WITHOUT_OAUTH, APP_NOT_INSTALLED_STATUS
|
||||
|
||||
|
||||
class App(db.Model):
|
||||
|
|
@ -16,8 +22,122 @@ class App(db.Model):
|
|||
def __repr__(self):
|
||||
return f"{self.id} <{self.name}>"
|
||||
|
||||
def get_kustomization_status(self):
|
||||
"""Returns True if the kustomization for this App is ready"""
|
||||
kustomization = k8s.get_kustomization(self.slug)
|
||||
if kustomization is None:
|
||||
return None
|
||||
return kustomization['status']
|
||||
|
||||
class AppRole(db.Model):
|
||||
def get_helmrelease_status(self):
|
||||
"""Returns True if the kustomization for this App is ready"""
|
||||
helmrelease = k8s.get_helmrelease(self.slug, self.namespace)
|
||||
if helmrelease is None:
|
||||
return None
|
||||
return helmrelease['status']
|
||||
|
||||
def get_status(self):
|
||||
"""Returns a string that describes the app state in the cluster"""
|
||||
ks_status = self.get_kustomization_status()
|
||||
if ks_status is not None:
|
||||
ks_ready, ks_message = App.check_condition(ks_status)
|
||||
else:
|
||||
ks_ready = None
|
||||
hr_status = self.get_helmrelease_status()
|
||||
if hr_status is not None:
|
||||
hr_ready, hr_message = App.check_condition(hr_status)
|
||||
else:
|
||||
hr_ready = None
|
||||
if ks_ready is None:
|
||||
return APP_NOT_INSTALLED_STATUS
|
||||
# *Should* not happen, but just in case:
|
||||
if (ks_ready is None and hr_ready is not None) or \
|
||||
(hr_ready is None and ks_ready is not None):
|
||||
return ("This app is in a strange state. Contact a Stackspin"
|
||||
" administrator if this status stays for longer than 5 minutes")
|
||||
if ks_ready and hr_ready:
|
||||
return "App installed and running"
|
||||
if not hr_ready:
|
||||
return f"App failed installing: {hr_message}"
|
||||
if not ks_ready:
|
||||
return f"App failed installing: {ks_message}"
|
||||
return "App is installing..."
|
||||
|
||||
|
||||
def install(self):
|
||||
"""Creates a Kustomization in the Kubernetes cluster that installs this application"""
|
||||
# Generate the necessary passwords, etc. from a template
|
||||
self.__generate_secrets()
|
||||
# Create add-<app> kustomization
|
||||
self.__create_kustomization()
|
||||
|
||||
def __generate_secrets(self):
|
||||
"""Generates passwords for app installation"""
|
||||
# Create app variables secret
|
||||
if self.variables_template_filepath:
|
||||
k8s.create_variables_secret(self.slug, self.variables_template_filepath)
|
||||
# Create a secret that contains the oauth variables for Hydra Maester
|
||||
if self.slug not in APPS_WITHOUT_OAUTH:
|
||||
k8s.create_variables_secret(
|
||||
self.slug,
|
||||
os.path.join(self.__get_templates_dir(),
|
||||
"stackspin-oauth-variables.yaml.jinja"))
|
||||
|
||||
def __create_kustomization(self):
|
||||
"""Creates the `add-{app_slug}` kustomization in the Kubernetes cluster"""
|
||||
kustomization_template_filepath = \
|
||||
os.path.join(self.__get_templates_dir(),
|
||||
"add-app-kustomization.yaml.jinja")
|
||||
k8s.store_kustomization(kustomization_template_filepath, self.slug)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __get_templates_dir():
|
||||
"""Returns directory that contains the Jinja templates used to create app secrets."""
|
||||
return os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates")
|
||||
|
||||
@property
|
||||
def variables_template_filepath(self):
|
||||
"""Path to the variables template used to generate secrets the app needs"""
|
||||
variables_template_filepath = os.path.join(self.__get_templates_dir(),
|
||||
f"stackspin-{self.slug}-variables.yaml.jinja")
|
||||
if os.path.exists(variables_template_filepath):
|
||||
return variables_template_filepath
|
||||
return None
|
||||
|
||||
@property
|
||||
def namespace(self):
|
||||
"""
|
||||
Returns the Kubernetes namespace of this app
|
||||
|
||||
FIXME: This should probably become a database field.
|
||||
"""
|
||||
if self.slug in ['nextcloud', 'wordpress', 'wekan', 'zulip']:
|
||||
return 'stackspin-apps'
|
||||
return 'stackspin'
|
||||
|
||||
@staticmethod
|
||||
def check_condition(status):
|
||||
"""
|
||||
Returns a tuple that has true/false for readiness and a message
|
||||
|
||||
Ready, in this case means that the condition's type == "Ready" and its
|
||||
status == "True". If the condition type "Ready" does not exist, the
|
||||
status is interpreted as not ready.
|
||||
|
||||
The message that is returned is the message that comes with the
|
||||
condition with type "Ready"
|
||||
|
||||
:param status: Kubernetes resource's "status" object.
|
||||
:type status: dict
|
||||
"""
|
||||
for condition in status["conditions"]:
|
||||
if condition["type"] == "Ready":
|
||||
return condition["status"] == "True", condition["message"]
|
||||
return False
|
||||
|
||||
|
||||
class AppRole(db.Model): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
The AppRole object, stores the roles Users have on Apps
|
||||
"""
|
||||
|
|
@ -29,4 +149,5 @@ class AppRole(db.Model):
|
|||
role = relationship("Role")
|
||||
|
||||
def __repr__(self):
|
||||
return f"role_id: {self.role_id}, user_id: {self.user_id}, app_id: {self.app_id}, role: {self.role}"
|
||||
return (f"role_id: {self.role_id}, user_id: {self.user_id},"
|
||||
f" app_id: {self.app_id}, role: {self.role}")
|
||||
|
|
|
|||
14
areas/apps/templates/add-app-kustomization.yaml.jinja
Normal file
14
areas/apps/templates/add-app-kustomization.yaml.jinja
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: add-{{ app }}
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 1h0m0s
|
||||
path: ./flux2/cluster/optional/{{ app }}
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: stackspin
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: stackspin-nextcloud-variables
|
||||
data:
|
||||
nextcloud_password: "{{ 32 | generate_password | b64encode }}"
|
||||
nextcloud_mariadb_password: "{{ 32 | generate_password | b64encode }}"
|
||||
nextcloud_mariadb_root_password: "{{ 32 | generate_password | b64encode }}"
|
||||
onlyoffice_database_password: "{{ 32 | generate_password | b64encode }}"
|
||||
onlyoffice_jwt_secret: "{{ 32 | generate_password | b64encode }}"
|
||||
onlyoffice_rabbitmq_password: "{{ 32 | generate_password | b64encode }}"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: stackspin-{{ app }}-oauth-variables
|
||||
data:
|
||||
client_id: "{{ app | b64encode }}"
|
||||
client_secret: "{{ 32 | generate_password | b64encode }}"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: stackspin-wekan-variables
|
||||
data:
|
||||
mongodb_password: "{{ 32 | generate_password | b64encode }}"
|
||||
mongodb_root_password: "{{ 32 | generate_password | b64encode }}"
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: stackspin-wordpress-variables
|
||||
data:
|
||||
wordpress_admin_password: "{{ 32 | generate_password | b64encode }}"
|
||||
wordpress_mariadb_password: "{{ 32 | generate_password | b64encode }}"
|
||||
wordpress_mariadb_root_password: "{{ 32 | generate_password | b64encode }}"
|
||||
12
areas/apps/templates/stackspin-zulip-variables.yaml.jinja
Normal file
12
areas/apps/templates/stackspin-zulip-variables.yaml.jinja
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: stackspin-zulip-variables
|
||||
data:
|
||||
admin_password: "{{ 32 | generate_password | b64encode }}"
|
||||
memcached_password: "{{ 32 | generate_password | b64encode }}"
|
||||
rabbitmq_password: "{{ 32 | generate_password | b64encode }}"
|
||||
rabbitmq_erlang_cookie: "{{ 32 | generate_password | b64encode }}"
|
||||
redis_password: "{{ 32 | generate_password | b64encode }}"
|
||||
postgresql_password: "{{ 32 | generate_password | b64encode }}"
|
||||
zulip_password: "{{ 32 | generate_password | b64encode }}"
|
||||
Reference in a new issue