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) }