mirror of
https://github.com/hanzoai/coredns-manager-operator.git
synced 2026-10-30 18:43:53 +00:00
456 lines
20 KiB
Go
456 lines
20 KiB
Go
/*
|
|
Copyright 2024 monkale.io.
|
|
|
|
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 controller
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/equality"
|
|
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/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
monkalev1alpha1 "github.com/monkale.io/coredns-manager-operator/api/v1alpha1"
|
|
)
|
|
|
|
// DNSZoneReconciler reconciles a DNSZone object
|
|
type DNSZoneReconciler struct {
|
|
client.Client
|
|
Scheme *runtime.Scheme
|
|
}
|
|
|
|
//+kubebuilder:rbac:groups=monkale.monkale.io,resources=dnszones,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups=monkale.monkale.io,resources=dnszones/status,verbs=get;update;patch
|
|
//+kubebuilder:rbac:groups=monkale.monkale.io,resources=dnszones/finalizers,verbs=update
|
|
|
|
// Reconcile is responsible to reconcile DNSZone resource.
|
|
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
|
|
func (r *DNSZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
|
_ = log.FromContext(ctx)
|
|
var dnsZone monkalev1alpha1.DNSZone
|
|
// Fetch DNSZone from kubernetes
|
|
if err := r.Get(ctx, req.NamespacedName, &dnsZone); err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return ctrl.Result{}, nil
|
|
} else if !apierrors.IsNotFound(err) {
|
|
log.Log.Error(err, "DNSZone instance. Failed to get DNSZone", "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
}
|
|
|
|
// DNSZone has been just created. Add finalizer.
|
|
if !controllerutil.ContainsFinalizer(&dnsZone, monkalev1alpha1.DnsZonesFinalizerName) {
|
|
dnsZoneObj := types.NamespacedName{Name: dnsZone.Name, Namespace: dnsZone.Namespace}
|
|
clientK8sObj := dnsZone.DeepCopy()
|
|
if err := addFinalizer(ctx, r.Client, dnsZoneObj, clientK8sObj, monkalev1alpha1.DnsZonesFinalizerName); err != nil {
|
|
log.Log.Error(err, "DNSZone instance. Failed to add finalizer", "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
return ctrl.Result{Requeue: true}, nil
|
|
}
|
|
|
|
// DNSZone has been deleted. Reconcile Delete
|
|
if !dnsZone.DeletionTimestamp.IsZero() {
|
|
log.Log.Info("DNSZone instance. Zone has been removed. Reconciling", "DNSZone.Name", dnsZone.Name)
|
|
return r.reconcileDelete(ctx, &dnsZone)
|
|
}
|
|
|
|
// DNSZone has been created or updated. Reconsile create or update
|
|
log.Log.Info("DNSZone instance. Reconciling", "DNSZone.Name", dnsZone.Name)
|
|
return r.reconcileCreateOrUpdate(ctx, &dnsZone)
|
|
}
|
|
|
|
// reconcileDelete reconciles if DNSZone resource has been removed.
|
|
func (r *DNSZoneReconciler) reconcileDelete(ctx context.Context, dnsZone *monkalev1alpha1.DNSZone) (ctrl.Result, error) {
|
|
_ = log.FromContext(ctx)
|
|
previousState := dnsZone.DeepCopy()
|
|
// Remove finalizer from the CM
|
|
var currentCM corev1.ConfigMap
|
|
cmConnObj := types.NamespacedName{Name: dnsZone.Spec.CMPrefix + dnsZone.Name, Namespace: dnsZone.Namespace}
|
|
cmErr := r.Get(ctx, cmConnObj, ¤tCM)
|
|
|
|
// Any not "isNotFound" error while fetching ConfigMap
|
|
if cmErr != nil && !apierrors.IsNotFound(cmErr) {
|
|
log.Log.Error(cmErr, "DNSZone instance is being deleted. Error while fetching ConfigMap", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, cmErr
|
|
} else if apierrors.IsNotFound(cmErr) {
|
|
log.Log.Info("DNSZone instance is being deleted. DNZZone configMap does not exist", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
} else {
|
|
log.Log.Info("DNSZone instance is being deleted. Removing finalizer from configmap", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
if err := removeFinalizer(ctx, r.Client, cmConnObj, ¤tCM, monkalev1alpha1.DnsZonesFinalizerName); err != nil {
|
|
log.Log.Error(err, "DNSZone instance is being deleted. Failed to delete finalizer from Zone CM", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
}
|
|
|
|
// Notify all DNSRecords that the zone has been removed
|
|
log.Log.Info("DNSZone instance is being deleted. Notify DNSRecords.", "DNSZone.Name", dnsZone.Name)
|
|
dnsRecordList, err := r.getGoodDnsRecords(ctx, dnsZone)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSZone instance is being deleted. Notify DNSRecords. Failed to get DNSRecords", "DNSZone.Name", dnsZone.Name)
|
|
message := fmt.Sprintf("Update dnsrecords failure. Preserving the previous version. Failed to get dnsrecords: %s", err)
|
|
setDnsZoneCondition(dnsZone, metav1.ConditionFalse, monkalev1alpha1.ConditionReasonZoneUpdateErr, message)
|
|
if err := r.dnsZoneUpdateStatus(ctx, previousState, dnsZone); err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
return ctrl.Result{}, err
|
|
}
|
|
|
|
for _, dnsRecord := range dnsRecordList.Items {
|
|
// refresh resource
|
|
dnsRecType := types.NamespacedName{Name: dnsRecord.Name, Namespace: dnsRecord.Namespace}
|
|
dnsRecObj := dnsRecord.DeepCopy()
|
|
if err := getObjFromK8s(ctx, r.Client, dnsRecType, dnsRecObj); err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("failed to refresh DNSRecord resource: %v", err)
|
|
}
|
|
// update resource
|
|
message := fmt.Sprintf("DNSZone has been removed: %s", dnsZone.Name)
|
|
setDnsRecordCondition(dnsRecObj, metav1.ConditionFalse, monkalev1alpha1.ConditionReasonRecordPending, message)
|
|
if err := r.Status().Update(ctx, dnsRecObj); err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
}
|
|
|
|
// Remove finazlizer from DNSZone
|
|
dnsZoneObj := types.NamespacedName{Name: dnsZone.Name, Namespace: dnsZone.Namespace}
|
|
clientK8sObj := dnsZone.DeepCopy()
|
|
log.Log.Info("DNSZone instance is being deleted. Removing finalizer from DNSZone", "DNSZone.Name", dnsZone.Name)
|
|
if err := removeFinalizer(ctx, r.Client, dnsZoneObj, clientK8sObj, monkalev1alpha1.DnsZonesFinalizerName); err != nil {
|
|
log.Log.Error(err, "Deleting DNSZone. Failed to delete finalizer", "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
|
|
// Done
|
|
log.Log.Info("DNSZone instance has deleted.", "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
// reconcileCreateOrUpdate reconciles if DNSZone resource has been created or updated
|
|
func (r *DNSZoneReconciler) reconcileCreateOrUpdate(ctx context.Context, dnsZone *monkalev1alpha1.DNSZone) (ctrl.Result, error) {
|
|
_ = log.FromContext(ctx)
|
|
// Get DNSRecords for the Zone.
|
|
log.Log.Info("DNSZone instance. Generate ZoneCM. Fetching DNSRecords", "DNSZone.Name", dnsZone.Name)
|
|
previousState := dnsZone.DeepCopy()
|
|
var records bakedRecords
|
|
dnsRecordList, err := r.getGoodDnsRecords(ctx, dnsZone)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSZone instance. Generate ZoneCM. Failed to get DNSRecords", "DNSZone.Name", dnsZone.Name)
|
|
message := fmt.Sprintf("Update dnsrecords failure. Preserving the previous version. Failed to get dnsrecords: %s", err)
|
|
setDnsZoneCondition(dnsZone, metav1.ConditionFalse, monkalev1alpha1.ConditionReasonZoneUpdateErr, message)
|
|
if err := r.dnsZoneUpdateStatus(ctx, previousState, dnsZone); err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
return ctrl.Result{}, err
|
|
} else if len(dnsRecordList.Items) <= 0 {
|
|
// If no records, make it empty. It will generate only SOA and NS 1
|
|
records = bakedRecords{
|
|
count: 0,
|
|
recordsString: "",
|
|
}
|
|
} else {
|
|
// Convert DNSRecords to coredns entries
|
|
records, err = bakeRecords(dnsRecordList)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSZone instance. Generate ZoneCM. Failed to construct record list for coredns", "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
}
|
|
|
|
// Construct and Apply zone CM
|
|
if err := r.createOrUpdateZoneCM(ctx, dnsZone, records); err != nil {
|
|
log.Log.Error(err, "DNSZone instance. Generate ZoneCM. Failed to create or update Zone CM", "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
|
|
// Update DNSRecord status - at this point all records are inserted into the Zonefile.
|
|
for _, dnsRecord := range dnsRecordList.Items {
|
|
// refresh resource
|
|
dnsRecType := types.NamespacedName{Name: dnsRecord.Name, Namespace: dnsRecord.Namespace}
|
|
dnsRecObj := dnsRecord.DeepCopy()
|
|
if err := getObjFromK8s(ctx, r.Client, dnsRecType, dnsRecObj); err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("failed to refresh DNSRecord resource: %v", err)
|
|
}
|
|
// update resource
|
|
message := fmt.Sprintf("Record has joined to the DNSZone: %s", dnsZone.Name)
|
|
setDnsRecordCondition(dnsRecObj, metav1.ConditionTrue, monkalev1alpha1.ConditionReasonRecordReady, message)
|
|
if err := r.Status().Update(ctx, dnsRecObj); err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
}
|
|
log.Log.Info("DNSZone instance. Generate ZoneCM. Reconciled successfully", "DNSZone.Name", dnsZone.Name)
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
// createOrUpdateZoneCM constructs SOA,NS, fetches DNSrecords, validates the zone and then creates/updates Zone Config Map
|
|
func (r *DNSZoneReconciler) createOrUpdateZoneCM(ctx context.Context, dnsZone *monkalev1alpha1.DNSZone, bakedRecords bakedRecords) error {
|
|
_ = log.FromContext(ctx)
|
|
previousState := dnsZone.DeepCopy()
|
|
var currentCM corev1.ConfigMap
|
|
// Get currentCM
|
|
cmConnObj := types.NamespacedName{Name: dnsZone.Spec.CMPrefix + dnsZone.Name, Namespace: dnsZone.Namespace}
|
|
cmErr := r.Get(ctx, cmConnObj, ¤tCM)
|
|
|
|
// Any not "isNotFound" error while fetching ConfigMap
|
|
if cmErr != nil && !apierrors.IsNotFound(cmErr) && !apierrors.IsAlreadyExists(cmErr) {
|
|
log.Log.Error(cmErr, "DNSZone instance. Reconciling ZoneCM. Error while fetching ConfigMap", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return cmErr
|
|
}
|
|
|
|
// Create new serial for the zone
|
|
serialNumber, err := monkalev1alpha1.DNSZoneGenerateSerial()
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSZone instance. Reconciling ZoneCM. Could not generate Serial number", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return err
|
|
}
|
|
|
|
// Construct the zone
|
|
log.Log.Info("DNSZone instance. Reconciling ZoneCM. Constructing zone", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
zone, err := constructZoneFile(dnsZone, bakedRecords.recordsString, serialNumber)
|
|
if err != nil {
|
|
message := fmt.Sprintf("Zone construction failure: %s", err)
|
|
setDnsZoneCondition(dnsZone, metav1.ConditionFalse, monkalev1alpha1.ConditionReasonRecordDegraded, message)
|
|
if err := r.dnsZoneUpdateStatus(ctx, previousState, dnsZone); err != nil {
|
|
return fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
log.Log.Error(err, "DNSZone instance. Reconciling ZoneCM. Failed to construct zone", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return err
|
|
}
|
|
|
|
// Validate zone
|
|
if err := validateRecords(zone); err != nil {
|
|
// update status
|
|
if err := r.refreshDNSZoneResource(ctx, previousState); err != nil {
|
|
return fmt.Errorf("failed to refresh DNSZone resource: %v", err)
|
|
}
|
|
message := fmt.Sprintf("Zone validation failure. Preserving the previous version. Error: %s", err)
|
|
dnsZone.Status.ValidationPassed = false
|
|
setDnsZoneCondition(dnsZone, metav1.ConditionFalse, monkalev1alpha1.ConditionReasonZoneUpdateErr, message)
|
|
if err := r.dnsZoneUpdateStatus(ctx, previousState, dnsZone); err != nil {
|
|
return fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
// if validation failed, no reason to reconcile again, it will create unneccessary reconcilation loops. user must fix it.
|
|
log.Log.Error(err, "DNSZone instance. Reconciling ZoneCM. Will not reconcile again.", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return nil
|
|
}
|
|
|
|
// Construct the Zone ConfigMap
|
|
upcomingCMAnnotations := map[string]string{"SerialNumber": serialNumber, "DomainName": dnsZone.Spec.Domain, "DNSZoneRef": dnsZone.Name}
|
|
upcomingCM, err := constructZoneConfigMap(cmConnObj.Name, dnsZone, zone, upcomingCMAnnotations)
|
|
if err != nil {
|
|
if err := r.refreshDNSZoneResource(ctx, previousState); err != nil {
|
|
return fmt.Errorf("failed to refresh DNSZone resource: %v", err)
|
|
}
|
|
message := fmt.Sprintf("Zone ConfigMap creation failure: %s", err)
|
|
setDnsZoneCondition(dnsZone, metav1.ConditionFalse, monkalev1alpha1.ConditionReasonZoneUpdateErr, message)
|
|
if err := r.dnsZoneUpdateStatus(ctx, previousState, dnsZone); err != nil {
|
|
return fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
log.Log.Error(err, "DNSZone instance. Reconciling ZoneCM. Failed to construct zoneCM", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return err
|
|
}
|
|
|
|
// Create ConfigMap if does not exist
|
|
// If ConfigMap does not exist -> Create, else Update.
|
|
if apierrors.IsNotFound(cmErr) {
|
|
// Create CM
|
|
log.Log.Info("DNSZone instance. Reconciling ZoneCM. Creating", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
if err := r.Create(ctx, &upcomingCM); err != nil {
|
|
log.Log.Error(err, "DNSZone instance. Reconciling ZoneCM. Failed to create zone", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// ConfigMap exists. Check if update is needed.
|
|
// If not needed, exit, if needed update the set the new status for serialNumber
|
|
same := compareZonefileConfigMaps(¤tCM, &upcomingCM)
|
|
if same {
|
|
log.Log.Info("DNSZone instance. No changes detected")
|
|
return nil
|
|
} else {
|
|
dnsZone.Status.CurrentZoneSerial = serialNumber
|
|
}
|
|
|
|
// Update needed. Update ConfigMap
|
|
log.Log.Info("DNSZone instance. Reconciling ZoneCM. Updating", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
if err := r.Update(ctx, &upcomingCM); err != nil {
|
|
log.Log.Error(err, "DNSZone instance. Reconciling ZoneCM. Failed to update zone configmap", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return err
|
|
}
|
|
|
|
// Update DNSZone Status
|
|
if err := r.refreshDNSZoneResource(ctx, previousState); err != nil {
|
|
return fmt.Errorf("failed to refresh DNSRecord resource: %v", err)
|
|
}
|
|
|
|
message := fmt.Sprintf("Zone ConfigMap has been created: %s", cmConnObj.Name)
|
|
setDnsZoneCondition(dnsZone, metav1.ConditionFalse, monkalev1alpha1.ConditionReasonZonePending, message)
|
|
dnsZone.Status.RecordCount = bakedRecords.count
|
|
dnsZone.Status.ValidationPassed = true
|
|
dnsZone.Status.Checkpoint = true
|
|
dnsZone.Status.ZoneConfigmap = cmConnObj.Name
|
|
if err := r.dnsZoneUpdateStatus(ctx, previousState, dnsZone); err != nil {
|
|
return fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
|
|
// Add finalizer
|
|
if err := addFinalizer(ctx, r.Client, cmConnObj, &corev1.ConfigMap{}, monkalev1alpha1.DnsZonesFinalizerName); err != nil {
|
|
log.Log.Error(err, "DNSZone instance. Reconciling ZoneCM. Failed to add finalizer", "ConfigMap.metadata.name", cmConnObj.Name, "DNSZone.Name", dnsZone.Name)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// setDnsZoneCondition adds or updates a given condition in the ManagedZone status.
|
|
func setDnsZoneCondition(dnsZone *monkalev1alpha1.DNSZone, status metav1.ConditionStatus, reason, message string) {
|
|
now := metav1.Now()
|
|
cond := metav1.Condition{
|
|
Type: string(monkalev1alpha1.ConditionZoneTypeReady),
|
|
Status: status,
|
|
LastTransitionTime: now,
|
|
Reason: reason,
|
|
Message: message,
|
|
ObservedGeneration: dnsZone.Generation,
|
|
}
|
|
meta.SetStatusCondition(&dnsZone.Status.Conditions, cond)
|
|
}
|
|
|
|
// refreshDNSZoneResources fetch from kubernetes a new version of DNSZoneResource
|
|
func (r *DNSZoneReconciler) refreshDNSZoneResource(ctx context.Context, dnsZone *monkalev1alpha1.DNSZone) error {
|
|
dnsZoneObj := types.NamespacedName{Name: dnsZone.Name, Namespace: dnsZone.Namespace}
|
|
clientK8sObj := dnsZone.DeepCopy()
|
|
if err := getObjFromK8s(ctx, r.Client, dnsZoneObj, clientK8sObj); err != nil {
|
|
return fmt.Errorf("failed to refresh DNSZone resource: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// dnsZoneUpdateStatus updates the status of the DNSzone Update Status, only if status has been changed. If the only thing that changed
|
|
// is LastTransitionTime skip!
|
|
func (r *DNSZoneReconciler) dnsZoneUpdateStatus(ctx context.Context, previous, current *monkalev1alpha1.DNSZone) error {
|
|
// avoid resource update if the only thing that changed is the LastTransitionTime
|
|
previousCopy := previous.DeepCopy()
|
|
currentCopy := current.DeepCopy()
|
|
timeNow := metav1.Time{}
|
|
for i := range previousCopy.Status.Conditions {
|
|
previousCopy.Status.Conditions[i].LastTransitionTime = timeNow
|
|
}
|
|
for i := range currentCopy.Status.Conditions {
|
|
currentCopy.Status.Conditions[i].LastTransitionTime = timeNow
|
|
}
|
|
// update status
|
|
if !equality.Semantic.DeepEqual(previousCopy.Status, currentCopy.Status) {
|
|
if err := r.Status().Update(ctx, current); err != nil {
|
|
return fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// compareZonefileConfigMaps compares two configmaps with the zonefile.
|
|
// during the check it will remove serial number. Returns true if they the same.
|
|
func compareZonefileConfigMaps(previousCM, upcomingCM *corev1.ConfigMap) bool {
|
|
previousCMCopy := previousCM.DeepCopy()
|
|
upcomingCMCopy := upcomingCM.DeepCopy()
|
|
|
|
// Remove serial number from both conifgMaps
|
|
for k, v := range previousCMCopy.Data {
|
|
previousCMCopy.Data[k] = removeSerialNumber(v)
|
|
}
|
|
|
|
for k, v := range upcomingCMCopy.Data {
|
|
upcomingCMCopy.Data[k] = removeSerialNumber(v)
|
|
}
|
|
|
|
// Do check
|
|
return equality.Semantic.DeepEqual(previousCMCopy.Data, upcomingCMCopy.Data)
|
|
}
|
|
|
|
// removeSerialNumber used to remove serial number from the zonefile string
|
|
func removeSerialNumber(zonefile string) string {
|
|
lines := strings.Split(zonefile, "\n")
|
|
for i, line := range lines {
|
|
if strings.Contains(line, "Serial") {
|
|
// Line for example: " 0525132744 ; Serial"
|
|
lines[i] = strings.Split(line, "\t")[0] + " ; Serial"
|
|
}
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
// dnsRecordChangedReconcileRequest requests DNSZone reconcilation if DNSRecord has been created/updated/deleted.
|
|
func (r *DNSZoneReconciler) dnsRecordChangedReconcileRequest(ctx context.Context, dnsRecord client.Object) []reconcile.Request {
|
|
_ = log.FromContext(ctx)
|
|
dnsRecordObj, ok := dnsRecord.(*monkalev1alpha1.DNSRecord)
|
|
if !ok {
|
|
log.Log.Error(nil, "DNSZone instance. Failed to cast dnsRecord to monkalev1alpha1.DNSRecord")
|
|
return []reconcile.Request{}
|
|
}
|
|
// delay before trying to reconcile. it avoids false positive logs
|
|
time.Sleep(3 * time.Second)
|
|
log.Log.Info("DNSZone instance. DNSRecord change detected. Requesting reconcilation for the zone", "DNSZone.Name", dnsRecordObj.Spec.DNSZoneRef.Name)
|
|
// Create a reconcile request for the associated DNSZone
|
|
return []reconcile.Request{
|
|
{
|
|
NamespacedName: types.NamespacedName{
|
|
Name: dnsRecordObj.Spec.DNSZoneRef.Name,
|
|
Namespace: dnsRecordObj.GetNamespace(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// SetupWithManager sets up the controller with the Manager.
|
|
// https://book.kubebuilder.io/reference/watching-resources/externally-managed
|
|
func (r *DNSZoneReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
|
// index DNSZone Reference name
|
|
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &monkalev1alpha1.DNSRecord{}, monkalev1alpha1.DnsRecordIndex, func(rawObj client.Object) []string {
|
|
// Extract the DNSZone name from the DNSRecord Spec
|
|
dnsRecord := rawObj.(*monkalev1alpha1.DNSRecord)
|
|
if dnsRecord.Spec.DNSZoneRef.Name == "" {
|
|
return nil
|
|
}
|
|
return []string{dnsRecord.Spec.DNSZoneRef.Name}
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// DNSZone is primary resource, DNSRecord is secondary.
|
|
return ctrl.NewControllerManagedBy(mgr).
|
|
For(&monkalev1alpha1.DNSZone{}).WithEventFilter(predicate.GenerationChangedPredicate{}).
|
|
Watches(
|
|
&monkalev1alpha1.DNSRecord{},
|
|
handler.EnqueueRequestsFromMapFunc(r.dnsRecordChangedReconcileRequest),
|
|
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
|
|
Complete(r)
|
|
}
|