authentik.blueprints.migrations.0001_initial

  1# Generated by Django 4.0.6 on 2022-07-31 17:35
  2import uuid
  3from glob import glob
  4from pathlib import Path
  5
  6import django.contrib.postgres.fields
  7from dacite.core import from_dict
  8from django.apps.registry import Apps
  9from django.db import migrations, models
 10from django.db.backends.base.schema import BaseDatabaseSchemaEditor
 11from yaml import load
 12
 13from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_SYSTEM
 14from authentik.lib.config import CONFIG
 15
 16
 17def check_blueprint_v1_file(BlueprintInstance: type, db_alias, path: Path):
 18    """Check if blueprint should be imported"""
 19    from authentik.blueprints.models import BlueprintInstanceStatus
 20    from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata
 21    from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE
 22
 23    with open(path, "r", encoding="utf-8") as blueprint_file:
 24        raw_blueprint = load(blueprint_file.read(), BlueprintLoader)
 25        if not raw_blueprint:
 26            return
 27        metadata = raw_blueprint.get("metadata", None)
 28        version = raw_blueprint.get("version", 1)
 29        if version != 1:
 30            return
 31        blueprint_file.seek(0)
 32    instance = BlueprintInstance.objects.using(db_alias).filter(path=path).first()
 33    rel_path = path.relative_to(Path(CONFIG.get("blueprints_dir")))
 34    meta = None
 35    if metadata:
 36        meta = from_dict(BlueprintMetadata, metadata)
 37        if meta.labels.get(LABEL_AUTHENTIK_INSTANTIATE, "").lower() == "false":
 38            return
 39    if not instance:
 40        BlueprintInstance.objects.using(db_alias).create(
 41            name=meta.name if meta else str(rel_path),
 42            path=str(rel_path),
 43            context={},
 44            status=BlueprintInstanceStatus.UNKNOWN,
 45            enabled=True,
 46            managed_models=[],
 47            last_applied_hash="",
 48            metadata=metadata or {},
 49        )
 50
 51
 52def migration_blueprint_import(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
 53    BlueprintInstance = apps.get_model("authentik_blueprints", "BlueprintInstance")
 54    Flow = apps.get_model("authentik_flows", "Flow")
 55
 56    db_alias = schema_editor.connection.alias
 57    for file in glob(f"{CONFIG.get('blueprints_dir')}/**/*.yaml", recursive=True):
 58        check_blueprint_v1_file(BlueprintInstance, db_alias, Path(file))
 59
 60    for blueprint in BlueprintInstance.objects.using(db_alias).all():
 61        # If we already have flows (and we should always run before flow migrations)
 62        # then this is an existing install and we want to disable all blueprints
 63        if Flow.objects.using(db_alias).all().exists():
 64            blueprint.enabled = False
 65        # System blueprints are always enabled
 66        if blueprint.metadata.get("labels", {}).get(LABEL_AUTHENTIK_SYSTEM, "").lower() == "true":
 67            blueprint.enabled = True
 68        blueprint.save()
 69
 70
 71class Migration(migrations.Migration):
 72    initial = True
 73
 74    dependencies = [("authentik_flows", "0001_initial")]
 75
 76    operations = [
 77        migrations.CreateModel(
 78            name="BlueprintInstance",
 79            fields=[
 80                ("created", models.DateTimeField(auto_now_add=True)),
 81                ("last_updated", models.DateTimeField(auto_now=True)),
 82                (
 83                    "managed",
 84                    models.TextField(
 85                        default=None,
 86                        help_text=(
 87                            "Objects which are managed by authentik. These objects are created and"
 88                            " updated automatically. This is flag only indicates that an object can"
 89                            " be overwritten by migrations. You can still modify the objects via"
 90                            " the API, but expect changes to be overwritten in a later update."
 91                        ),
 92                        null=True,
 93                        unique=True,
 94                        verbose_name="Managed by authentik",
 95                    ),
 96                ),
 97                (
 98                    "instance_uuid",
 99                    models.UUIDField(
100                        default=uuid.uuid4, editable=False, primary_key=True, serialize=False
101                    ),
102                ),
103                ("name", models.TextField()),
104                ("metadata", models.JSONField(default=dict)),
105                ("path", models.TextField()),
106                ("context", models.JSONField(default=dict)),
107                ("last_applied", models.DateTimeField(auto_now=True)),
108                ("last_applied_hash", models.TextField()),
109                (
110                    "status",
111                    models.TextField(
112                        choices=[
113                            ("successful", "Successful"),
114                            ("warning", "Warning"),
115                            ("error", "Error"),
116                            ("orphaned", "Orphaned"),
117                            ("unknown", "Unknown"),
118                        ],
119                        default="unknown",
120                    ),
121                ),
122                ("enabled", models.BooleanField(default=True)),
123                (
124                    "managed_models",
125                    django.contrib.postgres.fields.ArrayField(
126                        base_field=models.TextField(), default=list, size=None
127                    ),
128                ),
129            ],
130            options={
131                "verbose_name": "Blueprint Instance",
132                "verbose_name_plural": "Blueprint Instances",
133                "unique_together": {("name", "path")},
134            },
135        ),
136        migrations.RunPython(migration_blueprint_import),
137    ]
def check_blueprint_v1_file(BlueprintInstance: type, db_alias, path: pathlib.Path):
18def check_blueprint_v1_file(BlueprintInstance: type, db_alias, path: Path):
19    """Check if blueprint should be imported"""
20    from authentik.blueprints.models import BlueprintInstanceStatus
21    from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata
22    from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE
23
24    with open(path, "r", encoding="utf-8") as blueprint_file:
25        raw_blueprint = load(blueprint_file.read(), BlueprintLoader)
26        if not raw_blueprint:
27            return
28        metadata = raw_blueprint.get("metadata", None)
29        version = raw_blueprint.get("version", 1)
30        if version != 1:
31            return
32        blueprint_file.seek(0)
33    instance = BlueprintInstance.objects.using(db_alias).filter(path=path).first()
34    rel_path = path.relative_to(Path(CONFIG.get("blueprints_dir")))
35    meta = None
36    if metadata:
37        meta = from_dict(BlueprintMetadata, metadata)
38        if meta.labels.get(LABEL_AUTHENTIK_INSTANTIATE, "").lower() == "false":
39            return
40    if not instance:
41        BlueprintInstance.objects.using(db_alias).create(
42            name=meta.name if meta else str(rel_path),
43            path=str(rel_path),
44            context={},
45            status=BlueprintInstanceStatus.UNKNOWN,
46            enabled=True,
47            managed_models=[],
48            last_applied_hash="",
49            metadata=metadata or {},
50        )

Check if blueprint should be imported

def migration_blueprint_import( apps: django.apps.registry.Apps, schema_editor: django.db.backends.base.schema.BaseDatabaseSchemaEditor):
53def migration_blueprint_import(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
54    BlueprintInstance = apps.get_model("authentik_blueprints", "BlueprintInstance")
55    Flow = apps.get_model("authentik_flows", "Flow")
56
57    db_alias = schema_editor.connection.alias
58    for file in glob(f"{CONFIG.get('blueprints_dir')}/**/*.yaml", recursive=True):
59        check_blueprint_v1_file(BlueprintInstance, db_alias, Path(file))
60
61    for blueprint in BlueprintInstance.objects.using(db_alias).all():
62        # If we already have flows (and we should always run before flow migrations)
63        # then this is an existing install and we want to disable all blueprints
64        if Flow.objects.using(db_alias).all().exists():
65            blueprint.enabled = False
66        # System blueprints are always enabled
67        if blueprint.metadata.get("labels", {}).get(LABEL_AUTHENTIK_SYSTEM, "").lower() == "true":
68            blueprint.enabled = True
69        blueprint.save()
class Migration(django.db.migrations.migration.Migration):
 72class Migration(migrations.Migration):
 73    initial = True
 74
 75    dependencies = [("authentik_flows", "0001_initial")]
 76
 77    operations = [
 78        migrations.CreateModel(
 79            name="BlueprintInstance",
 80            fields=[
 81                ("created", models.DateTimeField(auto_now_add=True)),
 82                ("last_updated", models.DateTimeField(auto_now=True)),
 83                (
 84                    "managed",
 85                    models.TextField(
 86                        default=None,
 87                        help_text=(
 88                            "Objects which are managed by authentik. These objects are created and"
 89                            " updated automatically. This is flag only indicates that an object can"
 90                            " be overwritten by migrations. You can still modify the objects via"
 91                            " the API, but expect changes to be overwritten in a later update."
 92                        ),
 93                        null=True,
 94                        unique=True,
 95                        verbose_name="Managed by authentik",
 96                    ),
 97                ),
 98                (
 99                    "instance_uuid",
100                    models.UUIDField(
101                        default=uuid.uuid4, editable=False, primary_key=True, serialize=False
102                    ),
103                ),
104                ("name", models.TextField()),
105                ("metadata", models.JSONField(default=dict)),
106                ("path", models.TextField()),
107                ("context", models.JSONField(default=dict)),
108                ("last_applied", models.DateTimeField(auto_now=True)),
109                ("last_applied_hash", models.TextField()),
110                (
111                    "status",
112                    models.TextField(
113                        choices=[
114                            ("successful", "Successful"),
115                            ("warning", "Warning"),
116                            ("error", "Error"),
117                            ("orphaned", "Orphaned"),
118                            ("unknown", "Unknown"),
119                        ],
120                        default="unknown",
121                    ),
122                ),
123                ("enabled", models.BooleanField(default=True)),
124                (
125                    "managed_models",
126                    django.contrib.postgres.fields.ArrayField(
127                        base_field=models.TextField(), default=list, size=None
128                    ),
129                ),
130            ],
131            options={
132                "verbose_name": "Blueprint Instance",
133                "verbose_name_plural": "Blueprint Instances",
134                "unique_together": {("name", "path")},
135            },
136        ),
137        migrations.RunPython(migration_blueprint_import),
138    ]

The base class for all migrations.

Migration files will import this from django.db.migrations.Migration and subclass it as a class called Migration. It will have one or more of the following attributes:

  • operations: A list of Operation instances, probably from django.db.migrations.operations
  • dependencies: A list of tuples of (app_path, migration_name)
  • run_before: A list of tuples of (app_path, migration_name)
  • replaces: A list of migration_names

Note that all migrations come out of migrations and into the Loader or Graph as instances, having been initialized with their app label and name.

initial = True
dependencies = [('authentik_flows', '0001_initial')]
operations = [<CreateModel name='BlueprintInstance', fields=[('created', <django.db.models.fields.DateTimeField>), ('last_updated', <django.db.models.fields.DateTimeField>), ('managed', <django.db.models.fields.TextField>), ('instance_uuid', <django.db.models.fields.UUIDField>), ('name', <django.db.models.fields.TextField>), ('metadata', <django.db.models.fields.json.JSONField>), ('path', <django.db.models.fields.TextField>), ('context', <django.db.models.fields.json.JSONField>), ('last_applied', <django.db.models.fields.DateTimeField>), ('last_applied_hash', <django.db.models.fields.TextField>), ('status', <django.db.models.fields.TextField>), ('enabled', <django.db.models.fields.BooleanField>), ('managed_models', <django.contrib.postgres.fields.array.ArrayField>)], options={'verbose_name': 'Blueprint Instance', 'verbose_name_plural': 'Blueprint Instances', 'unique_together': {('name', 'path')}}>, <RunPython <function migration_blueprint_import>>]