From e7a616b8ff1246331fba1d08711cfd955729df2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 7 Nov 2022 19:31:47 +0100 Subject: [PATCH] Use location info helper for IP in Cloudflare DNS (#81714) * Use location info helper for IP in Cloudflare DNS * simplify * Blow up * coverage --- .../components/cloudflare/__init__.py | 29 ++++++++-- tests/components/cloudflare/test_init.py | 58 ++++++++++++++++--- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/cloudflare/__init__.py b/homeassistant/components/cloudflare/__init__.py index df5b08e9395..3a8f6b39ae7 100644 --- a/homeassistant/components/cloudflare/__init__.py +++ b/homeassistant/components/cloudflare/__init__.py @@ -4,6 +4,7 @@ from __future__ import annotations from datetime import timedelta import logging +from aiohttp import ClientSession from pycfdns import CloudflareUpdater from pycfdns.exceptions import ( CloudflareAuthenticationException, @@ -14,10 +15,16 @@ from pycfdns.exceptions import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_TOKEN, CONF_ZONE from homeassistant.core import HomeAssistant, ServiceCall -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.exceptions import ( + ConfigEntryAuthFailed, + ConfigEntryNotReady, + HomeAssistantError, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval +from homeassistant.util.location import async_detect_location_info +from homeassistant.util.network import is_ipv4_address from .const import CONF_RECORDS, DEFAULT_UPDATE_INTERVAL, DOMAIN, SERVICE_UPDATE_RECORDS @@ -28,8 +35,9 @@ CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Cloudflare from a config entry.""" + session = async_get_clientsession(hass) cfupdate = CloudflareUpdater( - async_get_clientsession(hass), + session, entry.data[CONF_API_TOKEN], entry.data[CONF_ZONE], entry.data[CONF_RECORDS], @@ -45,14 +53,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def update_records(now): """Set up recurring update.""" try: - await _async_update_cloudflare(cfupdate, zone_id) + await _async_update_cloudflare(session, cfupdate, zone_id) except CloudflareException as error: _LOGGER.error("Error updating zone %s: %s", entry.data[CONF_ZONE], error) async def update_records_service(call: ServiceCall) -> None: """Set up service for manual trigger.""" try: - await _async_update_cloudflare(cfupdate, zone_id) + await _async_update_cloudflare(session, cfupdate, zone_id) except CloudflareException as error: _LOGGER.error("Error updating zone %s: %s", entry.data[CONF_ZONE], error) @@ -76,11 +84,20 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def _async_update_cloudflare(cfupdate: CloudflareUpdater, zone_id: str): +async def _async_update_cloudflare( + session: ClientSession, + cfupdate: CloudflareUpdater, + zone_id: str, +) -> None: _LOGGER.debug("Starting update for zone %s", cfupdate.zone) records = await cfupdate.get_record_info(zone_id) _LOGGER.debug("Records: %s", records) - await cfupdate.update_records(zone_id, records) + location_info = await async_detect_location_info(session) + + if not location_info or not is_ipv4_address(location_info.ip): + raise HomeAssistantError("Could not get external IPv4 address") + + await cfupdate.update_records(zone_id, records, location_info.ip) _LOGGER.debug("Update for zone %s is complete", cfupdate.zone) diff --git a/tests/components/cloudflare/test_init.py b/tests/components/cloudflare/test_init.py index ab7dbdab78e..6e7f450d711 100644 --- a/tests/components/cloudflare/test_init.py +++ b/tests/components/cloudflare/test_init.py @@ -1,11 +1,16 @@ """Test the Cloudflare integration.""" +from unittest.mock import patch + from pycfdns.exceptions import ( CloudflareAuthenticationException, CloudflareConnectionException, ) +import pytest from homeassistant.components.cloudflare.const import DOMAIN, SERVICE_UPDATE_RECORDS from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState +from homeassistant.exceptions import HomeAssistantError +from homeassistant.util.location import LocationInfo from . import ENTRY_CONFIG, init_integration @@ -70,12 +75,51 @@ async def test_integration_services(hass, cfupdate): entry = await init_integration(hass) assert entry.state is ConfigEntryState.LOADED - await hass.services.async_call( - DOMAIN, - SERVICE_UPDATE_RECORDS, - {}, - blocking=True, - ) - await hass.async_block_till_done() + with patch( + "homeassistant.components.cloudflare.async_detect_location_info", + return_value=LocationInfo( + "0.0.0.0", + "US", + "USD", + "CA", + "California", + "San Diego", + "92122", + "America/Los_Angeles", + 32.8594, + -117.2073, + True, + ), + ): + + await hass.services.async_call( + DOMAIN, + SERVICE_UPDATE_RECORDS, + {}, + blocking=True, + ) + await hass.async_block_till_done() instance.update_records.assert_called_once() + + +async def test_integration_services_with_issue(hass, cfupdate): + """Test integration services with issue.""" + instance = cfupdate.return_value + + entry = await init_integration(hass) + assert entry.state is ConfigEntryState.LOADED + + with patch( + "homeassistant.components.cloudflare.async_detect_location_info", + return_value=None, + ), pytest.raises(HomeAssistantError, match="Could not get external IPv4 address"): + await hass.services.async_call( + DOMAIN, + SERVICE_UPDATE_RECORDS, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + instance.update_records.assert_not_called()