././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0876129 certbot-dns-google-2.10.0/0000775000175000017500000000000014603073204014347 5ustar00willgwillg././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/LICENSE.txt0000664000175000017500000002504214603073177016206 0ustar00willgwillg Copyright 2015 Electronic Frontier Foundation and others Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/MANIFEST.in0000664000175000017500000000033014603073177016112 0ustar00willgwillginclude LICENSE.txt include README.rst recursive-include docs * recursive-include certbot_dns_google/_internal/tests/testdata * include certbot_dns_google/py.typed global-exclude __pycache__ global-exclude *.py[cod] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0876129 certbot-dns-google-2.10.0/PKG-INFO0000644000175000017500000000273514603073204015451 0ustar00willgwillgMetadata-Version: 2.1 Name: certbot-dns-google Version: 2.10.0 Summary: Google Cloud DNS Authenticator plugin for Certbot Home-page: https://github.com/certbot/certbot Author: Certbot Project Author-email: certbot-dev@eff.org License: Apache License 2.0 Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Plugins Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Security Classifier: Topic :: System :: Installation/Setup Classifier: Topic :: System :: Networking Classifier: Topic :: System :: Systems Administration Classifier: Topic :: Utilities Requires-Python: >=3.8 License-File: LICENSE.txt Requires-Dist: google-api-python-client>=1.6.5 Requires-Dist: google-auth>=2.16.0 Requires-Dist: setuptools>=41.6.0 Requires-Dist: acme>=2.10.0 Requires-Dist: certbot>=2.10.0 Provides-Extra: docs Requires-Dist: Sphinx>=1.0; extra == "docs" Requires-Dist: sphinx_rtd_theme; extra == "docs" Provides-Extra: test Requires-Dist: pytest; extra == "test" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/README.rst0000664000175000017500000000006214603073177016045 0ustar00willgwillgGoogle Cloud DNS Authenticator plugin for Certbot ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0836127 certbot-dns-google-2.10.0/certbot_dns_google/0000775000175000017500000000000014603073204020211 5ustar00willgwillg././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/certbot_dns_google/__init__.py0000664000175000017500000002376114603073177022344 0ustar00willgwillg""" The `~certbot_dns_google.dns_google` plugin automates the process of completing a ``dns-01`` challenge (`~acme.challenges.DNS01`) by creating, and subsequently removing, TXT records using the Google Cloud DNS API. .. note:: The plugin is not installed by default. It can be installed by heading to `certbot.eff.org `_, choosing your system and selecting the Wildcard tab. Named Arguments --------------- ======================================== ===================================== ``--dns-google-credentials`` Google Cloud Platform credentials_ JSON file. (Required if not using `Application Default Credentials `_.) ``--dns-google-project`` The ID of the Google Cloud project that the Google Cloud DNS managed zone(s) reside in. (Default: project that the Google credentials_ belong to) ``--dns-google-propagation-seconds`` The number of seconds to wait for DNS to propagate before asking the ACME server to verify the DNS record. (Default: 60) ======================================== ===================================== Credentials ----------- Use of this plugin requires Google Cloud Platform credentials with the ability to modify the Cloud DNS managed zone(s) for which certificates are being issued. In most cases, configuring credentials for Certbot will require `creating a service account `_, and then either `granting permissions with predefined roles`_ or `granting permissions with custom roles`_ using IAM. Providing Credentials ^^^^^^^^^^^^^^^^^^^^^ The preferred method of providing credentials is to `set up Application Default Credentials `_ (ADC) in the environment that Certbot is running in. If you are running Certbot on Google Cloud then a service account can be assigned directly to most types of workload, including `Compute Engine VMs `_, `Kubernetes Engine Pods `_, `Cloud Run jobs `_, `Cloud Functions `_, and `Cloud Builds `_. If you are not running Certbot on Google Cloud then a credentials file should be provided using the ``--dns-google-credentials`` command-line argument. Google provides documentation for `creating service account keys `_, which is the most common method of using a service account outside of Google Cloud. .. code-block:: json :name: credentials-sa.json :caption: Example service account key file: { "type": "service_account", "project_id": "...", "private_key_id": "...", "private_key": "...", "client_email": "...", "client_id": "...", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "..." } .. caution:: You should protect these credentials as you would a password. Users who can read this file can use these credentials to issue some types of API calls on your behalf, limited by the permissions assigned to the account. Users who can cause Certbot to run using these credentials can complete a ``dns-01`` challenge to acquire new certificates or revoke existing certificates for domains these credentials are authorized to manage. Certbot will emit a warning if it detects that the credentials file can be accessed by other users on your system. The warning reads "Unsafe permissions on credentials configuration file", followed by the path to the credentials file. This warning will be emitted each time Certbot uses the credentials file, including for renewal, and cannot be silenced except by addressing the issue (e.g., by using a command like ``chmod 600`` to restrict access to the file). If you are running Certbot within another cloud platform, a CI platform, or any other platform that supports issuing OpenID Connect Tokens, then you may also have the option of securely authenticating with `workload identity federation `_. Instructions are generally available for most platforms, including `AWS or Azure `_, `GitHub Actions `_, and `GitLab CI `_. Granting Permissions with Predefined Roles ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The simplest method of granting the required permissions to the user or service account that Certbot is authenticating with is to use either of these predefined role strategies: * `dns.admin `_ against the *DNS zone(s)* that Certbot will be issuing certificates for. * `dns.reader `_ against the *project* containing the relevant DNS zones. *or* * `dns.admin `_ against the *project* containing the relevant DNS zones For instructions on how to grant roles, please read the Google provided documentation for `granting access roles against a project `_ and `granting access roles against zones `_. .. caution:: Granting the ``dns.admin`` role at the project level can present a significant security risk. It provides full administrative access to all DNS zones within the project, granting the ability to perform any action up to and including deleting all zones within a project. Granting Permissions with Custom Roles ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Custom roles are an alternative to predefined roles that provide the ability to define fine grained permission sets for specific use cases. They should generally be used when it is desirable to adhere to the principle of least privilege, such as within production or other security sensitive workloads. The following is an example strategy for granting granular permissions to Certbot using custom roles. If you are not already familiar with how to do so, Google provides documentation for `creating a custom IAM role `_. Firstly, create a custom role containing the permissions required to make DNS record updates. We suggest naming the custom role ``Certbot - Zone Editor`` with the ID ``certbot.zoneEditor``. The following permissions are required: * ``dns.changes.create`` * ``dns.changes.get`` * ``dns.changes.list`` * ``dns.resourceRecordSets.create`` * ``dns.resourceRecordSets.delete`` * ``dns.resourceRecordSets.list`` * ``dns.resourceRecordSets.update`` Next, create a custom role granting Certbot the ability to discover DNS zones. We suggest naming the custom role ``Certbot - Zone Lister`` with the ID ``certbot.zoneLister``. The following permissions are required: * ``dns.managedZones.get`` * ``dns.managedZones.list`` Finally, grant the custom roles to the user or service account that Certbot is authenticating with: * Grant your custom ``Certbot - Zone Editor`` role against the *DNS zone(s)* that Certbot will be issuing certificates for. * Grant your custom ``Certbot - Zone Lister`` role against the *project* containing the relevant DNS zones. For instructions on how to grant roles, please read the Google provided documentation for `granting access roles against a project `_ and `granting access roles against zones `_. Examples -------- .. code-block:: bash :caption: To acquire a certificate for ``example.com``, providing a credentials file certbot certonly \\ --dns-google \\ --dns-google-credentials ~/.secrets/certbot/google.json \\ -d example.com .. code-block:: bash :caption: To acquire a certificate for ``example.com``, where ADC is available and a credentials file is not required certbot certonly \\ --dns-google \\ -d example.com .. code-block:: bash :caption: To acquire a single certificate for both ``example.com`` and ``www.example.com`` certbot certonly \\ --dns-google \\ -d example.com \\ -d www.example.com .. code-block:: bash :caption: To acquire a certificate for ``example.com``, where the managed DNS zone resides in another Google Cloud project. certbot certonly \\ --dns-google \\ --dns-google-credentials ~/.secrets/certbot/google-project-test-foo.json \\ --dns-google-project test-bar \\ -d example.com .. code-block:: bash :caption: To acquire a certificate for ``example.com``, waiting 120 seconds for DNS propagation certbot certonly \\ --dns-google \\ --dns-google-propagation-seconds 120 \\ -d example.com """ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0836127 certbot-dns-google-2.10.0/certbot_dns_google/_internal/0000775000175000017500000000000014603073204022164 5ustar00willgwillg././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/certbot_dns_google/_internal/__init__.py0000664000175000017500000000011214603073177024300 0ustar00willgwillg"""Internal implementation of `~certbot_dns_google.dns_google` plugin.""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/certbot_dns_google/_internal/dns_google.py0000664000175000017500000003215314603073177024673 0ustar00willgwillg"""DNS Authenticator for Google Cloud DNS.""" import logging from typing import Any from typing import Callable from typing import Dict from typing import Optional import google.auth from google.auth import exceptions as googleauth_exceptions from googleapiclient import discovery from googleapiclient import errors as googleapiclient_errors from certbot import errors from certbot.plugins import dns_common logger = logging.getLogger(__name__) ADC_URL = 'https://cloud.google.com/docs/authentication/application-default-credentials' ACCT_URL = 'https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount' PERMISSIONS_URL = 'https://cloud.google.com/dns/access-control#permissions_and_roles' METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1/' METADATA_HEADERS = {'Metadata-Flavor': 'Google'} class Authenticator(dns_common.DNSAuthenticator): """DNS Authenticator for Google Cloud DNS This Authenticator uses the Google Cloud DNS API to fulfill a dns-01 challenge. """ description = ('Obtain certificates using a DNS TXT record (if you are using Google Cloud DNS ' 'for DNS).') ttl = 60 google_client = None @classmethod def add_parser_arguments(cls, add: Callable[..., None], default_propagation_seconds: int = 60) -> None: super().add_parser_arguments(add, default_propagation_seconds=60) add('credentials', help=('Path to Google Cloud DNS service account JSON file to use instead of relying on' ' Application Default Credentials (ADC). (See {0} for information about ADC, {1}' ' for information about creating a service account, and {2} for information about' ' the permissions required to modify Cloud DNS records.)') .format(ADC_URL, ACCT_URL, PERMISSIONS_URL), default=None) add('project', help=('The ID of the Google Cloud project that the Google Cloud DNS managed zone(s)' + ' reside in. This will be determined automatically if not specified.'), default=None) def more_info(self) -> str: return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Google Cloud DNS API.' def _setup_credentials(self) -> None: if self.conf('credentials') is not None: self._configure_file('credentials', 'path to Google Cloud DNS service account JSON file') dns_common.validate_file_permissions(self.conf('credentials')) try: self._get_google_client() except googleauth_exceptions.DefaultCredentialsError as e: raise errors.PluginError('Authentication using Google Application Default Credentials ' 'failed ({}). Please configure credentials using' ' --dns-google-credentials '.format(e)) def _perform(self, domain: str, validation_name: str, validation: str) -> None: self._get_google_client().add_txt_record(domain, validation_name, validation, self.ttl) def _cleanup(self, domain: str, validation_name: str, validation: str) -> None: self._get_google_client().del_txt_record(domain, validation_name, validation, self.ttl) def _get_google_client(self) -> '_GoogleClient': if self.google_client is None: self.google_client = _GoogleClient(self.conf('credentials'), self.conf('project')) return self.google_client class _GoogleClient: """ Encapsulates all communication with the Google Cloud DNS API. """ def __init__(self, account_json: Optional[str] = None, dns_project_id: Optional[str] = None, dns_api: Optional[discovery.Resource] = None) -> None: scopes = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite'] credentials = None project_id = None if account_json is not None: try: credentials, project_id = google.auth.load_credentials_from_file( account_json, scopes=scopes) except googleauth_exceptions.GoogleAuthError as e: raise errors.PluginError( "Error loading credentials file '{}': {}".format(account_json, e)) else: credentials, project_id = google.auth.default(scopes=scopes) if dns_project_id is not None: project_id = dns_project_id if not project_id: raise errors.PluginError('The Google Cloud project could not be automatically ' 'determined. Please configure it using --dns-google-project' ' .') self.project_id = project_id if not dns_api: self.dns = discovery.build('dns', 'v1', credentials=credentials, cache_discovery=False) else: self.dns = dns_api def add_txt_record(self, domain: str, record_name: str, record_content: str, record_ttl: int) -> None: """ 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) record_contents = self.get_existing_txt_rrset(zone_id, record_name) if record_contents is None: # If it wasn't possible to fetch the records at this label (missing .list permission), # assume there aren't any (#5678). If there are actually records here, this will fail # with HTTP 409/412 API errors. record_contents = {"rrdatas": []} add_records = record_contents["rrdatas"][:] if "\""+record_content+"\"" in record_contents["rrdatas"]: # The process was interrupted previously and validation token exists return add_records.append(record_content) data = { "kind": "dns#change", "additions": [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": record_name + ".", "rrdatas": add_records, "ttl": record_ttl, }, ], } if record_contents["rrdatas"]: # We need to remove old records in the same request data["deletions"] = [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": record_name + ".", "rrdatas": record_contents["rrdatas"], "ttl": record_contents["ttl"], }, ] changes = self.dns.changes() 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 del_txt_record(self, domain: str, record_name: str, record_content: str, record_ttl: int) -> None: """ Delete 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 """ try: zone_id = self._find_managed_zone_id(domain) except errors.PluginError: logger.warning('Error finding zone. Skipping cleanup.') return record_contents = self.get_existing_txt_rrset(zone_id, record_name) if record_contents is None: # If it wasn't possible to fetch the records at this label (missing .list permission), # assume there aren't any (#5678). If there are actually records here, this will fail # with HTTP 409/412 API errors. record_contents = {"rrdatas": ["\"" + record_content + "\""], "ttl": record_ttl} data = { "kind": "dns#change", "deletions": [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": record_name + ".", "rrdatas": record_contents["rrdatas"], "ttl": record_contents["ttl"], }, ], } # Remove the record being deleted from the list readd_contents = [r for r in record_contents["rrdatas"] if r != "\"" + record_content + "\""] if readd_contents: # We need to remove old records in the same request data["additions"] = [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": record_name + ".", "rrdatas": readd_contents, "ttl": record_contents["ttl"], }, ] changes = self.dns.changes() try: request = changes.create(project=self.project_id, managedZone=zone_id, body=data) request.execute() except googleapiclient_errors.Error as e: logger.warning('Encountered error deleting TXT record: %s', e) def get_existing_txt_rrset(self, zone_id: str, record_name: str) -> Optional[Dict[str, Any]]: """ Get existing TXT records from the RRset for the record name. If an error occurs while requesting the record set, it is suppressed and None is returned. :param str zone_id: The ID of the managed zone. :param str record_name: The record name (typically beginning with '_acme-challenge.'). :returns: The resourceRecordSet corresponding to `record_name` or None :rtype: `resourceRecordSet ` or `None` # pylint: disable=line-too-long """ rrs_request = self.dns.resourceRecordSets() # Add dot as the API returns absolute domains record_name += "." request = rrs_request.list(project=self.project_id, managedZone=zone_id, name=record_name, type="TXT") try: response = request.execute() except googleapiclient_errors.Error: logger.info("Unable to list existing records. If you're " "requesting a wildcard certificate, this might not work.") logger.debug("Error was:", exc_info=True) else: if response and response["rrsets"]: return response["rrsets"][0] return None def _find_managed_zone_id(self, domain: str) -> str: """ Find the managed zone for a given domain. :param str domain: The domain for which to find the managed zone. :returns: The ID of the managed zone, if found. :rtype: str :raises certbot.errors.PluginError: if the managed zone cannot be found. """ zone_dns_name_guesses = dns_common.base_domain_name_guesses(domain) mz = self.dns.managedZones() for zone_name in zone_dns_name_guesses: try: request = mz.list(project=self.project_id, dnsName=zone_name + '.') response = request.execute() zones = response['managedZones'] except googleapiclient_errors.Error as e: raise errors.PluginError('Encountered error finding managed zone: {0}' .format(e)) for zone in zones: zone_id = zone['id'] if zone['visibility'] == "public": logger.debug('Found id of %s for %s using name %s', zone_id, domain, zone_name) return zone_id raise errors.PluginError('Unable to determine managed zone for {0} using zone names: {1}.' .format(domain, zone_dns_name_guesses)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0836127 certbot-dns-google-2.10.0/certbot_dns_google/_internal/tests/0000775000175000017500000000000014603073204023326 5ustar00willgwillg././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/certbot_dns_google/_internal/tests/__init__.py0000664000175000017500000000003714603073177025450 0ustar00willgwillg"""certbot-dns-google tests""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/certbot_dns_google/_internal/tests/dns_google_test.py0000664000175000017500000005767714603073177027116 0ustar00willgwillg"""Tests for certbot_dns_google._internal.dns_google.""" import sys import unittest from unittest import mock from google.auth import exceptions as googleauth_exceptions from googleapiclient import discovery from googleapiclient.errors import Error from googleapiclient.http import HttpMock import pytest from certbot import errors from certbot.compat import os from certbot.errors import PluginError from certbot.plugins import dns_test_common from certbot.plugins.dns_test_common import DOMAIN from certbot.tests import util as test_util ACCOUNT_JSON_PATH = '/not/a/real/path.json' API_ERROR = Error() PROJECT_ID = "test-test-1" SCOPES = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite'] class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): def setUp(self): super().setUp() from certbot_dns_google._internal.dns_google import Authenticator path = os.path.join(self.tempdir, 'file.json') open(path, "wb").close() super().setUp() self.config = mock.MagicMock(google_credentials=path, google_project=PROJECT_ID, google_propagation_seconds=0) # don't wait during tests self.auth = Authenticator(self.config, "google") self.mock_client = mock.MagicMock() @test_util.patch_display_util() def test_perform(self, unused_mock_get_utility): # _get_google_client | pylint: disable=protected-access self.auth._get_google_client = mock.MagicMock(return_value=self.mock_client) self.auth.perform([self.achall]) expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] assert expected == self.mock_client.mock_calls def test_cleanup(self): # _get_google_client | pylint: disable=protected-access self.auth._get_google_client = mock.MagicMock(return_value=self.mock_client) # _attempt_cleanup | pylint: disable=protected-access self.auth._attempt_cleanup = True self.auth.cleanup([self.achall]) expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] assert expected == self.mock_client.mock_calls @test_util.patch_display_util() def test_without_auth(self, unused_mock_get_utility): self.auth._get_google_client = mock.MagicMock(side_effect=googleauth_exceptions.DefaultCredentialsError) self.config.google_credentials = None with pytest.raises(PluginError): self.auth.perform([self.achall]) @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient') def test_get_google_client(self, client_mock): test_client = mock.MagicMock() client_mock.return_value = test_client self.auth._get_google_client() assert client_mock.called assert self.auth.google_client is test_client def test_get_google_client_cached(self): test_client = mock.MagicMock() self.auth.google_client = test_client assert self.auth._get_google_client() is test_client class GoogleClientTest(unittest.TestCase): record_name = "foo" record_content = "bar" record_ttl = 42 zone = "ZONE_ID" change = "an-id" visibility = "public" def _setUp_client_with_mock(self, zone_request_side_effect, rrs_list_side_effect=None): from certbot_dns_google._internal.dns_google import _GoogleClient pwd = os.path.dirname(__file__) rel_path = 'testdata/discovery.json' discovery_file = os.path.join(pwd, rel_path) http_mock = HttpMock(discovery_file, {'status': '200'}) dns_api = discovery.build('dns', 'v1', http=http_mock) client = _GoogleClient(ACCOUNT_JSON_PATH, None, dns_api) # Setup mock_mz = mock.MagicMock() mock_mz.list.return_value.execute.side_effect = zone_request_side_effect mock_rrs = mock.MagicMock() def rrs_list(project=None, managedZone=None, name=None, type=None): response = {"rrsets": []} if name == "_acme-challenge.example.org.": response = {"rrsets": [{"name": "_acme-challenge.example.org.", "type": "TXT", "rrdatas": ["\"example-txt-contents\""], "ttl": 60}]} mock_return = mock.MagicMock() mock_return.execute.return_value = response mock_return.execute.side_effect = rrs_list_side_effect return mock_return mock_rrs.list.side_effect = rrs_list mock_changes = mock.MagicMock() client.dns.managedZones = mock.MagicMock(return_value=mock_mz) client.dns.changes = mock.MagicMock(return_value=mock_changes) client.dns.resourceRecordSets = mock.MagicMock(return_value=mock_rrs) return client, mock_changes @mock.patch('googleapiclient.discovery.build') @mock.patch('google.auth.default') def test_client_with_default_credentials(self, credential_mock, discovery_mock): test_credentials = mock.MagicMock() credential_mock.return_value = (test_credentials, PROJECT_ID) from certbot_dns_google._internal.dns_google import _GoogleClient client = _GoogleClient(None) credential_mock.assert_called_once_with(scopes=SCOPES) assert client.project_id == PROJECT_ID discovery_mock.assert_called_once_with('dns', 'v1', credentials=test_credentials, cache_discovery=False) @mock.patch('googleapiclient.discovery.build') @mock.patch('google.auth.load_credentials_from_file') def test_client_with_json_credentials(self, credential_mock, discovery_mock): test_credentials = mock.MagicMock() credential_mock.return_value = (test_credentials, PROJECT_ID) from certbot_dns_google._internal.dns_google import _GoogleClient client = _GoogleClient(ACCOUNT_JSON_PATH) credential_mock.assert_called_once_with(ACCOUNT_JSON_PATH, scopes=SCOPES) assert credential_mock.called assert client.project_id == PROJECT_ID discovery_mock.assert_called_once_with('dns', 'v1', credentials=test_credentials, cache_discovery=False) @mock.patch('google.auth.load_credentials_from_file') def test_client_bad_credentials_file(self, credential_mock): credential_mock.side_effect = googleauth_exceptions.DefaultCredentialsError('Some exception buried in google.auth') with pytest.raises(errors.PluginError) as exc_info: self._setUp_client_with_mock([]) assert str(exc_info.value) == \ "Error loading credentials file '/not/a/real/path.json': " \ "Some exception buried in google.auth" @mock.patch('google.auth.load_credentials_from_file') def test_client_missing_project_id(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), "") with pytest.raises(errors.PluginError) as exc_info: self._setUp_client_with_mock([]) assert str(exc_info.value) == \ "The Google Cloud project could not be automatically determined. " \ "Please configure it using --dns-google-project ." @mock.patch('googleapiclient.discovery.build') @mock.patch('google.auth.default') def test_client_with_project_id(self, credential_mock, unused_discovery_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) from certbot_dns_google._internal.dns_google import _GoogleClient client = _GoogleClient(None, "test-project-2") assert credential_mock.called assert client.project_id == "test-project-2" @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) credential_mock.assert_called_once_with('/not/a/real/path.json', scopes=SCOPES) client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) expected_body = { "kind": "dns#change", "additions": [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": self.record_name + ".", "rrdatas": [self.record_content, ], "ttl": self.record_ttl, }, ], } changes.create.assert_called_with(body=expected_body, managedZone=self.zone, project=PROJECT_ID) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_and_poll(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) changes.create.return_value.execute.return_value = {'status': 'pending', 'id': self.change} changes.get.return_value.execute.return_value = {'status': 'done'} client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) changes.create.assert_called_with(body=mock.ANY, managedZone=self.zone, project=PROJECT_ID) changes.get.assert_called_with(changeId=self.change, managedZone=self.zone, project=PROJECT_ID) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_and_poll_split_horizon(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': '{zone}-private'.format(zone=self.zone), 'dnsName': DOMAIN, 'visibility': 'private'},{'id': '{zone}-public'.format(zone=self.zone), 'dnsName': DOMAIN, 'visibility': self.visibility}]}]) changes.create.return_value.execute.return_value = {'status': 'pending', 'id': self.change} changes.get.return_value.execute.return_value = {'status': 'done'} client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) changes.create.assert_called_with(body=mock.ANY, managedZone='{zone}-public'.format(zone=self.zone), project=PROJECT_ID) changes.get.assert_called_with(changeId=self.change, managedZone='{zone}-public'.format(zone=self.zone), project=PROJECT_ID) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_delete_old(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) # pylint: disable=line-too-long mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" with mock.patch(mock_get_rrs) as mock_rrs: mock_rrs.return_value = {"rrdatas": ["sample-txt-contents"], "ttl": self.record_ttl} client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) assert changes.create.called is True deletions = changes.create.call_args_list[0][1]["body"]["deletions"][0] assert "sample-txt-contents" in deletions["rrdatas"] assert self.record_ttl == deletions["ttl"] @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_delete_old_ttl_case(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) # pylint: disable=line-too-long mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" with mock.patch(mock_get_rrs) as mock_rrs: custom_ttl = 300 mock_rrs.return_value = {"rrdatas": ["sample-txt-contents"], "ttl": custom_ttl} client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) assert changes.create.called is True deletions = changes.create.call_args_list[0][1]["body"]["deletions"][0] assert "sample-txt-contents" in deletions["rrdatas"] assert custom_ttl == deletions["ttl"] #otherwise HTTP 412 @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_noop(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) client.add_txt_record(DOMAIN, "_acme-challenge.example.org", "example-txt-contents", self.record_ttl) assert changes.create.called is False @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_error_during_zone_lookup(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, unused_changes = self._setUp_client_with_mock(API_ERROR) with pytest.raises(errors.PluginError): client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_zone_not_found(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, {'managedZones': []}]) with pytest.raises(errors.PluginError): client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_error_during_add(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) changes.create.side_effect = API_ERROR with pytest.raises(errors.PluginError): client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_multi_rrdatas(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) # pylint: disable=line-too-long mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" with mock.patch(mock_get_rrs) as mock_rrs: mock_rrs.return_value = {"rrdatas": ["\"sample-txt-contents\"", "\"example-txt-contents\""], "ttl": self.record_ttl} client.del_txt_record(DOMAIN, "_acme-challenge.example.org", "example-txt-contents", self.record_ttl) expected_body = { "kind": "dns#change", "deletions": [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": "_acme-challenge.example.org.", "rrdatas": ["\"sample-txt-contents\"", "\"example-txt-contents\""], "ttl": self.record_ttl, }, ], "additions": [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": "_acme-challenge.example.org.", "rrdatas": ["\"sample-txt-contents\"", ], "ttl": self.record_ttl, }, ], } changes.create.assert_called_with(body=expected_body, managedZone=self.zone, project=PROJECT_ID) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_single_rrdatas(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) # pylint: disable=line-too-long mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" with mock.patch(mock_get_rrs) as mock_rrs: mock_rrs.return_value = {"rrdatas": ["\"example-txt-contents\""], "ttl": self.record_ttl} client.del_txt_record(DOMAIN, "_acme-challenge.example.org", "example-txt-contents", self.record_ttl) expected_body = { "kind": "dns#change", "deletions": [ { "kind": "dns#resourceRecordSet", "type": "TXT", "name": "_acme-challenge.example.org.", "rrdatas": ["\"example-txt-contents\""], "ttl": self.record_ttl, }, ], } changes.create.assert_called_with(body=expected_body, managedZone=self.zone, project=PROJECT_ID) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_error_during_zone_lookup(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock(API_ERROR) client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) changes.create.assert_not_called() @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_zone_not_found(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock([{'managedZones': []}, {'managedZones': []}]) client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) changes.create.assert_not_called() @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_error_during_delete(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) changes.create.side_effect = API_ERROR client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_get_existing_found(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, unused_changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) # Record name mocked in setUp found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") assert found["rrdatas"] == ["\"example-txt-contents\""] assert found["ttl"] == 60 @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_get_existing_not_found(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, unused_changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}]) not_found = client.get_existing_txt_rrset(self.zone, "nonexistent.tld") assert not_found is None @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_get_existing_with_error(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, unused_changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}], API_ERROR) # Record name mocked in setUp found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") assert found is None @mock.patch('google.auth.load_credentials_from_file') @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_get_existing_fallback(self, credential_mock): credential_mock.return_value = (mock.MagicMock(), PROJECT_ID) client, unused_changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone, 'visibility': self.visibility}]}], API_ERROR) rrset = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") assert not rrset if __name__ == "__main__": sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0836127 certbot-dns-google-2.10.0/certbot_dns_google/_internal/tests/testdata/0000775000175000017500000000000014603073204025137 5ustar00willgwillg././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/certbot_dns_google/_internal/tests/testdata/discovery.json0000664000175000017500000013531414603073177030061 0ustar00willgwillg{ "kind": "discovery#restDescription", "etag": "\"-iA1DTNe4s-I6JZXPt1t1Ypy8IU/gSzgHqX4Zwypnde2YApimTf_qmE\"", "discoveryVersion": "v1", "id": "dns:v1", "name": "dns", "version": "v1", "revision": "20180314", "title": "Google Cloud DNS API", "description": "Configures and serves authoritative DNS records.", "ownerDomain": "google.com", "ownerName": "Google", "icons": { "x16": "https://www.gstatic.com/images/branding/product/1x/googleg_16dp.png", "x32": "https://www.gstatic.com/images/branding/product/1x/googleg_32dp.png" }, "documentationLink": "https://developers.google.com/cloud-dns", "protocol": "rest", "baseUrl": "https://www.googleapis.com/dns/v1/projects/", "basePath": "/dns/v1/projects/", "rootUrl": "https://www.googleapis.com/", "servicePath": "dns/v1/projects/", "batchPath": "batch/dns/v1", "parameters": { "alt": { "type": "string", "description": "Data format for the response.", "default": "json", "enum": [ "json" ], "enumDescriptions": [ "Responses with Content-Type of application/json" ], "location": "query" }, "fields": { "type": "string", "description": "Selector specifying which fields to include in a partial response.", "location": "query" }, "key": { "type": "string", "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", "location": "query" }, "oauth_token": { "type": "string", "description": "OAuth 2.0 token for the current user.", "location": "query" }, "prettyPrint": { "type": "boolean", "description": "Returns response with indentations and line breaks.", "default": "true", "location": "query" }, "quotaUser": { "type": "string", "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.", "location": "query" }, "userIp": { "type": "string", "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.", "location": "query" } }, "auth": { "oauth2": { "scopes": { "https://www.googleapis.com/auth/cloud-platform": { "description": "View and manage your data across Google Cloud Platform services" }, "https://www.googleapis.com/auth/cloud-platform.read-only": { "description": "View your data across Google Cloud Platform services" }, "https://www.googleapis.com/auth/ndev.clouddns.readonly": { "description": "View your DNS records hosted by Google Cloud DNS" }, "https://www.googleapis.com/auth/ndev.clouddns.readwrite": { "description": "View and manage your DNS records hosted by Google Cloud DNS" } } } }, "schemas": { "Change": { "id": "Change", "type": "object", "description": "An atomic update to a collection of ResourceRecordSets.", "properties": { "additions": { "type": "array", "description": "Which ResourceRecordSets to add?", "items": { "$ref": "ResourceRecordSet" } }, "deletions": { "type": "array", "description": "Which ResourceRecordSets to remove? Must match existing data exactly.", "items": { "$ref": "ResourceRecordSet" } }, "id": { "type": "string", "description": "Unique identifier for the resource; defined by the server (output only)." }, "isServing": { "type": "boolean", "description": "If the DNS queries for the zone will be served." }, "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#change\".", "default": "dns#change" }, "startTime": { "type": "string", "description": "The time that this operation was started by the server (output only). This is in RFC3339 text format." }, "status": { "type": "string", "description": "Status of the operation (output only).", "enum": [ "done", "pending" ], "enumDescriptions": [ "", "" ] } } }, "ChangesListResponse": { "id": "ChangesListResponse", "type": "object", "description": "The response to a request to enumerate Changes to a ResourceRecordSets collection.", "properties": { "changes": { "type": "array", "description": "The requested changes.", "items": { "$ref": "Change" } }, "header": { "$ref": "ResponseHeader" }, "kind": { "type": "string", "description": "Type of resource.", "default": "dns#changesListResponse" }, "nextPageToken": { "type": "string", "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a \"snapshot\" of collections larger than the maximum page size." } } }, "DnsKey": { "id": "DnsKey", "type": "object", "description": "A DNSSEC key pair.", "properties": { "algorithm": { "type": "string", "description": "String mnemonic specifying the DNSSEC algorithm of this key. Immutable after creation time.", "enum": [ "ecdsap256sha256", "ecdsap384sha384", "rsasha1", "rsasha256", "rsasha512" ], "enumDescriptions": [ "", "", "", "", "" ] }, "creationTime": { "type": "string", "description": "The time that this resource was created in the control plane. This is in RFC3339 text format. Output only." }, "description": { "type": "string", "description": "A mutable string of at most 1024 characters associated with this resource for the user's convenience. Has no effect on the resource's function." }, "digests": { "type": "array", "description": "Cryptographic hashes of the DNSKEY resource record associated with this DnsKey. These digests are needed to construct a DS record that points at this DNS key. Output only.", "items": { "$ref": "DnsKeyDigest" } }, "id": { "type": "string", "description": "Unique identifier for the resource; defined by the server (output only)." }, "isActive": { "type": "boolean", "description": "Active keys will be used to sign subsequent changes to the ManagedZone. Inactive keys will still be present as DNSKEY Resource Records for the use of resolvers validating existing signatures." }, "keyLength": { "type": "integer", "description": "Length of the key in bits. Specified at creation time then immutable.", "format": "uint32" }, "keyTag": { "type": "integer", "description": "The key tag is a non-cryptographic hash of the a DNSKEY resource record associated with this DnsKey. The key tag can be used to identify a DNSKEY more quickly (but it is not a unique identifier). In particular, the key tag is used in a parent zone's DS record to point at the DNSKEY in this child ManagedZone. The key tag is a number in the range [0, 65535] and the algorithm to calculate it is specified in RFC4034 Appendix B. Output only.", "format": "int32" }, "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#dnsKey\".", "default": "dns#dnsKey" }, "publicKey": { "type": "string", "description": "Base64 encoded public half of this key. Output only." }, "type": { "type": "string", "description": "One of \"KEY_SIGNING\" or \"ZONE_SIGNING\". Keys of type KEY_SIGNING have the Secure Entry Point flag set and, when active, will be used to sign only resource record sets of type DNSKEY. Otherwise, the Secure Entry Point flag will be cleared and this key will be used to sign only resource record sets of other types. Immutable after creation time.", "enum": [ "keySigning", "zoneSigning" ], "enumDescriptions": [ "", "" ] } } }, "DnsKeyDigest": { "id": "DnsKeyDigest", "type": "object", "properties": { "digest": { "type": "string", "description": "The base-16 encoded bytes of this digest. Suitable for use in a DS resource record." }, "type": { "type": "string", "description": "Specifies the algorithm used to calculate this digest.", "enum": [ "sha1", "sha256", "sha384" ], "enumDescriptions": [ "", "", "" ] } } }, "DnsKeySpec": { "id": "DnsKeySpec", "type": "object", "description": "Parameters for DnsKey key generation. Used for generating initial keys for a new ManagedZone and as default when adding a new DnsKey.", "properties": { "algorithm": { "type": "string", "description": "String mnemonic specifying the DNSSEC algorithm of this key.", "enum": [ "ecdsap256sha256", "ecdsap384sha384", "rsasha1", "rsasha256", "rsasha512" ], "enumDescriptions": [ "", "", "", "", "" ] }, "keyLength": { "type": "integer", "description": "Length of the keys in bits.", "format": "uint32" }, "keyType": { "type": "string", "description": "One of \"KEY_SIGNING\" or \"ZONE_SIGNING\". Keys of type KEY_SIGNING have the Secure Entry Point flag set and, when active, will be used to sign only resource record sets of type DNSKEY. Otherwise, the Secure Entry Point flag will be cleared and this key will be used to sign only resource record sets of other types.", "enum": [ "keySigning", "zoneSigning" ], "enumDescriptions": [ "", "" ] }, "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#dnsKeySpec\".", "default": "dns#dnsKeySpec" } } }, "DnsKeysListResponse": { "id": "DnsKeysListResponse", "type": "object", "description": "The response to a request to enumerate DnsKeys in a ManagedZone.", "properties": { "dnsKeys": { "type": "array", "description": "The requested resources.", "items": { "$ref": "DnsKey" } }, "header": { "$ref": "ResponseHeader" }, "kind": { "type": "string", "description": "Type of resource.", "default": "dns#dnsKeysListResponse" }, "nextPageToken": { "type": "string", "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a \"snapshot\" of collections larger than the maximum page size." } } }, "ManagedZone": { "id": "ManagedZone", "type": "object", "description": "A zone is a subtree of the DNS namespace under one administrative responsibility. A ManagedZone is a resource that represents a DNS zone hosted by the Cloud DNS service.", "properties": { "creationTime": { "type": "string", "description": "The time that this resource was created on the server. This is in RFC3339 text format. Output only." }, "description": { "type": "string", "description": "A mutable string of at most 1024 characters associated with this resource for the user's convenience. Has no effect on the managed zone's function." }, "dnsName": { "type": "string", "description": "The DNS name of this managed zone, for instance \"example.com.\"." }, "dnssecConfig": { "$ref": "ManagedZoneDnsSecConfig", "description": "DNSSEC configuration." }, "id": { "type": "string", "description": "Unique identifier for the resource; defined by the server (output only)", "format": "uint64" }, "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#managedZone\".", "default": "dns#managedZone" }, "labels": { "type": "object", "description": "User labels.", "additionalProperties": { "type": "string" } }, "name": { "type": "string", "description": "User assigned name for this resource. Must be unique within the project. The name must be 1-63 characters long, must begin with a letter, end with a letter or digit, and only contain lowercase letters, digits or dashes." }, "nameServerSet": { "type": "string", "description": "Optionally specifies the NameServerSet for this ManagedZone. A NameServerSet is a set of DNS name servers that all host the same ManagedZones. Most users will leave this field unset." }, "nameServers": { "type": "array", "description": "Delegate your managed_zone to these virtual name servers; defined by the server (output only)", "items": { "type": "string" } }, "visibility": { "type": "string", "description": "The zone's visibility: public zones are exposed to the Internet, while private zones are visible only to Virtual Private Cloud resources.", "default": "public" } } }, "ManagedZoneDnsSecConfig": { "id": "ManagedZoneDnsSecConfig", "type": "object", "properties": { "defaultKeySpecs": { "type": "array", "description": "Specifies parameters that will be used for generating initial DnsKeys for this ManagedZone. Output only while state is not OFF.", "items": { "$ref": "DnsKeySpec" } }, "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#managedZoneDnsSecConfig\".", "default": "dns#managedZoneDnsSecConfig" }, "nonExistence": { "type": "string", "description": "Specifies the mechanism used to provide authenticated denial-of-existence responses. Output only while state is not OFF.", "enum": [ "nsec", "nsec3" ], "enumDescriptions": [ "", "" ] }, "state": { "type": "string", "description": "Specifies whether DNSSEC is enabled, and what mode it is in.", "enum": [ "off", "on", "transfer" ], "enumDescriptions": [ "", "", "" ] } } }, "ManagedZoneOperationsListResponse": { "id": "ManagedZoneOperationsListResponse", "type": "object", "properties": { "header": { "$ref": "ResponseHeader" }, "kind": { "type": "string", "description": "Type of resource.", "default": "dns#managedZoneOperationsListResponse" }, "nextPageToken": { "type": "string", "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your page token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." }, "operations": { "type": "array", "description": "The operation resources.", "items": { "$ref": "Operation" } } } }, "ManagedZonesListResponse": { "id": "ManagedZonesListResponse", "type": "object", "properties": { "header": { "$ref": "ResponseHeader" }, "kind": { "type": "string", "description": "Type of resource.", "default": "dns#managedZonesListResponse" }, "managedZones": { "type": "array", "description": "The managed zone resources.", "items": { "$ref": "ManagedZone" } }, "nextPageToken": { "type": "string", "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your page token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." } } }, "Operation": { "id": "Operation", "type": "object", "description": "An operation represents a successful mutation performed on a Cloud DNS resource. Operations provide: - An audit log of server resource mutations. - A way to recover/retry API calls in the case where the response is never received by the caller. Use the caller specified client_operation_id.", "properties": { "dnsKeyContext": { "$ref": "OperationDnsKeyContext", "description": "Only populated if the operation targeted a DnsKey (output only)." }, "id": { "type": "string", "description": "Unique identifier for the resource. This is the client_operation_id if the client specified it when the mutation was initiated, otherwise, it is generated by the server. The name must be 1-63 characters long and match the regular expression [-a-z0-9]? (output only)" }, "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#operation\".", "default": "dns#operation" }, "startTime": { "type": "string", "description": "The time that this operation was started by the server. This is in RFC3339 text format (output only)." }, "status": { "type": "string", "description": "Status of the operation. Can be one of the following: \"PENDING\" or \"DONE\" (output only).", "enum": [ "done", "pending" ], "enumDescriptions": [ "", "" ] }, "type": { "type": "string", "description": "Type of the operation. Operations include insert, update, and delete (output only)." }, "user": { "type": "string", "description": "User who requested the operation, for example: user@example.com. cloud-dns-system for operations automatically done by the system. (output only)" }, "zoneContext": { "$ref": "OperationManagedZoneContext", "description": "Only populated if the operation targeted a ManagedZone (output only)." } } }, "OperationDnsKeyContext": { "id": "OperationDnsKeyContext", "type": "object", "properties": { "newValue": { "$ref": "DnsKey", "description": "The post-operation DnsKey resource." }, "oldValue": { "$ref": "DnsKey", "description": "The pre-operation DnsKey resource." } } }, "OperationManagedZoneContext": { "id": "OperationManagedZoneContext", "type": "object", "properties": { "newValue": { "$ref": "ManagedZone", "description": "The post-operation ManagedZone resource." }, "oldValue": { "$ref": "ManagedZone", "description": "The pre-operation ManagedZone resource." } } }, "Project": { "id": "Project", "type": "object", "description": "A project resource. The project is a top level container for resources including Cloud DNS ManagedZones. Projects can be created only in the APIs console.", "properties": { "id": { "type": "string", "description": "User assigned unique identifier for the resource (output only)." }, "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#project\".", "default": "dns#project" }, "number": { "type": "string", "description": "Unique numeric identifier for the resource; defined by the server (output only).", "format": "uint64" }, "quota": { "$ref": "Quota", "description": "Quotas assigned to this project (output only)." } } }, "Quota": { "id": "Quota", "type": "object", "description": "Limits associated with a Project.", "properties": { "dnsKeysPerManagedZone": { "type": "integer", "description": "Maximum allowed number of DnsKeys per ManagedZone.", "format": "int32" }, "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#quota\".", "default": "dns#quota" }, "managedZones": { "type": "integer", "description": "Maximum allowed number of managed zones in the project.", "format": "int32" }, "resourceRecordsPerRrset": { "type": "integer", "description": "Maximum allowed number of ResourceRecords per ResourceRecordSet.", "format": "int32" }, "rrsetAdditionsPerChange": { "type": "integer", "description": "Maximum allowed number of ResourceRecordSets to add per ChangesCreateRequest.", "format": "int32" }, "rrsetDeletionsPerChange": { "type": "integer", "description": "Maximum allowed number of ResourceRecordSets to delete per ChangesCreateRequest.", "format": "int32" }, "rrsetsPerManagedZone": { "type": "integer", "description": "Maximum allowed number of ResourceRecordSets per zone in the project.", "format": "int32" }, "totalRrdataSizePerChange": { "type": "integer", "description": "Maximum allowed size for total rrdata in one ChangesCreateRequest in bytes.", "format": "int32" }, "whitelistedKeySpecs": { "type": "array", "description": "DNSSEC algorithm and key length types that can be used for DnsKeys.", "items": { "$ref": "DnsKeySpec" } } } }, "ResourceRecordSet": { "id": "ResourceRecordSet", "type": "object", "description": "A unit of data that will be returned by the DNS servers.", "properties": { "kind": { "type": "string", "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#resourceRecordSet\".", "default": "dns#resourceRecordSet" }, "name": { "type": "string", "description": "For example, www.example.com." }, "rrdatas": { "type": "array", "description": "As defined in RFC 1035 (section 5) and RFC 1034 (section 3.6.1).", "items": { "type": "string" } }, "signatureRrdatas": { "type": "array", "description": "As defined in RFC 4034 (section 3.2).", "items": { "type": "string" } }, "ttl": { "type": "integer", "description": "Number of seconds that this ResourceRecordSet can be cached by resolvers.", "format": "int32" }, "type": { "type": "string", "description": "The identifier of a supported record type, for example, A, AAAA, MX, TXT, and so on." } } }, "ResourceRecordSetsListResponse": { "id": "ResourceRecordSetsListResponse", "type": "object", "properties": { "header": { "$ref": "ResponseHeader" }, "kind": { "type": "string", "description": "Type of resource.", "default": "dns#resourceRecordSetsListResponse" }, "nextPageToken": { "type": "string", "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." }, "rrsets": { "type": "array", "description": "The resource record set resources.", "items": { "$ref": "ResourceRecordSet" } } } }, "ResponseHeader": { "id": "ResponseHeader", "type": "object", "description": "Elements common to every response.", "properties": { "operationId": { "type": "string", "description": "For mutating operation requests that completed successfully. This is the client_operation_id if the client specified it, otherwise it is generated by the server (output only)." } } } }, "resources": { "changes": { "methods": { "create": { "id": "dns.changes.create", "path": "{project}/managedZones/{managedZone}/changes", "httpMethod": "POST", "description": "Atomically update the ResourceRecordSet collection.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone" ], "request": { "$ref": "Change" }, "response": { "$ref": "Change" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "get": { "id": "dns.changes.get", "path": "{project}/managedZones/{managedZone}/changes/{changeId}", "httpMethod": "GET", "description": "Fetch the representation of an existing Change.", "parameters": { "changeId": { "type": "string", "description": "The identifier of the requested change, from a previous ResourceRecordSetsChangeResponse.", "required": true, "location": "path" }, "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone", "changeId" ], "response": { "$ref": "Change" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "list": { "id": "dns.changes.list", "path": "{project}/managedZones/{managedZone}/changes", "httpMethod": "GET", "description": "Enumerate Changes to a ResourceRecordSet collection.", "parameters": { "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "maxResults": { "type": "integer", "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", "format": "int32", "location": "query" }, "pageToken": { "type": "string", "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", "location": "query" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" }, "sortBy": { "type": "string", "description": "Sorting criterion. The only supported value is change sequence.", "default": "changeSequence", "enum": [ "changeSequence" ], "enumDescriptions": [ "" ], "location": "query" }, "sortOrder": { "type": "string", "description": "Sorting order direction: 'ascending' or 'descending'.", "location": "query" } }, "parameterOrder": [ "project", "managedZone" ], "response": { "$ref": "ChangesListResponse" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] } } }, "dnsKeys": { "methods": { "get": { "id": "dns.dnsKeys.get", "path": "{project}/managedZones/{managedZone}/dnsKeys/{dnsKeyId}", "httpMethod": "GET", "description": "Fetch the representation of an existing DnsKey.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "digestType": { "type": "string", "description": "An optional comma-separated list of digest types to compute and display for key signing keys. If omitted, the recommended digest type will be computed and displayed.", "location": "query" }, "dnsKeyId": { "type": "string", "description": "The identifier of the requested DnsKey.", "required": true, "location": "path" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone", "dnsKeyId" ], "response": { "$ref": "DnsKey" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "list": { "id": "dns.dnsKeys.list", "path": "{project}/managedZones/{managedZone}/dnsKeys", "httpMethod": "GET", "description": "Enumerate DnsKeys to a ResourceRecordSet collection.", "parameters": { "digestType": { "type": "string", "description": "An optional comma-separated list of digest types to compute and display for key signing keys. If omitted, the recommended digest type will be computed and displayed.", "location": "query" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "maxResults": { "type": "integer", "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", "format": "int32", "location": "query" }, "pageToken": { "type": "string", "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", "location": "query" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone" ], "response": { "$ref": "DnsKeysListResponse" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] } } }, "managedZoneOperations": { "methods": { "get": { "id": "dns.managedZoneOperations.get", "path": "{project}/managedZones/{managedZone}/operations/{operation}", "httpMethod": "GET", "description": "Fetch the representation of an existing Operation.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request.", "required": true, "location": "path" }, "operation": { "type": "string", "description": "Identifies the operation addressed by this request.", "required": true, "location": "path" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone", "operation" ], "response": { "$ref": "Operation" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "list": { "id": "dns.managedZoneOperations.list", "path": "{project}/managedZones/{managedZone}/operations", "httpMethod": "GET", "description": "Enumerate Operations for the given ManagedZone.", "parameters": { "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request.", "required": true, "location": "path" }, "maxResults": { "type": "integer", "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", "format": "int32", "location": "query" }, "pageToken": { "type": "string", "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", "location": "query" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" }, "sortBy": { "type": "string", "description": "Sorting criterion. The only supported values are START_TIME and ID.", "default": "startTime", "enum": [ "id", "startTime" ], "enumDescriptions": [ "", "" ], "location": "query" } }, "parameterOrder": [ "project", "managedZone" ], "response": { "$ref": "ManagedZoneOperationsListResponse" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] } } }, "managedZones": { "methods": { "create": { "id": "dns.managedZones.create", "path": "{project}/managedZones", "httpMethod": "POST", "description": "Create a new ManagedZone.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project" ], "request": { "$ref": "ManagedZone" }, "response": { "$ref": "ManagedZone" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "delete": { "id": "dns.managedZones.delete", "path": "{project}/managedZones/{managedZone}", "httpMethod": "DELETE", "description": "Delete a previously created ManagedZone.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone" ], "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "get": { "id": "dns.managedZones.get", "path": "{project}/managedZones/{managedZone}", "httpMethod": "GET", "description": "Fetch the representation of an existing ManagedZone.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone" ], "response": { "$ref": "ManagedZone" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "list": { "id": "dns.managedZones.list", "path": "{project}/managedZones", "httpMethod": "GET", "description": "Enumerate ManagedZones that have been created but not yet deleted.", "parameters": { "dnsName": { "type": "string", "description": "Restricts the list to return only zones with this domain name.", "location": "query" }, "maxResults": { "type": "integer", "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", "format": "int32", "location": "query" }, "pageToken": { "type": "string", "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", "location": "query" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project" ], "response": { "$ref": "ManagedZonesListResponse" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "patch": { "id": "dns.managedZones.patch", "path": "{project}/managedZones/{managedZone}", "httpMethod": "PATCH", "description": "Update an existing ManagedZone. This method supports patch semantics.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone" ], "request": { "$ref": "ManagedZone" }, "response": { "$ref": "Operation" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] }, "update": { "id": "dns.managedZones.update", "path": "{project}/managedZones/{managedZone}", "httpMethod": "PUT", "description": "Update an existing ManagedZone.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project", "managedZone" ], "request": { "$ref": "ManagedZone" }, "response": { "$ref": "Operation" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] } } }, "projects": { "methods": { "get": { "id": "dns.projects.get", "path": "{project}", "httpMethod": "GET", "description": "Fetch the representation of an existing Project.", "parameters": { "clientOperationId": { "type": "string", "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", "location": "query" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" } }, "parameterOrder": [ "project" ], "response": { "$ref": "Project" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] } } }, "resourceRecordSets": { "methods": { "list": { "id": "dns.resourceRecordSets.list", "path": "{project}/managedZones/{managedZone}/rrsets", "httpMethod": "GET", "description": "Enumerate ResourceRecordSets that have been created but not yet deleted.", "parameters": { "managedZone": { "type": "string", "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", "required": true, "location": "path" }, "maxResults": { "type": "integer", "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", "format": "int32", "location": "query" }, "name": { "type": "string", "description": "Restricts the list to return only records with this fully qualified domain name.", "location": "query" }, "pageToken": { "type": "string", "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", "location": "query" }, "project": { "type": "string", "description": "Identifies the project addressed by this request.", "required": true, "location": "path" }, "type": { "type": "string", "description": "Restricts the list to return only records of this type. If present, the \"name\" parameter must also be present.", "location": "query" } }, "parameterOrder": [ "project", "managedZone" ], "response": { "$ref": "ResourceRecordSetsListResponse" }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform.read-only", "https://www.googleapis.com/auth/ndev.clouddns.readonly", "https://www.googleapis.com/auth/ndev.clouddns.readwrite" ] } } } } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/certbot_dns_google/py.typed0000664000175000017500000000000014603073177021707 0ustar00willgwillg././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0876129 certbot-dns-google-2.10.0/certbot_dns_google.egg-info/0000775000175000017500000000000014603073204021703 5ustar00willgwillg././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092804.0 certbot-dns-google-2.10.0/certbot_dns_google.egg-info/PKG-INFO0000644000175000017500000000273514603073204023005 0ustar00willgwillgMetadata-Version: 2.1 Name: certbot-dns-google Version: 2.10.0 Summary: Google Cloud DNS Authenticator plugin for Certbot Home-page: https://github.com/certbot/certbot Author: Certbot Project Author-email: certbot-dev@eff.org License: Apache License 2.0 Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Plugins Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Security Classifier: Topic :: System :: Installation/Setup Classifier: Topic :: System :: Networking Classifier: Topic :: System :: Systems Administration Classifier: Topic :: Utilities Requires-Python: >=3.8 License-File: LICENSE.txt Requires-Dist: google-api-python-client>=1.6.5 Requires-Dist: google-auth>=2.16.0 Requires-Dist: setuptools>=41.6.0 Requires-Dist: acme>=2.10.0 Requires-Dist: certbot>=2.10.0 Provides-Extra: docs Requires-Dist: Sphinx>=1.0; extra == "docs" Requires-Dist: sphinx_rtd_theme; extra == "docs" Provides-Extra: test Requires-Dist: pytest; extra == "test" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092804.0 certbot-dns-google-2.10.0/certbot_dns_google.egg-info/SOURCES.txt0000664000175000017500000000125514603073204023572 0ustar00willgwillgLICENSE.txt MANIFEST.in README.rst setup.py certbot_dns_google/__init__.py certbot_dns_google/py.typed certbot_dns_google.egg-info/PKG-INFO certbot_dns_google.egg-info/SOURCES.txt certbot_dns_google.egg-info/dependency_links.txt certbot_dns_google.egg-info/entry_points.txt certbot_dns_google.egg-info/requires.txt certbot_dns_google.egg-info/top_level.txt certbot_dns_google/_internal/__init__.py certbot_dns_google/_internal/dns_google.py certbot_dns_google/_internal/tests/__init__.py certbot_dns_google/_internal/tests/dns_google_test.py certbot_dns_google/_internal/tests/testdata/discovery.json docs/.gitignore docs/Makefile docs/api.rst docs/conf.py docs/index.rst docs/make.bat././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092804.0 certbot-dns-google-2.10.0/certbot_dns_google.egg-info/dependency_links.txt0000664000175000017500000000000114603073204025751 0ustar00willgwillg ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092804.0 certbot-dns-google-2.10.0/certbot_dns_google.egg-info/entry_points.txt0000664000175000017500000000012514603073204025177 0ustar00willgwillg[certbot.plugins] dns-google = certbot_dns_google._internal.dns_google:Authenticator ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092804.0 certbot-dns-google-2.10.0/certbot_dns_google.egg-info/requires.txt0000664000175000017500000000023014603073204024276 0ustar00willgwillggoogle-api-python-client>=1.6.5 google-auth>=2.16.0 setuptools>=41.6.0 acme>=2.10.0 certbot>=2.10.0 [docs] Sphinx>=1.0 sphinx_rtd_theme [test] pytest ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092804.0 certbot-dns-google-2.10.0/certbot_dns_google.egg-info/top_level.txt0000664000175000017500000000002314603073204024430 0ustar00willgwillgcertbot_dns_google ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0876129 certbot-dns-google-2.10.0/docs/0000775000175000017500000000000014603073204015277 5ustar00willgwillg././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/docs/.gitignore0000664000175000017500000000001114603073177017270 0ustar00willgwillg/_build/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/docs/Makefile0000664000175000017500000000114714603073177016753 0ustar00willgwillg# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = certbot-dns-google SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/docs/api.rst0000664000175000017500000000022514603073177016612 0ustar00willgwillg================= API Documentation ================= Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/docs/conf.py0000664000175000017500000001240014603073177016604 0ustar00willgwillg# -*- coding: utf-8 -*- # # certbot-dns-google documentation build configuration file, created by # sphinx-quickstart on Wed May 10 15:47:49 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('_ext')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx_rtd_theme'] autodoc_member_order = 'bysource' autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'certbot-dns-google' copyright = u'2017, Certbot Project' author = u'Certbot Project' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = u'0' # The full version, including alpha/beta/rc tags. release = u'0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] default_role = 'py:obj' # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'certbot-dns-googledoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'certbot-dns-google.tex', u'certbot-dns-google Documentation', u'Certbot Project', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'certbot-dns-google', u'certbot-dns-google Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'certbot-dns-google', u'certbot-dns-google Documentation', author, 'certbot-dns-google', 'One line description of project.', 'Miscellaneous'), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ('https://docs.python.org/', None), 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), 'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None), } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/docs/index.rst0000664000175000017500000000105514603073177017152 0ustar00willgwillg.. certbot-dns-google documentation master file, created by sphinx-quickstart on Wed May 10 15:47:49 2017. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to certbot-dns-google's documentation! ============================================== .. toctree:: :maxdepth: 2 :caption: Contents: .. automodule:: certbot_dns_google :members: .. toctree:: :maxdepth: 1 api Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092799.0 certbot-dns-google-2.10.0/docs/make.bat0000664000175000017500000000147314603073177016722 0ustar00willgwillg@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build set SPHINXPROJ=certbot-dns-google if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1712092804.0876129 certbot-dns-google-2.10.0/setup.cfg0000664000175000017500000000004614603073204016170 0ustar00willgwillg[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1712092800.0 certbot-dns-google-2.10.0/setup.py0000664000175000017500000000430714603073200016061 0ustar00willgwillgimport os import sys from setuptools import find_packages from setuptools import setup version = '2.10.0' install_requires = [ 'google-api-python-client>=1.6.5', 'google-auth>=2.16.0', 'setuptools>=41.6.0', ] if os.environ.get('SNAP_BUILD'): install_requires.append('packaging') else: install_requires.extend([ # We specify the minimum acme and certbot version as the current plugin # version for simplicity. See # https://github.com/certbot/certbot/issues/8761 for more info. f'acme>={version}', f'certbot>={version}', ]) docs_extras = [ 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags 'sphinx_rtd_theme', ] test_extras = [ 'pytest', ] setup( name='certbot-dns-google', version=version, description="Google Cloud DNS Authenticator plugin for Certbot", url='https://github.com/certbot/certbot', author="Certbot Project", author_email='certbot-dev@eff.org', license='Apache License 2.0', python_requires='>=3.8', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', 'Topic :: System :: Networking', 'Topic :: System :: Systems Administration', 'Topic :: Utilities', ], packages=find_packages(), include_package_data=True, install_requires=install_requires, extras_require={ 'docs': docs_extras, 'test': test_extras, }, entry_points={ 'certbot.plugins': [ 'dns-google = certbot_dns_google._internal.dns_google:Authenticator', ], }, )