diff --git a/src/servala/core/forms.py b/src/servala/core/forms.py index 6c8082a..d7740ae 100644 --- a/src/servala/core/forms.py +++ b/src/servala/core/forms.py @@ -1,5 +1,4 @@ from django import forms -from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from servala.core.models import ControlPlane @@ -40,35 +39,31 @@ class ControlPlaneAdminForm(forms.ModelForm): def clean(self): cleaned_data = super().clean() - # Get the three credential fields ca_data = cleaned_data.get("certificate_authority_data") server = cleaned_data.get("server") token = cleaned_data.get("token") - # Check if any of the fields are filled - has_ca = bool(ca_data) - has_server = bool(server) - has_token = bool(token) - - # If some but not all fields are filled, raise validation error - if (has_ca or has_server or has_token) and not ( - has_ca and has_server and has_token - ): - raise ValidationError( - _( - "All credential fields (certificate authority data, server, and token) must be provided together or left empty." - ) - ) - - # If all fields are filled, create the api_credentials JSON - if has_ca and has_server and has_token: + if ca_data and server and token: cleaned_data["api_credentials"] = { "certificate-authority-data": ca_data, "server": server, "token": token, } else: - cleaned_data["api_credentials"] = {} + if not (ca_data or server or token): + cleaned_data["api_credentials"] = {} + else: + # Some fields are filled but not all - validation will fail at model level, + # as model field validators are also called by the API. + # We still create the JSON with whatever we have so the model validator can run. + credentials = {} + if ca_data: + credentials["certificate-authority-data"] = ca_data + if server: + credentials["server"] = server + if token: + credentials["token"] = token + cleaned_data["api_credentials"] = credentials return cleaned_data diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py index 470fcec..26c930f 100644 --- a/src/servala/core/models/service.py +++ b/src/servala/core/models/service.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import gettext_lazy as _ from encrypted_fields.fields import EncryptedJSONField @@ -63,14 +64,34 @@ class Service(models.Model): return self.name +def validate_api_credentials(value): + """ + Validates that api_credentials either contains all required fields or is empty. + """ + # If empty dict, that's valid + if not value: + return + + # Check for required fields + required_fields = ("certificate-authority-data", "server", "token") + missing_fields = required_fields - set(value) + + if missing_fields: + raise ValidationError( + _("Missing required fields in API credentials: %(fields)s"), + params={"fields": ", ".join(missing_fields)}, + ) + + class ControlPlane(models.Model): name = models.CharField(max_length=100, verbose_name=_("Name")) description = models.TextField(blank=True, verbose_name=_("Description")) k8s_api_endpoint = models.URLField(verbose_name=_("Kubernetes API endpoint")) - # TODO: schema + # Either contains the fields "certificate_authority_data", "server" and "token", or is empty api_credentials = EncryptedJSONField( verbose_name=_("API credentials"), help_text="Required fields: certificate-authority-data, server (URL), token", + validators=[validate_api_credentials], ) cloud_provider = models.ForeignKey(