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 = ( fieldsets = (
( (
None, None,
{"fields": ("name", "description", "cloud_provider")}, {"fields": ("name", "description", "cloud_provider", "required_label")},
), ),
( (
_("API Credentials"), _("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 kubernetes
import urlman import urlman
from django.conf import settings
from django.core.cache import cache 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
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from encrypted_fields.fields import EncryptedJSONField from encrypted_fields.fields import EncryptedJSONField
from kubernetes import config from kubernetes import client, config
from kubernetes.client.rest import ApiException from kubernetes.client.rest import ApiException
from servala.core.models.mixins import ServalaModelMixin 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", help_text="Required fields: certificate-authority-data, server (URL), token",
validators=[validate_api_credentials], 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( cloud_provider = models.ForeignKey(
to="CloudProvider", to="CloudProvider",
@ -137,7 +147,6 @@ class ControlPlane(ServalaModelMixin, models.Model):
"clusters": [ "clusters": [
{ {
"cluster": { "cluster": {
"insecure-skip-tls-verify": True,
"certificate-authority-data": self.api_credentials[ "certificate-authority-data": self.api_credentials[
"certificate-authority-data" "certificate-authority-data"
], ],
@ -484,6 +493,35 @@ class ServiceInstance(ServalaModelMixin, models.Model):
# Ensure the namespace exists # Ensure the namespace exists
context.control_plane.get_or_create_namespace(organization.namespace) 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( return cls.objects.create(
name=name, name=name,
organization=organization, organization=organization,

View file

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

View file

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

View file

@ -15,12 +15,16 @@ def origin():
@pytest.fixture @pytest.fixture
def organization(origin): 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 @pytest.fixture
def other_organization(origin): 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 @pytest.fixture