mirror of
https://github.com/cwinfo/powerdns-admin.git
synced 2024-12-28 14:05:41 +00:00
cb835978df
PDNS checks that when a `CNAME` rrset is created that no other rrset of the same name but a different rtype exists. When changing a record type to `CNAME`, PDA will send two operations in one api call to PDNS: A deletion of the old rrset, and the addition of the new rrset. For the check in PDNS to pass, the deletion needs to happen before the addition. Before PR #1201 that was the case, the first api call did deletions and the second handled additions and changes. Currently the api payload contains additions first and deletions last. PDNS applies these in the order they are passed in the payload to the api, so to restore the original/correct/working behaviour the order of operations in the api payload has to be reversed. fixes #1251
625 lines
25 KiB
Python
625 lines
25 KiB
Python
import re
|
|
import traceback
|
|
import dns.reversename
|
|
import dns.inet
|
|
import dns.name
|
|
from flask import current_app
|
|
from urllib.parse import urljoin
|
|
from distutils.util import strtobool
|
|
from itertools import groupby
|
|
|
|
from .. import utils
|
|
from .base import db
|
|
from .setting import Setting
|
|
from .domain import Domain
|
|
from .domain_setting import DomainSetting
|
|
|
|
|
|
def by_record_content_pair(e):
|
|
return e[0]['content']
|
|
|
|
|
|
class Record(object):
|
|
"""
|
|
This is not a model, it's just an object
|
|
which be assigned data from PowerDNS API
|
|
"""
|
|
def __init__(self,
|
|
name=None,
|
|
type=None,
|
|
status=None,
|
|
ttl=None,
|
|
data=None,
|
|
comment_data=None):
|
|
self.name = name
|
|
self.type = type
|
|
self.status = status
|
|
self.ttl = ttl
|
|
self.data = data
|
|
self.comment_data = comment_data
|
|
# PDNS configs
|
|
self.PDNS_STATS_URL = Setting().get('pdns_api_url')
|
|
self.PDNS_API_KEY = Setting().get('pdns_api_key')
|
|
self.PDNS_VERSION = Setting().get('pdns_version')
|
|
self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)
|
|
self.PRETTY_IPV6_PTR = Setting().get('pretty_ipv6_ptr')
|
|
|
|
def get_rrsets(self, domain):
|
|
"""
|
|
Query domain's rrsets via PDNS API
|
|
"""
|
|
headers = {'X-API-Key': self.PDNS_API_KEY}
|
|
try:
|
|
jdata = utils.fetch_json(urljoin(
|
|
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
|
|
'/servers/localhost/zones/{0}'.format(domain)),
|
|
timeout=int(
|
|
Setting().get('pdns_api_timeout')),
|
|
headers=headers,
|
|
verify=Setting().get('verify_ssl_connections'))
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
"Cannot fetch domain's record data from remote powerdns api. DETAIL: {0}"
|
|
.format(e))
|
|
return []
|
|
|
|
rrsets=[]
|
|
for r in jdata['rrsets']:
|
|
if len(r['records']) == 0:
|
|
continue
|
|
|
|
while len(r['comments'])<len(r['records']):
|
|
r['comments'].append({"content": "", "account": ""})
|
|
r['records'], r['comments'] = (list(t) for t in zip(*sorted(zip(r['records'], r['comments']), key=by_record_content_pair)))
|
|
rrsets.append(r)
|
|
|
|
return rrsets
|
|
|
|
def add(self, domain_name, rrset):
|
|
"""
|
|
Add a record to a domain (Used by auto_ptr and DynDNS)
|
|
|
|
Args:
|
|
domain_name(str): The zone name
|
|
rrset(dict): The record in PDNS rrset format
|
|
|
|
Returns:
|
|
(dict): A dict contains status code and message
|
|
"""
|
|
# Validate record first
|
|
rrsets = self.get_rrsets(domain_name)
|
|
check = list(filter(lambda check: check['name'] == self.name, rrsets))
|
|
if check:
|
|
r = check[0]
|
|
if r['type'] in ('A', 'AAAA', 'CNAME'):
|
|
return {
|
|
'status': 'error',
|
|
'msg':
|
|
'Record already exists with type "A", "AAAA" or "CNAME"'
|
|
}
|
|
|
|
# Continue if the record is ready to be added
|
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
|
|
|
try:
|
|
jdata = utils.fetch_json(urljoin(
|
|
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
|
|
'/servers/localhost/zones/{0}'.format(domain_name)),
|
|
headers=headers,
|
|
timeout=int(
|
|
Setting().get('pdns_api_timeout')),
|
|
method='PATCH',
|
|
verify=Setting().get('verify_ssl_connections'),
|
|
data=rrset)
|
|
current_app.logger.debug(jdata)
|
|
return {'status': 'ok', 'msg': 'Record was added successfully'}
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
"Cannot add record to domain {}. Error: {}".format(
|
|
domain_name, e))
|
|
current_app.logger.debug("Submitted record rrset: \n{}".format(
|
|
utils.pretty_json(rrset)))
|
|
return {
|
|
'status': 'error',
|
|
'msg':
|
|
'There was something wrong, please contact administrator'
|
|
}
|
|
|
|
def merge_rrsets(self, rrsets):
|
|
"""
|
|
Merge the rrsets that has same "name" and
|
|
"type".
|
|
Return: a new rrset which has multiple "records"
|
|
and "comments"
|
|
"""
|
|
if not rrsets:
|
|
raise Exception("Empty rrsets to merge")
|
|
elif len(rrsets) == 1:
|
|
# It is unique rrset already
|
|
return rrsets[0]
|
|
else:
|
|
# Merge rrsets into one
|
|
rrset = rrsets[0]
|
|
for r in rrsets[1:]:
|
|
rrset['records'] = rrset['records'] + r['records']
|
|
rrset['comments'] = rrset['comments'] + r['comments']
|
|
while len(rrset['comments']) < len(rrset['records']):
|
|
rrset['comments'].append({"content": "", "account": ""})
|
|
zipped_list = zip(rrset['records'], rrset['comments'])
|
|
tuples = zip(*sorted(zipped_list, key=by_record_content_pair))
|
|
rrset['records'], rrset['comments'] = [list(t) for t in tuples]
|
|
return rrset
|
|
|
|
def build_rrsets(self, domain_name, submitted_records):
|
|
"""
|
|
Build rrsets from the datatable's records
|
|
|
|
Args:
|
|
domain_name(str): The zone name
|
|
submitted_records(list): List of records submitted from PDA datatable
|
|
|
|
Returns:
|
|
transformed_rrsets(list): List of rrsets converted from PDA datatable
|
|
"""
|
|
rrsets = []
|
|
for record in submitted_records:
|
|
# Format the record name
|
|
#
|
|
# Translate template placeholders into proper record data
|
|
record['record_data'] = record['record_data'].replace('[ZONE]', domain_name)
|
|
# Translate record name into punycode (IDN) as that's the only way
|
|
# to convey non-ascii records to the dns server
|
|
record['record_name'] = utils.to_idna(record["record_name"], "encode")
|
|
#TODO: error handling
|
|
# If the record is an alias (CNAME), we will also make sure that
|
|
# the target domain is properly converted to punycode (IDN)
|
|
if record['record_type'] == 'CNAME' or record['record_type'] == 'SOA':
|
|
record['record_data'] = utils.to_idna(record['record_data'], 'encode')
|
|
#TODO: error handling
|
|
# If it is ipv6 reverse zone and PRETTY_IPV6_PTR is enabled,
|
|
# We convert ipv6 address back to reverse record format
|
|
# before submitting to PDNS API.
|
|
if self.PRETTY_IPV6_PTR and re.search(
|
|
r'ip6\.arpa', domain_name
|
|
) and record['record_type'] == 'PTR' and ':' in record[
|
|
'record_name']:
|
|
record_name = dns.reversename.from_address(
|
|
record['record_name']).to_text()
|
|
# Else, it is forward zone, then record name should be
|
|
# in format "<name>.<domain>.". If it is root
|
|
# domain name (name == '@' or ''), the name should
|
|
# be in format "<domain>."
|
|
else:
|
|
record_name = "{}.{}.".format(
|
|
record["record_name"],
|
|
domain_name) if record["record_name"] not in [
|
|
'@', ''
|
|
] else domain_name + '.'
|
|
|
|
# Format the record content, it musts end
|
|
# with a dot character if in following types
|
|
if record["record_type"] in [
|
|
'MX', 'CNAME', 'SRV', 'NS', 'PTR'
|
|
] and record["record_data"].strip()[-1:] != '.':
|
|
record["record_data"] += '.'
|
|
|
|
record_content = {
|
|
"content": record["record_data"],
|
|
"disabled":
|
|
False if record['record_status'] == 'Active' else True
|
|
}
|
|
|
|
# Format the comment
|
|
record_comments = [{
|
|
"content": record["record_comment"],
|
|
"account": ""
|
|
}] if record.get("record_comment") else [{
|
|
"content": "",
|
|
"account": ""
|
|
}]
|
|
|
|
# Add the formatted record to rrsets list
|
|
rrsets.append({
|
|
"name": record_name,
|
|
"type": record["record_type"],
|
|
"ttl": int(record["record_ttl"]),
|
|
"records": [record_content],
|
|
"comments": record_comments
|
|
})
|
|
|
|
# Group the records which has the same name and type.
|
|
# The rrset then has multiple records inside.
|
|
transformed_rrsets = []
|
|
|
|
# Sort the list before using groupby
|
|
rrsets = sorted(rrsets, key=lambda r: (r['name'], r['type']))
|
|
groups = groupby(rrsets, key=lambda r: (r['name'], r['type']))
|
|
for _k, v in groups:
|
|
group = list(v)
|
|
transformed_rrsets.append(self.merge_rrsets(group))
|
|
|
|
return transformed_rrsets
|
|
|
|
def compare(self, domain_name, submitted_records):
|
|
"""
|
|
Compare the submitted records with PDNS's actual data
|
|
|
|
Args:
|
|
domain_name(str): The zone name
|
|
submitted_records(list): List of records submitted from PDA datatable
|
|
|
|
Returns:
|
|
new_rrsets(list): List of rrsets to be added
|
|
del_rrsets(list): List of rrsets to be deleted
|
|
"""
|
|
# Create submitted rrsets from submitted records
|
|
submitted_rrsets = self.build_rrsets(domain_name, submitted_records)
|
|
current_app.logger.debug(
|
|
"submitted_rrsets_data: \n{}".format(utils.pretty_json(submitted_rrsets)))
|
|
|
|
# Current domain's rrsets in PDNS
|
|
current_rrsets = self.get_rrsets(domain_name)
|
|
current_app.logger.debug("current_rrsets_data: \n{}".format(
|
|
utils.pretty_json(current_rrsets)))
|
|
|
|
# Remove comment's 'modified_at' key
|
|
# PDNS API always return the comments with modified_at
|
|
# info, we have to remove it to be able to do the dict
|
|
# comparison between current and submitted rrsets
|
|
for r in current_rrsets:
|
|
for comment in r['comments']:
|
|
if 'modified_at' in comment:
|
|
del comment['modified_at']
|
|
|
|
# List of rrsets to be added
|
|
new_rrsets = {"rrsets": []}
|
|
for r in submitted_rrsets:
|
|
if r not in current_rrsets and r['type'] in Setting(
|
|
).get_records_allow_to_edit():
|
|
r['changetype'] = 'REPLACE'
|
|
new_rrsets["rrsets"].append(r)
|
|
|
|
# List of rrsets to be removed
|
|
del_rrsets = {"rrsets": []}
|
|
for r in current_rrsets:
|
|
if r not in submitted_rrsets and r['type'] in Setting(
|
|
).get_records_allow_to_edit() and r['type'] != 'SOA':
|
|
r['changetype'] = 'DELETE'
|
|
del_rrsets["rrsets"].append(r)
|
|
|
|
current_app.logger.debug("new_rrsets: \n{}".format(utils.pretty_json(new_rrsets)))
|
|
current_app.logger.debug("del_rrsets: \n{}".format(utils.pretty_json(del_rrsets)))
|
|
|
|
return new_rrsets, del_rrsets
|
|
|
|
def apply_rrsets(self, domain_name, rrsets):
|
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
|
jdata = utils.fetch_json(urljoin(
|
|
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
|
|
'/servers/localhost/zones/{0}'.format(domain_name)),
|
|
headers=headers,
|
|
method='PATCH',
|
|
verify=Setting().get('verify_ssl_connections'),
|
|
data=rrsets)
|
|
return jdata
|
|
|
|
@staticmethod
|
|
def to_api_payload(new_rrsets, del_rrsets):
|
|
"""Turn the given changes into a single api payload."""
|
|
|
|
def replace_for_api(rrset):
|
|
"""Return a modified copy of the given RRset with changetype REPLACE."""
|
|
if not rrset or rrset.get('changetype', None) != 'REPLACE':
|
|
return rrset
|
|
replace_copy = dict(rrset)
|
|
# For compatibility with some backends: Remove comments from rrset if all are blank
|
|
if not any((bool(c.get('content', None)) for c in replace_copy.get('comments', []))):
|
|
replace_copy.pop('comments', None)
|
|
return replace_copy
|
|
|
|
def rrset_in(needle, haystack):
|
|
"""Return whether the given RRset (identified by name and type) is in the list."""
|
|
for hay in haystack:
|
|
if needle['name'] == hay['name'] and needle['type'] == hay['type']:
|
|
return True
|
|
return False
|
|
|
|
def delete_for_api(rrset):
|
|
"""Return a minified copy of the given RRset with changetype DELETE."""
|
|
if not rrset or rrset.get('changetype', None) != 'DELETE':
|
|
return rrset
|
|
delete_copy = dict(rrset)
|
|
delete_copy.pop('ttl', None)
|
|
delete_copy.pop('records', None)
|
|
delete_copy.pop('comments', None)
|
|
return delete_copy
|
|
|
|
replaces = [replace_for_api(r) for r in new_rrsets]
|
|
deletes = [delete_for_api(r) for r in del_rrsets if not rrset_in(r, replaces)]
|
|
return {
|
|
# order matters: first deletions, then additions+changes
|
|
'rrsets': deletes + replaces
|
|
}
|
|
|
|
def apply(self, domain_name, submitted_records):
|
|
"""
|
|
Apply record changes to a domain. This function
|
|
will make 1 call to the PDNS API to DELETE and
|
|
REPLACE records (rrsets)
|
|
"""
|
|
current_app.logger.debug(
|
|
"submitted_records: {}".format(submitted_records))
|
|
|
|
# Get the list of rrsets to be added and deleted
|
|
new_rrsets, del_rrsets = self.compare(domain_name, submitted_records)
|
|
|
|
# The history logic still needs *all* the deletes with full data to display a useful diff.
|
|
# So create a "minified" copy for the api call, and return the original data back up
|
|
api_payload = self.to_api_payload(new_rrsets['rrsets'], del_rrsets['rrsets'])
|
|
current_app.logger.debug(f"api payload: \n{utils.pretty_json(api_payload)}")
|
|
|
|
# Submit the changes to PDNS API
|
|
try:
|
|
if api_payload["rrsets"]:
|
|
result = self.apply_rrsets(domain_name, api_payload)
|
|
if 'error' in result.keys():
|
|
current_app.logger.error(
|
|
'Cannot apply record changes. PDNS error: {}'
|
|
.format(result['error']))
|
|
return {
|
|
'status': 'error',
|
|
'msg': result['error'].replace("'", "")
|
|
}
|
|
|
|
self.auto_ptr(domain_name, new_rrsets, del_rrsets)
|
|
self.update_db_serial(domain_name)
|
|
current_app.logger.info('Record was applied successfully.')
|
|
return {'status': 'ok', 'msg': 'Record was applied successfully', 'data': (new_rrsets, del_rrsets)}
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
"Cannot apply record changes to domain {0}. Error: {1}".format(
|
|
domain_name, e))
|
|
current_app.logger.debug(traceback.format_exc())
|
|
return {
|
|
'status': 'error',
|
|
'msg':
|
|
'There was something wrong, please contact administrator'
|
|
}
|
|
|
|
def auto_ptr(self, domain_name, new_rrsets, del_rrsets):
|
|
"""
|
|
Add auto-ptr records
|
|
"""
|
|
# Check if auto_ptr is enabled for this domain
|
|
auto_ptr_enabled = False
|
|
if Setting().get('auto_ptr'):
|
|
auto_ptr_enabled = True
|
|
else:
|
|
domain_obj = Domain.query.filter(Domain.name == domain_name).first()
|
|
domain_setting = DomainSetting.query.filter(
|
|
DomainSetting.domain == domain_obj).filter(
|
|
DomainSetting.setting == 'auto_ptr').first()
|
|
auto_ptr_enabled = strtobool(
|
|
domain_setting.value) if domain_setting else False
|
|
|
|
# If it is enabled, we create/delete the PTR records automatically
|
|
if auto_ptr_enabled:
|
|
try:
|
|
RECORD_TYPE_TO_PTR = ['A', 'AAAA']
|
|
new_rrsets = new_rrsets['rrsets']
|
|
del_rrsets = del_rrsets['rrsets']
|
|
|
|
if not new_rrsets and not del_rrsets:
|
|
msg = 'No changes detected. Skipping auto ptr...'
|
|
current_app.logger.info(msg)
|
|
return {'status': 'ok', 'msg': msg}
|
|
|
|
new_rrsets = [
|
|
r for r in new_rrsets if r['type'] in RECORD_TYPE_TO_PTR
|
|
]
|
|
del_rrsets = [
|
|
r for r in del_rrsets if r['type'] in RECORD_TYPE_TO_PTR
|
|
]
|
|
|
|
d = Domain()
|
|
for r in new_rrsets:
|
|
for record in r['records']:
|
|
# Format the reverse record name
|
|
# It is the reverse of forward record's content.
|
|
reverse_host_address = dns.reversename.from_address(
|
|
record['content']).to_text()
|
|
|
|
# Create the reverse domain name in PDNS
|
|
domain_reverse_name = d.get_reverse_domain_name(
|
|
reverse_host_address)
|
|
d.create_reverse_domain(domain_name,
|
|
domain_reverse_name)
|
|
|
|
# Build the rrset for reverse zone updating
|
|
rrset_data = [{
|
|
"changetype":
|
|
"REPLACE",
|
|
"name":
|
|
reverse_host_address,
|
|
"ttl":
|
|
r['ttl'],
|
|
"type":
|
|
"PTR",
|
|
"records": [{
|
|
"content": r['name'],
|
|
"disabled": record['disabled']
|
|
}],
|
|
"comments": []
|
|
}]
|
|
|
|
# Format the rrset
|
|
rrset = {"rrsets": rrset_data}
|
|
self.add(domain_reverse_name, rrset)
|
|
|
|
for r in del_rrsets:
|
|
for record in r['records']:
|
|
# Format the reverse record name
|
|
# It is the reverse of forward record's content.
|
|
reverse_host_address = dns.reversename.from_address(
|
|
record['content']).to_text()
|
|
|
|
# Create the reverse domain name in PDNS
|
|
domain_reverse_name = d.get_reverse_domain_name(
|
|
reverse_host_address)
|
|
d.create_reverse_domain(domain_name,
|
|
domain_reverse_name)
|
|
|
|
# Delete the reverse zone
|
|
self.name = reverse_host_address
|
|
self.type = 'PTR'
|
|
self.data = record['content']
|
|
self.delete(domain_reverse_name)
|
|
return {
|
|
'status': 'ok',
|
|
'msg': 'Auto-PTR record was updated successfully'
|
|
}
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
"Cannot update auto-ptr record changes to domain {0}. Error: {1}"
|
|
.format(domain_name, e))
|
|
current_app.logger.debug(traceback.format_exc())
|
|
return {
|
|
'status':
|
|
'error',
|
|
'msg':
|
|
'Auto-PTR creation failed. There was something wrong, please contact administrator.'
|
|
}
|
|
|
|
def delete(self, domain):
|
|
"""
|
|
Delete a record from domain
|
|
"""
|
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
|
data = {
|
|
"rrsets": [{
|
|
"name": self.name.rstrip('.') + '.',
|
|
"type": self.type,
|
|
"changetype": "DELETE",
|
|
"records": []
|
|
}]
|
|
}
|
|
try:
|
|
jdata = utils.fetch_json(urljoin(
|
|
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
|
|
'/servers/localhost/zones/{0}'.format(domain)),
|
|
headers=headers,
|
|
timeout=int(
|
|
Setting().get('pdns_api_timeout')),
|
|
method='PATCH',
|
|
verify=Setting().get('verify_ssl_connections'),
|
|
data=data)
|
|
current_app.logger.debug(jdata)
|
|
return {'status': 'ok', 'msg': 'Record was removed successfully'}
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
"Cannot remove record {0}/{1}/{2} from domain {3}. DETAIL: {4}"
|
|
.format(self.name, self.type, self.data, domain, e))
|
|
return {
|
|
'status': 'error',
|
|
'msg':
|
|
'There was something wrong, please contact administrator'
|
|
}
|
|
|
|
def is_allowed_edit(self):
|
|
"""
|
|
Check if record is allowed to edit
|
|
"""
|
|
return self.type in Setting().get_records_allow_to_edit()
|
|
|
|
def is_allowed_delete(self):
|
|
"""
|
|
Check if record is allowed to removed
|
|
"""
|
|
return (self.type in Setting().get_records_allow_to_edit()
|
|
and self.type != 'SOA')
|
|
|
|
def exists(self, domain):
|
|
"""
|
|
Check if record is present within domain records, and if it's present set self to found record
|
|
"""
|
|
rrsets = self.get_rrsets(domain)
|
|
for r in rrsets:
|
|
if r['name'].rstrip('.') == self.name and r['type'] == self.type and r['records']:
|
|
self.type = r['type']
|
|
self.status = r['records'][0]['disabled']
|
|
self.ttl = r['ttl']
|
|
self.data = r['records'][0]['content']
|
|
return True
|
|
return False
|
|
|
|
def update(self, domain, content):
|
|
"""
|
|
Update single record
|
|
"""
|
|
headers = {'X-API-Key': self.PDNS_API_KEY, 'Content-Type': 'application/json'}
|
|
|
|
data = {
|
|
"rrsets": [{
|
|
"name":
|
|
self.name + '.',
|
|
"type":
|
|
self.type,
|
|
"ttl":
|
|
self.ttl,
|
|
"changetype":
|
|
"REPLACE",
|
|
"records": [{
|
|
"content": content,
|
|
"disabled": self.status,
|
|
}]
|
|
}]
|
|
}
|
|
|
|
try:
|
|
utils.fetch_json(urljoin(
|
|
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
|
|
'/servers/localhost/zones/{0}'.format(domain)),
|
|
headers=headers,
|
|
timeout=int(Setting().get('pdns_api_timeout')),
|
|
method='PATCH',
|
|
verify=Setting().get('verify_ssl_connections'),
|
|
data=data)
|
|
current_app.logger.debug("dyndns data: {0}".format(data))
|
|
return {'status': 'ok', 'msg': 'Record was updated successfully'}
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
"Cannot add record {0}/{1}/{2} to domain {3}. DETAIL: {4}".
|
|
format(self.name, self.type, self.data, domain, e))
|
|
return {
|
|
'status': 'error',
|
|
'msg':
|
|
'There was something wrong, please contact administrator'
|
|
}
|
|
|
|
def update_db_serial(self, domain):
|
|
headers = {'X-API-Key': self.PDNS_API_KEY}
|
|
jdata = utils.fetch_json(urljoin(
|
|
self.PDNS_STATS_URL, self.API_EXTENDED_URL +
|
|
'/servers/localhost/zones/{0}'.format(domain)),
|
|
headers=headers,
|
|
timeout=int(
|
|
Setting().get('pdns_api_timeout')),
|
|
method='GET',
|
|
verify=Setting().get('verify_ssl_connections'))
|
|
serial = jdata['serial']
|
|
|
|
domain = Domain.query.filter(Domain.name == domain).first()
|
|
if domain:
|
|
domain.serial = serial
|
|
db.session.commit()
|
|
return {
|
|
'status': True,
|
|
'msg': 'Synced local serial for domain name {0}'.format(domain)
|
|
}
|
|
else:
|
|
return {
|
|
'status': False,
|
|
'msg':
|
|
'Could not find domain name {0} in local db'.format(domain)
|
|
}
|