admin: store version history (#11520)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							0976e05c7d
						
					
				
				
					commit
					3262e70eac
				
			
							
								
								
									
										33
									
								
								authentik/admin/api/version_history.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								authentik/admin/api/version_history.py
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										22
									
								
								authentik/admin/models.py
									
									
									
									
									
										Normal 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})"
 | 
			
		||||
@ -6,6 +6,7 @@ from authentik.admin.api.meta import AppsViewSet, ModelViewSet
 | 
			
		||||
from authentik.admin.api.metrics import AdministrationMetricsViewSet
 | 
			
		||||
from authentik.admin.api.system import SystemView
 | 
			
		||||
from authentik.admin.api.version import VersionView
 | 
			
		||||
from authentik.admin.api.version_history import VersionHistoryViewSet
 | 
			
		||||
from authentik.admin.api.workers import WorkerView
 | 
			
		||||
 | 
			
		||||
api_urlpatterns = [
 | 
			
		||||
@ -17,6 +18,7 @@ api_urlpatterns = [
 | 
			
		||||
        name="admin_metrics",
 | 
			
		||||
    ),
 | 
			
		||||
    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/system/", SystemView.as_view(), name="admin_system"),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -84,7 +84,9 @@ def run_migrations():
 | 
			
		||||
    curr = conn.cursor()
 | 
			
		||||
    try:
 | 
			
		||||
        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)
 | 
			
		||||
            if not spec:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								lifecycle/system_migrations/version_history_create.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lifecycle/system_migrations/version_history_create.py
									
									
									
									
									
										Normal 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;
 | 
			
		||||
        """
 | 
			
		||||
        )
 | 
			
		||||
							
								
								
									
										38
									
								
								lifecycle/system_migrations/version_history_update.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lifecycle/system_migrations/version_history_update.py
									
									
									
									
									
										Normal 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()
 | 
			
		||||
							
								
								
									
										103
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								schema.yml
									
									
									
									
									
								
							@ -263,6 +263,90 @@ paths:
 | 
			
		||||
              schema:
 | 
			
		||||
                $ref: '#/components/schemas/GenericError'
 | 
			
		||||
          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/:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: admin_workers_retrieve
 | 
			
		||||
@ -53109,6 +53193,25 @@ components:
 | 
			
		||||
      - version_current
 | 
			
		||||
      - version_latest
 | 
			
		||||
      - 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:
 | 
			
		||||
      type: object
 | 
			
		||||
      description: Serializer for WebAuthn authenticator devices
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user