Add model field, migrations, and backend methods
This commit is contained in:
parent
9cff1e85ac
commit
582c4ed564
2 changed files with 103 additions and 4 deletions
46
src/servala/core/migrations/0019_add_display_name.py
Normal file
46
src/servala/core/migrations/0019_add_display_name.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
import servala.core.validators
|
||||
|
||||
|
||||
def populate_display_name(apps, schema_editor):
|
||||
"""
|
||||
For existing instances, copy name to display_name.
|
||||
Existing instances already have their name matching the k8s resource,
|
||||
so we cannot add a prefix.
|
||||
"""
|
||||
ServiceInstance = apps.get_model("core", "ServiceInstance")
|
||||
for instance in ServiceInstance.objects.all():
|
||||
instance.display_name = instance.name
|
||||
instance.save(update_fields=["display_name"])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("core", "0018_add_invoice_grouping_to_organization_origin"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="serviceinstance",
|
||||
name="name",
|
||||
field=models.CharField(
|
||||
max_length=63,
|
||||
validators=[servala.core.validators.kubernetes_name_validator],
|
||||
verbose_name="Resource Name",
|
||||
),
|
||||
),
|
||||
# Add display_name field with a temporary default
|
||||
migrations.AddField(
|
||||
model_name="serviceinstance",
|
||||
name="display_name",
|
||||
field=models.CharField(
|
||||
default="",
|
||||
help_text="Display name for this instance",
|
||||
max_length=100,
|
||||
verbose_name="Name",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(populate_display_name, migrations.RunPython.noop),
|
||||
]
|
||||
|
|
@ -18,6 +18,10 @@ from encrypted_fields.fields import EncryptedJSONField
|
|||
from kubernetes import client, config
|
||||
from kubernetes.client.rest import ApiException
|
||||
|
||||
import hashlib
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from servala.core import rules as perms
|
||||
from servala.core.models.mixins import ServalaModelMixin
|
||||
from servala.core.validators import kubernetes_name_validator
|
||||
|
|
@ -615,8 +619,16 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
on the fly.
|
||||
"""
|
||||
|
||||
# The Kubernetes resource name (metadata.name). This field is immutable after
|
||||
# creation and is auto-generated for new instances. Do not modify directly!
|
||||
name = models.CharField(
|
||||
max_length=63, verbose_name=_("Name"), validators=[kubernetes_name_validator]
|
||||
max_length=63,
|
||||
verbose_name=_("Resource Name"),
|
||||
validators=[kubernetes_name_validator],
|
||||
)
|
||||
display_name = models.CharField(
|
||||
max_length=100,
|
||||
verbose_name=_("Name"),
|
||||
)
|
||||
organization = models.ForeignKey(
|
||||
to="core.Organization",
|
||||
|
|
@ -686,6 +698,24 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
spec_data = prune_empty_data(spec_data)
|
||||
return spec_data
|
||||
|
||||
@staticmethod
|
||||
def generate_resource_name(organization, display_name, service, attempt=0):
|
||||
"""
|
||||
Generate a unique Kubernetes-compatible resource name.
|
||||
|
||||
Format: {prefix}-{sha256[:8]}
|
||||
|
||||
The hash input is: org_slug:display_name:service_slug[:attempt if > 0]
|
||||
On collision, we retry with an incremented attempt number included in hash.
|
||||
"""
|
||||
hash_input = (
|
||||
f"{organization.slug}:{display_name.lower().strip()}:{service.slug}"
|
||||
)
|
||||
if attempt > 0:
|
||||
hash_input += f":{attempt}"
|
||||
hash_value = hashlib.sha256(hash_input.encode("utf-8")).hexdigest()[:8]
|
||||
return f"{settings.SERVALA_INSTANCE_NAME_PREFIX}-{hash_value}"
|
||||
|
||||
@staticmethod
|
||||
def _apply_compute_plan_to_spec(spec_data, compute_plan_assignment):
|
||||
"""
|
||||
|
|
@ -719,16 +749,20 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
compute_plan_assignment,
|
||||
control_plane,
|
||||
instance_name=None,
|
||||
display_name=None,
|
||||
organization=None,
|
||||
service=None,
|
||||
):
|
||||
"""
|
||||
Build Kubernetes annotations for billing integration.
|
||||
Build Kubernetes annotations for billing integration and display name.
|
||||
"""
|
||||
from servala.core.models.organization import InvoiceGroupingChoice
|
||||
|
||||
annotations = {}
|
||||
|
||||
if display_name:
|
||||
annotations["servala.com/displayName"] = display_name
|
||||
|
||||
if compute_plan_assignment:
|
||||
annotations["servala.com/erp_product_id_resource"] = str(
|
||||
compute_plan_assignment.odoo_product_id
|
||||
|
|
@ -830,18 +864,35 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
@transaction.atomic
|
||||
def create_instance(
|
||||
cls,
|
||||
name,
|
||||
display_name,
|
||||
organization,
|
||||
context,
|
||||
created_by,
|
||||
spec_data,
|
||||
compute_plan_assignment=None,
|
||||
):
|
||||
service = context.service_offering.service
|
||||
name = None
|
||||
for attempt in range(10):
|
||||
name = cls.generate_resource_name(
|
||||
organization, display_name, service, attempt
|
||||
)
|
||||
if not cls.objects.filter(
|
||||
name=name, organization=organization, context=context
|
||||
).exists():
|
||||
break
|
||||
else:
|
||||
message = _(
|
||||
"Could not generate a unique resource name. Please try a different display name."
|
||||
)
|
||||
raise ValidationError(organization.add_support_message(message))
|
||||
|
||||
# Ensure the namespace exists
|
||||
context.control_plane.get_or_create_namespace(organization)
|
||||
try:
|
||||
instance = cls.objects.create(
|
||||
name=name,
|
||||
display_name=display_name,
|
||||
organization=organization,
|
||||
created_by=created_by,
|
||||
context=context,
|
||||
|
|
@ -883,8 +934,9 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
compute_plan_assignment=compute_plan_assignment,
|
||||
control_plane=context.control_plane,
|
||||
instance_name=name,
|
||||
display_name=display_name,
|
||||
organization=organization,
|
||||
service=context.service_offering.service,
|
||||
service=service,
|
||||
)
|
||||
if annotations:
|
||||
create_data["metadata"]["annotations"] = annotations
|
||||
|
|
@ -941,6 +993,7 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
|||
compute_plan_assignment=plan_to_use,
|
||||
control_plane=self.context.control_plane,
|
||||
instance_name=self.name,
|
||||
display_name=self.display_name,
|
||||
organization=self.organization,
|
||||
service=self.context.service_offering.service,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue