Cliente SDK Python

El apartado actual detalla el cliente SDK Python de Jotelulu para el acceso simplificado al API Pública de Jotelulu.

📖 Documentación de API

Endpoints Principales

ServicioEndpointDescripción
Core/core/v1/organizationsGestión de organizaciones
Core/core/v1/organizations/{id}/usersGestión de usuarios
Servers/servers/v1/subscriptions/{id}/instancesInstancias de servidor
Servers/servers/v1/subscriptions/{id}/networksGestión de redes
Remote Desktops/remote-desktops/v1/subscriptions/{id}/instancesInstancias de escritorio remoto
Remote Desktops/remote-desktops/v1/subscriptions/{id}/applicationsAplicaciones

Modelos Principales

  • Organization: Representa una organización
  • User: Usuario del sistema
  • Subscription: Suscripción a servicios
  • Instance: Instancia de servidor o escritorio remoto
  • Application: Aplicación instalable
  • NetworkInterface: Interfaz de red
  • FirewallInboundRule: Regla de firewall

🚀 Instalación en Entorno Virtual (Recomendado)

# Crear entorno virtual
python -m venv jotelulu-env

# Activar entorno virtual
# En Windows:
jotelulu-env\Scripts\activate
# En Linux/macOS:
source jotelulu-env/bin/activate

# Instalar el SDK
pip install generated-client-python

🔐 Autenticación con JWT

Configuración Básica

import openapi_client
from openapi_client.configuration import Configuration
from openapi_client.api_client import ApiClient

# Configurar el cliente con JWT
configuration = Configuration(
    host="https://connect-eu1.jotelulu.com",  # Producción
    access_token="your_jwt_token_here"
)

# Crear cliente API
api_client = ApiClient(configuration)

Configuración de Entornos

# Entornos disponibles
ENVIRONMENTS = {
    'production': 'https://connect-eu1.jotelulu.com'
}

# Configuración por entorno
def create_jotelulu_client(environment='production', token=None):
    configuration = Configuration(
        host=ENVIRONMENTS[environment],
        access_token=token
    )
    return ApiClient(configuration)

Manejo Seguro de Tokens

import os
from openapi_client import Configuration, ApiClient

class JoteluluClient:
    def __init__(self, environment='production'):
        # Obtener token desde variables de entorno
        token = os.getenv('JOTELULU_JWT_TOKEN')
        if not token:
            raise ValueError("JOTELULU_JWT_TOKEN environment variable is required")
        
        self.configuration = Configuration(
            host=ENVIRONMENTS.get(environment, ENVIRONMENTS['production']),
            access_token=token
        )
        self.api_client = ApiClient(self.configuration)
    
    def get_organizations_api(self):
        from openapi_client.api.organizations_api import OrganizationsApi
        return OrganizationsApi(self.api_client)
    
    def get_instances_api(self):
        from openapi_client.api.instances_api import InstancesApi
        return InstancesApi(self.api_client)

🐍 Integración con Django

1. Instalación en Proyecto Django

# Crear nuevo proyecto Django
django-admin startproject mi_proyecto_jotelulu
cd mi_proyecto_jotelulu

# Crear aplicación
python manage.py startapp jotelulu_integration

# Instalar dependencias
pip install django
pip install generated-client-python

2. Configuración en settings.py

# settings.py
import os

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'jotelulu_integration',  # Tu aplicación
]

# Configuración Jotelulu
JOTELULU_SETTINGS = {
    'ENVIRONMENT': os.getenv('JOTELULU_ENV', 'production'),
    'JWT_TOKEN': os.getenv('JOTELULU_JWT_TOKEN'),
    'API_ENDPOINTS': {
        'production': 'https://connect-eu1.jotelulu.com'
    }
}

3. Servicio Django para Jotelulu

# jotelulu_integration/services.py
from django.conf import settings
import openapi_client
from openapi_client.api.organizations_api import OrganizationsApi
from openapi_client.api.instances_api import InstancesApi
from openapi_client.api.applications_api import ApplicationsApi
from openapi_client.exceptions import ApiException

