Compare commits
No commits in common. "83533129bd438f6369df5d4a14195671568a10af" and "751f1ed0b085077035bb0347ac4ab489163cf640" have entirely different histories.
83533129bd
...
751f1ed0b0
5 changed files with 72 additions and 34 deletions
|
@ -30,7 +30,6 @@ Then use ``uv`` to install the project and run its commands while you’re devel
|
||||||
```bash
|
```bash
|
||||||
uv sync --dev
|
uv sync --dev
|
||||||
uv run --env-file=.env src/manage.py migrate
|
uv run --env-file=.env src/manage.py migrate
|
||||||
uv run --env-file=.env src/manage.py createcachetable
|
|
||||||
uv run --env-file=.env src/manage.py runserver
|
uv run --env-file=.env src/manage.py runserver
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ export XDG_CONFIG_HOME="/app/config"
|
||||||
|
|
||||||
echo "Applying database migrations"
|
echo "Applying database migrations"
|
||||||
uv run src/manage.py migrate
|
uv run src/manage.py migrate
|
||||||
uv run src/manage.py createcachetable
|
|
||||||
|
|
||||||
echo "Starting Caddy"
|
echo "Starting Caddy"
|
||||||
exec caddy run --config /app/config/caddy/Caddyfile --adapter caddyfile 2>&1 &
|
exec caddy run --config /app/config/caddy/Caddyfile --adapter caddyfile 2>&1 &
|
||||||
|
|
|
@ -26,8 +26,8 @@ def generate_django_model(schema, group, version, kind):
|
||||||
|
|
||||||
|
|
||||||
def build_object_fields(schema, name, verbose_name_prefix=None):
|
def build_object_fields(schema, name, verbose_name_prefix=None):
|
||||||
required_fields = schema.get("required") or []
|
required_fields = schema.get("required", [])
|
||||||
properties = schema.get("properties") or {}
|
properties = schema.get("properties", {})
|
||||||
fields = {}
|
fields = {}
|
||||||
|
|
||||||
for field_name, field_schema in properties.items():
|
for field_name, field_schema in properties.items():
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import kubernetes
|
import kubernetes
|
||||||
from django.core.cache import cache
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
@ -319,33 +318,81 @@ class ServiceOfferingControlPlane(models.Model):
|
||||||
version = self.service_definition.api_definition["version"]
|
version = self.service_definition.api_definition["version"]
|
||||||
client = self.control_plane.get_kubernetes_client()
|
client = self.control_plane.get_kubernetes_client()
|
||||||
|
|
||||||
|
# We have to determine the ``name`` first, via the discovery API
|
||||||
|
# The official kubernetes client APIs only include the regular discovery API,
|
||||||
|
# but not the APIGroupDiscoveryList that we actually need, and passing it as
|
||||||
|
# response_type leads to the client receiving the data, but refusing to return
|
||||||
|
# it since the model is not defined. Instead, we have to manually
|
||||||
|
# construct the API call:
|
||||||
|
content_type = "application/json"
|
||||||
|
response_type = "APIGroupDiscoveryList"
|
||||||
|
accept_header = ",".join(
|
||||||
|
[
|
||||||
|
f"{content_type};g=apidiscovery.k8s.io;v={api_version};as={response_type}"
|
||||||
|
for api_version in ("v2", "v2beta1")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
accept_header = f"{accept_header},{content_type}"
|
||||||
|
groups = client.call_api(
|
||||||
|
"/apis",
|
||||||
|
"GET",
|
||||||
|
header_params={"Accept": accept_header},
|
||||||
|
auth_settings=["BearerToken"],
|
||||||
|
_return_http_data_only=True,
|
||||||
|
_preload_content=False,
|
||||||
|
).json()
|
||||||
|
api_group = [
|
||||||
|
g for g in groups.get("items", []) if g["metadata"]["name"] == group
|
||||||
|
][0]
|
||||||
|
api_version = [v for v in api_group["versions"] if v["version"] == version][0]
|
||||||
|
resource = [
|
||||||
|
r for r in api_version["resources"] if r["responseKind"]["kind"] == kind
|
||||||
|
][0]
|
||||||
|
# This is the thing we went to all that effort for!
|
||||||
|
name = f"{resource['resource']}.{group}"
|
||||||
|
|
||||||
extensions_api = kubernetes.client.ApiextensionsV1Api(client)
|
extensions_api = kubernetes.client.ApiextensionsV1Api(client)
|
||||||
crds = extensions_api.list_custom_resource_definition()
|
crd = extensions_api.read_custom_resource_definition(name)
|
||||||
matching_crd = None
|
return crd
|
||||||
for crd in crds.items:
|
|
||||||
if matching_crd:
|
|
||||||
break
|
|
||||||
if crd.spec.group == group:
|
|
||||||
for served_version in crd.spec.versions:
|
|
||||||
if served_version.name == version and served_version.served:
|
|
||||||
if crd.spec.names.kind == kind:
|
|
||||||
matching_crd = crd
|
|
||||||
break
|
|
||||||
return matching_crd
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def resource_schema(self):
|
def resource_schema(self):
|
||||||
cache_key = f"servala:crd:schema:{self.pk}"
|
# We extract the schema directly from the API. As an alternative, you can
|
||||||
if result := cache.get(cache_key):
|
# also get it using self.resource_definition, though that requires some
|
||||||
return result
|
# extra serialization to remove the API client types:
|
||||||
|
# for version in self.resource_definition.spec.versions:
|
||||||
|
# if self.service_definition.api_definition["version"] != version.name:
|
||||||
|
# continue
|
||||||
|
# if version.schema and version.schema.open_apiv3_schema:
|
||||||
|
# schema_dict = kubernetes.client.ApiClient().sanitize_for_serialization(
|
||||||
|
# version.schema.open_apiv3_schema
|
||||||
|
# )
|
||||||
|
# return schema_dict
|
||||||
|
kind = self.service_definition.api_definition["kind"]
|
||||||
|
group = self.service_definition.api_definition["group"]
|
||||||
version = self.service_definition.api_definition["version"]
|
version = self.service_definition.api_definition["version"]
|
||||||
for v in self.resource_definition.spec.versions:
|
client = self.control_plane.get_kubernetes_client()
|
||||||
if v.name == version:
|
response = client.call_api(
|
||||||
result = v.schema.open_apiv3_schema.to_dict()
|
f"/openapi/v3/apis/{group}/{version}",
|
||||||
timeout_seconds = 60 * 60 * 24
|
"GET",
|
||||||
cache.set(cache_key, result, timeout=timeout_seconds)
|
header_params={"Accept": "application/json"},
|
||||||
return result
|
auth_settings=["BearerToken"],
|
||||||
|
_return_http_data_only=True,
|
||||||
|
_preload_content=False,
|
||||||
|
).json()
|
||||||
|
for schema in response["components"]["schemas"].values():
|
||||||
|
gkvs = schema.get("x-kubernetes-group-version-kind")
|
||||||
|
if not gkvs or not isinstance(gkvs, list):
|
||||||
|
continue
|
||||||
|
if any(
|
||||||
|
[
|
||||||
|
gkv["group"] == group
|
||||||
|
and gkv["version"] == version
|
||||||
|
and gkv["kind"] == kind
|
||||||
|
for gkv in gkvs
|
||||||
|
]
|
||||||
|
):
|
||||||
|
return schema
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def django_model(self):
|
def django_model(self):
|
||||||
|
|
|
@ -132,13 +132,6 @@ STATIC_URL = "static/" # CSS, JavaScript, etc.
|
||||||
STATIC_ROOT = BASE_DIR / "static.dist"
|
STATIC_ROOT = BASE_DIR / "static.dist"
|
||||||
MEDIA_URL = "media/" # User uploads, e.g. images
|
MEDIA_URL = "media/" # User uploads, e.g. images
|
||||||
|
|
||||||
CACHES = {
|
|
||||||
"default": {
|
|
||||||
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
|
||||||
"LOCATION": "servala_cache",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Additional locations of static files
|
# Additional locations of static files
|
||||||
STATICFILES_FINDERS = (
|
STATICFILES_FINDERS = (
|
||||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue