-
-
Notifications
You must be signed in to change notification settings - Fork 35.9k
Reimplement Cloudflare using coordinator #156817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
davidrapan
wants to merge
8
commits into
home-assistant:dev
Choose a base branch
from
davidrapan:dev-cloudflare
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+224
−228
Open
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
fa84cfe
Reimplement Cloudflare using coordinator
davidrapan 0764563
Use location_info fixture
davidrapan ef94763
Remove Mock import
davidrapan 3f7e0f8
Fix ruffik
davidrapan 4ded7ae
Add None
davidrapan 85d2751
Add missing dummy listener!
davidrapan 25e240c
Fix error map during setup
davidrapan 07cc995
Align to UpdateFailed error
davidrapan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| """Contains the Coordinator for updating the IP addresses of your Cloudflare DNS records.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| from datetime import timedelta | ||
| from logging import getLogger | ||
| import socket | ||
|
|
||
| import pycfdns | ||
|
|
||
| from homeassistant.config_entries import ConfigEntry | ||
| from homeassistant.const import CONF_API_TOKEN, CONF_ZONE | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady | ||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
| from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
| 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 | ||
|
|
||
| _LOGGER = getLogger(__name__) | ||
|
|
||
| type CloudflareConfigEntry = ConfigEntry[CloudflareCoordinator] | ||
|
|
||
|
|
||
| class CloudflareCoordinator(DataUpdateCoordinator[None]): | ||
| """Coordinates records updates.""" | ||
|
|
||
| config_entry: CloudflareConfigEntry | ||
| client: pycfdns.Client | ||
| zone: pycfdns.ZoneModel | ||
|
|
||
| def __init__( | ||
| self, hass: HomeAssistant, config_entry: CloudflareConfigEntry | ||
| ) -> None: | ||
| """Initialize an coordinator.""" | ||
| super().__init__( | ||
| hass, | ||
| _LOGGER, | ||
| config_entry=config_entry, | ||
| name=config_entry.title, | ||
| update_interval=timedelta(minutes=DEFAULT_UPDATE_INTERVAL), | ||
| ) | ||
|
|
||
| async def _async_setup(self) -> None: | ||
| """Set up the coordinator.""" | ||
| self.client = pycfdns.Client( | ||
| api_token=self.config_entry.data[CONF_API_TOKEN], | ||
| client_session=async_get_clientsession(self.hass), | ||
| ) | ||
|
|
||
| try: | ||
| self.zone = next( | ||
| zone | ||
| for zone in await self.client.list_zones() | ||
| if zone["name"] == self.config_entry.data[CONF_ZONE] | ||
| ) | ||
| except pycfdns.AuthenticationException as e: | ||
| raise ConfigEntryAuthFailed from e | ||
| except pycfdns.ComunicationException as e: | ||
| raise ConfigEntryNotReady from e | ||
|
|
||
| async def _async_update_data(self) -> None: | ||
| """Update records.""" | ||
| _LOGGER.debug("Starting update for zone %s", self.zone["name"]) | ||
| try: | ||
| records = await self.client.list_dns_records( | ||
| zone_id=self.zone["id"], type="A" | ||
| ) | ||
| _LOGGER.debug("Records: %s", records) | ||
|
|
||
| target_records: list[str] = self.config_entry.data[CONF_RECORDS] | ||
|
|
||
| location_info = await async_detect_location_info( | ||
| async_get_clientsession(self.hass, family=socket.AF_INET) | ||
| ) | ||
|
|
||
| if not location_info or not is_ipv4_address(location_info.ip): | ||
| raise UpdateFailed("Could not get external IPv4 address") | ||
|
|
||
| filtered_records = [ | ||
| record | ||
| for record in records | ||
| if record["name"] in target_records | ||
| and record["content"] != location_info.ip | ||
| ] | ||
|
|
||
| if len(filtered_records) == 0: | ||
| _LOGGER.debug("All target records are up to date") | ||
| return | ||
|
|
||
| await asyncio.gather( | ||
| *[ | ||
| self.client.update_dns_record( | ||
| zone_id=self.zone["id"], | ||
| record_id=record["id"], | ||
| record_content=location_info.ip, | ||
| record_name=record["name"], | ||
| record_type=record["type"], | ||
| record_proxied=record["proxied"], | ||
| ) | ||
| for record in filtered_records | ||
| ] | ||
| ) | ||
|
|
||
| _LOGGER.debug("Update for zone %s is complete", self.zone["name"]) | ||
|
|
||
| except ( | ||
| pycfdns.AuthenticationException, | ||
| pycfdns.ComunicationException, | ||
| ) as e: | ||
| raise UpdateFailed( | ||
| f"Error updating zone {self.config_entry.data[CONF_ZONE]}" | ||
| ) from e | ||
|
|
||
| async def init(self): | ||
| """Asynchronously initialize an coordinator.""" | ||
| await super().async_config_entry_first_refresh() | ||
| return self | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this method. It doesn't add much value and makes the code logic harder to follow. One of the common things that's checked is whether the
async_setup_entrymethod is executing theasync_config_entry_first_refreshmethod , and now that's not done explicitly, but through this additional method. This should be moved toasync_setup_entry.https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/test-before-setup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's also
_async_setupin the coordinator for one time setup tasks automatically called duringasync_config_entry_first_refreshUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I do use
_async_setup😉 I'm just wrappingasync_config_entry_first_refreshtogether w/return selfso I can "one-line it" inasync_setup_entry.It's coming from my template which I use when creating custom components, etc. (and changes in this PR are semi-based on it too) But sure, I can remove it. :)