From 7c63ba28b5833169d97682e81a1dc929a105c4ce Mon Sep 17 00:00:00 2001 From: Hardik Dodiya Date: Mon, 3 Jun 2024 12:24:32 +0000 Subject: [PATCH 1/2] Support configuring ipxe-scripts --- api/v1alpha1/ipxebootconfig_types.go | 6 ++- api/v1alpha1/zz_generated.deepcopy.go | 5 ++ server/bootserver.go | 74 ++++++++++++++++----------- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/api/v1alpha1/ipxebootconfig_types.go b/api/v1alpha1/ipxebootconfig_types.go index f8f153d1..ed843c1b 100644 --- a/api/v1alpha1/ipxebootconfig_types.go +++ b/api/v1alpha1/ipxebootconfig_types.go @@ -22,8 +22,9 @@ type IPXEBootConfigSpec struct { InitrdURL string `json:"initrdURL,omitempty"` SquashfsURL string `json:"squashfsURL,omitempty"` // TODO: remove later - IPXEServerURL string `json:"ipxeServerURL,omitempty"` - IgnitionSecretRef *corev1.LocalObjectReference `json:"ignitionSecretRef,omitempty"` + IPXEServerURL string `json:"ipxeServerURL,omitempty"` + IgnitionSecretRef *corev1.LocalObjectReference `json:"ignitionSecretRef,omitempty"` + IPXEScriptSecretRef *corev1.LocalObjectReference `json:"ipxeScriptSecretRef,omitempty"` } type IPXEBootConfigState string @@ -35,6 +36,7 @@ const ( ) const DefaultIgnitionKey = "ignition" +const DefaultIPXEScriptKey = "ipxe-script" // IPXEBootConfigStatus defines the observed state of IPXEBootConfig type IPXEBootConfigStatus struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 23c34053..b6d33221 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -173,6 +173,11 @@ func (in *IPXEBootConfigSpec) DeepCopyInto(out *IPXEBootConfigSpec) { *out = new(v1.LocalObjectReference) **out = **in } + if in.IPXEScriptSecretRef != nil { + in, out := &in.IPXEScriptSecretRef, &out.IPXEScriptSecretRef + *out = new(v1.LocalObjectReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPXEBootConfigSpec. diff --git a/server/bootserver.go b/server/bootserver.go index 0c2b2261..b07a8cc5 100644 --- a/server/bootserver.go +++ b/server/bootserver.go @@ -13,11 +13,13 @@ import ( "text/template" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" butaneconfig "github.com/coreos/butane/config" butanecommon "github.com/coreos/butane/config/common" "github.com/go-logr/logr" + "github.com/ironcore-dev/ipxe-operator/api/v1alpha1" bootv1alpha1 "github.com/ironcore-dev/ipxe-operator/api/v1alpha1" ) @@ -74,10 +76,7 @@ func handleIPXE(w http.ResponseWriter, r *http.Request, k8sClient client.Client, return } - clientIPs := []string{} - clientIPs = append(clientIPs, clientIP) - - // Attempt to extract IPs from X-Forwarded-For if present + clientIPs := []string{clientIP} if xff := r.Header.Get("X-Forwarded-For"); xff != "" { for _, ip := range strings.Split(xff, ",") { trimmedIP := strings.TrimSpace(ip) @@ -100,40 +99,40 @@ func handleIPXE(w http.ResponseWriter, r *http.Request, k8sClient client.Client, } } - data := defaultIpxeTemplateData if len(ipxeConfigs.Items) == 0 { log.Info("No IPXEBootConfig found for client IP, delivering default script", "clientIP", clientIP) + serveDefaultIPXETemplate(w, log, ipxeServiceURL, defaultIpxeTemplateData) } else { config := ipxeConfigs.Items[0] - data = IPXETemplateData{ + if config.Spec.IPXEScriptSecretRef != nil { + secret := &corev1.Secret{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: config.Spec.IPXEScriptSecretRef.Name, Namespace: config.Namespace}, secret) + if err != nil { + log.Error(err, "Failed to fetch IPXE script from secret", "SecretName", config.Spec.IPXEScriptSecretRef.Name) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + ipxeScript, exists := secret.Data[v1alpha1.DefaultIPXEScriptKey] + if !exists { + log.Info("IPXE script not found in the secret", "ExpectedKey", v1alpha1.DefaultIPXEScriptKey) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + if _, err := w.Write(ipxeScript); err != nil { + log.Info("Failed to write custom IPXE script", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + return + } + + serveDefaultIPXETemplate(w, log, ipxeServiceURL, IPXETemplateData{ KernelURL: config.Spec.KernelURL, InitrdURL: config.Spec.InitrdURL, SquashfsURL: config.Spec.SquashfsURL, IPXEServerURL: ipxeServiceURL, - } - } - - tmplPath := filepath.Join("templates", "ipxe-script.tpl") - tmpl, err := template.ParseFiles(tmplPath) - if err != nil { - log.Info("Failed to parse iPXE script template", "error", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - - if err := tmpl.Execute(w, data); err != nil { - log.Info("Failed to execute template", "error", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - - log.Info("Successfully generated iPXE script", "clientIP", clientIP) - - _, err = w.Write(nil) - if err != nil { - log.Info("Failed to write the ipxe http response", "error", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return + }) } } @@ -187,6 +186,21 @@ func handleIgnitionIPXEBoot(w http.ResponseWriter, r *http.Request, k8sClient cl } } +func serveDefaultIPXETemplate(w http.ResponseWriter, log logr.Logger, ipxeServiceURL string, data IPXETemplateData) { + tmplPath := filepath.Join("templates", "ipxe-script.tpl") + tmpl, err := template.ParseFiles(tmplPath) + if err != nil { + log.Info("Failed to parse iPXE script template", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + if err := tmpl.Execute(w, data); err != nil { + log.Info("Failed to execute template", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } +} + 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() From 5fb6129027c460ef00a605485c9e9639488a3e37 Mon Sep 17 00:00:00 2001 From: Hardik Dodiya Date: Mon, 3 Jun 2024 14:55:19 +0200 Subject: [PATCH 2/2] Generate IPXEBootConfig CRD --- .../boot.ironcore.dev_ipxebootconfigs.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/config/crd/bases/boot.ironcore.dev_ipxebootconfigs.yaml b/config/crd/bases/boot.ironcore.dev_ipxebootconfigs.yaml index 621fde92..e6e931ef 100644 --- a/config/crd/bases/boot.ironcore.dev_ipxebootconfigs.yaml +++ b/config/crd/bases/boot.ironcore.dev_ipxebootconfigs.yaml @@ -69,6 +69,24 @@ spec: type: string initrdURL: type: string + ipxeScriptSecretRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + TODO: Add other useful fields. apiVersion, kind, uid? + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + type: string + type: object + x-kubernetes-map-type: atomic ipxeServerURL: description: 'TODO: remove later' type: string