Exemplo n.º 1
0
def record_state_in_domain(dns_record: DnsRecord, domain_records: list) -> RecordState:
    """Report if the record is missing or present, different or the same.

    First the record will be searched for, using filter_domain_records.
    Raises an exception if multiple records are found. As this script is not
    designed to handle this, it will raise an exception.

    The single record is tagged with the possible enumerations of the RecordState class
    NOTFOUND: The record is not present
    FOUND_SAME: Record is present and the content is (already) the same
    FOUND_DIFFERENT: Record is present, but with different content
    FOUND_NO_REQUEST_DATA: If the content of the (requested) dns_record is empty.
                           This may occur when deleting a record (just) by name.


    Note on expire/TTL: To create/change/delete a record, type, content and TTL
    must all be present for each of the API calls. If the TTL was not provided
    on the commandline, the (only) )found record will be assumed to be the
    targeted record, therefore it's TTL will be entered in the dns_record if it
    is missing.

    :param dns_record: Record to search for in the domain list
    :type dns_record: DnsRecord
    :param domain_records: List of domain records
    :type domain_records: list
    :raises DuplicateDnsRecords: More the one record was found.
    :return: The state of the record in the domain list
    :rtype: RecordState
    """
    record_list = filter_domain_records(domain_records, dns_record, ignore_content=True)

    if len(record_list) > 1:
        records_data = ", ".join([record["content"] for record in record_list])
        raise DuplicateDnsRecords(
            (
                f"Multiple records found for '{dns_record.fqdn}' "
                f"('{dns_record.rtype}'); '{records_data}'. "
                "Not processing as this may lead to unexpected results"
            )
        )

    if len(record_list) == 0:
        logger.info(f"Record '{dns_record.fqdn}', type '{dns_record.rtype}' not found!")
        return RecordState.NOTFOUND

    if len(record_list) == 1:
        if dns_record.expire is None:
            dns_record.expire = record_list[0]["expire"]

        if dns_record.content is None:
            dns_record.content = record_list[0]["content"]
            return RecordState.FOUND_NO_REQUEST_DATA

        if dns_record.content == record_list[0]["content"]:
            return RecordState.FOUND_SAME

        return RecordState.FOUND_DIFFERENT
Exemplo n.º 2
0
    def test_init(self, mocker, record_data: dict):
        """Test the DnsRecord class initialization.

        Args:
            mocker (pytest_mock.plugin.MockerFixture): mocking
            record_data (dict): Dictionary with data for the DnsRecord
        """
        record_data = stl(record_data)
        if record_data["query_data"]:
            mocker.patch(
                "transip_dns.transip_interface.DnsRecord.query_for_content",
                return_value=record_data["query_data"],
            )
        dns_record = DnsRecord(**record_data)

        if record_data["query_data"] is not None:
            # If query, test rdata separate that is has the query_result
            assert dns_record.content == record_data["query_data"]
            # rdata has been tested, take it out of the test
            del record_data["content"]

        # query_data is no attribute so remove it, but used in setting rdata
        del record_data["query_data"]

        for field in record_data:
            assert getattr(dns_record, field) == record_data[field]
        assert dns_record.fqdn == f"{dns_record.name}.{dns_record.zone}"
Exemplo n.º 3
0
def process_parameters(
    args: argparse.Namespace,
) -> Tuple[TransipInterface, DnsRecord, List]:
    """Functionally process the parameters into usable objects.

    Provide a connection with TransIP, a record object of the targeted record
    and a domainlisting.


    :param args: the parsed arguments from command line and environment
    :type args: argparse.Namespace
    :return: [description]
    :rtype: Tuple[TransipInterface, DnsRecord, List]
    """
    dns_record = DnsRecord(
        name=args.record_name,
        rtype=args.record_type,
        expire=args.record_ttl,
        content=args.record_data,
        zone=args.domainname,
        query_data=args.query_url,
    )

    transip_interface = TransipInterface(
        login=args.user,
        private_key_pem=args.private_key,
        private_key_pem_file=args.private_key_file,
        access_token=args.token,
        global_key=True,
    )

    domain_records = []
    if args.domains is False:
        response = transip_interface.get_dns_entry(dns_zone_name=dns_record.zone)
        domain_records = response.json()["dnsEntries"]

        if dns_record.name is not None:
            # Can only occur when list of domain is requested
            dns_record.record_state = record_state_in_domain(dns_record, domain_records)

    return (transip_interface, dns_record, domain_records)
