mirror of
https://github.com/hanzoai/coredns-manager-operator.git
synced 2026-10-30 18:43:53 +00:00
247 lines
8.1 KiB
Go
247 lines
8.1 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 (
|
|
"fmt"
|
|
"strings"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
monkalev1alpha1 "github.com/monkale.io/coredns-manager-operator/api/v1alpha1"
|
|
)
|
|
|
|
// generateCorefile is used to generate Corefile based on originalCorefile(string) and DNSZone's zonefile configMaps.
|
|
// receives original corednsConfCM and zoneConfigMaps as args.
|
|
func generateCorefileCM(dnsConnector *monkalev1alpha1.DNSConnector, corednsConfCM *corev1.ConfigMap, zoneConfigMaps *corev1.ConfigMapList) (corev1.ConfigMap, error) {
|
|
corefileConfigBlockStartPrefix := "# COREDNS CONTROLLER MANAGED BLOCK BEGINNING -- "
|
|
corefileConfigBlockEndPrefix := "# COREDNS CONTROLLER MANAGED BLOCK END -- "
|
|
corefileBlocks := make(map[string]string)
|
|
configMapDomains := make(map[string]bool)
|
|
var newCorefileBuilder strings.Builder
|
|
|
|
cmDataKey := dnsConnector.Spec.CorednsCM.CorefileKey
|
|
newCorednsConfCM := corednsConfCM.DeepCopy()
|
|
|
|
corednsCorefileContent, ok := corednsConfCM.Data[cmDataKey]
|
|
if !ok {
|
|
return corev1.ConfigMap{}, fmt.Errorf("key %s not found in CoreDNS ConfigMap", cmDataKey)
|
|
}
|
|
|
|
for _, configMap := range zoneConfigMaps.Items {
|
|
// extract domain
|
|
domainName, ok := configMap.Annotations["DomainName"]
|
|
if !ok {
|
|
return corev1.ConfigMap{}, fmt.Errorf("configMap %s does not have a domain annotation", configMap.Name)
|
|
}
|
|
|
|
// get enabled plugins
|
|
pluginString := ""
|
|
for _, plugin := range dnsConnector.Spec.CorednsZoneEnaledPlugins {
|
|
pluginString += fmt.Sprintf("\n\t%s", plugin)
|
|
}
|
|
|
|
// Ensure that zonefile contains zonefile in the cm.data
|
|
zonefileName := monkalev1alpha1.EnsureFQDN(domainName) + "zone"
|
|
if _, ok := configMap.Data[zonefileName]; !ok {
|
|
return corev1.ConfigMap{}, fmt.Errorf("configMap %s does not contain zonefile data", configMap.Name)
|
|
}
|
|
|
|
// generate zone config block
|
|
configBlock := fmt.Sprintf(`
|
|
%s %s
|
|
%s:53 {
|
|
file %s/%s%s
|
|
}
|
|
%s %s`, corefileConfigBlockStartPrefix, domainName, domainName, dnsConnector.Spec.CorednsDeployment.ZoneFileMountDir, zonefileName, pluginString, corefileConfigBlockEndPrefix, domainName)
|
|
|
|
corefileBlocks[domainName] = configBlock
|
|
configMapDomains[domainName] = true
|
|
}
|
|
|
|
// iterate over corefile
|
|
lines := strings.Split(corednsCorefileContent, "\n")
|
|
inBlock := false
|
|
|
|
for _, line := range lines {
|
|
trimmedLine := strings.TrimSpace(line)
|
|
if strings.HasPrefix(trimmedLine, corefileConfigBlockStartPrefix) {
|
|
inBlock = true
|
|
currentDomain := strings.TrimPrefix(trimmedLine, corefileConfigBlockStartPrefix)
|
|
if _, exists := corefileBlocks[currentDomain]; exists {
|
|
newCorefileBuilder.WriteString(corefileBlocks[currentDomain])
|
|
delete(corefileBlocks, currentDomain)
|
|
}
|
|
} else if strings.HasPrefix(trimmedLine, corefileConfigBlockEndPrefix) {
|
|
inBlock = false
|
|
} else if !inBlock {
|
|
newCorefileBuilder.WriteString(line + "\n")
|
|
}
|
|
}
|
|
|
|
for _, block := range corefileBlocks {
|
|
newCorefileBuilder.WriteString(block)
|
|
}
|
|
|
|
corefileContent := newCorefileBuilder.String()
|
|
// modify configmap
|
|
newCorednsConfCM.Data[cmDataKey] = corefileContent
|
|
|
|
return *newCorednsConfCM, nil
|
|
}
|
|
|
|
// getDesiredVolumes iterates over zone configmaps list and returns a map where the key is volume name based on the
|
|
// domain name annotation, and values are two string: configMap.Name and configmap.Data zone key
|
|
func getDesiredVolumes(configMaps *corev1.ConfigMapList) (map[string][2]string, error) {
|
|
desiredVolumes := make(map[string][2]string)
|
|
for _, configMap := range configMaps.Items {
|
|
domainName, ok := configMap.Annotations["DomainName"]
|
|
if !ok {
|
|
return nil, fmt.Errorf("configMap %s does not have a domain annotation", configMap.Name)
|
|
}
|
|
volumeName := fmt.Sprintf("dnszone-%s", strings.ReplaceAll(domainName, ".", "-"))
|
|
volumeName = strings.TrimSuffix(volumeName, "-")
|
|
|
|
if len(configMap.Data) != 1 {
|
|
return nil, fmt.Errorf("configMap %s should contain only one key", configMap.Name)
|
|
}
|
|
var cmZoneKey string
|
|
for k := range configMap.Data {
|
|
cmZoneKey = k
|
|
break
|
|
}
|
|
|
|
desiredVolumes[volumeName] = [2]string{configMap.Name, cmZoneKey}
|
|
}
|
|
return desiredVolumes, nil
|
|
}
|
|
|
|
// setZoneFileConifgMaps attaches ConfigMaps to a CoreDNS deployment by adding them as volumes
|
|
// and volume mounts to the PodTemplateSpec of the provided StatefulSet, Deployment, or DaemonSet.
|
|
// It returns the modified deployment object.
|
|
func setZoneFileConifgMaps(dnsConnector monkalev1alpha1.DNSConnector, corednsDeployment client.Object, configMaps *corev1.ConfigMapList) (client.Object, error) {
|
|
var podTemplateSpec *corev1.PodTemplateSpec
|
|
switch res := corednsDeployment.(type) {
|
|
case *appsv1.StatefulSet:
|
|
podTemplateSpec = &res.Spec.Template
|
|
case *appsv1.Deployment:
|
|
podTemplateSpec = &res.Spec.Template
|
|
case *appsv1.DaemonSet:
|
|
podTemplateSpec = &res.Spec.Template
|
|
default:
|
|
return nil, fmt.Errorf("unsupported resource type: %T", res)
|
|
}
|
|
|
|
// trigger coredns deployment reconciliation
|
|
if podTemplateSpec.Annotations == nil {
|
|
podTemplateSpec.Annotations = make(map[string]string)
|
|
}
|
|
podTemplateSpec.Annotations["reconcilation-request"] = fmt.Sprintf("%d", metav1.Now().Unix())
|
|
|
|
// get desired volumes from the provided configmaps
|
|
desiredVolumes, err := getDesiredVolumes(configMaps)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// filter exisitng volumes and volume mounts to remove old DNS zone volume
|
|
newVolumes := make([]corev1.Volume, 0)
|
|
newVolumeMounts := make([]corev1.VolumeMount, 0)
|
|
for _, volume := range podTemplateSpec.Spec.Volumes {
|
|
if strings.HasPrefix(volume.Name, "dnszone-") {
|
|
if _, exists := desiredVolumes[volume.Name]; exists {
|
|
newVolumes = append(newVolumes, volume) // keep desired volume
|
|
}
|
|
} else {
|
|
newVolumes = append(newVolumes, volume) // keep other volume
|
|
}
|
|
}
|
|
|
|
for _, volumeMount := range podTemplateSpec.Spec.Containers[0].VolumeMounts {
|
|
if strings.HasPrefix(volumeMount.Name, "dnszone-") {
|
|
if _, exists := desiredVolumes[volumeMount.Name]; exists {
|
|
newVolumeMounts = append(newVolumeMounts, volumeMount) // keep desired volumemount
|
|
}
|
|
} else {
|
|
newVolumeMounts = append(newVolumeMounts, volumeMount) // keep other volume mounts
|
|
}
|
|
}
|
|
|
|
// init volume and volumemounts
|
|
for volumeName, value := range desiredVolumes {
|
|
configMapName := value[0]
|
|
cmZoneKey := value[1]
|
|
volume := corev1.Volume{
|
|
Name: volumeName,
|
|
VolumeSource: corev1.VolumeSource{
|
|
ConfigMap: &corev1.ConfigMapVolumeSource{
|
|
LocalObjectReference: corev1.LocalObjectReference{
|
|
Name: configMapName,
|
|
},
|
|
Items: []corev1.KeyToPath{
|
|
{
|
|
Key: cmZoneKey,
|
|
Path: cmZoneKey,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
volumeMount := corev1.VolumeMount{
|
|
Name: volumeName,
|
|
MountPath: fmt.Sprintf("%s/%s", dnsConnector.Spec.CorednsDeployment.ZoneFileMountDir, cmZoneKey),
|
|
SubPath: cmZoneKey,
|
|
ReadOnly: true,
|
|
}
|
|
// Check if the volume already exists
|
|
volumeExists := false
|
|
for _, v := range newVolumes {
|
|
if v.Name == volumeName {
|
|
volumeExists = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// Check if the volume mount already exists
|
|
volumeMountExists := false
|
|
for _, vm := range newVolumeMounts {
|
|
if vm.MountPath == volumeMount.MountPath {
|
|
volumeMountExists = true
|
|
break
|
|
}
|
|
}
|
|
// Add the volume and volume mount if they don't already exist
|
|
if !volumeExists {
|
|
newVolumes = append(newVolumes, volume)
|
|
}
|
|
if !volumeMountExists {
|
|
newVolumeMounts = append(newVolumeMounts, volumeMount)
|
|
}
|
|
}
|
|
|
|
// update podtemplate with new volumes and volumemount
|
|
podTemplateSpec.Spec.Volumes = newVolumes
|
|
for i := range podTemplateSpec.Spec.Containers {
|
|
podTemplateSpec.Spec.Containers[i].VolumeMounts = newVolumeMounts
|
|
}
|
|
|
|
return corednsDeployment, nil
|
|
}
|