class JoteluluService:
    def __init__(self):
        jotelulu_config = settings.JOTELULU_SETTINGS
        environment = jotelulu_config['ENVIRONMENT']
        
        self.configuration = openapi_client.Configuration(
            host=jotelulu_config['API_ENDPOINTS'][environment],
            access_token=jotelulu_config['JWT_TOKEN']
        )
        self.api_client = openapi_client.ApiClient(self.configuration)
    
    def get_user_organizations(self):
        """Obtener organizaciones del usuario actual"""
        try:
            api_instance = OrganizationsApi(self.api_client)
            response = api_instance.list_user_organizations()
            return response.data
        except ApiException as e:
            print(f"Error al obtener organizaciones: {e}")
            return []
    
    def get_organization_users(self, organization_id):
        """Obtener usuarios de una organización"""
        try:
            api_instance = OrganizationsApi(self.api_client)
            response = api_instance.list_organization_users(organization_id)
            return response.data
        except ApiException as e:
            print(f"Error al obtener usuarios: {e}")
            return []
    
    def get_remote_desktop_instances(self, subscription_id):
        """Obtener instancias de escritorio remoto"""
        try:
            api_instance = InstancesApi(self.api_client)
            response = api_instance.list_remote_desktop_subscription_instances(subscription_id)
            return response.data
        except ApiException as e:
            print(f"Error al obtener instancias: {e}")
            return []

4. Views Django

# jotelulu_integration/views.py
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .services import JoteluluService

def dashboard(request):
    """Vista principal del dashboard"""
    jotelulu = JoteluluService()
    organizations = jotelulu.get_user_organizations()
    
    context = {
        'organizations': organizations,
        'user': request.user
    }
    return render(request, 'jotelulu_integration/dashboard.html', context)

def organization_detail(request, organization_id):
    """Detalle de una organización"""
    jotelulu = JoteluluService()
    users = jotelulu.get_organization_users(organization_id)
    
    return JsonResponse({
        'organization_id': organization_id,
        'users': [{'id': user.id, 'name': user.name, 'email': user.email} for user in users]
    })

@csrf_exempt
def create_organization_user(request, organization_id):
    """Crear usuario en organización"""
    if request.method == 'POST':
        import json
        from openapi_client.model.create_organization_user_request import CreateOrganizationUserRequest
        from openapi_client.model.create_organization_user_request_data import CreateOrganizationUserRequestData
        
        data = json.loads(request.body)
        jotelulu = JoteluluService()
        
        try:
            api_instance = jotelulu.get_organizations_api()
            user_request = CreateOrganizationUserRequest(
                data=CreateOrganizationUserRequestData(
                    email=data['email'],
                    name=data['name'],
                    last_name=data['lastName'],
                    password=data.get('password')
                )
            )
            
            response = api_instance.create_organization_user(organization_id, user_request)
            return JsonResponse({'success': True, 'user_id': response.data.user_id})
            
        except Exception as e:
            return JsonResponse({'success': False, 'error': str(e)})
    
    return JsonResponse({'error': 'Method not allowed'}, status=405)

5. URLs Django

# jotelulu_integration/urls.py
from django.urls import path
from . import views

app_name = 'jotelulu_integration'

urlpatterns = [
    path('', views.dashboard, name='dashboard'),
    path('organization/<str:organization_id>/', views.organization_detail, name='organization_detail'),
    path('organization/<str:organization_id>/users/', views.create_organization_user, name='create_user'),
]

# urls.py principal del proyecto
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('jotelulu/', include('jotelulu_integration.urls')),
]

📚 Ejemplos de Uso

Gestión de Organizaciones

from openapi_client.api.organizations_api import OrganizationsApi
from openapi_client.model.create_organization_user_request import CreateOrganizationUserRequest

# Inicializar cliente
client = JoteluluClient()
org_api = client.get_organizations_api()

# Listar organizaciones del usuario
organizations = org_api.list_user_organizations()
print(f"Organizaciones encontradas: {len(organizations.data)}")

# Listar usuarios de una organización
org_id = "ORG-123"
users = org_api.list_organization_users(org_id)
for user in users.data:
    print(f"Usuario: {user.name} ({user.email})")

# Crear nuevo usuario
new_user_request = CreateOrganizationUserRequest(
    data={
        'email': '[email protected]',
        'name': 'Nuevo',
        'lastName': 'Usuario',
        'password': 'password123',
        'twoFactorAuthRequired': True
    }
)
result = org_api.create_organization_user(org_id, new_user_request)
print(f"Usuario creado con ID: {result.data.user_id}")

Gestión de Instancias y Servidores

from openapi_client.api.instances_api import InstancesApi
from openapi_client.api.instance_api import InstanceApi

# APIs de instancias
instances_api = InstancesApi(api_client)
instance_api = InstanceApi(api_client)

# Listar instancias de escritorio remoto
subscription_id = "SUB-456"
rd_instances = instances_api.list_remote_desktop_subscription_instances(subscription_id)

for instance in rd_instances.data:
    print(f"Instancia: {instance.name} - Estado: {instance.state}")
    
    # Obtener progreso de despliegue
    if instance.state == 'creating':
        progress = instance_api.get_instance_progress(subscription_id, instance.id)
        print(f"Progreso: {progress.data.progress}")