Exemplo n.º 4
0
def delete_by_name_and_type(
    transip_interface,
    record_name: str,
    record_type: str = "A",
    raise_exception_if_missing: bool = False,
    raise_exception_if_found: bool = False,
):
    """Delete a specified record, if found.

    By arguments raise_exception_if_missing and raise_exception_if_found, a validation,
    assertion, can be performed to test expected outcomes. For example when doing a
    cleanup after a test has created records, then they they should actually be there.

    Args:
        transip_interface ([type]): [description]
        record_name (str): Name of the DNS record
        record_type (str, optional): DNS record type. Defaults to "A".
        raise_exception_if_missing (bool =False): raise exception if should be there
        raise_exception_if_found (bool =False): raise exception if should not be there

    Exception:
        Raise exception if an entry was supposed to be there
    """
    # Destroy record, but should results in error as it should have been deleted
    if transip_interface._token == transip_demo_token:
        return  # pragma: not live account skip live coverage
    else:  # pragma: not demo account skip demo coverage
        record_missing = True
        for record in transip_interface.get_dns_entry(
                transip_domain).json()["dnsEntries"]:
            if record["name"] == record_name and record["type"] == record_type:
                record_missing = False
                transip_interface.delete_dns_entry(
                    DnsRecord(
                        zone=transip_domain,
                        name=record_name,
                        rtype=record_type,
                        expire=record["expire"],
                        content=record["content"],
                    ))
                break
        if (raise_exception_if_missing and record_missing
            ):  # pragma: code in case test and/or cleanup failed
            raise Exception(
                "{record_name}, {record_type}, not found for deletion")
        if (raise_exception_if_found and not record_missing
            ):  # pragma: code in case test and/or cleanup failed
            raise Exception(
                "{record_name}, {record_type} found, should not be here anymore"
            )
Exemplo n.º 5
0
    def test_query_for_content(self, requests_mock):
        """Test query_ip function.

        Fairly simple test that the function will pass on the result of the url request

        Args:
            requests_mock (requests_mock.mocker.Mocker): a mock specifically for the url request
        """
        query_url = "https://ipv4_or_ipv6_address"
        ip_address = "::ffff:198.51.100.1"
        requests_mock.get(query_url, text=ip_address)
        returned_ip = DnsRecord.query_for_content(query_url)

        assert returned_ip == ip_address
Exemplo n.º 6
0
    def test_init_errors(self, content, expire, name, rtype, error_expected,
                         error_type):
        """Test if (in)correct record types are allowed/raised."""
        if error_expected:
            pytest.raises(error_type, DnsRecord, name, rtype, expire, content,
                          "x.net")
        else:
            entry = DnsRecord(name, rtype, expire, content, "x.net")

            assert entry.content == content
            assert entry.expire == expire
            assert entry.name == name

            assert entry.rtype == rtype
Exemplo n.º 7
0
def create_record_of_each_type(request, record_data_for_each_record_type,
                               transip_interface):
    """Loop over each RECORD_TYPEs and create such a record instance.

    Args:
        request (pytest.fixtures.SubRequest):
            Provides access to iteration in params; record types
        record_data_for_each_record_type (fixture/function):
            Hash of valid name/value pairs for a/each DNS record type
        transip_interface (fixture): Connection with TransIP

    Yields:
        dns_record [hash]: Command line parameters for the record to be created
        test_string [str]: Expected success string to be produced by the script
    """
    record_type = request.param
    record = record_data_for_each_record_type(request.param)
    record_name = record["name"]
    record_data = record["value"]
    record_ttl = "300"
    dns_record_parameters = {
        "--record_type": record_type,
        "--record_name": record_name,
        "--record_data": record_data,
        "--record_ttl": record_ttl,
    }

    yield (dns_record_parameters, record)

    rec = DnsRecord(
        content=record_data,
        name=record_name,
        rtype=record_type,
        expire=record_ttl,
        zone=transip_domain,
    )
    # Destroy record
    if transip_interface._token != transip_demo_token:
        transip_interface.delete_dns_entry(  # pragma: not demo account skip demo coverage
            rec)
