Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements
- Add comprehensive database migrations (001-024) for schema evolution - Enhance API schema with expanded type definitions and resolvers - Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth - Implement new services: AI optimization, billing, blockchain, compliance, marketplace - Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage) - Update Crossplane provider with enhanced VM management capabilities - Add comprehensive test suite for API endpoints and services - Update frontend components with improved GraphQL subscriptions and real-time updates - Enhance security configurations and headers (CSP, CORS, etc.) - Update documentation and configuration files - Add new CI/CD workflows and validation scripts - Implement design system improvements and UI enhancements
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
package vmscaleset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
proxmoxv1alpha1 "github.com/sankofa/crossplane-provider-proxmox/apis/v1alpha1"
|
||||
"github.com/sankofa/crossplane-provider-proxmox/pkg/metrics"
|
||||
"github.com/sankofa/crossplane-provider-proxmox/pkg/proxmox"
|
||||
"github.com/sankofa/crossplane-provider-proxmox/pkg/scaling"
|
||||
)
|
||||
|
||||
// ProxmoxVMScaleSetReconciler reconciles a ProxmoxVMScaleSet object
|
||||
type ProxmoxVMScaleSetReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=proxmox.sankofa.nexus,resources=proxmoxvmscalesets,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=proxmox.sankofa.nexus,resources=proxmoxvmscalesets/status,verbs=get;update;patch
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop
|
||||
func (r *ProxmoxVMScaleSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
var vmss proxmoxv1alpha1.ProxmoxVMScaleSet
|
||||
if err := r.Get(ctx, req.NamespacedName, &vmss); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
// Validate ProviderConfigReference
|
||||
if vmss.Spec.ProviderConfigReference == nil {
|
||||
return ctrl.Result{}, errors.New("providerConfigRef is required")
|
||||
}
|
||||
if vmss.Spec.ProviderConfigReference.Name == "" {
|
||||
return ctrl.Result{}, errors.New("providerConfigRef.name is required")
|
||||
}
|
||||
|
||||
// Get ProviderConfig
|
||||
var providerConfig proxmoxv1alpha1.ProviderConfig
|
||||
if err := r.Get(ctx, client.ObjectKey{Name: vmss.Spec.ProviderConfigReference.Name}, &providerConfig); err != nil {
|
||||
return ctrl.Result{}, errors.Wrapf(err, "cannot get provider config")
|
||||
}
|
||||
|
||||
// Get credentials from secret (similar to virtualmachine controller)
|
||||
creds, err := r.getCredentials(ctx, &providerConfig)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot get credentials")
|
||||
return ctrl.Result{RequeueAfter: 30 * time.Second}, errors.Wrap(err, "cannot get credentials")
|
||||
}
|
||||
|
||||
// Find the site configuration (use first site or from spec if available)
|
||||
var site *proxmoxv1alpha1.ProxmoxSite
|
||||
if len(providerConfig.Spec.Sites) > 0 {
|
||||
site = &providerConfig.Spec.Sites[0]
|
||||
} else {
|
||||
return ctrl.Result{}, errors.New("no sites configured in provider config")
|
||||
}
|
||||
|
||||
// Create Proxmox client with proper credentials
|
||||
proxmoxClient, err := proxmox.NewClient(
|
||||
site.Endpoint,
|
||||
creds.Username,
|
||||
creds.Password,
|
||||
site.InsecureSkipTLSVerify,
|
||||
)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, errors.Wrap(err, "cannot create Proxmox client")
|
||||
}
|
||||
|
||||
// Create metrics collector with Prometheus client
|
||||
// Get Prometheus endpoint from environment or ProviderConfig
|
||||
// For now, we'll use a default endpoint - in production this should come from config
|
||||
prometheusEndpoint := "http://prometheus:9090"
|
||||
if prometheusURL := os.Getenv("PROMETHEUS_ENDPOINT"); prometheusURL != "" {
|
||||
prometheusEndpoint = prometheusURL
|
||||
}
|
||||
prometheusClient := metrics.NewPrometheusAPIClient(prometheusEndpoint)
|
||||
metricsCollector := metrics.NewCollector(prometheusClient)
|
||||
|
||||
// Create policy engine
|
||||
policyEngine := scaling.NewPolicyEngine(metricsCollector)
|
||||
|
||||
// Create instance manager
|
||||
instanceManager := scaling.NewInstanceManager(proxmoxClient)
|
||||
|
||||
// Evaluate scaling policies
|
||||
decision, err := policyEngine.Evaluate(ctx, vmss.Spec, vmss.Status, vmss.Status.Instances)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to evaluate scaling policies")
|
||||
return ctrl.Result{RequeueAfter: 30 * time.Second}, err
|
||||
}
|
||||
|
||||
// Check cooldown period
|
||||
if vmss.Status.LastScaleTime != nil {
|
||||
cooldownPeriod := time.Duration(vmss.Spec.CooldownPeriod) * time.Second
|
||||
timeSinceLastScale := time.Since(vmss.Status.LastScaleTime.Time)
|
||||
if timeSinceLastScale < cooldownPeriod && decision.Action != "NO_ACTION" {
|
||||
logger.Info("Cooldown period active, skipping scaling", "timeSinceLastScale", timeSinceLastScale)
|
||||
return ctrl.Result{RequeueAfter: cooldownPeriod - timeSinceLastScale}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Execute scaling decision
|
||||
if decision.Action != "NO_ACTION" {
|
||||
logger.Info("Scaling decision", "action", decision.Action, "newReplicas", decision.NewReplicas, "reason", decision.Reason)
|
||||
|
||||
// Scale instances
|
||||
newInstances, err := instanceManager.ScaleTo(
|
||||
ctx,
|
||||
vmss.Spec.Template,
|
||||
vmss.Status.Instances,
|
||||
decision.NewReplicas,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to scale instances")
|
||||
return ctrl.Result{RequeueAfter: 30 * time.Second}, err
|
||||
}
|
||||
|
||||
// Update status
|
||||
now := metav1.Now()
|
||||
vmss.Status.CurrentReplicas = len(newInstances)
|
||||
vmss.Status.DesiredReplicas = decision.NewReplicas
|
||||
vmss.Status.Instances = newInstances
|
||||
vmss.Status.LastScaleTime = &now
|
||||
|
||||
// Add scaling event
|
||||
event := proxmoxv1alpha1.ScalingEvent{
|
||||
Type: decision.Action,
|
||||
OldReplicas: len(vmss.Status.Instances),
|
||||
NewReplicas: decision.NewReplicas,
|
||||
Reason: decision.Reason,
|
||||
Timestamp: now,
|
||||
}
|
||||
vmss.Status.ScalingEvents = append(vmss.Status.ScalingEvents, event)
|
||||
|
||||
// Keep only last 10 events
|
||||
if len(vmss.Status.ScalingEvents) > 10 {
|
||||
vmss.Status.ScalingEvents = vmss.Status.ScalingEvents[len(vmss.Status.ScalingEvents)-10:]
|
||||
}
|
||||
|
||||
// Count ready replicas
|
||||
readyCount := 0
|
||||
for _, instance := range newInstances {
|
||||
if instanceManager.HealthCheck(ctx, instance) {
|
||||
readyCount++
|
||||
}
|
||||
}
|
||||
vmss.Status.ReadyReplicas = readyCount
|
||||
|
||||
if err := r.Status().Update(ctx, &vmss); err != nil {
|
||||
logger.Error(err, "failed to update status")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Requeue for periodic evaluation
|
||||
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
|
||||
}
|
||||
|
||||
// getCredentials retrieves credentials from the provider config secret
|
||||
func (r *ProxmoxVMScaleSetReconciler) getCredentials(ctx context.Context, config *proxmoxv1alpha1.ProviderConfig) (*credentials, error) {
|
||||
if config.Spec.Credentials.SecretRef == nil {
|
||||
return nil, fmt.Errorf("no secret reference in provider config")
|
||||
}
|
||||
|
||||
secretRef := config.Spec.Credentials.SecretRef
|
||||
|
||||
// Get secret from Kubernetes
|
||||
secret := &corev1.Secret{}
|
||||
secretKey := client.ObjectKey{
|
||||
Namespace: secretRef.Namespace,
|
||||
Name: secretRef.Name,
|
||||
}
|
||||
|
||||
if err := r.Get(ctx, secretKey, secret); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot get secret")
|
||||
}
|
||||
|
||||
// Parse credentials from secret
|
||||
var username, password string
|
||||
|
||||
// Try username/password format first
|
||||
if userData, ok := secret.Data["username"]; ok {
|
||||
username = string(userData)
|
||||
}
|
||||
if passData, ok := secret.Data["password"]; ok {
|
||||
password = string(passData)
|
||||
}
|
||||
|
||||
// Try token format (for Proxmox API tokens)
|
||||
if tokenData, ok := secret.Data["token"]; ok {
|
||||
if userData, ok := secret.Data["tokenid"]; ok {
|
||||
username = string(userData)
|
||||
}
|
||||
password = string(tokenData)
|
||||
}
|
||||
|
||||
if username == "" || password == "" {
|
||||
return nil, fmt.Errorf("username/password or token missing in secret")
|
||||
}
|
||||
|
||||
return &credentials{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type credentials struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *ProxmoxVMScaleSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&proxmoxv1alpha1.ProxmoxVMScaleSet{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user