680 lines
24 KiB
Go
680 lines
24 KiB
Go
/*
|
|
Copyright © 2022 - 2025 SUSE LLC
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package controllers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"gopkg.in/yaml.v3"
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
errorutils "k8s.io/apimachinery/pkg/util/errors"
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/utils/ptr"
|
|
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/event"
|
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
|
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
elementalv1 "github.com/rancher/elemental-operator/api/v1beta1"
|
|
systemagent "github.com/rancher/elemental-operator/internal/system-agent"
|
|
"github.com/rancher/elemental-operator/pkg/log"
|
|
"github.com/rancher/elemental-operator/pkg/util"
|
|
)
|
|
|
|
// MachineInventorySelectorReconciler reconciles a MachineInventorySelector object.
|
|
type MachineInventorySelectorReconciler struct {
|
|
client.Client
|
|
}
|
|
|
|
// +kubebuilder:rbac:groups=elemental.cattle.io,resources=machineinventoryselectors,verbs=get;list;watch;create;update;patch;delete
|
|
// +kubebuilder:rbac:groups=elemental.cattle.io,resources=machineinventoryselectors/status,verbs=get;update;patch;list
|
|
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machines,verbs=get;list;watch
|
|
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;patch;list;watch
|
|
|
|
func (r *MachineInventorySelectorReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
|
return ctrl.NewControllerManagedBy(mgr).
|
|
For(&elementalv1.MachineInventorySelector{}).
|
|
Watches(
|
|
&elementalv1.MachineInventory{},
|
|
handler.EnqueueRequestsFromMapFunc(r.MachineInventoryToSelector),
|
|
).
|
|
WithEventFilter(filterSelectorUpdateEvents()).
|
|
Complete(r)
|
|
}
|
|
|
|
func (r *MachineInventorySelectorReconciler) Reconcile(ctx context.Context, req reconcile.Request) (ctrl.Result, error) { //nolint:dupl
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
machineInventorySelector := &elementalv1.MachineInventorySelector{}
|
|
err := r.Get(ctx, req.NamespacedName, machineInventorySelector)
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
logger.V(log.DebugDepth).Info("Object was not found, not an error")
|
|
return reconcile.Result{}, nil
|
|
}
|
|
return reconcile.Result{}, fmt.Errorf("failed to get machine inventory selector object: %w", err)
|
|
}
|
|
|
|
// Ensure we patch the latest version otherwise we could erratically
|
|
// overwrite the adopted reference when it was already set
|
|
patchBase := client.MergeFromWithOptions(machineInventorySelector.DeepCopy(), client.MergeFromWithOptimisticLock{})
|
|
|
|
// We have to sanitize the conditions because old API definitions didn't have proper validation.
|
|
machineInventorySelector.Status.Conditions = util.RemoveInvalidConditions(machineInventorySelector.Status.Conditions)
|
|
|
|
// Collect errors as an aggregate to return together after all patches have been performed.
|
|
var errs []error
|
|
|
|
result, err := r.reconcile(ctx, machineInventorySelector)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("error reconciling machine inventory selector object: %w", err))
|
|
}
|
|
|
|
machineInventorySelectorStatusCopy := machineInventorySelector.Status.DeepCopy() // Patch call will erase the status
|
|
|
|
if err := r.Patch(ctx, machineInventorySelector, patchBase); err != nil {
|
|
errs = append(errs, fmt.Errorf("failed to patch machine inventory selector object: %w", err))
|
|
}
|
|
|
|
machineInventorySelector.Status = *machineInventorySelectorStatusCopy
|
|
|
|
if err := r.Status().Patch(ctx, machineInventorySelector, patchBase); err != nil {
|
|
errs = append(errs, fmt.Errorf("failed to patch status for machine inventory selector object: %w", err))
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return ctrl.Result{}, errorutils.NewAggregate(errs)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (r *MachineInventorySelectorReconciler) reconcile(ctx context.Context, miSelector *elementalv1.MachineInventorySelector) (ctrl.Result, error) {
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
logger.Info("Reconciling machine inventory selector object")
|
|
|
|
if miSelector.GetDeletionTimestamp() != nil {
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
var mInventory *elementalv1.MachineInventory
|
|
|
|
if err := r.findAndAdoptInventory(ctx, miSelector, mInventory); err != nil {
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.ReadyCondition,
|
|
Reason: elementalv1.FailedToAdoptInventoryReason,
|
|
Status: metav1.ConditionFalse,
|
|
Message: err.Error(),
|
|
})
|
|
return ctrl.Result{}, fmt.Errorf("failed to find and adopt machine inventory: %w", err)
|
|
}
|
|
|
|
requeue, err := r.updateAdoptionStatus(ctx, miSelector, mInventory)
|
|
if err != nil {
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.InventoryReadyCondition,
|
|
Reason: elementalv1.FailedToAdoptInventoryReason,
|
|
Status: metav1.ConditionFalse,
|
|
Message: err.Error(),
|
|
})
|
|
return ctrl.Result{}, fmt.Errorf("failed to set adoption status: %w", err)
|
|
}
|
|
|
|
if err := r.updatePlanSecretWithBootstrap(ctx, miSelector, mInventory); err != nil {
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.ReadyCondition,
|
|
Reason: elementalv1.FailedToUpdatePlanReason,
|
|
Status: metav1.ConditionFalse,
|
|
Message: err.Error(),
|
|
})
|
|
return ctrl.Result{}, fmt.Errorf("failed to set bootstrap plan: %w", err)
|
|
}
|
|
|
|
if err := r.setInvetorySelectorAddresses(ctx, miSelector, mInventory); err != nil {
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.ReadyCondition,
|
|
Reason: elementalv1.FailedToSetAdressesReason,
|
|
Status: metav1.ConditionFalse,
|
|
Message: err.Error(),
|
|
})
|
|
return ctrl.Result{}, fmt.Errorf("failed to set inventory selector address: %w", err)
|
|
}
|
|
|
|
if requeue {
|
|
return ctrl.Result{RequeueAfter: time.Second}, nil
|
|
}
|
|
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
func (r *MachineInventorySelectorReconciler) findAndAdoptInventory(ctx context.Context, miSelector *elementalv1.MachineInventorySelector, mInventory *elementalv1.MachineInventory) error {
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
if miSelector.Status.MachineInventoryRef != nil {
|
|
logger.V(log.DebugDepth).Info("machine inventory reference already set", "machineInvetoryName", miSelector.Status.MachineInventoryRef.Name)
|
|
return nil
|
|
}
|
|
|
|
logger.V(log.DebugDepth).Info("Trying to find matching machine inventory")
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.ReadyCondition,
|
|
Reason: elementalv1.WaitingForInventoryReason,
|
|
Status: metav1.ConditionFalse,
|
|
})
|
|
|
|
labelSelector, err := metav1.LabelSelectorAsSelector(&miSelector.Spec.Selector)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to convert spec to a label selector: %w", err)
|
|
}
|
|
|
|
machineInventories := &elementalv1.MachineInventoryList{}
|
|
if err := r.List(ctx, machineInventories, &client.ListOptions{LabelSelector: labelSelector}); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range machineInventories.Items {
|
|
// Skip machines up for deletion/reset
|
|
if machineInventories.Items[i].DeletionTimestamp != nil {
|
|
continue
|
|
}
|
|
if !isAlreadyOwned(&machineInventories.Items[i]) {
|
|
mInventory = &machineInventories.Items[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if mInventory == nil {
|
|
logger.V(log.DebugDepth).Info("No matching machine inventories found")
|
|
return nil
|
|
}
|
|
|
|
// Ensure we patch the latest version otherwise we could erratically
|
|
// set the ownership of an already owned inventory
|
|
patchBase := client.MergeFromWithOptions(mInventory.DeepCopy(), client.MergeFromWithOptimisticLock{})
|
|
|
|
mInventory.OwnerReferences = []metav1.OwnerReference{
|
|
{
|
|
APIVersion: elementalv1.GroupVersion.String(),
|
|
Kind: "MachineInventorySelector",
|
|
Name: miSelector.Name,
|
|
UID: miSelector.UID,
|
|
Controller: ptr.To(true),
|
|
},
|
|
}
|
|
|
|
if err := r.Patch(ctx, mInventory, patchBase); err != nil {
|
|
return fmt.Errorf("failed to patch machine inventory: %w", err)
|
|
}
|
|
|
|
miSelector.Status.MachineInventoryRef = &corev1.LocalObjectReference{
|
|
Name: mInventory.Name,
|
|
}
|
|
|
|
logger.Info("Inventory adoption started")
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.InventoryReadyCondition,
|
|
Reason: elementalv1.WaitForInventoryCheckReason,
|
|
Status: metav1.ConditionUnknown,
|
|
LastTransitionTime: metav1.NewTime(time.Now()),
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *MachineInventorySelectorReconciler) updateAdoptionStatus(ctx context.Context, miSelector *elementalv1.MachineInventorySelector, mInventory *elementalv1.MachineInventory) (bool, error) {
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
if miSelector.Status.MachineInventoryRef == nil {
|
|
logger.V(log.DebugDepth).Info("Waiting for a machine inventory match")
|
|
return false, nil
|
|
}
|
|
|
|
inventoryReady := meta.FindStatusCondition(miSelector.Status.Conditions, elementalv1.InventoryReadyCondition)
|
|
if inventoryReady == nil {
|
|
return false, fmt.Errorf("missing required InventoryReadyCondition it must be already set at this phase")
|
|
}
|
|
if inventoryReady.Status == metav1.ConditionTrue {
|
|
logger.V(log.DebugDepth).Info("Machine inventory is successfully adopted already")
|
|
return false, nil
|
|
}
|
|
|
|
if mInventory == nil {
|
|
mInventory = &elementalv1.MachineInventory{}
|
|
if err := r.Get(ctx, types.NamespacedName{
|
|
Namespace: miSelector.Namespace,
|
|
Name: miSelector.Status.MachineInventoryRef.Name,
|
|
},
|
|
mInventory,
|
|
); err != nil {
|
|
return false, fmt.Errorf("failed to get machine inventory: %w", err)
|
|
}
|
|
}
|
|
|
|
// Check the machine inventory ownership is sane and it is successfully adopted
|
|
adoptedCondition := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.AdoptionReadyCondition)
|
|
owner := getSelectorOwner(mInventory)
|
|
orphanInventory := owner == nil || adoptedCondition == nil || adoptedCondition.Status != metav1.ConditionTrue
|
|
deadLine := inventoryReady.LastTransitionTime.Add(adoptionTimeout * time.Second)
|
|
now := time.Now()
|
|
|
|
switch {
|
|
case owner != nil && owner.Name != miSelector.Name:
|
|
miSelector.Status.MachineInventoryRef = nil
|
|
return false, fmt.Errorf("machine inventory ownership mismatch detected, restart adoption")
|
|
case orphanInventory && now.After(deadLine):
|
|
miSelector.Status.MachineInventoryRef = nil
|
|
return false, fmt.Errorf("machine inventory adoption validation timeout, restart adoption. Deadline was: %v", deadLine)
|
|
case orphanInventory:
|
|
logger.V(log.DebugDepth).Info("Machine inventory adoption not completed")
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.InventoryReadyCondition,
|
|
Reason: elementalv1.WaitForInventoryCheckReason,
|
|
Status: metav1.ConditionUnknown,
|
|
})
|
|
return true, nil
|
|
default:
|
|
logger.Info("Machine inventory adoption successfully completed")
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.InventoryReadyCondition,
|
|
Reason: elementalv1.SuccessfullyAdoptedInventoryReason,
|
|
Status: metav1.ConditionTrue,
|
|
})
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func (r *MachineInventorySelectorReconciler) updatePlanSecretWithBootstrap(ctx context.Context, miSelector *elementalv1.MachineInventorySelector, mInventory *elementalv1.MachineInventory) error {
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
inventoryReady := meta.FindStatusCondition(miSelector.Status.Conditions, elementalv1.InventoryReadyCondition)
|
|
if inventoryReady == nil || inventoryReady.Status != metav1.ConditionTrue {
|
|
logger.V(log.DebugDepth).Info("Waiting for machine inventory to be adopted before updating plan secret")
|
|
return nil
|
|
}
|
|
|
|
if miSelector.Status.BootstrapPlanChecksum != "" {
|
|
logger.V(log.DebugDepth).Info("Secret plan already updated with bootstrap")
|
|
return nil
|
|
}
|
|
|
|
if mInventory == nil {
|
|
mInventory = &elementalv1.MachineInventory{}
|
|
if err := r.Get(ctx, types.NamespacedName{
|
|
Namespace: miSelector.Namespace,
|
|
Name: miSelector.Status.MachineInventoryRef.Name,
|
|
},
|
|
mInventory,
|
|
); err != nil {
|
|
return fmt.Errorf("failed to get machine inventory: %w", err)
|
|
}
|
|
}
|
|
|
|
if mInventory.Status.Plan == nil || mInventory.Status.Plan.PlanSecretRef == nil {
|
|
logger.V(log.DebugDepth).Info("Machine inventory plan reference not set yet")
|
|
return nil
|
|
}
|
|
|
|
checksum, plan, err := r.newBootstrapPlan(ctx, miSelector, mInventory)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get bootstrap plan: %w", err)
|
|
}
|
|
|
|
planSecret := &corev1.Secret{}
|
|
if err := r.Get(ctx, types.NamespacedName{
|
|
Namespace: mInventory.Status.Plan.PlanSecretRef.Namespace,
|
|
Name: mInventory.Status.Plan.PlanSecretRef.Name,
|
|
}, planSecret); err != nil {
|
|
return fmt.Errorf("failed to get plan secret: %w", err)
|
|
}
|
|
|
|
patchBase := client.MergeFrom(planSecret.DeepCopy())
|
|
|
|
planSecret.Data["plan"] = plan
|
|
planSecret.Annotations = map[string]string{elementalv1.PlanTypeAnnotation: elementalv1.PlanTypeBootstrap}
|
|
|
|
if err := r.Patch(ctx, planSecret, patchBase); err != nil {
|
|
return fmt.Errorf("failed to patch plan secret: %w", err)
|
|
}
|
|
|
|
miSelector.Status.BootstrapPlanChecksum = checksum
|
|
|
|
logger.Info("Machine inventory plan updated")
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.ReadyCondition,
|
|
Reason: elementalv1.SuccessfullyUpdatedPlanReason,
|
|
Status: metav1.ConditionFalse,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *MachineInventorySelectorReconciler) newBootstrapPlan(ctx context.Context, miSelector *elementalv1.MachineInventorySelector, mInventory *elementalv1.MachineInventory) (string, []byte, error) {
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
machine, err := r.getOwnerMachine(ctx, miSelector)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("failed to find an owner machine for inventory selector: %w", err)
|
|
}
|
|
|
|
if machine == nil {
|
|
return "", nil, fmt.Errorf("machine for machine inventory selector doesn't exist")
|
|
}
|
|
|
|
if machine.Spec.Bootstrap.DataSecretName == nil {
|
|
return "", nil, fmt.Errorf("machine %s/%s missing bootstrap data secret name", machine.Namespace, machine.Name)
|
|
}
|
|
|
|
logger.V(log.DebugDepth).Info("setting a bootstrap plan for the selector")
|
|
bootstrapSecret := &corev1.Secret{}
|
|
|
|
if err := r.Get(ctx, types.NamespacedName{
|
|
Namespace: machine.Namespace,
|
|
Name: *machine.Spec.Bootstrap.DataSecretName,
|
|
}, bootstrapSecret); err != nil {
|
|
return "", nil, fmt.Errorf("failed to get a boostrap plan for the machine: %w", err)
|
|
}
|
|
|
|
type LabelsFromInventory struct {
|
|
// NOTE: The '+' is not a typo and is needed when adding labels to k3s/rke
|
|
// instead of replacing them.
|
|
NodeLabels []string `yaml:"node-label+"`
|
|
}
|
|
|
|
nodeLabelsFromInventory := LabelsFromInventory{NodeLabels: []string{}}
|
|
for label, value := range mInventory.Labels {
|
|
nodeLabelsFromInventory.NodeLabels = append(nodeLabelsFromInventory.NodeLabels, fmt.Sprintf("%s=%s", label, value))
|
|
}
|
|
|
|
nodeLabelsFromInventoryInYaml, err := yaml.Marshal(nodeLabelsFromInventory)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("failed to marshal node labels from inventory: %w", err)
|
|
}
|
|
|
|
p := systemagent.Plan{
|
|
Files: []systemagent.File{
|
|
{
|
|
Content: base64.StdEncoding.EncodeToString(bootstrapSecret.Data["value"]),
|
|
Path: "/var/lib/rancher/bootstrap.sh",
|
|
Permissions: "0700",
|
|
},
|
|
{
|
|
Content: base64.StdEncoding.EncodeToString([]byte("node-name: " + mInventory.Name)),
|
|
Path: "/etc/rancher/rke2/config.yaml.d/99-elemental-name.yaml",
|
|
Permissions: "0600",
|
|
},
|
|
{
|
|
Content: base64.StdEncoding.EncodeToString(nodeLabelsFromInventoryInYaml),
|
|
Path: "/etc/rancher/rke2/config.yaml.d/99-elemental-inventory-labels.yaml",
|
|
Permissions: "0600",
|
|
},
|
|
{
|
|
Content: base64.StdEncoding.EncodeToString([]byte("node-name: " + mInventory.Name)),
|
|
Path: "/etc/rancher/k3s/config.yaml.d/99-elemental-name.yaml",
|
|
Permissions: "0600",
|
|
},
|
|
{
|
|
Content: base64.StdEncoding.EncodeToString(nodeLabelsFromInventoryInYaml),
|
|
Path: "/etc/rancher/k3s/config.yaml.d/99-elemental-inventory-labels.yaml",
|
|
Permissions: "0600",
|
|
},
|
|
{
|
|
Content: base64.StdEncoding.EncodeToString([]byte(mInventory.Name)),
|
|
Path: "/usr/local/etc/hostname",
|
|
Permissions: "0644",
|
|
},
|
|
{
|
|
Content: base64.StdEncoding.EncodeToString([]byte(mInventory.Name)),
|
|
Path: "/run/elemental/persistent/etc/hostname",
|
|
Permissions: "0644",
|
|
},
|
|
},
|
|
OneTimeInstructions: []systemagent.OneTimeInstruction{
|
|
{
|
|
CommonInstruction: systemagent.CommonInstruction{
|
|
Name: "configure hostname",
|
|
Command: "hostnamectl",
|
|
Args: []string{
|
|
"set-hostname",
|
|
"--transient",
|
|
mInventory.Name,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CommonInstruction: systemagent.CommonInstruction{
|
|
Command: "bash",
|
|
Args: []string{
|
|
"-c", "[ -f /var/lib/rancher/bootstrap_done ] || /var/lib/rancher/bootstrap.sh && touch /var/lib/rancher/bootstrap_done",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := json.NewEncoder(&buf).Encode(p); err != nil {
|
|
return "", nil, fmt.Errorf("failed to encode plan: %w", err)
|
|
}
|
|
|
|
plan := buf.Bytes()
|
|
|
|
checksum := util.PlanChecksum(plan)
|
|
|
|
return checksum, plan, nil
|
|
}
|
|
|
|
func (r *MachineInventorySelectorReconciler) getOwnerMachine(ctx context.Context, miSelector *elementalv1.MachineInventorySelector) (*clusterv1.Machine, error) {
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
logger.V(log.DebugDepth).Info("Trying to find CAPI machine that owns machine inventory selector")
|
|
for _, owner := range miSelector.GetOwnerReferences() {
|
|
if owner.APIVersion == clusterv1.GroupVersion.String() && owner.Kind == "Machine" {
|
|
logger.V(log.DebugDepth).Info("Found owner CAPI machine for machine inventory selector", "capiMachineName", owner.Name)
|
|
machine := &clusterv1.Machine{}
|
|
err := r.Client.Get(ctx, types.NamespacedName{Namespace: miSelector.Namespace, Name: owner.Name}, machine)
|
|
return machine, err
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *MachineInventorySelectorReconciler) setInvetorySelectorAddresses(ctx context.Context, miSelector *elementalv1.MachineInventorySelector, mInventory *elementalv1.MachineInventory) error {
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
if miSelector.Status.MachineInventoryRef == nil {
|
|
logger.V(log.DebugDepth).Info("Waiting for machine inventory to be adopted before setting adresses")
|
|
return nil
|
|
}
|
|
|
|
if miSelector.Status.BootstrapPlanChecksum == "" {
|
|
logger.V(log.DebugDepth).Info("Waiting for the bootstrap plan to be created")
|
|
return nil
|
|
}
|
|
|
|
if mInventory == nil {
|
|
mInventory = &elementalv1.MachineInventory{}
|
|
if err := r.Get(ctx, types.NamespacedName{
|
|
Namespace: miSelector.Namespace,
|
|
Name: miSelector.Status.MachineInventoryRef.Name,
|
|
},
|
|
mInventory,
|
|
); err != nil {
|
|
return fmt.Errorf("failed to get machine inventory: %w", err)
|
|
}
|
|
}
|
|
|
|
if val := mInventory.Labels["elemental.cattle.io/ExternalIP"]; val != "" {
|
|
miSelector.Status.Addresses = append(miSelector.Status.Addresses, clusterv1.MachineAddress{
|
|
Type: clusterv1.MachineExternalIP,
|
|
Address: val,
|
|
})
|
|
}
|
|
|
|
if val := mInventory.Labels["elemental.cattle.io/InternalIP"]; val != "" {
|
|
miSelector.Status.Addresses = append(miSelector.Status.Addresses, clusterv1.MachineAddress{
|
|
Type: clusterv1.MachineInternalIP,
|
|
Address: val,
|
|
})
|
|
}
|
|
|
|
if val := mInventory.Labels["elemental.cattle.io/Hostname"]; val != "" {
|
|
miSelector.Status.Addresses = append(miSelector.Status.Addresses, clusterv1.MachineAddress{
|
|
Type: clusterv1.MachineHostName,
|
|
Address: val,
|
|
})
|
|
}
|
|
|
|
logger.Info("Inventory selector is ready")
|
|
miSelector.Status.Ready = true
|
|
meta.SetStatusCondition(&miSelector.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.ReadyCondition,
|
|
Reason: elementalv1.SelectorReadyReason,
|
|
Status: metav1.ConditionTrue,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func filterSelectorUpdateEvents() predicate.Funcs {
|
|
return predicate.Funcs{
|
|
|
|
UpdateFunc: func(e event.UpdateEvent) bool {
|
|
logger := ctrl.LoggerFrom(context.Background())
|
|
|
|
if oldS, ok := e.ObjectOld.(*elementalv1.MachineInventorySelector); ok {
|
|
// Avoid reconciling if the event triggering the reconciliation is related to
|
|
// incremental status updates for MachineInventorySelector resources only
|
|
oldS = oldS.DeepCopy()
|
|
newS := e.ObjectNew.(*elementalv1.MachineInventorySelector).DeepCopy()
|
|
|
|
// Ignore all fields that might be updated on a status update
|
|
oldS.Status = elementalv1.MachineInventorySelectorStatus{}
|
|
newS.Status = elementalv1.MachineInventorySelectorStatus{}
|
|
oldS.ObjectMeta.ResourceVersion = ""
|
|
newS.ObjectMeta.ResourceVersion = ""
|
|
oldS.ManagedFields = []metav1.ManagedFieldsEntry{}
|
|
newS.ManagedFields = []metav1.ManagedFieldsEntry{}
|
|
|
|
update := !cmp.Equal(oldS, newS)
|
|
if !update {
|
|
logger.V(log.DebugDepth).Info("Ignoring status update", "MISelector", oldS.Name)
|
|
}
|
|
return update
|
|
}
|
|
if oldI, ok := e.ObjectOld.(*elementalv1.MachineInventory); ok {
|
|
// Avoid reconciling if the event triggering the reconciliation is related to
|
|
// adopting an inventory resource
|
|
newI := e.ObjectNew.(*elementalv1.MachineInventory)
|
|
update := len(newI.ObjectMeta.OwnerReferences) <= len(oldI.ObjectMeta.OwnerReferences)
|
|
if !update {
|
|
logger.V(log.DebugDepth).Info("Ignoring new additional owner update", "MInventory", oldI.Name)
|
|
}
|
|
return update
|
|
}
|
|
// Return true in case it watches other types
|
|
return true
|
|
},
|
|
}
|
|
}
|
|
|
|
// MachineInventoryToSelector is a handler.ToRequestsFunc to be used to enqueue requests for reconciliation
|
|
// for MachineInventoryToSelector that might adopt a MachineInventory.
|
|
func (r *MachineInventorySelectorReconciler) MachineInventoryToSelector(ctx context.Context, o client.Object) []ctrl.Request {
|
|
result := []reconcile.Request{}
|
|
|
|
mInventory, ok := o.(*elementalv1.MachineInventory)
|
|
if !ok {
|
|
panic(fmt.Sprintf("Expected a MachineInventory but got a %T", o))
|
|
}
|
|
|
|
// This won't log unless the global logger is set
|
|
logger := ctrl.LoggerFrom(ctx, "MachineInventory", klog.KObj(mInventory))
|
|
|
|
// If machine inventory is already owned reconcile its owner
|
|
if owner := getSelectorOwner(mInventory); owner != nil {
|
|
return append(result, reconcile.Request{
|
|
NamespacedName: client.ObjectKey{
|
|
Name: owner.Name,
|
|
Namespace: mInventory.Namespace,
|
|
},
|
|
})
|
|
}
|
|
|
|
// If machine inventory has no labels it can't be adopted.
|
|
if mInventory.Labels == nil {
|
|
return result
|
|
}
|
|
|
|
miSelectorList := &elementalv1.MachineInventorySelectorList{}
|
|
err := r.List(ctx, miSelectorList, client.InNamespace(mInventory.Namespace))
|
|
if err != nil {
|
|
logger.Error(err, "failed to list machine inventories")
|
|
return result
|
|
}
|
|
|
|
for i := range miSelectorList.Items {
|
|
if hasMatchingLabels(ctx, &miSelectorList.Items[i], mInventory) {
|
|
result = append(result, reconcile.Request{
|
|
NamespacedName: client.ObjectKey{
|
|
Namespace: miSelectorList.Items[i].Namespace, Name: miSelectorList.Items[i].Name,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func hasMatchingLabels(ctx context.Context, miSelector *elementalv1.MachineInventorySelector, mInventory *elementalv1.MachineInventory) bool {
|
|
logger := ctrl.LoggerFrom(ctx)
|
|
|
|
selector, err := metav1.LabelSelectorAsSelector(&miSelector.Spec.Selector)
|
|
if err != nil {
|
|
log.Error(err, "Unable to convert selector")
|
|
return false
|
|
}
|
|
|
|
if selector.Empty() {
|
|
logger.V(log.DebugDepth).Info("machine selector has empty selector", "mSelector.Name", miSelector.Name)
|
|
return false
|
|
}
|
|
|
|
if !selector.Matches(labels.Set(mInventory.Labels)) {
|
|
logger.V(log.DebugDepth).Info("machine inventory has mismatch labels", "mInventory.Name", mInventory.Name, "mSelector.Name", miSelector.Name)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|