admin: store version history (#11520)

Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Marc 'risson' Schmitt
2024-10-17 14:09:44 +02:00
committed by GitHub
parent 0976e05c7d
commit 3262e70eac
7 changed files with 225 additions and 1 deletions

View File

@ -0,0 +1,33 @@
from rest_framework.permissions import IsAdminUser
from rest_framework.viewsets import ReadOnlyModelViewSet
from authentik.admin.models import VersionHistory
from authentik.core.api.utils import ModelSerializer
class VersionHistorySerializer(ModelSerializer):
"""VersionHistory Serializer"""
class Meta:
model = VersionHistory
fields = [
"id",
"timestamp",
"version",
"build",
]
class VersionHistoryViewSet(ReadOnlyModelViewSet):
"""VersionHistory Viewset"""
queryset = VersionHistory.objects.all()
serializer_class = VersionHistorySerializer
permission_classes = [IsAdminUser]
filterset_fields = [
"version",
"build",
]
search_fields = ["version", "build"]
ordering = ["-timestamp"]
pagination_class = None

22
authentik/admin/models.py Normal file
View File

@ -0,0 +1,22 @@
"""authentik admin models"""
from django.db import models
from django.utils.translation import gettext_lazy as _
class VersionHistory(models.Model):
id = models.BigAutoField(primary_key=True)
timestamp = models.DateTimeField()
version = models.TextField()
build = models.TextField()
class Meta:
managed = False
db_table = "authentik_version_history"
ordering = ("-timestamp",)
verbose_name = _("Version history")
verbose_name_plural = _("Version history")
default_permissions = []
def __str__(self):
return f"{self.version}.{self.build} ({self.timestamp})"

View File

@ -6,6 +6,7 @@ from authentik.admin.api.meta import AppsViewSet, ModelViewSet
from authentik.admin.api.metrics import AdministrationMetricsViewSet from authentik.admin.api.metrics import AdministrationMetricsViewSet
from authentik.admin.api.system import SystemView from authentik.admin.api.system import SystemView
from authentik.admin.api.version import VersionView from authentik.admin.api.version import VersionView
from authentik.admin.api.version_history import VersionHistoryViewSet
from authentik.admin.api.workers import WorkerView from authentik.admin.api.workers import WorkerView
api_urlpatterns = [ api_urlpatterns = [
@ -17,6 +18,7 @@ api_urlpatterns = [
name="admin_metrics", name="admin_metrics",
), ),
path("admin/version/", VersionView.as_view(), name="admin_version"), path("admin/version/", VersionView.as_view(), name="admin_version"),
("admin/version/history", VersionHistoryViewSet, "version_history"),
path("admin/workers/", WorkerView.as_view(), name="admin_workers"), path("admin/workers/", WorkerView.as_view(), name="admin_workers"),
path("admin/system/", SystemView.as_view(), name="admin_system"), path("admin/system/", SystemView.as_view(), name="admin_system"),
] ]

View File

@ -84,7 +84,9 @@ def run_migrations():
curr = conn.cursor() curr = conn.cursor()
try: try:
wait_for_lock(curr) wait_for_lock(curr)
for migration_path in Path(__file__).parent.absolute().glob("system_migrations/*.py"): for migration_path in sorted(
Path(__file__).parent.absolute().glob("system_migrations/*.py")
):
spec = spec_from_file_location("lifecycle.system_migrations", migration_path) spec = spec_from_file_location("lifecycle.system_migrations", migration_path)
if not spec: if not spec:
continue continue

View File

@ -0,0 +1,24 @@
# flake8: noqa
from lifecycle.migrate import BaseMigration
class Migration(BaseMigration):
def needs_migration(self) -> bool:
self.cur.execute(
"SELECT * FROM information_schema.tables WHERE table_name = 'authentik_version_history';"
)
return not bool(self.cur.rowcount)
def run(self):
self.cur.execute(
"""
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS authentik_version_history (
id BIGSERIAL PRIMARY KEY,
"timestamp" timestamp with time zone NOT NULL,
version text NOT NULL,
build text NOT NULL
);
COMMIT;
"""
)

View File

@ -0,0 +1,38 @@
# flake8: noqa
from lifecycle.migrate import BaseMigration
from datetime import datetime
from authentik import __version__, get_build_hash
class Migration(BaseMigration):
def needs_migration(self) -> bool:
self.cur.execute(
"""
SELECT * FROM authentik_version_history
WHERE version = %s AND build = %s
ORDER BY "timestamp" DESC
LIMIT 1
""",
(__version__, get_build_hash()),
)
return not bool(self.cur.rowcount)
def run(self):
self.cur.execute(
"""
INSERT INTO authentik_version_history ("timestamp", version, build)
VALUES (%s, %s, %s)
""",
(datetime.now(), __version__, get_build_hash()),
)
self.cur.execute(
"""
DELETE FROM authentik_version_history WHERE id NOT IN (
SELECT id FROM authentik_version_history
ORDER BY "timestamp" DESC
LIMIT 1000
)
"""
)
self.con.commit()

View File

@ -263,6 +263,90 @@ paths:
schema: schema:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
description: '' description: ''
/admin/version/history/:
get:
operationId: admin_version_history_list
description: VersionHistory Viewset
parameters:
- in: query
name: build
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: version
schema:
type: string
tags:
- admin
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/VersionHistory'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/admin/version/history/{id}/:
get:
operationId: admin_version_history_retrieve
description: VersionHistory Viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Version history.
required: true
tags:
- admin
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VersionHistory'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/admin/workers/: /admin/workers/:
get: get:
operationId: admin_workers_retrieve operationId: admin_workers_retrieve
@ -53109,6 +53193,25 @@ components:
- version_current - version_current
- version_latest - version_latest
- version_latest_valid - version_latest_valid
VersionHistory:
type: object
description: VersionHistory Serializer
properties:
id:
type: integer
readOnly: true
timestamp:
type: string
format: date-time
version:
type: string
build:
type: string
required:
- build
- id
- timestamp
- version
WebAuthnDevice: WebAuthnDevice:
type: object type: object
description: Serializer for WebAuthn authenticator devices description: Serializer for WebAuthn authenticator devices