66from collections import defaultdict
77from collections .abc import Awaitable
88from contextlib import suppress
9+ from http import HTTPStatus
910import logging
1011import re
1112from time import time
1213from typing import Any , cast
1314from uuid import uuid4
1415
16+ import aiodocker
1517from awesomeversion import AwesomeVersion
1618from awesomeversion .strategy import AwesomeVersionStrategy
1719import docker
1820from docker .models .containers import Container
19- from docker .models .images import Image
2021import requests
2122
2223from ..bus import EventListener
3334from ..exceptions import (
3435 DockerAPIError ,
3536 DockerError ,
37+ DockerHubRateLimitExceeded ,
3638 DockerJobError ,
3739 DockerLogOutOfOrder ,
3840 DockerNotFound ,
@@ -215,7 +217,7 @@ async def _docker_login(self, image: str) -> None:
215217 if not credentials :
216218 return
217219
218- await self .sys_run_in_executor (self .sys_docker .docker .login , ** credentials )
220+ await self .sys_run_in_executor (self .sys_docker .dockerpy .login , ** credentials )
219221
220222 def _process_pull_image_log ( # noqa: C901
221223 self , install_job_id : str , reference : PullLogEntry
@@ -418,8 +420,7 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
418420 )
419421
420422 # Pull new image
421- docker_image = await self .sys_run_in_executor (
422- self .sys_docker .pull_image ,
423+ docker_image = await self .sys_docker .pull_image (
423424 self .sys_jobs .current .uuid ,
424425 image ,
425426 str (version ),
@@ -431,22 +432,37 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
431432 _LOGGER .info (
432433 "Tagging image %s with version %s as latest" , image , version
433434 )
434- await self .sys_run_in_executor (docker_image .tag , image , tag = "latest" )
435+ await self .sys_docker .images .tag (
436+ docker_image ["Id" ], image , tag = "latest"
437+ )
435438 except docker .errors .APIError as err :
436- if err .status_code == 429 :
439+ if err .status_code == HTTPStatus . TOO_MANY_REQUESTS :
437440 self .sys_resolution .create_issue (
438441 IssueType .DOCKER_RATELIMIT ,
439442 ContextType .SYSTEM ,
440443 suggestions = [SuggestionType .REGISTRY_LOGIN ],
441444 )
442- _LOGGER .info (
443- "Your IP address has made too many requests to Docker Hub which activated a rate limit. "
444- "For more details see https://www.home-assistant.io/more-info/dockerhub-rate-limit"
445+ raise DockerHubRateLimitExceeded (_LOGGER .error ) from err
446+ await async_capture_exception (err )
447+ raise DockerError (
448+ f"Can't install { image } :{ version !s} : { err } " , _LOGGER .error
449+ ) from err
450+ except aiodocker .DockerError as err :
451+ if err .status == HTTPStatus .TOO_MANY_REQUESTS :
452+ self .sys_resolution .create_issue (
453+ IssueType .DOCKER_RATELIMIT ,
454+ ContextType .SYSTEM ,
455+ suggestions = [SuggestionType .REGISTRY_LOGIN ],
445456 )
457+ raise DockerHubRateLimitExceeded (_LOGGER .error ) from err
458+ await async_capture_exception (err )
446459 raise DockerError (
447460 f"Can't install { image } :{ version !s} : { err } " , _LOGGER .error
448461 ) from err
449- except (docker .errors .DockerException , requests .RequestException ) as err :
462+ except (
463+ docker .errors .DockerException ,
464+ requests .RequestException ,
465+ ) as err :
450466 await async_capture_exception (err )
451467 raise DockerError (
452468 f"Unknown error with { image } :{ version !s} -> { err !s} " , _LOGGER .error
@@ -455,14 +471,12 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
455471 if listener :
456472 self .sys_bus .remove_listener (listener )
457473
458- self ._meta = docker_image . attrs
474+ self ._meta = docker_image
459475
460476 async def exists (self ) -> bool :
461477 """Return True if Docker image exists in local repository."""
462- with suppress (docker .errors .DockerException , requests .RequestException ):
463- await self .sys_run_in_executor (
464- self .sys_docker .images .get , f"{ self .image } :{ self .version !s} "
465- )
478+ with suppress (aiodocker .DockerError , requests .RequestException ):
479+ await self .sys_docker .images .inspect (f"{ self .image } :{ self .version !s} " )
466480 return True
467481 return False
468482
@@ -521,11 +535,11 @@ async def attach(
521535 ),
522536 )
523537
524- with suppress (docker . errors . DockerException , requests .RequestException ):
538+ with suppress (aiodocker . DockerError , requests .RequestException ):
525539 if not self ._meta and self .image :
526- self ._meta = self .sys_docker .images .get (
540+ self ._meta = await self .sys_docker .images .inspect (
527541 f"{ self .image } :{ version !s} "
528- ). attrs
542+ )
529543
530544 # Successful?
531545 if not self ._meta :
@@ -593,14 +607,17 @@ def start(self) -> Awaitable[None]:
593607 )
594608 async def remove (self , * , remove_image : bool = True ) -> None :
595609 """Remove Docker images."""
610+ if not self .image or not self .version :
611+ raise DockerError (
612+ "Cannot determine image and/or version from metadata!" , _LOGGER .error
613+ )
614+
596615 # Cleanup container
597616 with suppress (DockerError ):
598617 await self .stop ()
599618
600619 if remove_image :
601- await self .sys_run_in_executor (
602- self .sys_docker .remove_image , self .image , self .version
603- )
620+ await self .sys_docker .remove_image (self .image , self .version )
604621
605622 self ._meta = None
606623
@@ -622,18 +639,16 @@ async def check_image(
622639 image_name = f"{ expected_image } :{ version !s} "
623640 if self .image == expected_image :
624641 try :
625- image : Image = await self .sys_run_in_executor (
626- self .sys_docker .images .get , image_name
627- )
628- except (docker .errors .DockerException , requests .RequestException ) as err :
642+ image = await self .sys_docker .images .inspect (image_name )
643+ except (aiodocker .DockerError , requests .RequestException ) as err :
629644 raise DockerError (
630645 f"Could not get { image_name } for check due to: { err !s} " ,
631646 _LOGGER .error ,
632647 ) from err
633648
634- image_arch = f"{ image . attrs ['Os' ]} /{ image . attrs ['Architecture' ]} "
635- if "Variant" in image . attrs :
636- image_arch = f"{ image_arch } /{ image . attrs ['Variant' ]} "
649+ image_arch = f"{ image ['Os' ]} /{ image ['Architecture' ]} "
650+ if "Variant" in image :
651+ image_arch = f"{ image_arch } /{ image ['Variant' ]} "
637652
638653 # If we have an image and its the right arch, all set
639654 # It seems that newer Docker version return a variant for arm64 images.
@@ -695,11 +710,13 @@ async def cleanup(
695710 version : AwesomeVersion | None = None ,
696711 ) -> None :
697712 """Check if old version exists and cleanup."""
698- await self .sys_run_in_executor (
699- self .sys_docker .cleanup_old_images ,
700- image or self .image ,
701- version or self .version ,
702- {old_image } if old_image else None ,
713+ if not (use_image := image or self .image ):
714+ raise DockerError ("Cannot determine image from metadata!" , _LOGGER .error )
715+ if not (use_version := version or self .version ):
716+ raise DockerError ("Cannot determine version from metadata!" , _LOGGER .error )
717+
718+ await self .sys_docker .cleanup_old_images (
719+ use_image , use_version , {old_image } if old_image else None
703720 )
704721
705722 @Job (
@@ -751,10 +768,10 @@ async def get_latest_version(self) -> AwesomeVersion:
751768 """Return latest version of local image."""
752769 available_version : list [AwesomeVersion ] = []
753770 try :
754- for image in await self .sys_run_in_executor (
755- self . sys_docker . images . list , self .image
771+ for image in await self .sys_docker . images . list (
772+ filters = f'{{"reference": [" { self .image } "]}}'
756773 ):
757- for tag in image . tags :
774+ for tag in image [ "RepoTags" ] :
758775 version = AwesomeVersion (tag .partition (":" )[2 ])
759776 if version .strategy == AwesomeVersionStrategy .UNKNOWN :
760777 continue
@@ -763,7 +780,7 @@ async def get_latest_version(self) -> AwesomeVersion:
763780 if not available_version :
764781 raise ValueError ()
765782
766- except (docker . errors . DockerException , ValueError ) as err :
783+ except (aiodocker . DockerError , ValueError ) as err :
767784 raise DockerNotFound (
768785 f"No version found for { self .image } " , _LOGGER .info
769786 ) from err
0 commit comments