- 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
184 lines
5.6 KiB
Go
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)
|
|
}
|
|
|