Exemplo n.º 8
0
    def test_execute_dns_retry(
        self,
        mocker,
        requests_mock,
        caplog,
        retries,
        negative_responses,
        method,
    ):

        dns_record = DnsRecord("name", "A", 300, "ip", "example.com")

        response_error = {"status_code": 409}
        response_ok = {
            "status_code": 204,
            "text": '{"dnsEntries": "whatever"}',
        }
        mocked_response = []
        # Return a certain number of"negative_responses"
        for _ in range(negative_responses):
            mocked_response.append(response_error)
        # Before a positive response
        mocked_response.append(response_ok)

        # Dynamically mock requests.get, post, patch and delete
        request_mock_action = getattr(requests_mock, method)
        request_mock_action(
            "https://api.transip.nl/v6/domains/example.com/dns",
            mocked_response)

        transip_interface = TransipInterface(
            access_token="complex key",
            retry=retries,
            retry_delay=0.01,
        )

        # Dynamically pick transip_interface.get_dns_entry, post, patch and delete
        transip_interface_test_dns_entry = getattr(transip_interface,
                                                   f"{method}_dns_entry")

        # Post, patch and delete need the full record
        dns_parameter = dns_record
        if method == "get":  # get only needs the zone to list
            dns_parameter = dns_record.zone

        caplog.set_level(logging.DEBUG)
        if negative_responses > retries:
            pytest.raises(
                requests.exceptions.HTTPError,
                transip_interface_test_dns_entry,
                dns_parameter,
            )
            assert (len(re.findall(r"(API request returned 409)",
                                   caplog.text)) == retries + 1)

        else:
            response = transip_interface_test_dns_entry(dns_parameter)
            if method == "get":  # get "unpacks" the response into actual content
                assert response.json()["dnsEntries"] == "whatever"

            assert response.status_code == 204
            assert (len(re.findall(r"(API request returned 409)",
                                   caplog.text)) == negative_responses)
            assert len(re.findall(r"(API request returned 204)",
                                  caplog.text)) == 1
Exemplo n.º 9
0
def delete_record_of_each_type(request, record_data_for_each_record_type,
                               transip_interface):
    """Loop over each RECORD_TYPEs and delete such a record.

    Args:
        request (pytest.fixtures.SubRequest):
            Provides access to iteration in params; record types.
        record_data_for_each_record_type (fixture/function):
            Hash of valid name/value pairs for a/each DNS record type.
        transip_interface (fixture): Connection with TransIP.

    Yields:
        dns_record [hash]: Command line parameters for the record to be deleted.
        test_string [str]: Expected success string to be produced by the script.
    """
    record_type = request.param
    record = record_data_for_each_record_type(record_type=request.param,
                                              for_create=False)
    record_name = record["name"]
    record_data = record["value"]
    record_ttl = "300"
    dns_record_parameters = {
        "--record_type": record_type,
        "--record_name": record_name,
        "--record_data": record_data,
        "--record_ttl": record_ttl,
    }
    dns_record_object = DnsRecord(
        name=record_name,
        rtype=record_type,
        expire=record_ttl,
        content=record_data,
        zone=transip_domain,
    )
    try:
        transip_interface.post_dns_entry(dns_record_object)
    except requests.exceptions.HTTPError as e:  # pragma: no cover
        if "this exact record already exists" in e.response.content.decode():
            # Record might be left by previous (failed) attempt.
            # If tests run according to plan, this will not be executed
            pass

    partial_record = False
    if record["hide_ttl"]:
        del dns_record_parameters["--record_ttl"]
        partial_record = True
    if record["hide_value"]:
        del dns_record_parameters["--record_data"]
        partial_record = True

    dns_record_parameters["--delete"] = None
    yield (dns_record_parameters, dns_record_object, partial_record)

    # Destroy record, as the test should have delete all the records
    # the exception will never be triggered
    try:
        transip_interface.delete_dns_entry(dns_record_object)
    except Exception as e:
        if (  # pragma: no cover
                e.response.status_code == 404
                and "Dns entry not found" in e.response.content.decode()):
            pass