diff --git a/cmd/main.go b/cmd/main.go index dcc48f13..51823579 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -29,7 +29,7 @@ import ( bootv1alpha1 "github.com/ironcore-dev/ipxe-operator/api/v1alpha1" "github.com/ironcore-dev/ipxe-operator/internal/controller" - ipxeserver "github.com/ironcore-dev/ipxe-operator/server" + bootserver "github.com/ironcore-dev/ipxe-operator/server" //+kubebuilder:scaffold:imports ) @@ -217,16 +217,21 @@ func main() { os.Exit(1) } + if err := IndexHTTPBootConfigBySystemUUID(ctx, mgr); err != nil { + setupLog.Error(err, "unable to set up indexer for HTTPBootConfig SystemUUID") + os.Exit(1) + } + if err := IndexIPXEBootConfigBySystemIP(ctx, mgr); err != nil { setupLog.Error(err, "unable to set up indexer for IPXEBootConfig SystemIP") os.Exit(1) } - setupLog.Info("starting ipxe-server") - go ipxeserver.RunIPXEServer(ipxeServerAddr, ipxeServiceURL, mgr.GetClient(), serverLog.WithName("ipxeserver"), *defaultIpxeTemplateData) + setupLog.Info("starting boot-server") + go bootserver.RunBootServer(ipxeServerAddr, ipxeServiceURL, mgr.GetClient(), serverLog.WithName("bootserver"), *defaultIpxeTemplateData) setupLog.Info("starting image-proxy-server") - go ipxeserver.RunImageProxyServer(imageProxyServerAddr, mgr.GetClient(), serverLog.WithName("imageproxyserver")) + go bootserver.RunImageProxyServer(imageProxyServerAddr, mgr.GetClient(), serverLog.WithName("imageproxyserver")) setupLog.Info("starting manager") if err := mgr.Start(ctx); err != nil { @@ -258,8 +263,20 @@ func IndexIPXEBootConfigBySystemIP(ctx context.Context, mgr ctrl.Manager) error ) } -func NewDefaultIPXETemplateData() *ipxeserver.IPXETemplateData { - var cfg ipxeserver.IPXETemplateData +func IndexHTTPBootConfigBySystemUUID(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField( + ctx, + &bootv1alpha1.HTTPBootConfig{}, + systemUUIDIndexKey, + func(Obj client.Object) []string { + HTTPBootConfig := Obj.(*bootv1alpha1.HTTPBootConfig) + return []string{HTTPBootConfig.Spec.SystemUUID} + }, + ) +} + +func NewDefaultIPXETemplateData() *bootserver.IPXETemplateData { + var cfg bootserver.IPXETemplateData flag.StringVar(&cfg.KernelURL, "default-kernel-url", "", "Default URL for the kernel") flag.StringVar(&cfg.InitrdURL, "default-initrd-url", "", "Default URL for the initrd") flag.StringVar(&cfg.SquashfsURL, "default-squashfs-url", "", "Default URL for the squashfs") diff --git a/server/ipxeserver.go b/server/bootserver.go similarity index 64% rename from server/ipxeserver.go rename to server/bootserver.go index c126a5ce..d947a95a 100644 --- a/server/ipxeserver.go +++ b/server/bootserver.go @@ -28,19 +28,36 @@ type IPXETemplateData struct { IPXEServerURL string } -func RunIPXEServer(ipxeServerAddr string, ipxeServiceURL string, k8sClient client.Client, log logr.Logger, defaultIpxeTemplateData IPXETemplateData) { +func RunBootServer(ipxeServerAddr string, ipxeServiceURL string, k8sClient client.Client, log logr.Logger, defaultIpxeTemplateData IPXETemplateData) { http.HandleFunc("/ipxe", func(w http.ResponseWriter, r *http.Request) { handleIPXE(w, r, k8sClient, log, ipxeServiceURL, defaultIpxeTemplateData) }) http.HandleFunc("/ignition/", func(w http.ResponseWriter, r *http.Request) { - handleIgnition(w, r, k8sClient, log) + uuid := path.Base(r.URL.Path) + if uuid == "" { + http.Error(w, "Bad Request: UUID is required", http.StatusBadRequest) + return + } + + ipxeBootConfigList := &bootv1alpha1.IPXEBootConfigList{} + err := k8sClient.List(r.Context(), ipxeBootConfigList, client.MatchingFields{"spec.systemUUID": uuid}) + if client.IgnoreNotFound(err) != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + if len(ipxeBootConfigList.Items) == 0 { + log.Info("No IPXEBootConfig found with given UUID") + handleIgnitionHTTPBoot(w, r, k8sClient, log, uuid) + } else { + handleIgnitionIPXEBoot(w, r, k8sClient, log, uuid) + } }) - // TODO: Use Logger - log.Info("Starting ipxe server", "address", ipxeServerAddr) + log.Info("Starting boot server", "address", ipxeServerAddr) if err := http.ListenAndServe(ipxeServerAddr, nil); err != nil { - log.Error(err, "failed to start ipxe server") + log.Error(err, "failed to start boot server") panic(err) } } @@ -100,17 +117,10 @@ func handleIPXE(w http.ResponseWriter, r *http.Request, k8sClient client.Client, } } -func handleIgnition(w http.ResponseWriter, r *http.Request, k8sClient client.Client, log logr.Logger) { +func handleIgnitionIPXEBoot(w http.ResponseWriter, r *http.Request, k8sClient client.Client, log logr.Logger, uuid string) { log.Info("Processing Ignition request", "method", r.Method, "path", r.URL.Path, "clientIP", r.RemoteAddr) ctx := r.Context() - uuid := path.Base(r.URL.Path) - if uuid == "" { - http.Error(w, "Bad Request", http.StatusBadRequest) - log.Info("Error: UUID is required") - return - } - ipxeBootConfigList := &bootv1alpha1.IPXEBootConfigList{} if err := k8sClient.List(ctx, ipxeBootConfigList, client.MatchingFields{"spec.systemUUID": uuid}); err != nil { http.Error(w, "Resource Not Found", http.StatusNotFound) @@ -120,7 +130,7 @@ func handleIgnition(w http.ResponseWriter, r *http.Request, k8sClient client.Cli if len(ipxeBootConfigList.Items) == 0 { http.Error(w, "Resource Not Found", http.StatusNotFound) - log.Info("Error: No IPXEBootConfig found with given UUID") + log.Info("No IPXEBootConfig found with given UUID") return } @@ -157,6 +167,56 @@ func handleIgnition(w http.ResponseWriter, r *http.Request, k8sClient client.Cli } } +func handleIgnitionHTTPBoot(w http.ResponseWriter, r *http.Request, k8sClient client.Client, log logr.Logger, uuid string) { + log.Info("Processing Ignition request", "method", r.Method, "path", r.URL.Path, "clientIP", r.RemoteAddr) + ctx := r.Context() + + HTTPBootConfigList := &bootv1alpha1.HTTPBootConfigList{} + if err := k8sClient.List(ctx, HTTPBootConfigList, client.MatchingFields{"spec.systemUUID": uuid}); err != nil { + http.Error(w, "Resource Not Found", http.StatusNotFound) + log.Info("Failed to find HTTPBootConfigList", "error", err.Error()) + return + } + + if len(HTTPBootConfigList.Items) == 0 { + http.Error(w, "Resource Not Found", http.StatusNotFound) + log.Info("No HTTPBootConfig found with given UUID") + return + } + + // TODO: Assuming UUID is unique. + HTTPBootConfig := HTTPBootConfigList.Items[0] + + ignitionSecret := &corev1.Secret{} + if err := k8sClient.Get(ctx, client.ObjectKey{Name: HTTPBootConfig.Spec.IgnitionSecretRef.Name, Namespace: HTTPBootConfig.Namespace}, ignitionSecret); err != nil { + http.Error(w, "Resource Not Found", http.StatusNotFound) + log.Info("Error: Failed to get Ignition secret", "error", err.Error()) + return + } + + ignitionData, ok := ignitionSecret.Data[bootv1alpha1.DefaultIgnitionKey] + if !ok { + http.Error(w, "Resource Not Found", http.StatusNotFound) + log.Info("Error: Ignition data not found in secret") + return + } + + ignitionJSONData, err := renderIgnition(ignitionData) + if err != nil { + log.Info("Failed to render the ignition data to json", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + _, err = w.Write(ignitionJSONData) + if err != nil { + log.Info("Failed to write the ignition http response", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } +} + func renderIgnition(yamlData []byte) ([]byte, error) { translateOptions := butanecommon.TranslateBytesOptions{ Raw: true,