def get_dns_ip(self, zone_uuid: str) -> str: """Get the DNS A @ record.""" url = '{}/zones/{}/records'.format(self.api, zone_uuid) headers = {'X-Api-Key': self.api_secret} response = get(url, headers=headers) try: tld_a_record = [ entry for entry in response if entry['rrset_type'] == 'A' and entry['rrset_name'] == '@' ] ip_record = tld_a_record[0]['rrset_values'][0] self.validate_subdomain_entries(response) message = 'Discovered IP address of {} for {} DNS A @ record' logger.info(message.format(ip_record, self.domain)) return ip_record except (KeyError, IndexError): message = 'Could not find A record for {}'.format(self.domain) logger.critical(message) exit(EXIT_CODE_1_BAD)
def get_user() -> str: """Determine who the current user is.""" try: return check_output('whoami', timeout=0.5).decode('utf-8').strip() except OSError as exception: message = 'Canot determine user. Raised {}'.format(str(exception)) logger.critical(message) exit(EXIT_CODE_1_BAD)
def validate_subdomain_entries(self, response): """Validate that user specified subdomain entries exist.""" entries = set([entry['rrset_name'] for entry in response]) entries_match = all((subd in entries for subd in self.subdomains)) if not entries_match: message = 'Missing subdomain DNS record from {} under {}' subdomains = ','.join(self.subdomains) logger.critical(message.format(subdomains, self.domain)) exit(EXIT_CODE_1_BAD)
def validate_api_secrets(config, sections) -> None: """Validate the DNS providers are actually supported.""" for section in sections: try: config[section]['api_secret'] except KeyError: message = 'No api_secret in {} config section'.format(section) logger.critical(message) exit(EXIT_CODE_1_BAD)
def put(url, payload, headers=None): """Run a HTTP PUT on some web resource.""" headers = headers or [] try: request = Request(url, headers=headers, method='PUT') dumped = dumps(payload).encode('utf-8') return (urlopen(request, data=dumped, timeout=REQUEST_TIMEOUT).read().decode('utf-8')) except (URLError, ValueError, timeout) as exception: print(exception.file.read()) message = 'Unable to PUT against {}: {}'.format(url, str(exception)) logger.critical(message) exit(EXIT_CODE_1_BAD)
def validate_dns_providers(config, sections) -> None: """Validate the DNS providers are actually supported.""" for section in sections: try: provider = config[section]['provider'] if provider not in SUPPORTED_DNS_PROVIDERS: message = 'Unsupported DNS provider {}'.format(provider) logger.critical(message) exit(EXIT_CODE_1_BAD) except KeyError: message = 'No provider in {} config section'.format(section) logger.critical(message) exit(EXIT_CODE_1_BAD)
def read_config(user: str, root: Union[str, None] = None) -> Any: """Read the INI configuration file.""" cfg_path = '{root}/{user}/.hdyndns/hdyndns.ini'.format( root=root if root else '/home', user=user) config = ConfigParser() if not config.read(cfg_path): message = 'Missing configuration from {}'.format(cfg_path) logger.critical(message) exit(EXIT_CODE_1_BAD) return config
def get_zone_uuid(self) -> List[str]: """Get the DNS zone UUID.""" url = '{}/domains/{}'.format(self.api, self.domain) headers = {'X-Api-Key': self.api_secret} response = get(url, headers=headers) try: zone_uuid = response['zone_uuid'] message = 'Discovered Gandi zone UUID of {} for {}' logger.info(message.format(zone_uuid, self.domain)) return zone_uuid except KeyError as exception: message = 'Missing {} from {}' logger.critical(message.format(str(exception), dumps(response))) exit(EXIT_CODE_1_BAD)
def cli_entrypoint() -> None: """The command line entrypoint.""" user = get_user() create_home(user) config = read_config(user) sections = [section for section in config.keys() if section != 'DEFAULT'] validate_dns_providers(config, sections) validate_api_secrets(config, sections) for section in sections: if 'gandi' == config[section]['provider']: logger.info('Processing {} with provider Gandi'.format(section)) GandiDynDNS(config[section]).update_dns() exit(EXIT_CODE_0_OK) else: logger.critical('No supported provider found') exit(EXIT_CODE_1_BAD)
def get(url, headers=None, load_json=True): """Run a HTTP GET on some web resource.""" headers = headers or {} try: request = Request(url, headers=headers, method='GET') response = (urlopen(request, timeout=REQUEST_TIMEOUT).read().decode('utf-8')) if not load_json: return response try: return loads(response) except JSONDecodeError: message = 'Unable to JSON load {}'.format(response) logger.critical(message) exit(EXIT_CODE_1_BAD) except (URLError, ValueError, timeout) as exception: message = 'Unable to GET against {}: {}'.format(url, str(exception)) logger.critical(message) exit(EXIT_CODE_1_BAD)