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
12 changes: 7 additions & 5 deletions api/v1alpha1/httpbootconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ type HTTPBootConfigSpec struct {

// HTTPBootConfigStatus defines the observed state of HTTPBootConfig
type HTTPBootConfigStatus struct {
State HTTPConfigState `json:"state,omitempty"`
State HTTPBootConfigState `json:"state,omitempty"`
}

type HTTPConfigState string
type HTTPBootConfigState string

const (
HTTPConfigStateReady HTTPConfigState = "Ready"
HTTPConfigStatePending HTTPConfigState = "Pending"
HTTPConfigStateError HTTPConfigState = "Error"
HTTPBootConfigStateReady HTTPBootConfigState = "Ready"
HTTPBootConfigStatePending HTTPBootConfigState = "Pending"
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`

// HTTPBootConfig is the Schema for the httpbootconfigs API
type HTTPBootConfig struct {
Expand Down
20 changes: 13 additions & 7 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
// core controllers
ipxeBootConfigController = "ipxebootconfig"
serverBootConfigController = "serverbootconfig"
httpBootConfigController = "httpbootconfig"
)

func init() {
Expand Down Expand Up @@ -92,6 +93,7 @@ func main() {
// core controllers
ipxeBootConfigController,
serverBootConfigController,
httpBootConfigController,
)

flag.Var(controllers, "controllers",
Expand Down Expand Up @@ -184,17 +186,21 @@ func main() {
Scheme: mgr.GetScheme(),
IPXEServiceURL: ipxeServiceURL,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServerBootConfiguration")
setupLog.Error(err, "unable to create controller", "controller", "ServerBootConfig")
os.Exit(1)
}
}
if err = (&controller.HTTPBootConfigReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "HTTPBootConfig")
os.Exit(1)

if controllers.Enabled(httpBootConfigController) {
if err = (&controller.HTTPBootConfigReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "HTTPBootConfig")
os.Exit(1)
}
}

//+kubebuilder:scaffold:builder

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
Expand Down
9 changes: 8 additions & 1 deletion config/crd/bases/boot.ironcore.dev_httpbootconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ spec:
singular: httpbootconfig
scope: Namespaced
versions:
- name: v1alpha1
- additionalPrinterColumns:
- jsonPath: .status.state
name: State
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: HTTPBootConfig is the Schema for the httpbootconfigs API
Expand Down
123 changes: 112 additions & 11 deletions internal/controller/httpbootconfig_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ package controller

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/go-logr/logr"
bootv1alpha1 "github.com/ironcore-dev/ipxe-operator/api/v1alpha1"
)

Expand All @@ -23,20 +29,69 @@ type HTTPBootConfigReconciler struct {
//+kubebuilder:rbac:groups=boot.ironcore.dev,resources=httpbootconfigs,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=boot.ironcore.dev,resources=httpbootconfigs/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=boot.ironcore.dev,resources=httpbootconfigs/finalizers,verbs=update
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the HTTPBootConfig object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.0/pkg/reconcile
func (r *HTTPBootConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
log := log.FromContext(ctx)

// TODO(user): your logic here
HTTPBootConfig := &bootv1alpha1.HTTPBootConfig{}
if err := r.Get(ctx, req.NamespacedName, HTTPBootConfig); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

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

func (r *HTTPBootConfigReconciler) reconcileExists(ctx context.Context, log logr.Logger, HTTPBootConfig *bootv1alpha1.HTTPBootConfig) (ctrl.Result, error) {
if !HTTPBootConfig.DeletionTimestamp.IsZero() {
return r.delete(ctx, log, HTTPBootConfig)
}

return r.reconcile(ctx, log, HTTPBootConfig)
}

func (r *HTTPBootConfigReconciler) reconcile(ctx context.Context, log logr.Logger, HTTPBootConfig *bootv1alpha1.HTTPBootConfig) (ctrl.Result, error) {
log.V(1).Info("Ensuring Ignition")
state, ignitionErr := r.ensureIgnition(ctx, log, HTTPBootConfig)
if ignitionErr != nil {
patchError := r.patchStatus(ctx, HTTPBootConfig, state)
if patchError != nil {
return ctrl.Result{}, fmt.Errorf("failed to patch status %w %w", ignitionErr, patchError)
}

log.V(1).Info("Failed to Ensure Ignition", "Error", ignitionErr)
return ctrl.Result{}, nil
}

patchErr := r.patchStatus(ctx, HTTPBootConfig, state)
if patchErr != nil {
return ctrl.Result{}, fmt.Errorf("failed to patch status %w", patchErr)
}

return ctrl.Result{}, nil
}

func (r *HTTPBootConfigReconciler) ensureIgnition(ctx context.Context, _ logr.Logger, HTTPBootConfig *bootv1alpha1.HTTPBootConfig) (bootv1alpha1.HTTPBootConfigState, error) {
// Verify if the IgnitionRef is set, and it has the intended data key.
if HTTPBootConfig.Spec.IgnitionSecretRef != nil {
IgnitionSecret := &corev1.Secret{}
if err := r.Get(ctx, client.ObjectKey{Name: HTTPBootConfig.Spec.IgnitionSecretRef.Name, Namespace: HTTPBootConfig.Namespace}, IgnitionSecret); err != nil {
return bootv1alpha1.HTTPBootConfigStateError, err
// TODO: Add some validation steps to ensure that the IgntionData is compliant, if necessary.
// Assume for now, that it's going to json format.
}
if IgnitionSecret.Data[bootv1alpha1.DefaultIgnitionKey] == nil {
return bootv1alpha1.HTTPBootConfigStateError, fmt.Errorf("ignition data is missing")
}
}

return bootv1alpha1.HTTPBootConfigStateReady, nil
}

func (r *HTTPBootConfigReconciler) delete(_ context.Context, log logr.Logger, HTTPBootConfig *bootv1alpha1.HTTPBootConfig) (ctrl.Result, error) {
log.V(1).Info("Deleting HTTPBootConfig")

// TODO

return ctrl.Result{}, nil
}
Expand All @@ -45,5 +100,51 @@ func (r *HTTPBootConfigReconciler) Reconcile(ctx context.Context, req ctrl.Reque
func (r *HTTPBootConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&bootv1alpha1.HTTPBootConfig{}).
Watches(
&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(r.enqueueHTTPBootConfigReferencingIgnitionSecret),
).
Complete(r)
}

func (r *HTTPBootConfigReconciler) patchStatus(
ctx context.Context,
HTTPBootConfig *bootv1alpha1.HTTPBootConfig,
state bootv1alpha1.HTTPBootConfigState,
) error {
base := HTTPBootConfig.DeepCopy()
HTTPBootConfig.Status.State = state

if err := r.Status().Patch(ctx, HTTPBootConfig, client.MergeFrom(base)); err != nil {
return fmt.Errorf("error patching HTTPBootConfig: %w", err)
}
return nil
}

func (r *HTTPBootConfigReconciler) enqueueHTTPBootConfigReferencingIgnitionSecret(ctx context.Context, secret client.Object) []reconcile.Request {
log := log.Log.WithValues("secret", secret.GetName())
secretObj, ok := secret.(*corev1.Secret)
if !ok {
log.Error(nil, "cant decode object into Secret", secret)
return nil
}

list := &bootv1alpha1.HTTPBootConfigList{}
if err := r.Client.List(ctx, list, client.InNamespace(secretObj.Namespace)); err != nil {
log.Error(err, "failed to list HTTPBootConfig for secret", secret)
return nil
}

var requests []reconcile.Request
for _, HTTPBootConfig := range list.Items {
if HTTPBootConfig.Spec.IgnitionSecretRef != nil && HTTPBootConfig.Spec.IgnitionSecretRef.Name == secretObj.Name {
requests = append(requests, reconcile.Request{
NamespacedName: types.NamespacedName{
Name: HTTPBootConfig.Name,
Namespace: HTTPBootConfig.Namespace,
},
})
}
}
return requests
}