# Listar instancias de servidor
server_instances = instance_api.list_server_subscription_instances(subscription_id)
print(f"Instancias de servidor: {len(server_instances.data)}")

Gestión de Aplicaciones

from openapi_client.api.applications_api import ApplicationsApi
from openapi_client.model.create_instance_application_request import CreateInstanceApplicationRequest

apps_api = ApplicationsApi(api_client)

# Listar aplicaciones disponibles
available_apps = apps_api.list_applications()
print("Aplicaciones disponibles:")
for app in available_apps.data:
    print(f"- {app.name} (ID: {app.id})")

# Listar aplicaciones de una suscripción
subscription_apps = apps_api.list_subscription_applications(subscription_id)
print(f"Aplicaciones instaladas: {len(subscription_apps.data)}")

# Instalar nueva aplicación
install_request = CreateInstanceApplicationRequest(
    data={
        'instanceId': 'INST-789',
        'applicationId': 'APP-123'
    }
)
result = apps_api.create_instance_application(subscription_id, install_request)
print(f"Aplicación instalada: {result.data[0].id}")

Gestión de Redes y Firewall

from openapi_client.api.networks_api import NetworksApi
from openapi_client.api.firewall_api import FirewallApi
from openapi_client.model.create_isolated_network_request import CreateIsolatedNetworkRequest
from openapi_client.model.create_firewall_inbound_rule_request import CreateFirewallInboundRuleRequest

networks_api = NetworksApi(api_client)
firewall_api = FirewallApi(api_client)

# Crear red aislada
network_request = CreateIsolatedNetworkRequest(
    data={
        'name': 'Mi Red Privada',
        'network': '192.168.100.0',
        'netmask': '255.255.255.0',
        'gateway': '192.168.100.1'
    }
)
network = networks_api.create_isolated_network(subscription_id, network_request)
print(f"Red creada: {network.data.name}")

# Crear regla de firewall
firewall_rule = CreateFirewallInboundRuleRequest(
    data={
        'protocol': 'TCP',
        'publicNetworkId': 'NET-456',
        'privateNicAddressIpId': 'NIC-789',
        'publicPort': 80,
        'privatePort': 8080,
        'origin': '0.0.0.0/0'
    }
)
rule = firewall_api.create_firewall_inbound_rule(
    subscription_id, 
    network.data.main_network_id, 
    firewall_rule
)
print(f"Regla de firewall creada: {rule.data.id}")

🛠️ Manejo de Errores

from openapi_client.exceptions import ApiException
import logging

# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def safe_api_call(api_function, *args, **kwargs):
    """Wrapper para llamadas seguras a la API"""
    try:
        return api_function(*args, **kwargs)
    except ApiException as e:
        logger.error(f"Error de API: {e.status} - {e.reason}")
        logger.error(f"Cuerpo de respuesta: {e.body}")
        
        # Manejo específico por código de error
        if e.status == 401:
            logger.error("Token JWT inválido o expirado")
        elif e.status == 403:
            logger.error("Permisos insuficientes")
        elif e.status == 404:
            logger.error("Recurso no encontrado")
        elif e.status >= 500:
            logger.error("Error interno del servidor")
        
        return None
    except Exception as e:
        logger.error(f"Error inesperado: {str(e)}")
        return None

# Ejemplo de uso
organizations = safe_api_call(org_api.list_user_organizations)
if organizations:
    print(f"Organizaciones obtenidas: {len(organizations.data)}")
else:
    print("No se pudieron obtener las organizaciones")

🔧 Configuración Avanzada

Cliente Personalizado con Retry

import time
from functools import wraps

def retry_on_failure(max_retries=3, delay=1):
    """Decorador para reintentar llamadas fallidas"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except ApiException as e:
                    if e.status >= 500 and attempt < max_retries - 1:
                        time.sleep(delay * (2 ** attempt))  # Backoff exponencial
                        continue
                    raise
            return None
        return wrapper
    return decorator

class RobustJoteluluClient(JoteluluClient):
    @retry_on_failure(max_retries=3)
    def get_organizations_with_retry(self):
        api = self.get_organizations_api()
        return api.list_user_organizations()

Configuración de Timeout

from openapi_client.configuration import Configuration
from openapi_client.api_client import ApiClient

# Configuración con timeouts personalizados
configuration = Configuration(
    host="https://connect-eu1.jotelulu.com",
    access_token="your_token"
)

# Configurar timeouts
api_client = ApiClient(configuration)
api_client.rest_client.pool_manager.connection_pool_kw['timeout'] = 30