Works with digital ocean api. Need to work out the env stuff

This commit is contained in:
tanshu 2020-04-28 01:31:54 +05:30
parent 31566bf459
commit 2d7414f02b
4 changed files with 111 additions and 44 deletions

98
bifrost/digital_ocean.py Normal file
View File

@ -0,0 +1,98 @@
import json
import requests
def create_domain_a_record(domain, name, ip_address, api_url_base, api_token, logger):
headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {0}'.format(api_token)}
api_url = f"{api_url_base}/domains/{domain}/records"
data = {"type": "A", "name": name, "data": ip_address, "priority": None, "port": None, "ttl": 1800, "weight": None,
"flags": None, "tag": None}
response = requests.post(api_url, headers=headers, json=data)
if response.status_code > 499:
logger.error(f"[!] [{response.status_code}] Server Error")
return None
elif response.status_code == 404:
logger.error(f"[!] [{response.status_code}] URL not found: [{api_url}]")
return None
elif response.status_code == 401:
logger.error(f"[!] [{response.status_code}] Authentication Failed")
return None
elif response.status_code > 399:
logger.error(f"[!] [{response.status_code}] Bad Request")
logger.error(f"Domain: {domain}, Name: {name}, New IP Address: {ip_address}")
logger.error(response.content)
return None
elif response.status_code > 299:
logger.error(f"[!] [{response.status_code}] Unexpected redirect")
return None
elif response.status_code == 201:
logger.info(f'{domain} updated to {ip_address}')
return json.loads(response.content)
else:
logger.error(f"[?] Unexpected Error: [HTTP {response.status_code}]: Content: {response.content}")
return None
def update_domain_a_record(domain, record_id, new_ip_address, api_url_base, api_token, logger):
headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {0}'.format(api_token)}
api_url = f"{api_url_base}/domains/{domain}/records/{record_id}"
data = {"data": new_ip_address}
response = requests.put(api_url, headers=headers, json=data)
if response.status_code > 499:
logger.error(f"[!] [{response.status_code}] Server Error")
return None
elif response.status_code == 404:
logger.error(f"[!] [{response.status_code}] URL not found: [{api_url}]")
return None
elif response.status_code == 401:
logger.error(f"[!] [{response.status_code}] Authentication Failed")
return None
elif response.status_code > 399:
logger.error(f"[!] [{response.status_code}] Bad Request")
logger.error(f"Domain: {domain}, Record ID: {record_id}, New IP Address: {new_ip_address}")
logger.error(response.content)
return None
elif response.status_code > 299:
logger.error(f"[!] [{response.status_code}] Unexpected redirect")
return None
elif response.status_code == 201:
logger.info(f'{domain} updated to {new_ip_address}')
return json.loads(response.content)
else:
logger.error(f"[?] Unexpected Error: [HTTP {response.status_code}]: Content: {response.content}")
return None
def list_all_domain_records(domain, api_url_base, api_token, logger):
headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {0}'.format(api_token)}
api_url = f"{api_url_base}/domains/{domain}/records"
response = requests.get(api_url, headers=headers)
if response.status_code == 200:
return json.loads(response.content.decode('utf-8'))["domain_records"]
else:
logger.error(f"[?] Unexpected Error: [HTTP {response.status_code}]: Content: {response.content}")
return None
def get_domain_a_record(domain, name, api_url_base, api_token, logger):
for item in list_all_domain_records(domain, api_url_base, api_token, logger):
if item["name"] == name and item["type"] == "A":
return item
else:
return None
def update_domain(domain, name, new_ip_address, api_url_base, api_token, logger):
record = get_domain_a_record(domain, name, api_url_base, api_token, logger)
if record is None:
logger.info(f'Creating domain a record for {domain} with ip address {new_ip_address}')
create_domain_a_record(domain, name, new_ip_address, api_url_base, api_token, logger)
elif record["data"] != new_ip_address:
logger.info(f'Updating domain a record for {domain}. Old ip address {record["data"]}, new ip address {new_ip_address}')
update_domain_a_record(domain, record["id"], new_ip_address, api_url_base, api_token, logger)
else:
logger.info(f'Not updating domain a record for {domain}. IP address {record["data"]} is current')

View File

@ -1,20 +1,17 @@
import logging import logging
import os
import re
import xmlrpc.client
from pyramid.httpexceptions import HTTPUnauthorized from pyramid.httpexceptions import HTTPUnauthorized
from pyramid.security import forget, Authenticated from pyramid.security import forget, Authenticated
from pyramid.view import view_config, forbidden_view_config from pyramid.view import view_config, forbidden_view_config
from bifrost.digital_ocean import update_domain
log = logging.getLogger('bifrost.updater') log = logging.getLogger('bifrost.updater')
api_url_base = 'https://api.digitalocean.com/v2'
api_token = None
@view_config(route_name='update', request_param='domain', renderer='json', permission=Authenticated) @view_config(route_name='update', request_param='domain', renderer='json', permission=Authenticated)
def update_view(request): def update_view(request):
file = request.registry.settings['biforst.file']
username = request.registry.settings['webfaction.username']
password = request.registry.settings['webfaction.password']
current_ip = request.GET.get('ip', None) current_ip = request.GET.get('ip', None)
if current_ip is None: if current_ip is None:
if 'X-Forwarded-For' in request.headers: if 'X-Forwarded-For' in request.headers:
@ -22,43 +19,11 @@ def update_view(request):
else: else:
current_ip = request.remote_addr current_ip = request.remote_addr
domain = request.GET['domain'] name, domain = request.GET['domain'].split('.', maxsplit=1)
db = load(file) update_domain(domain, name, current_ip, api_url_base, api_token, log)
if domain in db and db[domain] != current_ip:
server = xmlrpc.client.ServerProxy('https://api.webfaction.com/')
session_id, account = server.login(username, password)
server.delete_dns_override(session_id, domain)
server.create_dns_override(session_id, domain, current_ip, '', '', '', '')
db[domain] = current_ip
update(file, db)
log.info('{0} updated to {1}'.format(domain, current_ip))
else:
log.info('{0} not updated'.format(domain))
return {} return {}
def load(file):
exp = re.compile(r"^([a-z1-9.]+):([0-9]{1,3}(?:\.[0-9]{1,3}){3})$", re.I)
if not os.path.isfile(file):
return {}
current = {}
with open(file) as f:
for line in f:
match = exp.match(line)
if match:
domain, ip = match.groups()
current[domain] = ip
return current
def update(file, db):
with open(file, 'w') as f:
f.writelines(['{0}:{1}\n'.format(key, value) for key, value in db.items()])
@forbidden_view_config() @forbidden_view_config()
def basic_challenge(request): def basic_challenge(request):
response = HTTPUnauthorized() response = HTTPUnauthorized()

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
pyramid
waitress
requests

View File

@ -9,10 +9,11 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
requires = [ requires = [
'pyramid', 'pyramid',
'waitress', 'waitress',
'requests'
] ]
setup(name='bifrost', setup(name='bifrost',
version='0.0', version='1.0',
description='bifrost', description='bifrost',
long_description=README + '\n\n' + CHANGES, long_description=README + '\n\n' + CHANGES,
classifiers=[ classifiers=[
@ -21,9 +22,9 @@ setup(name='bifrost',
"Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
], ],
author='', author='tanshu',
author_email='', author_email='programming@tanshu.com',
url='', url='https://git.tanshu.com/tanshu',
keywords='web pyramid pylons', keywords='web pyramid pylons',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,