Compare commits

...

4 commits

Author SHA1 Message Date
a2ac202f26 Fix label metadata
All checks were successful
Tests / test (push) Successful in 26s
2025-04-03 17:41:07 +02:00
bc8c7a80b2 Add required_label field to ControlPlane 2025-04-03 17:39:49 +02:00
08fada04e0 Successfully create an instance 2025-04-03 17:17:33 +02:00
a2c3695611 Fix breaking tests 2025-04-03 16:20:52 +02:00
6 changed files with 80 additions and 6 deletions

View file

@ -131,7 +131,7 @@ class ControlPlaneAdmin(admin.ModelAdmin):
fieldsets = (
(
None,
{"fields": ("name", "description", "cloud_provider")},
{"fields": ("name", "description", "cloud_provider", "required_label")},
),
(
_("API Credentials"),

View file

@ -0,0 +1,31 @@
# Generated by Django 5.2b1 on 2025-04-03 15:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
name="controlplanecrd",
options={
"verbose_name": "ControlPlane CRD",
"verbose_name_plural": "ControlPlane CRDs",
},
),
migrations.AddField(
model_name="controlplane",
name="required_label",
field=models.CharField(
blank=True,
help_text="Label value for the 'appcat.vshn.io/provider-config' added to every instance on this plane.",
max_length=100,
null=True,
verbose_name="Required Label",
),
),
]

View file

@ -1,12 +1,13 @@
import kubernetes
import urlman
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from encrypted_fields.fields import EncryptedJSONField
from kubernetes import config
from kubernetes import client, config
from kubernetes.client.rest import ApiException
from servala.core.models.mixins import ServalaModelMixin
@ -106,6 +107,15 @@ class ControlPlane(ServalaModelMixin, models.Model):
help_text="Required fields: certificate-authority-data, server (URL), token",
validators=[validate_api_credentials],
)
required_label = models.CharField(
max_length=100,
blank=True,
null=True,
verbose_name=_("Required Label"),
help_text=_(
"Label value for the 'appcat.vshn.io/provider-config' added to every instance on this plane."
),
)
cloud_provider = models.ForeignKey(
to="CloudProvider",
@ -137,7 +147,6 @@ class ControlPlane(ServalaModelMixin, models.Model):
"clusters": [
{
"cluster": {
"insecure-skip-tls-verify": True,
"certificate-authority-data": self.api_credentials[
"certificate-authority-data"
],
@ -484,6 +493,35 @@ class ServiceInstance(ServalaModelMixin, models.Model):
# Ensure the namespace exists
context.control_plane.get_or_create_namespace(organization.namespace)
group = context.service_definition.api_definition["group"]
version = context.service_definition.api_definition["version"]
kind = context.service_definition.api_definition["kind"]
create_data = {
"apiVersion": f"{group}/{version}",
"kind": kind,
"metadata": {
"name": name,
"namespace": organization.namespace,
},
"spec": spec_data or {},
}
if label := context.control_plane.required_label:
create_data["metadata"]["labels"] = {settings.DEFAULT_LABEL_KEY: label}
api_instance = client.CustomObjectsApi(
context.control_plane.get_kubernetes_client()
)
plural = kind.lower()
if not plural.endswith("s"):
plural = f"{plural}s"
api_instance.create_namespaced_custom_object(
group=group,
version=version,
namespace=organization.namespace,
plural=plural,
body=create_data,
)
return cls.objects.create(
name=name,
organization=organization,

View file

@ -131,7 +131,7 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView
name=form.cleaned_data["name"],
context=self.context_object,
created_by=request.user,
spec_data=form.get_nested_data(),
spec_data=form.get_nested_data().get("spec"),
)
return redirect(service_instance.urls.base)
except Exception as e:

View file

@ -210,6 +210,7 @@ LANGUAGE_COOKIE_NAME = "servala_lang"
SESSION_COOKIE_NAME = "servala_sess"
SESSION_COOKIE_SECURE = not DEBUG
DEFAULT_LABEL_KEY = "appcat.vshn.io/provider-config"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# TODO

View file

@ -15,12 +15,16 @@ def origin():
@pytest.fixture
def organization(origin):
return Organization.objects.create(name="Test Org", origin=origin)
return Organization.objects.create(
name="Test Org", namespace="test-org", origin=origin
)
@pytest.fixture
def other_organization(origin):
return Organization.objects.create(name="Test Org Alternate", origin=origin)
return Organization.objects.create(
name="Test Org Alternate", namespace="test-org-alternate", origin=origin
)
@pytest.fixture