Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions api/v1alpha1/httpbootconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ const (
HTTPBootConfigStateError HTTPBootConfigState = "Error"
)

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state`
//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +genclient

// HTTPBootConfig is the Schema for the httpbootconfigs API
type HTTPBootConfig struct {
Expand Down
29 changes: 22 additions & 7 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ var (

const (
// core controllers
ipxeBootConfigController = "ipxebootconfig"
serverBootConfigController = "serverbootconfig"
httpBootConfigController = "httpbootconfig"
ipxeBootConfigController = "ipxebootconfig"
serverBootConfigControllerPxe = "serverbootconfigpxe"
httpBootConfigController = "httpbootconfig"
serverBootConfigControllerHttp = "serverbootconfighttp"
)

func init() {
Expand All @@ -69,10 +70,12 @@ func main() {
var ipxeServiceURL string
var ipxeServiceProtocol string
var ipxeServicePort int
var imageServerURL string

flag.IntVar(&ipxeServicePort, "ipxe-service-port", 5000, "IPXE Service port to listen on.")
flag.StringVar(&ipxeServiceProtocol, "ipxe-service-protocol", "http", "IPXE Service Protocol.")
flag.StringVar(&ipxeServiceURL, "ipxe-service-url", "", "IPXE Service URL.")
flag.StringVar(&imageServerURL, "image-server-url", "", "OS Image Server URL.")
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.StringVar(&ipxeServerAddr, "ipxe-server-address", ":8082", "The address the ipxe-server binds to.")
Expand All @@ -88,7 +91,8 @@ func main() {
controllers := switches.New(
// core controllers
ipxeBootConfigController,
serverBootConfigController,
serverBootConfigControllerPxe,
serverBootConfigControllerHttp,
httpBootConfigController,
)

Expand Down Expand Up @@ -176,13 +180,24 @@ func main() {
}
}

if controllers.Enabled(serverBootConfigController) {
if err = (&controller.ServerBootConfigurationReconciler{
if controllers.Enabled(serverBootConfigControllerPxe) {
if err = (&controller.ServerBootConfigurationPXEReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
IPXEServiceURL: ipxeServiceURL,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServerBootConfig")
setupLog.Error(err, "unable to create controller", "controller", "ServerBootConfigPxe")
os.Exit(1)
}
}

if controllers.Enabled(serverBootConfigControllerHttp) {
if err = (&controller.ServerBootConfigurationHTTPReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
ImageServerURL: imageServerURL,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServerBootConfigHttp")
os.Exit(1)
}
}
Expand Down
17 changes: 17 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ rules:
- get
- list
- watch
- apiGroups:
- boot.ironcore.dev
resources:
- httpbootconfig
verbs:
- create
- delete
- get
- list
- patch
- watch
- apiGroups:
- boot.ironcore.dev
resources:
- httpbootconfig/status
verbs:
- get
- apiGroups:
- boot.ironcore.dev
resources:
Expand Down
180 changes: 180 additions & 0 deletions internal/controller/serverbootconfiguration_http_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package controller

import (
"context"
"fmt"
"strings"

"github.com/go-logr/logr"
"github.com/ironcore-dev/ipxe-operator/api/v1alpha1"
bootv1alpha1 "github.com/ironcore-dev/ipxe-operator/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

metalv1alpha1 "github.com/afritzler/metal-operator/api/v1alpha1"
)

type ServerBootConfigurationHTTPReconciler struct {
client.Client
Scheme *runtime.Scheme
ImageServerURL string
}

//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=serverbootconfigurations,verbs=get;list;watch
//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=serverbootconfigurations/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=serverbootconfigurations/finalizers,verbs=update
//+kubebuilder:rbac:groups=boot.ironcore.dev,resources=httpbootconfig,verbs=get;list;watch;create;delete;patch
//+kubebuilder:rbac:groups=boot.ironcore.dev,resources=httpbootconfig/status,verbs=get
//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=servers,verbs=get;list;watch

func (r *ServerBootConfigurationHTTPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := ctrl.LoggerFrom(ctx)
bootConfig := &metalv1alpha1.ServerBootConfiguration{}
if err := r.Get(ctx, req.NamespacedName, bootConfig); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

return r.reconcileExists(ctx, log, bootConfig)
}

func (r *ServerBootConfigurationHTTPReconciler) reconcileExists(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
if !config.DeletionTimestamp.IsZero() {
return r.delete(ctx, log, config)
}
return r.reconcile(ctx, log, config)
}

func (r *ServerBootConfigurationHTTPReconciler) delete(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
// TODO
return ctrl.Result{}, nil
}

func (r *ServerBootConfigurationHTTPReconciler) reconcile(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
log.V(1).Info("Reconciling ServerBootConfiguration for HTTPBoot")

systemUUID, err := r.getSystemUUIDFromServer(ctx, config)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get system UUID from Server: %w", err)
}
log.V(1).Info("Got system UUID from Server", "systemUUID", systemUUID)

systemIPs, err := r.getSystemIPFromServer(ctx, config)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get system IPs from Server: %w", err)
}
log.V(1).Info("Got system IPs from Server", "systemIPs", systemIPs)

ukiURL, err := r.constructUKIURL(config.Spec.Image)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get UKI URL from Config: %w", err)
}
log.V(1).Info("Extracted UKI URL for boot")

httpBootConfig := &bootv1alpha1.HTTPBootConfig{
TypeMeta: metav1.TypeMeta{
APIVersion: "boot.ironcore.dev/v1alpha1",
Kind: "HTTPBootConfig",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: config.Namespace,
Name: config.Name,
},
Spec: bootv1alpha1.HTTPBootConfigSpec{
SystemUUID: systemUUID,
SystemIPs: systemIPs,
UKIURL: ukiURL,
IgnitionSecretRef: &corev1.LocalObjectReference{Name: config.Spec.IgnitionSecretRef.Name},
},
}

if err := controllerutil.SetControllerReference(config, httpBootConfig, r.Scheme); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to set controller reference: %w", err)
}
log.V(1).Info("Set controller reference")

if err := r.Patch(ctx, httpBootConfig, client.Apply, client.FieldOwner("server-boot-controller"), client.ForceOwnership); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to apply HTTPBoot config: %w", err)
}
log.V(1).Info("Applied HTTPBoot config for server boot configuration")

if err := r.Get(ctx, client.ObjectKey{Namespace: config.Namespace, Name: config.Name}, httpBootConfig); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get HTTPBoot config: %w", err)
}

if err := r.patchConfigStateFromHTTPState(ctx, httpBootConfig, config); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to patch server boot config state to %s: %w", httpBootConfig.Status.State, err)
}
log.V(1).Info("Patched server boot config state")

log.V(1).Info("Reconciled ServerBootConfiguration")

return ctrl.Result{}, nil
}

func (r *ServerBootConfigurationHTTPReconciler) patchConfigStateFromHTTPState(ctx context.Context, httpBootConfig *v1alpha1.HTTPBootConfig, config *metalv1alpha1.ServerBootConfiguration) error {
if httpBootConfig.Status.State == bootv1alpha1.HTTPBootConfigStateReady {
return r.patchState(ctx, config, metalv1alpha1.ServerBootConfigurationStateReady)
}

if httpBootConfig.Status.State == bootv1alpha1.HTTPBootConfigStateError {
return r.patchState(ctx, config, metalv1alpha1.ServerBootConfigurationStateError)
}
return nil
}

func (r *ServerBootConfigurationHTTPReconciler) patchState(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration, state metalv1alpha1.ServerBootConfigurationState) error {
configBase := config.DeepCopy()
config.Status.State = state
if err := r.Status().Patch(ctx, config, client.MergeFrom(configBase)); err != nil {
return err
}
return nil
}

// getSystemUUIDFromServer fetches the UUID from the referenced Server object.
func (r *ServerBootConfigurationHTTPReconciler) getSystemUUIDFromServer(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) (string, error) {
server := &metalv1alpha1.Server{}
if err := r.Get(ctx, client.ObjectKey{Name: config.Spec.ServerRef.Name}, server); err != nil {
return "", fmt.Errorf("failed to get Server: %w", err)
}
return server.Spec.UUID, nil
}

// getSystemIPFromServer fetches the IPs from the network interfaces of the referenced Server object.
func (r *ServerBootConfigurationHTTPReconciler) getSystemIPFromServer(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) ([]string, error) {
server := &metalv1alpha1.Server{}
if err := r.Get(ctx, client.ObjectKey{Name: config.Spec.ServerRef.Name}, server); err != nil {
return nil, fmt.Errorf("failed to get Server: %w", err)
}

var systemIPs []string
for _, nic := range server.Status.NetworkInterfaces {
systemIPs = append(systemIPs, nic.IP.String())
systemIPs = append(systemIPs, nic.MACAddress)
}
return systemIPs, nil
}

func (r *ServerBootConfigurationHTTPReconciler) constructUKIURL(image string) (string, error) {
sanitizedImage := strings.Replace(image, "/", "-", -1)
sanitizedImage = strings.Replace(sanitizedImage, ":", "-", -1)
sanitizedImage = strings.Replace(sanitizedImage, "https://", "", -1)
sanitizedImage = strings.Replace(sanitizedImage, "http://", "", -1)

filename := fmt.Sprintf("%s-uki.efi", sanitizedImage)

ukiURL := fmt.Sprintf("%s/%s", r.ImageServerURL, filename)
return ukiURL, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *ServerBootConfigurationHTTPReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&metalv1alpha1.ServerBootConfiguration{}).
Owns(&v1alpha1.HTTPBootConfig{}).
Complete(r)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

type ServerBootConfigurationReconciler struct {
type ServerBootConfigurationPXEReconciler struct {
client.Client
Scheme *runtime.Scheme
IPXEServiceURL string
Expand All @@ -49,7 +49,7 @@ type ServerBootConfigurationReconciler struct {

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *ServerBootConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
func (r *ServerBootConfigurationPXEReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := ctrl.LoggerFrom(ctx)
bootConfig := &metalv1alpha1.ServerBootConfiguration{}
if err := r.Get(ctx, req.NamespacedName, bootConfig); err != nil {
Expand All @@ -59,18 +59,18 @@ func (r *ServerBootConfigurationReconciler) Reconcile(ctx context.Context, req c
return r.reconcileExists(ctx, log, bootConfig)
}

func (r *ServerBootConfigurationReconciler) reconcileExists(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
func (r *ServerBootConfigurationPXEReconciler) reconcileExists(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
if !config.DeletionTimestamp.IsZero() {
return r.delete(ctx, log, config)
}
return r.reconcile(ctx, log, config)
}

func (r *ServerBootConfigurationReconciler) delete(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
func (r *ServerBootConfigurationPXEReconciler) delete(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
return ctrl.Result{}, nil
}

func (r *ServerBootConfigurationReconciler) reconcile(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
func (r *ServerBootConfigurationPXEReconciler) reconcile(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) {
log.V(1).Info("Reconciling ServerBootConfiguration")

systemUUID, err := r.getSystemUUIDFromBootConfig(ctx, config)
Expand Down Expand Up @@ -133,7 +133,7 @@ func (r *ServerBootConfigurationReconciler) reconcile(ctx context.Context, log l
return ctrl.Result{}, nil
}

func (r *ServerBootConfigurationReconciler) patchConfigStateFromIPXEState(ctx context.Context, ipxeConfig *v1alpha1.IPXEBootConfig, config *metalv1alpha1.ServerBootConfiguration) error {
func (r *ServerBootConfigurationPXEReconciler) patchConfigStateFromIPXEState(ctx context.Context, ipxeConfig *v1alpha1.IPXEBootConfig, config *metalv1alpha1.ServerBootConfiguration) error {
if ipxeConfig.Status.State == v1alpha1.IPXEBootConfigStateReady {
return r.patchState(ctx, config, metalv1alpha1.ServerBootConfigurationStateReady)
}
Expand All @@ -144,7 +144,7 @@ func (r *ServerBootConfigurationReconciler) patchConfigStateFromIPXEState(ctx co
return nil
}

func (r *ServerBootConfigurationReconciler) patchState(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration, state metalv1alpha1.ServerBootConfigurationState) error {
func (r *ServerBootConfigurationPXEReconciler) patchState(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration, state metalv1alpha1.ServerBootConfigurationState) error {
configBase := config.DeepCopy()
config.Status.State = state
if err := r.Status().Patch(ctx, config, client.MergeFrom(configBase)); err != nil {
Expand All @@ -153,7 +153,7 @@ func (r *ServerBootConfigurationReconciler) patchState(ctx context.Context, conf
return nil
}

func (r *ServerBootConfigurationReconciler) getSystemUUIDFromBootConfig(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) (string, error) {
func (r *ServerBootConfigurationPXEReconciler) getSystemUUIDFromBootConfig(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) (string, error) {
server := &metalv1alpha1.Server{}
if err := r.Get(ctx, client.ObjectKey{Name: config.Spec.ServerRef.Name}, server); err != nil {
return "", fmt.Errorf("failed to get Server: %w", err)
Expand All @@ -162,7 +162,7 @@ func (r *ServerBootConfigurationReconciler) getSystemUUIDFromBootConfig(ctx cont
return server.Spec.UUID, nil
}

func (r *ServerBootConfigurationReconciler) getSystemIPFromBootConfig(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) ([]string, error) {
func (r *ServerBootConfigurationPXEReconciler) getSystemIPFromBootConfig(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) ([]string, error) {
server := &metalv1alpha1.Server{}
if err := r.Get(ctx, client.ObjectKey{Name: config.Spec.ServerRef.Name}, server); err != nil {
return nil, fmt.Errorf("failed to get Server: %w", err)
Expand All @@ -171,13 +171,12 @@ func (r *ServerBootConfigurationReconciler) getSystemIPFromBootConfig(ctx contex
systemIPs := []string{}
for _, nic := range server.Status.NetworkInterfaces {
systemIPs = append(systemIPs, nic.IP.String())
return systemIPs, nil
}

return nil, nil
return systemIPs, nil
}

func (r *ServerBootConfigurationReconciler) getImageDetailsFromConfig(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) (string, string, string, error) {
func (r *ServerBootConfigurationPXEReconciler) getImageDetailsFromConfig(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) (string, string, string, error) {
imageDetails := strings.Split(config.Spec.Image, ":")
if len(imageDetails) != 2 {
return "", "", "", fmt.Errorf("invalid image format")
Expand All @@ -192,7 +191,7 @@ func (r *ServerBootConfigurationReconciler) getImageDetailsFromConfig(ctx contex
}

// SetupWithManager sets up the controller with the Manager.
func (r *ServerBootConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error {
func (r *ServerBootConfigurationPXEReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&metalv1alpha1.ServerBootConfiguration{}).
Owns(&v1alpha1.IPXEBootConfig{}).
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func SetupTest() *corev1.Namespace {
})
Expect(err).ToNot(HaveOccurred())

Expect((&ServerBootConfigurationReconciler{
Expect((&ServerBootConfigurationPXEReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
IPXEServiceURL: "http://localhost:5000",
Expand Down