Files
Sankofa/crossplane-provider-proxmox/pkg/controller/virtualmachine/controller.go
defiQUG 6f28146ac3 Initial Phoenix Sankofa Cloud setup
- Complete project structure with Next.js frontend
- GraphQL API backend with Apollo Server
- Portal application with NextAuth
- Crossplane Proxmox provider
- GitOps configurations
- CI/CD pipelines
- Testing infrastructure (Vitest, Jest, Go tests)
- Error handling and monitoring
- Security hardening
- UI component library
- Documentation
2025-11-28 12:54:33 -08:00

184 lines
5.6 KiB
Go

package virtualmachine
import (
"context"
"fmt"
"time"
"github.com/pkg/errors"
"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/yourorg/crossplane-provider-proxmox/apis/v1alpha1"
"github.com/yourorg/crossplane-provider-proxmox/pkg/proxmox"
)
// ProxmoxVMReconciler reconciles a ProxmoxVM object
type ProxmoxVMReconciler struct {
client.Client
Scheme *runtime.Scheme
}
//+kubebuilder:rbac:groups=proxmox.yourorg.io,resources=proxmoxvms,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=proxmox.yourorg.io,resources=proxmoxvms/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=proxmox.yourorg.io,resources=proxmoxvms/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop
func (r *ProxmoxVMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
var vm proxmoxv1alpha1.ProxmoxVM
if err := r.Get(ctx, req.NamespacedName, &vm); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Get ProviderConfig
var providerConfig proxmoxv1alpha1.ProviderConfig
providerConfigName := vm.Spec.ProviderConfigReference.Name
if err := r.Get(ctx, client.ObjectKey{Name: providerConfigName}, &providerConfig); err != nil {
return ctrl.Result{}, errors.Wrapf(err, "cannot get provider config %s", providerConfigName)
}
// Get credentials from secret
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
site, err := r.findSite(&providerConfig, vm.Spec.ForProvider.Site)
if err != nil {
logger.Error(err, "cannot find site", "site", vm.Spec.ForProvider.Site)
return ctrl.Result{RequeueAfter: 30 * time.Second}, errors.Wrapf(err, "cannot find site %s", vm.Spec.ForProvider.Site)
}
// Create Proxmox client
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")
}
// Reconcile VM
if vm.Status.VMID == 0 {
// Create VM
logger.Info("Creating VM", "name", vm.Name, "node", vm.Spec.ForProvider.Node)
vmConfig := proxmox.VMConfig{
Name: vm.Spec.ForProvider.Name,
CPU: vm.Spec.ForProvider.CPU,
Memory: vm.Spec.ForProvider.Memory,
Disk: vm.Spec.ForProvider.Disk,
Storage: vm.Spec.ForProvider.Storage,
Network: vm.Spec.ForProvider.Network,
Image: vm.Spec.ForProvider.Image,
UserData: vm.Spec.ForProvider.UserData,
SSHKeys: vm.Spec.ForProvider.SSHKeys,
}
createdVM, err := proxmoxClient.CreateVM(vm.Spec.ForProvider.Node, vmConfig)
if err != nil {
return ctrl.Result{}, errors.Wrap(err, "cannot create VM")
}
vm.Status.VMID = createdVM.ID
vm.Status.State = createdVM.Status
vm.Status.IPAddress = createdVM.IPAddress
if err := r.Status().Update(ctx, &vm); err != nil {
return ctrl.Result{}, errors.Wrap(err, "cannot update VM status")
}
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
// Update VM if needed
currentVM, err := proxmoxClient.GetVM(vm.Spec.ForProvider.Node, vm.Status.VMID)
if err != nil {
return ctrl.Result{}, errors.Wrap(err, "cannot get VM")
}
// Update status
vm.Status.State = currentVM.Status
vm.Status.IPAddress = currentVM.IPAddress
if err := r.Status().Update(ctx, &vm); err != nil {
return ctrl.Result{}, errors.Wrap(err, "cannot update VM status")
}
// Check if VM needs to be updated
needsUpdate := false
if vm.Spec.ForProvider.CPU != 0 && currentVM.Config.CPU != vm.Spec.ForProvider.CPU {
needsUpdate = true
}
if vm.Spec.ForProvider.Memory != "" && currentVM.Config.Memory != vm.Spec.ForProvider.Memory {
needsUpdate = true
}
if needsUpdate {
logger.Info("Updating VM", "name", vm.Name, "vmId", vm.Status.VMID)
vmConfig := proxmox.VMConfig{
CPU: vm.Spec.ForProvider.CPU,
Memory: vm.Spec.ForProvider.Memory,
}
if err := proxmoxClient.UpdateVM(vm.Spec.ForProvider.Node, vm.Status.VMID, vmConfig); err != nil {
return ctrl.Result{}, errors.Wrap(err, "cannot update VM")
}
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
// SetupWithManager sets up the controller with the Manager
func (r *ProxmoxVMReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&proxmoxv1alpha1.ProxmoxVM{}).
Complete(r)
}
// Helper functions
type credentials struct {
Username string
Password string
}
func (r *ProxmoxVMReconciler) 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
// In a real implementation, you would:
// 1. Get the secret from Kubernetes
// 2. Parse the credentials (JSON, username/password, etc.)
// 3. Return the credentials
// This is a placeholder
return &credentials{
Username: "root@pam",
Password: "placeholder",
}, nil
}
func (r *ProxmoxVMReconciler) findSite(config *proxmoxv1alpha1.ProviderConfig, siteName string) (*proxmoxv1alpha1.ProxmoxSite, error) {
for _, site := range config.Spec.Sites {
if site.Name == siteName {
return &site, nil
}
}
return nil, fmt.Errorf("site %s not found", siteName)
}