def _check_response_status(self, response, **kwargs): if 200 <= response.status_code <= 299: return elif response.status_code in [401, 403]: raise errors.PluginError( f"Could not authenticate against deSEC API: {response.content}" ) elif response.status_code == 404: raise errors.PluginError( f"Not found ({kwargs}): {response.content}") elif response.status_code == 429: raise errors.PluginError( f"deSEC throttled your request even after we waited the prescribed cool-down " f"time. Did you use the API in parallel? {response.content}") elif response.status_code >= 500: raise errors.PluginError( f"deSEC API server error (status {response.status_code}): {response.content}" ) else: raise errors.PluginError( f"Unknown error when talking to deSEC (status {response.status_code}: " f"Request was on '{response.request.url}' with payload {response.request.body}. " f"Response was '{response.content}'.")
def _validate_webroot(webroot_path): """Validates and returns the absolute path of webroot_path. :param str webroot_path: path to the webroot directory :returns: absolute path of webroot_path :rtype: str """ if not os.path.isdir(webroot_path): raise errors.PluginError(webroot_path + " does not exist or is not a directory") return os.path.abspath(webroot_path)
def rollback_checkpoints(self, rollback=1): """Rollback saved checkpoints. :param int rollback: Number of checkpoints to revert :raises .errors.PluginError: If there is a problem with the input or the function is unable to correctly revert the configuration """ try: self.reverter.rollback_checkpoints(rollback) except errors.ReverterError as err: raise errors.PluginError(str(err)) self.parser.load()
def _prompt_for_new_webroot(self, domain, allowraise=False): code, webroot = ops.validated_directory( _validate_webroot, "Input the webroot for {0}:".format(domain), force_interactive=True) if code == display_util.CANCEL: if not allowraise: return None else: raise errors.PluginError( "Every requested domain must have a " "webroot when using the webroot plugin.") else: # code == display_util.OK return _validate_webroot(webroot)
def add_txt_record(self, domain, record_name, record_content, record_ttl): """ Add a TXT record using the supplied information. :param str domain: The domain to use to look up the managed zone. :param str record_name: The record name (typically beginning with '_acme-challenge.'). :param str record_content: The record content (typically the challenge validation). :param int record_ttl: The record TTL (number of seconds that the record may be cached). :raises certbot.errors.PluginError: if an error occurs communicating with the Google API """ zone_id = self._find_managed_zone_id(domain) data = { "kind": "dns#change", "additions": [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": record_name + ".", "rrdatas": [ record_content, ], "ttl": record_ttl, }, ], } changes = self.dns.changes() # changes | pylint: disable=no-member try: request = changes.create(project=self.project_id, managedZone=zone_id, body=data) response = request.execute() status = response['status'] change = response['id'] while status == 'pending': request = changes.get(project=self.project_id, managedZone=zone_id, changeId=change) response = request.execute() status = response['status'] except googleapiclient_errors.Error as e: logger.error('Encountered error adding TXT record: %s', e) raise errors.PluginError( 'Error communicating with the Google Cloud DNS API: {0}'. format(e))
def add_txt_record(self, domain, record_name, record_content, record_ttl=RECORD_TTL): """ Add a TXT record using the supplied information. :param str domain: The domain to use to look up the managed zone. :param str record_name: The record name (typically beginning with '_acme-challenge.'). :param str record_content: The record content (typically the challenge validation). :param int record_ttl: The record TTL (number of seconds that the record may be cached). :raises certbot.errors.PluginError: if an error occurs communicating with the EdgeDNS API """ logger.debug("EDGEDNS: add_text_record. domain: {0}, name: {1}, content: {2}".format(domain, record_name, record_content)) try: txt_recordset, zone = self.get_text_record(domain, record_name, record_ttl) except errors.PluginError as pe: raise pe except: raise errors.PluginError("{0}".format(sys.exc_info()[0])) self.recordset_semaphore.acquire() if self.session == None: self.session = requests.Session() with self.session as session: session.auth = self.edgegrid_auth try: self._process_add_record(session, zone, txt_recordset, record_content) except errors.PluginError as pe: self.recordset_semaphore.release() raise pe except: self.recordset_semaphore.release() raise errors.PluginError( "EdgeDNS: API invocation resulted in a session error: {0}".format(sys.exc_info()[0]) ) self.recordset_semaphore.release() return
def del_txt_record(self, record_name, record_value): """ Creates a TXT with given record_name and record_value :param str record_name: The record name (typically beginning with '_acme-challenge.'). :param str record_value: The record value :raises certbot.errors.PluginError: if an error occurs communicating with the 1cloud API """ try: parsed = self._split_record_name(record_name) domain_info = self._load_domain_info(parsed['domain']) text_1cloud_value = '"' + record_value + '"' for record in domain_info['LinkedRecords']: if record['TypeRecord'] == 'TXT' and record[ 'HostName'] == record_name + '.' and record[ 'Text'].strip() == text_1cloud_value: record_id = record['ID'] domain_id = domain_info['ID'] response = requests.delete( 'https://api.1cloud.ru/dns/{0}/{1}'.format( domain_id, record_id), headers=self._create_headers()) response.raise_for_status() return raise RecordNotFoundError() except requests.RequestException as e: logger.error('Encountered error removing TXT record: %d %s', e, e) raise errors.PluginError( 'Error communicating with 1cloud API: {0}'.format(e)) except RecordNotFoundError as e: logger.error('Encountered error removing TXT record: %d %s', e, e) raise errors.PluginError( 'Error communicating with 1cloud API: {0}'.format(e)) except DomainNotFoundError as e: logger.error('Encountered error removing TXT record: %d %s', e, e) raise errors.PluginError( 'Error communicating with 1cloud API: {0}'.format(e))
def _enable_ocsp_stapling(self, domain, chain_path): """Include OCSP response in TLS handshake :param str domain: domain to enable OCSP response for :param chain_path: chain file path :type chain_path: `str` or `None` """ vhost = self.choose_vhost(domain) if self.version < (1, 3, 7): raise errors.PluginError("Version 1.3.7 or greater of nginx " "is needed to enable OCSP stapling") if chain_path is None: raise errors.PluginError( "--chain-path is required to enable " "Online Certificate Status Protocol (OCSP) stapling " "on nginx >= 1.3.7.") stapling_directives = [ ['\n ', 'ssl_trusted_certificate', ' ', chain_path], ['\n ', 'ssl_stapling', ' ', 'on'], ['\n ', 'ssl_stapling_verify', ' ', 'on'], ['\n']] try: self.parser.add_server_directives(vhost, stapling_directives, replace=False) except errors.MisconfigurationError as error: logger.debug(error) raise errors.PluginError("An error occurred while enabling OCSP " "stapling for {0}.".format(vhost.names)) self.save_notes += ("OCSP Stapling was enabled " "on SSL Vhost: {0}.\n".format(vhost.filep)) self.save_notes += "\tssl_trusted_certificate {0}\n".format(chain_path) self.save_notes += "\tssl_stapling on\n" self.save_notes += "\tssl_stapling_verify on\n"
def _do_post(self, url, data): """ Do request DNSPod API :param str url: URL for DNSPod API. :param Dict[str, Any] data: request parameters :returns: API response :rtype: Dict[str, Any] """ if not data: data = {} common_data = { 'login_token': self.api_token, 'format': 'json', 'error_on_empty': 'no', 'lang': 'en' } data.update(common_data) headers = {'User-Agent': self.user_agent} resp = requests.post(url, data=data, headers=headers) if resp.status_code != 200: raise errors.PluginError( '[DNSPod] HTTP Error, status_code: {0}, url: {1}'.format( resp.status_code, url)) try: result = resp.json() except Exception: raise errors.PluginError( '[DNSPod] API response with non JSON, url: {0}, content: {1}'. format(url, resp.text)) return result
def add_txt_record(self, domain_name, record_name, record_content): """ Add a TXT record using the supplied information. :param str domain_name: The domain to use to associate the record with. :param str record_name: The record name (typically beginning with '_acme-challenge.'). :param str record_content: The record content (typically the challenge validation). :raises certbot.errors.PluginError: if an error occurs communicating with the DigitalOcean API """ try: domain = self._find_domain(domain_name) except digitalocean.Error as e: hint = None if str(e).startswith("Unable to authenticate"): hint = 'Did you provide a valid API token?' logger.debug('Error finding domain using the DigitalOcean API: %s', e) raise errors.PluginError('Error finding domain using the DigitalOcean API: {0}{1}' .format(e, ' ({0})'.format(hint) if hint else '')) try: result = domain.create_new_domain_record( type='TXT', ttl=30, name=self._compute_record_name(domain, record_name), data=record_content) record_id = result['domain_record']['id'] logger.debug('Successfully added TXT record with id: %d', record_id) except digitalocean.Error as e: logger.debug('Error adding TXT record using the DigitalOcean API: %s', e) raise errors.PluginError('Error adding TXT record using the DigitalOcean API: {0}' .format(e))
def _get_private_key(self, domain): filename = 'certbot_django_id_rsa_{}'.format(domain).replace('.', '') private_key_file = os.path.join(self._get_key_dir(), filename) private_key = None try: with open(private_key_file, 'r') as keyfile: private_key = keyfile.read() except (IOError, OSError): if self.config.noninteractive_mode: raise errors.PluginError('Could not read file from %s' % self._get_key_dir()) if not private_key: private_key, public_key = generate_key_pair() try: logger.info('Trying to load private key: %s' % private_key_file) with open(private_key_file, 'w') as keyfile: keyfile.write(private_key) except (IOError, OSError): raise errors.PluginError('Could not write private key to %s' % self._get_key_dir()) msg = ( "Couldn't read private key from {private_key_file}, so a new key has been generated and saved. " "Please go your website ({url}) and add the following public key to the user '{username}'. " "Please also make sure that '{username}' is a staff user and has permission to add and " "delete AcmeChallenge objects." "\n\n" "{public_key}" ) msg = msg.format( private_key_file=private_key_file, url="http://{}/admin/asymmetric_jwt_auth/publickey/add/".format(domain), username=self._get_username(), public_key=public_key) display = zope.component.getUtility(interfaces.IDisplay) display.notification(msg, wrap=False, force_interactive=True) return private_key
def __init__(self, aug, root, vhostroot=None, version=(2, 4), configurator=None): # Note: Order is important here. # Needed for calling save() with reverter functionality that resides in # AugeasConfigurator superclass of ApacheConfigurator. This resolves # issues with aug.load() after adding new files / defines to parse tree self.configurator = configurator self.modules = set() # type: Set[str] self.parser_paths = {} # type: Dict[str, List[str]] self.variables = {} # type: Dict[str, str] self.aug = aug # Find configuration root and make sure augeas can parse it. self.root = os.path.abspath(root) self.loc = {"root": self._find_config_root()} self.parse_file(self.loc["root"]) if version >= (2, 4): # Look up variables from httpd and add to DOM if not already parsed self.update_runtime_variables() # This problem has been fixed in Augeas 1.0 self.standardize_excl() # Parse LoadModule directives from configuration files self.parse_modules() # Set up rest of locations self.loc.update(self._set_locations()) # list of the active include paths, before modifications self.existing_paths = copy.deepcopy(self.parser_paths) # Must also attempt to parse additional virtual host root if vhostroot: self.parse_file( os.path.abspath(vhostroot) + "/" + self.configurator.option("vhost_files")) # check to see if there were unparsed define statements if version < (2, 4): if self.find_dir("Define", exclude=False): raise errors.PluginError("Error parsing runtime variables")
def deploy_cert(self, domain, cert_path, key_path, chain_path=None, fullchain_path=None): if not fullchain_path: raise errors.PluginError( "CASTLE Installer plugin requires --fullchain-path to generate a PKCS12 container." ) logger.info("Generating PKCS12 container") logger.debug('Loading cert ') cert = x509.load_pem_x509_certificate(open(cert_path, 'rb').read()) logger.debug('Loading key ') privkey = serialization.load_pem_private_key(open(key_path, 'rb').read(), password=None) logger.debug('Loading chain ') chain = x509.load_pem_x509_certificate(open(chain_path, 'rb').read()) passphrase = None if (not self.conf('no-passphrase')): if (self.conf('passphrase')): passphrase = self.conf('passphrase').encode('utf-8') else: text = 'A passphrase is needed for protecting the PKCS12 container. ' display_util.notification(text, pause=False) pf = getpass.getpass('Enter passphrase: ') vpf = getpass.getpass('Re-enter passphrase: ') while (pf != vpf): display_util.notify('Passphrases do not match.') vpf = getpass.getpass('Re-enter passphrase: ') passphrase = pf.encode('utf-8') algo = serialization.BestAvailableEncryption( passphrase) if passphrase else serialization.NoEncryption() pfxdata = pkcs12.serialize_key_and_certificates( name=domain.encode('utf-8'), key=privkey, cert=cert, cas=[chain], encryption_algorithm=algo) path, _ = os.path.split(cert_path) pfx_f, pfx_filename = util.unique_file(os.path.join(path, 'cert.pfx'), 0o600, "wb") with pfx_f: pfx_f.write(pfxdata) display_util.notification('PKCS12 container generated at ' + pfx_filename, pause=False)
def __init__(self, filename, mapper=lambda x: x): """ :param str filename: A path to the configuration file. :param callable mapper: A transformation to apply to configuration key names :raises errors.PluginError: If the file does not exist or is not a valid format. """ validate_file_permissions(filename) try: self.confobj = configobj.ConfigObj(filename) except configobj.ConfigObjError as e: logger.debug("Error parsing credentials configuration: %s", e, exc_info=True) raise errors.PluginError("Error parsing credentials configuration: {0}".format(e)) self.mapper = mapper
def _setup_credentials(self): token = os.getenv("INFOMANIAK_API_TOKEN") if token is None: self.credentials = self._configure_credentials( "credentials", "Infomaniak credentials INI file", { "token": "Infomaniak API token.", }, ) if not self.credentials: raise errors.PluginError("INFOMANIAK API Token not defined") self.token = self.credentials.conf("token") else: self.token = token
def del_txt_record(self, domain, record_name): zone = self._find_zone(domain) if not record_name.endswith('.' + zone): raise PluginError("Record name {0} is not in DNS zone {1}.".format( record_name, zone)) subdomain = record_name[:-len(zone) - 1] record = self._find_record_id(zone, subdomain) if record is not None: try: self.ovh.delete('/domain/zone/{zone}/record/{record}'.format( zone=zone, record=record)) except ovh.exceptions.APIError as e: raise errors.PluginError( "Error deleting TXT record: {0}".format(e)) self._refresh_zone(zone)
def del_txt_record(self, domain, source, target): """Delete a TXT DNS record from a domain :param str source: record key in zone (left prefix before domain) :param str target: value of record """ logger.debug("del_txt_record %s %s %s", domain, source, target) (domain_id, domain_name) = self._find_zone(domain) if source.endswith("." + domain_name): relative_source = source[:source.rfind("." + domain_name)] else: relative_source = source records = self._get_records( domain_name, domain_id, {"type": "TXT", "source": relative_source, "target": target}, ) if records is None: raise errors.PluginError("Record not found") if len(records) > 1: raise errors.PluginError("Several records match") record_id = records[0]["id"] self._delete_request("/1/domain/{domain_id}/dns/record/{record_id}".format( domain_id=domain_id, record_id=record_id))
def _cleanup(self, domain: str, validation_name: str, validation: str) -> None: """ Clear the TXT record of the provided DuckDNS domain. :param domain: the DuckDNS domain :param validation_name: value to validate the dns challenge :param validation: the value for the TXT record :raise PluginError: if the TXT record can not be cleared of something goes wrong """ try: self._get_duckdns_client().clear_txt_record(domain) except Exception as e: raise errors.PluginError(e)
def del_txt_record(self, record_name, record_content): """ Delete a TXT record using the supplied information. :param str record_name: The record name (typically beginning with '_acme-challenge.'). :param str record_content: The record content (typically the challenge validation). :param int record_ttl: The record TTL (number of seconds that the record may be cached). :raises certbot.errors.PluginError: if an error occurs communicating with the DNS server """ record_name = self.cname_check1(record_name, quiet=true) domain = self._find_domain(record_name) n = dns.name.from_text(record_name) o = dns.name.from_text(domain) rel = n.relativize(o) update = dns.update.Update( domain, keyring=self.keyring, keyalgorithm=self.algorithm) update.delete(rel, dns.rdatatype.TXT, record_content) try: response = dns.query.tcp(update, self.server, port=self.port) except Exception as e: raise errors.PluginError('Encountered error deleting TXT record: {0}' .format(e)) rcode = response.rcode() if rcode == dns.rcode.NOERROR: logger.debug('Successfully deleted TXT record') else: raise errors.PluginError('Received response from server: {0}' .format(dns.rcode.to_text(rcode)))
def add_txt_record(self, domain_name, record_name, record_content): """ Add a TXT record using the supplied information. :param str domain_name: The domain to use to associate the record with. :param str record_name: The record name (typically beginning with '_acme-challenge.'). :param str record_content: The record content (typically the challenge validation). :raises certbot.errors.PluginError: if an error occurs communicating with the SoftLayer API """ # extract first level domain domain = get_tld(domain_name, as_object=True, fix_protocol=True) try: zone_id = self.dns.resolve_ids(domain.fld)[0] except (SoftLayerAPIError, IndexError) as e: logger.debug('Error finding domain using the SoftLayer API: %s', e) raise errors.PluginError( 'Error finding domain using the SoftLayer API: {}'.format(e)) try: logger.debug('Creating TXT record with name: %s', record_name) result = self.dns.create_record( zone_id, self._compute_record_name(domain.fld, record_name), 'TXT', record_content) record_id = result['id'] logger.debug('Successfully added TXT record with id: %d', record_id) except SoftLayerAPIError as e: logger.debug('Error adding TXT record using the SoftLayer API: %s', e) raise errors.PluginError( 'Error adding TXT record using the SoftLayer API: {0}'.format( e))
def _get_dns_entries(self, domain, retries=3, backoff=5): """ Get all DNS entries for this domain. :param str domain: The domain to use to associate the record with. :raises certbot.errors.PluginError: if an error occurs communicating with the Transip API """ dns_entries = [] for _ in range(retries + 1): try: dns_entries = self.domain_service.get_info( domain_name=domain).dnsEntries except suds.WebFault as error: self.logger.error( 'Error getting DNS records using the Transip API: %s', error) raise errors.PluginError( 'Error finding DNS entries using the Transip API: {0}'. format(domain)) if dns_entries: break self.logger.warning( 'Error getting DNS records using the Transip API: retry in {} seconds' .format(backoff)) time.sleep(backoff) backoff = backoff * 2 # If there are still no entries then the Transip API returned the wrong data if not dns_entries: error = 'Error finding DNS entries using the Transip API: Empty record set for {}'.format( domain) self.logger.error(error) raise errors.PluginError(error) return dns_entries
def _perform(self, domain, validation_name, validation): azure_domain, subscription_id, resource_group_name = self._get_ids_for_domain( domain) client = self._get_azure_client(subscription_id) relative_validation_name = self._get_relative_domain( validation_name, azure_domain) # Check to see if there are any existing TXT validation record values txt_value = {validation} try: existing_rr = client.record_sets.get( resource_group_name=resource_group_name, zone_name=azure_domain, relative_record_set_name=relative_validation_name, record_type='TXT') for record in existing_rr.txt_records: for value in record.value: txt_value.add(value) except CloudError as err: if err.status_code != 404: # Ignore RR not found raise errors.PluginError( 'Failed to check TXT record for domain ' '{}, error: {}'.format(domain, err)) try: client.record_sets.create_or_update( resource_group_name=resource_group_name, zone_name=azure_domain, relative_record_set_name=relative_validation_name, record_type='TXT', parameters=RecordSet( ttl=self.ttl, txt_records=[TxtRecord(value=list(txt_value))])) except CloudError as err: raise errors.PluginError('Failed to add TXT record to domain ' '{}, error: {}'.format(domain, err))
def _find_record_set(self, validation_domain_name): """Find the resource group, zone and record set for a given FQDN. :param str validation_domain_name: The validation record domain. :return: A 3-tuple (Resource group, Zone name, TXT resource name) """ if not self.conf('resource-goup'): raise errors.PluginError("No resource groups specified.") zones = [] for resourceGroup in self.conf('resource-goup'): for zone in self.dnsMgtClient.zones.list_by_resource_group(resourceGroup): if zone.zone_type != ZoneType.public: continue if validation_domain_name.rstrip('.').endswith(zone.name.rstrip('.')): zones.append((resourceGroup, zone.name)) if not zones: raise errors.PluginError("Unable to find an Azure hosted zone for {0}".format(validation_domain_name)) zones.sort(key=lambda z: len(z[1]), reverse=True) # Use the longest matching DNS zone. resourceGroup, zone = zones[0] record = validation_domain_name[0:len(validation_domain_name)- len(zone)].rstrip('.') return resourceGroup, zone, record
def add_txt_record(self, record_name: str, record_content: str, record_ttl: int) -> None: """ Add a TXT record using the supplied information. :param str record_name: The record name (typically beginning with '_acme-challenge.'). :param str record_content: The record content (typically the challenge validation). :param int record_ttl: The record TTL (number of seconds that the record may be cached). :raises certbot.errors.PluginError: if an error occurs communicating with the DNS server """ domain = self._find_domain(record_name) n = dns.name.from_text(record_name) o = dns.name.from_text(domain) rel = n.relativize(o) update = dns.update.Update(domain, keyring=self.keyring, keyalgorithm=self.algorithm) update.add(rel, record_ttl, dns.rdatatype.TXT, record_content) try: response = dns.query.tcp(update, self.server, self._default_timeout, self.port) except Exception as e: raise errors.PluginError( 'Encountered error adding TXT record: {0}'.format(e)) rcode = response.rcode() # type: ignore[attr-defined] if rcode == dns.rcode.NOERROR: logger.debug('Successfully added TXT record %s', record_name) else: raise errors.PluginError( 'Received response from server: {0}'.format( dns.rcode.to_text(rcode)))
def _perform(self, domain, validation_name, validation): _, domain = domain.split('.', 1) validation_name = '.'.join(validation_name.split('.')[0:2]) result = requests.post('https://i.hostker.com/api/dnsAddRecord', data={ 'email': self.credentials.conf('email'), 'token': self.credentials.conf('token'), 'domain': domain, 'header': validation_name, 'data': validation, 'type': 'TXT', 'ttl': self.ttl, }) if int(result.json()['success']) == 0: raise errors.PluginError(result.json())
def _find_zone_id_for_domain(self, domain): """Find the zone id responsible a given FQDN. That is, the id for the zone whose name is the longest parent of the domain. """ if self.conf('zone-id'): zone = self.r53.get_hosted_zone(Id=self.conf('zone-id')) if not zone: raise errors.PluginError( "Unable to find a Route53 hosted zone with id {0}".format( self.conf('zone-id'))) return zone["HostedZone"]["Id"] paginator = self.r53.get_paginator("list_hosted_zones") zones = [] target_labels = domain.rstrip(".").split(".") for page in paginator.paginate(): for zone in page["HostedZones"]: if zone["Config"]["PrivateZone"]: continue candidate_labels = zone["Name"].rstrip(".").split(".") if candidate_labels == target_labels[-len(candidate_labels):]: zones.append((zone["Name"], zone["Id"])) if not zones: raise errors.PluginError( "Unable to find a Route53 hosted zone for {0}".format(domain)) # Order the zones that are suffixes for our desired to domain by # length, this puts them in an order like: # ["foo.bar.baz.com", "bar.baz.com", "baz.com", "com"] # And then we choose the first one, which will be the most specific. zones.sort(key=lambda z: len(z[0]), reverse=True) return zones[0][1]
def _prompt_with_webroot_list(self, domain, known_webroots): display = zope.component.getUtility(interfaces.IDisplay) path_flag = "--" + self.option_name("path") while True: code, index = display.menu( "Select the webroot for {0}:".format(domain), ["Enter a new webroot"] + known_webroots, cli_flag=path_flag, force_interactive=True) if code == display_util.CANCEL: raise errors.PluginError( "Every requested domain must have a " "webroot when using the webroot plugin.") else: # code == display_util.OK return None if index == 0 else known_webroots[index - 1]
def _relevant_vhosts(self): http01_port = str(self.configurator.config.http01_port) relevant_vhosts = [] for vhost in self.configurator.vhosts: if any(a.is_wildcard() or a.get_port() == http01_port for a in vhost.addrs): if not vhost.ssl: relevant_vhosts.append(vhost) if not relevant_vhosts: raise errors.PluginError( "Unable to find a virtual host listening on port {0} which is" " currently needed for Certbot to prove to the CA that you" " control your domain. Please add a virtual host for port" " {0}.".format(http01_port)) return relevant_vhosts
def add_txt_record(self, domain_name, record_name, record_content, ttl): zone_name = self._find_zone_name(domain_name) if zone_name is None: raise errors.PluginError( 'Cannot find zone for domain name {}'.format(domain_name)) try: logger.debug('Update TXT record with data: %s', record_content) update_rr_set_details = UpdateRRSetDetails(items=[ RecordDetails(domain=record_name, rdata=record_content, rtype='TXT', ttl=ttl) ]) self.client.update_rr_set( zone_name_or_id=zone_name, domain=record_name, rtype='TXT', update_rr_set_details=update_rr_set_details) except ServiceError as e: logger.warning( 'Error updating TXT record %s using the OCI API: %s', record_name, e) raise errors.PluginError('Cannot create TXT record: {}'.format(e))
def __call__(self, parser, namespace, webroot_path, option_string=None): if self._domain_before_webroot: raise errors.PluginError( "If you specify multiple webroot paths, " "one of them must precede all domain flags") if namespace.webroot_path: # Apply previous webroot to all matched # domains before setting the new webroot path prev_webroot = namespace.webroot_path[-1] for domain in namespace.domains: namespace.webroot_map.setdefault(domain, prev_webroot) elif namespace.domains: self._domain_before_webroot = True namespace.webroot_path.append(_validate_webroot(webroot_path))