Enable deleting apps by using CLI
This commit is contained in:
parent
cea3f5a3cb
commit
6529636b3d
4 changed files with 95 additions and 33 deletions
|
@ -58,9 +58,9 @@ class App(db.Model):
|
|||
if ks_ready and hr_ready:
|
||||
return "App installed and running"
|
||||
if not hr_ready:
|
||||
return f"App failed installing: {hr_message}"
|
||||
return f"App HelmRelease status: {hr_message}"
|
||||
if not ks_ready:
|
||||
return f"App failed installing: {ks_message}"
|
||||
return f"App Kustomization status: {ks_message}"
|
||||
return "App is installing..."
|
||||
|
||||
|
||||
|
@ -71,6 +71,32 @@ class App(db.Model):
|
|||
# Create add-<app> kustomization
|
||||
self.__create_kustomization()
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Fully deletes an application
|
||||
|
||||
This includes user roles, all kubernetes objects and also PVCs, so your
|
||||
data will be *gone*
|
||||
"""
|
||||
# Delete all roles first
|
||||
for role in self.roles:
|
||||
db.session.delete(role)
|
||||
|
||||
# Delete the kustomization
|
||||
if self.__delete_kustomization():
|
||||
|
||||
# TODO: This is where we might want to poll for status changes in the
|
||||
# app, so that only once the kustomization and all its stuff (other ks,
|
||||
# helmrelease, etc.) is deleted, we continue
|
||||
|
||||
# If the kustomization delete went well, commit DB changes.
|
||||
db.session.commit()
|
||||
# Then delete the app
|
||||
db.session.delete(self)
|
||||
db.session.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def __generate_secrets(self):
|
||||
"""Generates passwords for app installation"""
|
||||
# Create app variables secret
|
||||
|
@ -90,11 +116,10 @@ class App(db.Model):
|
|||
"add-app-kustomization.yaml.jinja")
|
||||
k8s.store_kustomization(kustomization_template_filepath, self.slug)
|
||||
|
||||
def __delete_kustomization(self):
|
||||
"""Deletes kustomization for this app"""
|
||||
k8s.delete_kustomization(f"add-{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):
|
||||
|
@ -116,6 +141,20 @@ class App(db.Model):
|
|||
return 'stackspin-apps'
|
||||
return 'stackspin'
|
||||
|
||||
@property
|
||||
def roles(self):
|
||||
"""
|
||||
All roles that are linked to this app
|
||||
"""
|
||||
return AppRole.query.filter_by(
|
||||
app_id=self.id
|
||||
).all()
|
||||
|
||||
@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")
|
||||
|
||||
@staticmethod
|
||||
def check_condition(status):
|
||||
"""
|
||||
|
|
|
@ -88,27 +88,23 @@ def list_app():
|
|||
)
|
||||
@click.argument("slug")
|
||||
def delete_app(slug):
|
||||
"""Removes app from database
|
||||
"""Removes app from database as well as uninstalls it from the cluster
|
||||
:param slug: str Slug of app to remove
|
||||
"""
|
||||
current_app.logger.info(f"Trying to delete app: {slug}")
|
||||
obj = App.query.filter_by(slug=slug).first()
|
||||
app_obj = App.query.filter_by(slug=slug).first()
|
||||
|
||||
if not obj:
|
||||
if not app_obj:
|
||||
current_app.logger.info("Not found")
|
||||
return
|
||||
|
||||
# Deleting will (probably) fail if there are still roles attached. This is a
|
||||
# PoC implementation only. Actually management of apps and roles will be
|
||||
# done by the backend application
|
||||
db.session.delete(obj)
|
||||
db.session.commit()
|
||||
current_app.logger.info("Success")
|
||||
deleted = app_obj.delete()
|
||||
current_app.logger.info(f"Success: {deleted}")
|
||||
return
|
||||
|
||||
@app_cli.command("get_status")
|
||||
@app_cli.command("status")
|
||||
@click.argument("slug")
|
||||
def get_status_app(slug):
|
||||
def status_app(slug):
|
||||
"""Gets the current app status from the Kubernetes cluster
|
||||
:param slug: str Slug of app to remove
|
||||
"""
|
||||
|
@ -125,8 +121,8 @@ def get_status_app(slug):
|
|||
@app_cli.command("install")
|
||||
@click.argument("slug")
|
||||
def install_app(slug):
|
||||
"""Gets the current app status from the Kubernetes cluster
|
||||
:param slug: str Slug of app to remove
|
||||
"""Installs app into Kubernetes cluster
|
||||
:param slug: str Slug of app to install
|
||||
"""
|
||||
current_app.logger.info(f"Installing app: {slug}")
|
||||
|
||||
|
@ -140,11 +136,28 @@ def install_app(slug):
|
|||
if current_status == APP_NOT_INSTALLED_STATUS:
|
||||
app.install()
|
||||
current_app.logger.info(
|
||||
f"App {slug} installing... use `get_status` to see status")
|
||||
f"App {slug} installing... use `status` to see status")
|
||||
else:
|
||||
current_app.logger.error("App {slug} should have status"
|
||||
f" {APP_NOT_INSTALLED_STATUS} but has status: {current_status}")
|
||||
|
||||
@app_cli.command("roles")
|
||||
@click.argument("slug")
|
||||
def roles_app(slug):
|
||||
"""Gets a list of roles for this app
|
||||
:param slug: str Slug of app queried
|
||||
"""
|
||||
current_app.logger.info(f"Getting roles for app: {slug}")
|
||||
|
||||
app = App.query.filter_by(slug=slug).first()
|
||||
|
||||
if not app:
|
||||
current_app.logger.error(f"App {slug} does not exist")
|
||||
return
|
||||
|
||||
current_app.logger.info("Roles: ")
|
||||
for role in app.roles:
|
||||
current_app.logger.info(role)
|
||||
|
||||
|
||||
cli.cli.add_command(app_cli)
|
||||
|
|
|
@ -31,7 +31,7 @@ services:
|
|||
# ENV variables that are deployment-specific
|
||||
- SECRET_KEY=$FLASK_SECRET_KEY
|
||||
- HYDRA_CLIENT_SECRET=$HYDRA_CLIENT_SECRET
|
||||
# - OAUTHLIB_INSECURE_TRANSPORT=1
|
||||
- KUBECONFIG=/.kube/config
|
||||
ports:
|
||||
- "5000:5000"
|
||||
user: "${KUBECTL_UID}:${KUBECTL_GID}"
|
||||
|
|
|
@ -13,6 +13,8 @@ from kubernetes.client.exceptions import ApiException
|
|||
from kubernetes.utils import create_from_yaml
|
||||
from kubernetes.utils.create_from_yaml import FailToCreateError
|
||||
|
||||
# Load the kube config once
|
||||
config.load_kube_config()
|
||||
|
||||
def create_variables_secret(app_slug, variables_filepath):
|
||||
"""Checks if a variables secret for app_name already exists, generates it if necessary.
|
||||
|
@ -117,19 +119,31 @@ def store_kustomization(kustomization_template_filepath, app_slug):
|
|||
namespace="flux-system",
|
||||
plural="kustomizations",
|
||||
body=kustomization_dict)
|
||||
|
||||
|
||||
# create_from_yaml(
|
||||
# api_client_instance,
|
||||
# yaml_objects=[kustomization_dict],
|
||||
# # All kustomizations live in the flux-system namespace
|
||||
# namespace="flux-system"
|
||||
# )
|
||||
except FailToCreateError as ex:
|
||||
print(f"Could not create {app_slug} Kustomization because of exception {ex}")
|
||||
return
|
||||
print(f"Kustomization created with api response: {api_response}")
|
||||
|
||||
def delete_kustomization(kustomization_name):
|
||||
"""Deletes kustomization for an app_slug. Should also result in the
|
||||
deletion of the app's HelmReleases, PVCs, OAuth2Client, etc. Nothing will
|
||||
remain"""
|
||||
api_client_instance = api_client.ApiClient()
|
||||
custom_objects_api = client.CustomObjectsApi(api_client_instance)
|
||||
body = client.V1DeleteOptions()
|
||||
try:
|
||||
api_response = custom_objects_api.delete_namespaced_custom_object(
|
||||
group="kustomize.toolkit.fluxcd.io",
|
||||
version="v1beta2",
|
||||
namespace="flux-system",
|
||||
plural="kustomizations",
|
||||
name=kustomization_name,
|
||||
body=body)
|
||||
except ApiException as ex:
|
||||
print(f"Could not delete {kustomization_name} Kustomization because of exception {ex}")
|
||||
return False
|
||||
print(f"Kustomization deleted with api response: {api_response}")
|
||||
|
||||
|
||||
def read_template_to_dict(template_filepath, template_globals):
|
||||
"""Reads a Jinja2 template that contains yaml and turns it into a dict
|
||||
|
@ -198,7 +212,6 @@ def get_all_kustomizations(namespace='flux-system'):
|
|||
:return: Kustomizations as returned by CustomObjectsApi.list_namespaced_custom_object()
|
||||
:rtype: object
|
||||
"""
|
||||
config.load_kube_config()
|
||||
api = client.CustomObjectsApi()
|
||||
api_response = api.list_namespaced_custom_object(
|
||||
group="kustomize.toolkit.fluxcd.io",
|
||||
|
@ -231,7 +244,6 @@ def get_all_helmreleases(namespace='stackspin'):
|
|||
:return: Helmreleases as returned by CustomObjectsApi.list_namespaced_custom_object()
|
||||
:rtype: object
|
||||
"""
|
||||
config.load_kube_config()
|
||||
api = client.CustomObjectsApi()
|
||||
api_response = api.list_namespaced_custom_object(
|
||||
group="helm.toolkit.fluxcd.io",
|
||||
|
@ -244,7 +256,6 @@ def get_all_helmreleases(namespace='stackspin'):
|
|||
|
||||
def get_kustomization(name, namespace='flux-system'):
|
||||
"""Returns all info of a Flux kustomization with name 'name'"""
|
||||
config.load_kube_config()
|
||||
api = client.CustomObjectsApi()
|
||||
try:
|
||||
resource = api.get_namespaced_custom_object(
|
||||
|
@ -264,7 +275,6 @@ def get_kustomization(name, namespace='flux-system'):
|
|||
|
||||
def get_helmrelease(name, namespace='stackspin-apps'):
|
||||
"""Returns all info of a Flux helmrelease with name 'name'"""
|
||||
config.load_kube_config()
|
||||
api = client.CustomObjectsApi()
|
||||
try:
|
||||
resource = api.get_namespaced_custom_object(
|
||||
|
|
Loading…
Reference in a new issue