diff --git a/main.go b/main.go index cb7214c1..c6a00bdc 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,12 @@ func main() { os.Exit(1) } - zoneManager := zonemgr.NewZoneManager() + zoneManager, err := zonemgr.NewZoneManager() + if err != nil { + setupLog.Error(err, "unable to create zone manager") + os.Exit(1) + } + if err = (&controllers.VirtualMachineInstanceReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("VirtualMachineInstance"), diff --git a/pkg/zonemgr/zone_file.go b/pkg/zonemgr/zone_file.go index f0fd3c69..9d7560c5 100644 --- a/pkg/zonemgr/zone_file.go +++ b/pkg/zonemgr/zone_file.go @@ -1,11 +1,17 @@ package zonemgr import ( + "errors" + "fmt" "os" + "regexp" + "strconv" ) const zoneFilePerm = 0644 +var soaSerialReg = regexp.MustCompile("SOA .*\\(([0-9]+) ") + type ZoneFile struct { zoneFileFullName string } @@ -19,3 +25,42 @@ func NewZoneFile(fileName string) *ZoneFile { func (zoneFile *ZoneFile) writeFile(content string) (err error) { return os.WriteFile(zoneFile.zoneFileFullName, []byte(content), zoneFilePerm) } + +func (zoneFile *ZoneFile) readFile() ([]byte, error) { + return os.ReadFile(zoneFile.zoneFileFullName) +} + +func (zoneFile *ZoneFile) isFileExist() (bool, error) { + var err error + isExist := false + if _, err = os.Stat(zoneFile.zoneFileFullName); err == nil { + isExist = true + } else if errors.Is(err, os.ErrNotExist) { + err = nil + } + return isExist, err +} + +func (zoneFile *ZoneFile) ReadSoaSerial() (*int, error) { + if isFileExist, err := zoneFile.isFileExist(); !isFileExist || err != nil { + return nil, err + } + if content, err := zoneFile.readFile(); content == nil || err != nil { + return nil, err + } else { + return fetchSoaSerial(string(content)) + } +} + +func fetchSoaSerial(content string) (*int, error) { + if result := soaSerialReg.FindStringSubmatch(content); result != nil && len(result) > 0 { + soaSerial := result[1] + if soaSerialInt, err := strconv.Atoi(soaSerial); err == nil { + return &soaSerialInt, nil + } else { + return nil, err + } + } else { + return nil, errors.New(fmt.Sprintf("failed to fetch SOA serial value from the zone file content: %s", content)) + } +} diff --git a/pkg/zonemgr/zone_file_cache.go b/pkg/zonemgr/zone_file_cache.go index 8f706963..4b05fdee 100644 --- a/pkg/zonemgr/zone_file_cache.go +++ b/pkg/zonemgr/zone_file_cache.go @@ -18,7 +18,6 @@ const ( expire = "1209600" // 2 weeks (seconds) - how long a nameserver should wait prior to considering data from a secondary zone invalid and stop answering queries for that zone ttl = "3600" // 1 hour (seconds) - the duration that the record may be cached by any resolver - domainDefault = "vm" nameServerDefault = "ns" adminEmailDefault = "email" ) @@ -40,10 +39,16 @@ type ZoneFileCache struct { vmiRecordsMap map[string][]string } -func NewZoneFileCache(nameServerIP string, domain string) *ZoneFileCache { +func NewZoneFileCache(nameServerIP string, domain string, soaSerial *int) *ZoneFileCache { + soaSerialInt := 0 + if soaSerial != nil { + soaSerialInt = *soaSerial + } + zoneFileCache := &ZoneFileCache{ nameServerIP: nameServerIP, domain: domain, + soaSerial: soaSerialInt, } zoneFileCache.prepare() return zoneFileCache @@ -53,18 +58,11 @@ func (zoneFileCache *ZoneFileCache) prepare() { zoneFileCache.initCustomFields() zoneFileCache.generateHeaderPrefix() zoneFileCache.generateHeaderSuffix() - zoneFileCache.soaSerial = 0 zoneFileCache.header = zoneFileCache.generateHeader() - zoneFileCache.content = zoneFileCache.header zoneFileCache.vmiRecordsMap = make(map[string][]string) } func (zoneFileCache *ZoneFileCache) initCustomFields() { - if zoneFileCache.domain == "" { - zoneFileCache.domain = domainDefault - } else { - zoneFileCache.domain = fmt.Sprintf("%s.%s", domainDefault, zoneFileCache.domain) - } zoneFileCache.nameServerName = fmt.Sprintf("%s.%s", nameServerDefault, zoneFileCache.domain) zoneFileCache.adminEmail = fmt.Sprintf("%s.%s", adminEmailDefault, zoneFileCache.domain) } diff --git a/pkg/zonemgr/zone_manager.go b/pkg/zonemgr/zone_manager.go index e1aa5263..748942f9 100644 --- a/pkg/zonemgr/zone_manager.go +++ b/pkg/zonemgr/zone_manager.go @@ -2,17 +2,18 @@ package zonemgr import ( "errors" + "fmt" "os" k8stypes "k8s.io/apimachinery/pkg/types" - v1 "kubevirt.io/api/core/v1" ) const ( - zoneFileName = "/zones/db." envVarDomain = "DOMAIN" envVarNameServerIP = "NAME_SERVER_IP" + zoneFileNamePrefix = "/zones/db." + domainDefault = "vm" ) type SecIfaceData struct { @@ -27,19 +28,27 @@ type ZoneManager struct { zoneFile *ZoneFile } -func NewZoneManager() *ZoneManager { +func NewZoneManager() (*ZoneManager, error) { zoneMgr := &ZoneManager{} - zoneMgr.prepare() - return zoneMgr + err := zoneMgr.prepare() + return zoneMgr, err } -func (zoneMgr *ZoneManager) prepare() { - domain := os.Getenv(envVarDomain) +func (zoneMgr *ZoneManager) prepare() error { + domain := domainDefault nameServerIP := os.Getenv(envVarNameServerIP) + if customDomain := os.Getenv(envVarDomain); customDomain != "" { + domain = fmt.Sprintf("%s.%s", domain, customDomain) + } + zoneFileName := zoneFileNamePrefix + domain + zoneMgr.zoneFile = NewZoneFile(zoneFileName) - zoneMgr.zoneFileCache = NewZoneFileCache(nameServerIP, domain) - - zoneMgr.zoneFile = NewZoneFile(zoneFileName + zoneMgr.zoneFileCache.domain) + soaSerial, err := zoneMgr.zoneFile.ReadSoaSerial() + if err != nil { + return err + } + zoneMgr.zoneFileCache = NewZoneFileCache(nameServerIP, domain, soaSerial) + return nil } func (zoneMgr *ZoneManager) UpdateZone(namespacedName k8stypes.NamespacedName, interfaces []v1.VirtualMachineInstanceNetworkInterface) error {