2025-01-31 15:34:10 +01:00
|
|
|
import logging
|
2025-01-31 16:17:16 +01:00
|
|
|
from django.urls import reverse
|
|
|
|
from django.conf import settings
|
2025-01-31 15:34:10 +01:00
|
|
|
from rest_framework import viewsets, status
|
|
|
|
from rest_framework.response import Response
|
|
|
|
from rest_framework.decorators import action
|
|
|
|
from django.shortcuts import get_object_or_404
|
2025-01-31 16:17:16 +01:00
|
|
|
from hub.services.models import Lead
|
2025-01-31 15:34:10 +01:00
|
|
|
from hub.services.odoo import OdooAPI
|
|
|
|
from .models import ServiceInstance, ServiceBrokerUser
|
2025-01-31 16:35:33 +01:00
|
|
|
from .serializers import (
|
|
|
|
ServiceInstanceProvisioningSerializer,
|
|
|
|
CatalogSerializer,
|
|
|
|
ServiceInstanceUpdateSerializer,
|
|
|
|
)
|
2025-01-31 15:34:10 +01:00
|
|
|
from .authentication import ServiceBrokerAuthentication
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceBrokerViewSet(viewsets.ViewSet):
|
|
|
|
authentication_classes = [ServiceBrokerAuthentication]
|
|
|
|
|
2025-01-31 15:46:22 +01:00
|
|
|
@action(detail=False, methods=["get"], url_path="catalog")
|
|
|
|
def get_catalog(self, request):
|
|
|
|
"""
|
|
|
|
Return the catalog of services available to the authenticated user.
|
|
|
|
"""
|
|
|
|
# Get broker user and their allowed offerings
|
|
|
|
broker_user = get_object_or_404(ServiceBrokerUser, user=request.user)
|
|
|
|
offerings = broker_user.allowed_offerings.prefetch_related(
|
|
|
|
"plans",
|
|
|
|
"service",
|
|
|
|
"cloud_provider",
|
|
|
|
).all()
|
|
|
|
|
|
|
|
# Serialize the catalog
|
|
|
|
serializer = CatalogSerializer({"services": offerings})
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
2025-01-31 15:34:10 +01:00
|
|
|
def _create_lead_from_provision_data(self, offering, plan, parameters, context):
|
|
|
|
"""Create a lead in Odoo from provisioning data"""
|
|
|
|
# Get the first user from parameters as the main contact
|
|
|
|
user_data = parameters["users"][0]
|
|
|
|
|
|
|
|
lead = Lead(
|
|
|
|
service=offering.service,
|
|
|
|
offering=offering,
|
|
|
|
plan=plan,
|
|
|
|
name=user_data["full_name"],
|
|
|
|
company=context["organization_display_name"],
|
|
|
|
email=user_data["email"],
|
|
|
|
phone="", # Not provided in broker API
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
|
|
|
odoo = OdooAPI()
|
2025-01-31 16:17:16 +01:00
|
|
|
lead.odoo_lead_id = odoo.create_lead(lead, source="osbapi")
|
2025-01-31 15:34:10 +01:00
|
|
|
lead.save()
|
|
|
|
return lead
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(f"Failed to create lead in Odoo: {str(e)}")
|
|
|
|
raise
|
|
|
|
|
|
|
|
@action(
|
|
|
|
detail=False,
|
|
|
|
methods=["put"],
|
|
|
|
url_path="service_instances/(?P<instance_id>[^/.]+)",
|
|
|
|
)
|
|
|
|
def provision_instance(self, request, instance_id):
|
|
|
|
# Validate request data
|
|
|
|
serializer = ServiceInstanceProvisioningSerializer(data=request.data)
|
|
|
|
if not serializer.is_valid():
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
# Get broker user and check offering access
|
|
|
|
broker_user = get_object_or_404(ServiceBrokerUser, user=request.user)
|
|
|
|
offering = serializer.validated_data["service_id"] # This is now the offering
|
|
|
|
|
|
|
|
if not broker_user.allowed_offerings.filter(id=offering.id).exists():
|
|
|
|
return Response(
|
|
|
|
{"error": "Service offering not available for this user"},
|
|
|
|
status=status.HTTP_403_FORBIDDEN,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Check if instance already exists
|
|
|
|
if ServiceInstance.objects.filter(instance_id=instance_id).exists():
|
|
|
|
return Response(status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
try:
|
|
|
|
# Create lead in Odoo
|
|
|
|
lead = self._create_lead_from_provision_data(
|
|
|
|
offering=offering,
|
|
|
|
plan=serializer.validated_data["plan_id"],
|
|
|
|
parameters=serializer.validated_data["parameters"],
|
|
|
|
context=serializer.validated_data["context"],
|
|
|
|
)
|
|
|
|
|
|
|
|
# Create service instance
|
|
|
|
instance = ServiceInstance.objects.create(
|
|
|
|
instance_id=instance_id,
|
|
|
|
offering=offering,
|
|
|
|
plan=serializer.validated_data["plan_id"],
|
|
|
|
organization_guid=serializer.validated_data["organization_guid"],
|
|
|
|
space_guid=serializer.validated_data["space_guid"],
|
|
|
|
organization_name=serializer.validated_data["context"][
|
|
|
|
"organization_name"
|
|
|
|
],
|
|
|
|
parameters=serializer.validated_data["parameters"],
|
|
|
|
context=serializer.validated_data["context"],
|
|
|
|
lead=lead,
|
|
|
|
)
|
|
|
|
|
|
|
|
return Response(
|
|
|
|
{
|
2025-01-31 16:17:16 +01:00
|
|
|
"dashboard_url": settings.WEBSITE_URL
|
|
|
|
+ reverse(
|
2025-02-28 14:25:35 +01:00
|
|
|
"services:offering_detail",
|
|
|
|
kwargs={
|
|
|
|
"provider_slug": offering.cloud_provider.slug,
|
|
|
|
"service_slug": offering.service.slug,
|
|
|
|
},
|
2025-01-31 16:17:16 +01:00
|
|
|
),
|
2025-01-31 15:34:10 +01:00
|
|
|
"operation": "provision",
|
|
|
|
},
|
|
|
|
status=status.HTTP_201_CREATED,
|
|
|
|
)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(f"Error provisioning instance: {str(e)}")
|
|
|
|
return Response(
|
|
|
|
{"error": "Failed to provision service instance"},
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
)
|
|
|
|
|
|
|
|
@action(
|
|
|
|
detail=False,
|
|
|
|
methods=["delete"],
|
|
|
|
url_path="service_instances/(?P<instance_id>[^/.]+)",
|
|
|
|
)
|
|
|
|
def deprovision_instance(self, request, instance_id):
|
|
|
|
instance = get_object_or_404(ServiceInstance, instance_id=instance_id)
|
|
|
|
instance.delete()
|
|
|
|
return Response(status=status.HTTP_200_OK)
|
2025-01-31 16:35:33 +01:00
|
|
|
|
|
|
|
@action(
|
|
|
|
detail=False,
|
|
|
|
methods=["get"],
|
|
|
|
url_path="service_instances/(?P<instance_id>[^/.]+)",
|
|
|
|
)
|
|
|
|
def get_instance(self, request, instance_id):
|
|
|
|
instance = get_object_or_404(ServiceInstance, instance_id=instance_id)
|
|
|
|
return Response(
|
|
|
|
{
|
|
|
|
"service_id": str(instance.offering.id),
|
|
|
|
"plan_id": str(instance.plan.id),
|
|
|
|
"dashboard_url": settings.WEBSITE_URL
|
|
|
|
+ reverse(
|
2025-02-28 14:25:35 +01:00
|
|
|
"services:offering_detail",
|
|
|
|
kwargs={
|
|
|
|
"provider_slug": instance.offering.cloud_provider.slug,
|
|
|
|
"service_slug": instance.offering.service.slug,
|
|
|
|
},
|
2025-01-31 16:35:33 +01:00
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
@action(
|
|
|
|
detail=False,
|
|
|
|
methods=["patch"],
|
|
|
|
url_path="service_instances/(?P<instance_id>[^/.]+)",
|
|
|
|
)
|
|
|
|
def update_instance(self, request, instance_id):
|
|
|
|
instance = get_object_or_404(ServiceInstance, instance_id=instance_id)
|
|
|
|
serializer = ServiceInstanceUpdateSerializer(
|
|
|
|
data=request.data, context={"instance": instance}
|
|
|
|
)
|
|
|
|
|
|
|
|
if not serializer.is_valid():
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
instance.plan = serializer.validated_data["plan_id"]
|
|
|
|
instance.save()
|
|
|
|
|
|
|
|
return Response(
|
|
|
|
{
|
|
|
|
"operation": "update",
|
|
|
|
"dashboard_url": settings.WEBSITE_URL
|
|
|
|
+ reverse(
|
2025-02-28 14:25:35 +01:00
|
|
|
"services:offering_detail",
|
|
|
|
kwargs={
|
|
|
|
"provider_slug": instance.offering.cloud_provider.slug,
|
|
|
|
"service_slug": instance.offering.service.slug,
|
|
|
|
},
|
2025-01-31 16:35:33 +01:00
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|