migrate outpost to soft-delete

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer
2024-04-23 20:36:59 +02:00
parent b65b72d910
commit a4853a1e09
5 changed files with 37 additions and 23 deletions

View File

@ -3,11 +3,14 @@
from typing import Any
from django.db import models
from django.db.models.signals import post_delete, pre_delete
from django.dispatch import Signal
from django.utils import timezone
from model_utils.managers import InheritanceManager
from rest_framework.serializers import BaseSerializer
pre_soft_delete = Signal()
post_soft_delete = Signal()
class SerializerModel(models.Model):
"""Base Abstract Model which has a serializer"""
@ -83,7 +86,7 @@ class SoftDeleteModel(models.Model):
return self.deleted_at is not None
def delete(self, using: Any = ..., keep_parents: bool = ...) -> tuple[int, dict[str, int]]:
pre_delete.send(sender=self.__class__, instance=self)
pre_soft_delete.send(sender=self.__class__, instance=self)
now = timezone.now()
self.deleted_at = now
self.save(
@ -91,7 +94,7 @@ class SoftDeleteModel(models.Model):
"deleted_at",
]
)
post_delete.send(sender=self.__class__, instance=self)
post_soft_delete.send(sender=self.__class__, instance=self)
return tuple()
def force_delete(self, using: Any = ...):

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-04-23 21:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_outposts", "0021_alter_outpost_type"),
]
operations = [
migrations.AddField(
model_name="outpost",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -33,7 +33,7 @@ from authentik.core.models import (
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.lib.models import InheritanceForeignKey, SerializerModel, SoftDeleteModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.outposts.controllers.k8s.utils import get_namespace
@ -131,7 +131,7 @@ class OutpostServiceConnection(models.Model):
verbose_name = _("Outpost Service-Connection")
verbose_name_plural = _("Outpost Service-Connections")
def __str__(self) -> __version__:
def __str__(self):
return f"Outpost service connection {self.name}"
@property
@ -241,7 +241,7 @@ class KubernetesServiceConnection(SerializerModel, OutpostServiceConnection):
return "ak-service-connection-kubernetes-form"
class Outpost(SerializerModel, ManagedModel):
class Outpost(SoftDeleteModel, SerializerModel, ManagedModel):
"""Outpost instance which manages a service user and token"""
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)

View File

@ -2,13 +2,14 @@
from django.core.cache import cache
from django.db.models import Model
from django.db.models.signals import m2m_changed, post_save, pre_delete, pre_save
from django.db.models.signals import m2m_changed, post_save, pre_save
from django.dispatch import receiver
from structlog.stdlib import get_logger
from authentik.brands.models import Brand
from authentik.core.models import Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.models import post_soft_delete
from authentik.lib.utils.reflection import class_to_path
from authentik.outposts.models import Outpost, OutpostServiceConnection
from authentik.outposts.tasks import CACHE_KEY_OUTPOST_DOWN, outpost_controller, outpost_post_save
@ -67,9 +68,7 @@ def post_save_update(sender, instance: Model, created: bool, **_):
outpost_post_save.delay(class_to_path(instance.__class__), instance.pk)
@receiver(pre_delete, sender=Outpost)
def pre_delete_cleanup(sender, instance: Outpost, **_):
@receiver(post_soft_delete, sender=Outpost)
def outpost_cleanup(sender, instance: Outpost, **_):
"""Ensure that Outpost's user is deleted (which will delete the token through cascade)"""
instance.user.delete()
cache.set(CACHE_KEY_OUTPOST_DOWN % instance.pk.hex, instance)
outpost_controller.delay(instance.pk.hex, action="down", from_cache=True)
outpost_controller.delay(instance.pk.hex, action="down")

View File

@ -129,17 +129,10 @@ def outpost_controller_all():
@CELERY_APP.task(bind=True, base=SystemTask)
def outpost_controller(
self: SystemTask, outpost_pk: str, action: str = "up", from_cache: bool = False
):
def outpost_controller(self: SystemTask, outpost_pk: str, action: str = "up"):
"""Create/update/monitor/delete the deployment of an Outpost"""
logs = []
if from_cache:
outpost: Outpost = cache.get(CACHE_KEY_OUTPOST_DOWN % outpost_pk)
LOGGER.debug("Getting outpost from cache to delete")
else:
outpost: Outpost = Outpost.objects.filter(pk=outpost_pk).first()
LOGGER.debug("Getting outpost from DB")
outpost: Outpost = Outpost.objects.filter(pk=outpost_pk).first()
if not outpost:
LOGGER.warning("No outpost")
return
@ -155,9 +148,10 @@ def outpost_controller(
except (ControllerException, ServiceConnectionInvalid) as exc:
self.set_error(exc)
else:
if from_cache:
cache.delete(CACHE_KEY_OUTPOST_DOWN % outpost_pk)
self.set_status(TaskStatus.SUCCESSFUL, *logs)
finally:
if action == "down":
outpost.force_delete()
@CELERY_APP.task(bind=True, base=SystemTask)