From 440f31fede35b7d8a45574b67fc503f44e8a4bbd Mon Sep 17 00:00:00 2001 From: Kevin Midboe Date: Mon, 12 Feb 2024 12:26:09 +0100 Subject: [PATCH] Moved all source files to src/ --- README.md | 12 +++- main.py | 65 ------------------ src/__init__.py | 7 ++ bulk_dns_update.py => src/bulk_dns_update.py | 25 +++++-- logger.py => src/logger.py | 0 src/main.py | 70 ++++++++++++++++++++ notify.py => src/notify.py | 0 7 files changed, 106 insertions(+), 73 deletions(-) delete mode 100644 main.py create mode 100644 src/__init__.py rename bulk_dns_update.py => src/bulk_dns_update.py (78%) rename logger.py => src/logger.py (100%) create mode 100644 src/main.py rename notify.py => src/notify.py (100%) diff --git a/README.md b/README.md index dfd3887..93eafbf 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ virtualenv source -p $(which python3) env/bin/activate pip install -r requirements.txt -python main.py +python src/main.py ``` ## Kubernetes @@ -46,8 +46,14 @@ metadata: namespace: cloudflare-ddns type: Opaque data: - API_KEY: BASE_64_ENCODED_CLOUDFLARE_API_KEY - DDNS_ZONE: BASE64_ENCODED_CLOUDFLARE_ZONE_ID + API_KEY: CLOUDFLARE_API_KEY + DDNS_ZONE: CLOUDFLARE_ZONE_ID +``` + +(Optional: receive a SMS from gateway API by appending to secrets data) +```yaml + SMS_API_KEY: GATEWAY_API_API_KEY + SMS_RECIPIENT: PHONE_NUMBER_TO_RECEIVE ``` ``` diff --git a/main.py b/main.py deleted file mode 100644 index f97342c..0000000 --- a/main.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import re -import requests -from bulk_dns_update import updateAllZones, setAPIKey, getDDNSAddresszoneId -from notify import notify -from dotenv import load_dotenv -from logger import logger - -load_dotenv() - -currentIP = None -recordedIP = None -DDNS_ZONE = os.getenv('DDNS_ZONE') - - -def validIP(ipString): - ipRegex = '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' - return re.search(ipRegex, ipString) - -def publicAddress(): - global currentIP - logger.info('Getting public IP from ifconfg.me...') - - r = requests.get('https://ifconfig.me') - if r.status_code != 200 or not validIP(r.text): - return - - currentIP = r.text - logger.info('Public IP: {}'.format(currentIP)) - - -def cloudflareDDNS(): - global recordedIP - logger.info('Checking IP recorded in Cloudflare...') - ddnsRecord = getDDNSAddresszoneId(DDNS_ZONE) - recordedIP = ddnsRecord['content'] - logger.info('Found ddns recorded IP: {}'.format(recordedIP)) - - if currentIP != recordedIP and validIP(recordedIP): - logger.info('Public IP has changed, updating all A records.') - return True - else: - logger.info('is same, exiting') - return False - - -def main(): - apiKey = os.getenv('API_KEY') - if apiKey is None: - raise Exception('In .env file or environment set Cloudflare variable: API_KEY') - if DDNS_ZONE is None: - raise Exception('In .env file or environment; set Cloudflare zone where addr. points to current IP.') - - setAPIKey(apiKey) - - publicAddress() - changed = cloudflareDDNS() - - if changed is True: - notify("IP changed to: {}. Updating all cloudflare zones!".format(currentIP)) - updateAllZones(recordedIP, currentIP) - - -if __name__ == '__main__': - main() diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..b9fa92b --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,7 @@ +import os +import sys +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) diff --git a/bulk_dns_update.py b/src/bulk_dns_update.py similarity index 78% rename from bulk_dns_update.py rename to src/bulk_dns_update.py index ab62cfe..f63d595 100644 --- a/bulk_dns_update.py +++ b/src/bulk_dns_update.py @@ -63,6 +63,11 @@ def getZones(): url = 'https://api.cloudflare.com/client/v4/zones' data = cloudflareRequest(url) + if 'success' not in data and 'errors' not in data: + logger.info("Unexpected cloudflare error when getting zones, no response!") + logger.info("data:" + str(data)) + raise Exception('Unexpected Cloudflare error, missing response! Check logs.') + if data['success'] is False: logger.info('Request to cloudflare was unsuccessful, error:') logger.info(data['errors']) @@ -77,9 +82,15 @@ def getZones(): def getRecordsForZone(zoneId): - url = 'https://api.cloudflare.com/client/v4/zones/{}/dns_records?type=A'.format(zoneId) + url = 'https://api.cloudflare.com/client/v4/zones/{}/dns_records?type=A'.format( + zoneId) data = cloudflareRequest(url) + if 'success' not in data and 'errors' not in data: + logger.info("Unexpected cloudflare error when getting records, no response!") + logger.info("data:" + str(data)) + raise Exception('Unexpected Cloudflare error, missing response! Check logs.') + if data['success'] is False: logger.info('Request from cloudflare was unsuccessful, error:') logger.info(data['errors']) @@ -105,7 +116,8 @@ def getDDNSAddresszoneId(ddnsZone): def updateRecord(zoneId, recordId, name, newIP, ttl, proxied): - url = 'https://api.cloudflare.com/client/v4/zones/{}/dns_records/{}'.format(zoneId, recordId) + url = 'https://api.cloudflare.com/client/v4/zones/{}/dns_records/{}'.format( + zoneId, recordId) data = { 'type': 'A', 'name': name, @@ -115,7 +127,8 @@ def updateRecord(zoneId, recordId, name, newIP, ttl, proxied): } response = cloudflareUpdateRequest(url, data) - logger.info('\tRecord updated: {}'.format('✅' if response['success'] is True else '❌')) + logger.info('\tRecord updated: {}'.format( + '✅' if response['success'] is True else '❌')) def getMatchingRecordsForZone(zoneId, oldIP): @@ -131,8 +144,10 @@ def updateZone(zone, oldIP, newIP): return for record in records: - logger.info('\tRecord {}: {} -> {}'.format(record['name'], record['content'], newIP)) - updateRecord(zone['id'], record['id'], record['name'], newIP, record['ttl'], record['proxied']) + logger.info( + '\tRecord {}: {} -> {}'.format(record['name'], record['content'], newIP)) + updateRecord(zone['id'], record['id'], record['name'], + newIP, record['ttl'], record['proxied']) def updateAllZones(oldIP, newIP): diff --git a/logger.py b/src/logger.py similarity index 100% rename from logger.py rename to src/logger.py diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..01f8d10 --- /dev/null +++ b/src/main.py @@ -0,0 +1,70 @@ +import os +import re +import requests +from bulk_dns_update import updateAllZones, setAPIKey, getDDNSAddresszoneId +from notify import notify +from dotenv import load_dotenv +from logger import logger + +load_dotenv() + +DDNS_ZONE = os.getenv('DDNS_ZONE') + + +def validIP(ipString): + ipRegex = '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' + return bool(re.search(ipRegex, str(ipString))) + + +def publicAddress(): + logger.info('Getting public IP from ifconfg.me...') + + r = requests.get('https://ifconfig.me') + if r.status_code != 200: + return + + ip = r.text + if not validIP(ip): + return + + logger.info('Public IP: {}'.format(ip)) + return ip + + +def cloudflareDDNS(): + logger.info('Checking IP recorded in Cloudflare...') + ddnsRecord = getDDNSAddresszoneId(DDNS_ZONE) + ip = ddnsRecord['content'] + logger.info('Found ddns recorded IP: {}'.format(ip)) + + if not validIP(ip): + return + + return ip + + +def main(): + apiKey = os.getenv('API_KEY') + if apiKey is None: + raise Exception( + 'In .env file or environment set Cloudflare variable: API_KEY') + if DDNS_ZONE is None: + raise Exception( + 'In .env file or environment; set Cloudflare zone where addr. points to current IP.') + + setAPIKey(apiKey) + + currentIP = publicAddress() + recordedIP = cloudflareDDNS() + + if currentIP == recordedIP or None in [currentIP, recordedIP]: + logger.info('is same, exiting') + return + + logger.info('Public IP has changed, updating all A records.') + notify("IP changed to: {}. Updating all cloudflare zones!".format(currentIP)) + updateAllZones(recordedIP, currentIP) + + +if __name__ == '__main__': + main() diff --git a/notify.py b/src/notify.py similarity index 100% rename from notify.py rename to src/notify.py