mirror of
https://github.com/hanzoai/coredns-manager-operator.git
synced 2026-10-30 18:43:53 +00:00
249 lines
11 KiB
Go
249 lines
11 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"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"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/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
|
|
|
monkalev1alpha1 "github.com/monkale.io/coredns-manager-operator/api/v1alpha1"
|
|
)
|
|
|
|
// DNSRecordReconciler reconciles a DNSRecord object
|
|
type DNSRecordReconciler struct {
|
|
client.Client
|
|
Scheme *runtime.Scheme
|
|
}
|
|
|
|
// +kubebuilder:rbac:groups=monkale.monkale.io,resources=dnsrecords,verbs=get;list;watch;create;update;patch;delete
|
|
// +kubebuilder:rbac:groups=monkale.monkale.io,resources=dnsrecords/status,verbs=get;update;patch
|
|
// +kubebuilder:rbac:groups=monkale.monkale.io,resources=dnsrecords/finalizers,verbs=update
|
|
|
|
// For more details, check Reconcile and its Result here:
|
|
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
|
|
func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
|
_ = log.FromContext(ctx)
|
|
var dnsRecord monkalev1alpha1.DNSRecord
|
|
|
|
// Fetch DNSRecord from kubernetes
|
|
if err := r.Get(ctx, req.NamespacedName, &dnsRecord); err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return ctrl.Result{}, nil
|
|
} else if !apierrors.IsNotFound(err) {
|
|
log.Log.Error(err, "DNSRecord instance. Failed to get DNSRecord", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
}
|
|
|
|
// DNSRecord has been just created. Add finalizer.
|
|
if !controllerutil.ContainsFinalizer(&dnsRecord, monkalev1alpha1.DnsRecorsFinalizerName) {
|
|
dnsRecObj := types.NamespacedName{Name: dnsRecord.Name, Namespace: dnsRecord.Namespace}
|
|
clientK8sObj := dnsRecord.DeepCopy()
|
|
if err := addFinalizer(ctx, r.Client, dnsRecObj, clientK8sObj, monkalev1alpha1.DnsRecorsFinalizerName); err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Failed to add finalizer", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
return ctrl.Result{Requeue: true}, nil
|
|
}
|
|
|
|
// DNSRecord has been deleted. Reconcile Delete
|
|
if !dnsRecord.DeletionTimestamp.IsZero() {
|
|
log.Log.Info("DNSRecord instance. Record is being deleted", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return r.reconcileDelete(ctx, &dnsRecord)
|
|
}
|
|
|
|
// DNSRecord has been created or updated. Reconsile create or update
|
|
log.Log.Info("DNSRecord instance. Reconciling", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return r.reconcileDNSRecord(ctx, &dnsRecord)
|
|
}
|
|
|
|
// reconcileDelete reconciles if DNSRecord resource has been removed.
|
|
func (r *DNSRecordReconciler) reconcileDelete(ctx context.Context, dnsRecord *monkalev1alpha1.DNSRecord) (ctrl.Result, error) {
|
|
_ = log.FromContext(ctx)
|
|
// Remove finazlizer from DNSRecord
|
|
dnsRecObj := types.NamespacedName{Name: dnsRecord.Name, Namespace: dnsRecord.Namespace}
|
|
clientK8sObj := dnsRecord.DeepCopy()
|
|
log.Log.Info("DNSRecord instance. DNSRecord is being deleted. Removing finalizer from DNSRecord", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
if err := removeFinalizer(ctx, r.Client, dnsRecObj, clientK8sObj, monkalev1alpha1.DnsRecorsFinalizerName); err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Failed to delete finalizer", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
|
|
// Done
|
|
log.Log.Info("DNSRecord instance. The DNSRecord has been deleted.", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
// reconcileDNSRecord - processes DNSRecord. It generates the entry for the zonefile, performs syntax check and updates Status.
|
|
func (r *DNSRecordReconciler) reconcileDNSRecord(ctx context.Context, dnsRecord *monkalev1alpha1.DNSRecord) (ctrl.Result, error) {
|
|
_ = log.FromContext(ctx)
|
|
recordType := dnsRecord.Spec.Record.Type
|
|
switch recordType {
|
|
case "A":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "AAAA":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "CNAME":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "MX":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "TXT":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "NS":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "PTR":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "SRV":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "CAA":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "DNSKEY":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "DS":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "NAPTR":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "RRSIG":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "DNAME":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
case "HINFO":
|
|
_, err := r.handleGenericRecord(ctx, dnsRecord)
|
|
if err != nil {
|
|
log.Log.Error(err, "DNSRecord instance. Proccess Record Failure", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
default:
|
|
err := errors.New("unknown redord type: " + recordType)
|
|
log.Log.Error(err, "DNSRecord instance. Bad record Type", "DNSRecord.Name", dnsRecord.Name, "DNSZone.Name", dnsRecord.Spec.DNSZoneRef.Name)
|
|
return ctrl.Result{}, err
|
|
}
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
// setDnsRecordCondition adds or updates a given condition in the DNSRecord status.
|
|
func setDnsRecordCondition(dnsRecord *monkalev1alpha1.DNSRecord, status metav1.ConditionStatus, reason string, message string) {
|
|
now := metav1.Now()
|
|
cond := metav1.Condition{
|
|
Type: string(monkalev1alpha1.ConditionRecordTypeReady),
|
|
Status: status,
|
|
LastTransitionTime: now,
|
|
Reason: reason,
|
|
Message: message,
|
|
ObservedGeneration: dnsRecord.Generation,
|
|
}
|
|
meta.SetStatusCondition(&dnsRecord.Status.Conditions, cond)
|
|
}
|
|
|
|
// refreshDNSRecordResource fetch from kubernetes a new version of DNSRecordResource
|
|
func (r *DNSRecordReconciler) refreshDNSRecordResource(ctx context.Context, dnsRecord *monkalev1alpha1.DNSRecord) error {
|
|
dnsRecObj := types.NamespacedName{Name: dnsRecord.Name, Namespace: dnsRecord.Namespace}
|
|
clientK8sObj := dnsRecord.DeepCopy()
|
|
if err := getObjFromK8s(ctx, r.Client, dnsRecObj, clientK8sObj); err != nil {
|
|
return fmt.Errorf("failed to refresh DNSRecord resource: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// dnsRecordUpdateStatus updates the status of the DNSRecord Update Status, only if status has been changed
|
|
func (r *DNSRecordReconciler) dnsRecordUpdateStatus(ctx context.Context, previous, current *monkalev1alpha1.DNSRecord) error {
|
|
if !equality.Semantic.DeepEqual(previous.Status, current.Status) {
|
|
if err := r.Status().Update(ctx, current); err != nil {
|
|
return fmt.Errorf("failed to update status and condition: %v", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetupWithManager sets up the controller with the Manager.
|
|
// Watches https://book.kubebuilder.io/reference/watching-resources/externally-managed
|
|
func (r *DNSRecordReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
|
return ctrl.NewControllerManagedBy(mgr).
|
|
For(&monkalev1alpha1.DNSRecord{}).
|
|
WithEventFilter(predicate.GenerationChangedPredicate{}).
|
|
Complete(r)
|
|
}
|