import os import requests import json import configparser import schedule import time from backend.hetzner_backend import HetznerBackend from backend.ovh_backend import OvhBackend def compare_zone_records(saved_records, new_records, backend_type): saved_records_dict = {} for record in saved_records["rrsets"]: saved_records_dict[record["name"]] = record["records"] new_records_dict = {} for record in new_records["rrsets"]: new_records_dict[record["name"]] = record["records"] if backend_type == "hetzner": backend = HetznerBackend() print("Using Hetzner backend") elif backend_type == "ovh": backend = OvhBackend() print("Using OVH backend") else: print(f"Specified backend not supported: {backend_type}") return for name, new_records_list in new_records_dict.items(): if name not in saved_records_dict: # Do create here backend.create_record(name, new_records_list) # Pass the name along with the content elif new_records_list != saved_records_dict[name]: # Do update here backend.update_record(name, new_records_list) # Pass the name along with the content for name, saved_records_list in saved_records_dict.items(): if name not in new_records_dict: # Do delete here backend.delete_record(name) # Pass the name to the delete_record method class PowerDNSProxy: def __init__(self, config_file='config.ini', zones_file='zones.ini'): self.api_url = None self.api_key = None self.config_file = config_file self.freq = 5 # Default frequency self.zones_with_backend = {} # Dictionary to store zones with their backend self.zones_file = zones_file # noinspection PyTypeChecker def load_config(self): config = configparser.ConfigParser() config.read(self.config_file) if "PowerDNS" not in config: print("PowerDNS section not found in config.ini") return False else: self.api_url = config["PowerDNS"]["api_endpoint"] self.api_key = config["PowerDNS"]["api_key"] if not self.api_url or not self.api_key: print("API URL or API Key not provided. Please check the configuration.") return False self.freq = int(config["PowerDNS"].get("frequency", 5)) # Set the frequency from config self.zones_with_backend = self.read_zones_from_config() # Call read_zones_from_config here return True def main(self): if not self.load_config(): return zones = self.get_zones() if type(zones) is int: print("Error getting zones: http code " + str(zones)) return tracked_zones = {} for zone in zones: zone_name = zone["name"] if zone_name in self.zones_with_backend: # Use self.zones_with_backend instead of config_zones tracked_zones[zone_name] = { "id": zone["id"], "serial": zone["serial"], "backend": self.zones_with_backend[zone_name]["backend"] } print("Zones tracked: " + str(len(tracked_zones))) with open("states.json", "w") as f: json.dump(tracked_zones, f, indent=4) print("States saved to states.json") # Define the scheduled job function def scheduled_job(): self.check_zone_serial() # Schedule the job to run every 'freq' seconds schedule.every(self.freq).seconds.do(scheduled_job) while True: schedule.run_pending() time.sleep(1) def get_zones(self): headers = {"X-API-Key": self.api_key} response = requests.get(self.api_url + "/zones", headers=headers) if response.status_code == 200: return response.json() else: return response.status_code def read_zones_from_config(self): config = configparser.ConfigParser(allow_no_value=True) config.read(self.zones_file) zones_with_backend = {} for section in config.sections(): backend = "default" # Initialize the backend variable if "backend" in config[section]: backend = config[section]["backend"].strip('"') # Remove any quotes around the backend value zones_with_backend[section.strip()] = {"backend": backend} return zones_with_backend def get_zone_records(self, zone_id): headers = {"X-API-Key": self.api_key} response = requests.get(self.api_url + f"/zones/{zone_id}", headers=headers) if response.status_code == 200: return response.json() else: print(f"Error getting zone records: http code {response.status_code}") return None def save_zone_records(self, zone_data, backend): zone_name = zone_data["name"] zone_id = zone_data["id"] records = self.get_zone_records(zone_id) if records is not None: states_dir = "states" if not os.path.exists(states_dir): os.makedirs(states_dir) filename = os.path.join(states_dir, f"{zone_name}.json") try: with open(filename, "r") as f: saved_records = json.load(f) except (FileNotFoundError, json.JSONDecodeError): print(f"File {filename} not found or invalid, creating new file") saved_records = [] with open(filename, "w") as f: json.dump(records, f, indent=4) print(f"Zone records for {zone_name} saved to {filename}") compare_zone_records(saved_records, records, backend) else: print(f"Error getting zone records for {zone_name}") def check_zone_serial(self): zones = self.get_zones() # Open states.json with open("states.json", "r") as f: tracked_zones = json.load(f) print("Checking zone serials from saved states") for zone in zones: if zone["name"] in tracked_zones: if tracked_zones[zone["name"]]["serial"] != zone["serial"]: print("Zone " + zone["name"] + " has changed!") self.save_zone_records(zone, tracked_zones[zone["name"]]["backend"]) tracked_zones[zone["name"]]["serial"] = zone["serial"] # Update the state.json file with the updated tracked_zones data with open("states.json", "w") as f: json.dump(tracked_zones, f, indent=4) if __name__ == "__main__": pdns_proxy = PowerDNSProxy() pdns_proxy.main()