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
3536 CodeNotaryUntrusted ,
3637 DockerAPIError ,
3738 DockerError ,
39+ DockerHubRateLimitExceeded ,
3840 DockerJobError ,
3941 DockerLogOutOfOrder ,
4042 DockerNotFound ,
@@ -218,7 +220,7 @@ async def _docker_login(self, image: str) -> None:
218220 if not credentials :
219221 return
220222
221- await self .sys_run_in_executor (self .sys_docker .docker .login , ** credentials )
223+ await self .sys_run_in_executor (self .sys_docker .dockerpy .login , ** credentials )
222224
223225 def _process_pull_image_log (
224226 self , install_job_id : str , reference : PullLogEntry
@@ -415,8 +417,7 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
415417 )
416418
417419 # Pull new image
418- docker_image = await self .sys_run_in_executor (
419- self .sys_docker .pull_image ,
420+ docker_image = await self .sys_docker .pull_image (
420421 self .sys_jobs .current .uuid ,
421422 image ,
422423 str (version ),
@@ -425,13 +426,11 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
425426
426427 # Validate content
427428 try :
428- await self ._validate_trust (cast (str , docker_image . id ))
429+ await self ._validate_trust (cast (str , docker_image [ "Id" ] ))
429430 except CodeNotaryError :
430- with suppress (docker .errors .DockerException ):
431- await self .sys_run_in_executor (
432- self .sys_docker .images .remove ,
433- image = f"{ image } :{ version !s} " ,
434- force = True ,
431+ with suppress (aiodocker .DockerError , requests .RequestException ):
432+ await self .sys_docker .images .delete (
433+ f"{ image } :{ version !s} " , force = True
435434 )
436435 raise
437436
@@ -440,22 +439,25 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
440439 _LOGGER .info (
441440 "Tagging image %s with version %s as latest" , image , version
442441 )
443- await self .sys_run_in_executor (docker_image .tag , image , tag = "latest" )
442+ await self .sys_docker .images .tag (
443+ docker_image ["Id" ], image , tag = "latest"
444+ )
444445 except docker .errors .APIError as err :
445- if err .status_code == 429 :
446+ if err .status_code == HTTPStatus . TOO_MANY_REQUESTS :
446447 self .sys_resolution .create_issue (
447448 IssueType .DOCKER_RATELIMIT ,
448449 ContextType .SYSTEM ,
449450 suggestions = [SuggestionType .REGISTRY_LOGIN ],
450451 )
451- _LOGGER .info (
452- "Your IP address has made too many requests to Docker Hub which activated a rate limit. "
453- "For more details see https://www.home-assistant.io/more-info/dockerhub-rate-limit"
454- )
452+ raise DockerHubRateLimitExceeded (_LOGGER .error ) from err
455453 raise DockerError (
456454 f"Can't install { image } :{ version !s} : { err } " , _LOGGER .error
457455 ) from err
458- except (docker .errors .DockerException , requests .RequestException ) as err :
456+ except (
457+ aiodocker .DockerError ,
458+ docker .errors .DockerException ,
459+ requests .RequestException ,
460+ ) as err :
459461 await async_capture_exception (err )
460462 raise DockerError (
461463 f"Unknown error with { image } :{ version !s} -> { err !s} " , _LOGGER .error
@@ -474,14 +476,12 @@ async def process_pull_image_log(reference: PullLogEntry) -> None:
474476 if listener :
475477 self .sys_bus .remove_listener (listener )
476478
477- self ._meta = docker_image . attrs
479+ self ._meta = docker_image
478480
479481 async def exists (self ) -> bool :
480482 """Return True if Docker image exists in local repository."""
481- with suppress (docker .errors .DockerException , requests .RequestException ):
482- await self .sys_run_in_executor (
483- self .sys_docker .images .get , f"{ self .image } :{ self .version !s} "
484- )
483+ with suppress (aiodocker .DockerError , requests .RequestException ):
484+ await self .sys_docker .images .inspect (f"{ self .image } :{ self .version !s} " )
485485 return True
486486 return False
487487
@@ -540,11 +540,11 @@ async def attach(
540540 ),
541541 )
542542
543- with suppress (docker . errors . DockerException , requests .RequestException ):
543+ with suppress (aiodocker . DockerError , requests .RequestException ):
544544 if not self ._meta and self .image :
545- self ._meta = self .sys_docker .images .get (
545+ self ._meta = await self .sys_docker .images .inspect (
546546 f"{ self .image } :{ version !s} "
547- ). attrs
547+ )
548548
549549 # Successful?
550550 if not self ._meta :
@@ -612,14 +612,17 @@ def start(self) -> Awaitable[None]:
612612 )
613613 async def remove (self , * , remove_image : bool = True ) -> None :
614614 """Remove Docker images."""
615+ if not self .image or not self .version :
616+ raise DockerError (
617+ "Cannot determine image and/or version from metadata!" , _LOGGER .error
618+ )
619+
615620 # Cleanup container
616621 with suppress (DockerError ):
617622 await self .stop ()
618623
619624 if remove_image :
620- await self .sys_run_in_executor (
621- self .sys_docker .remove_image , self .image , self .version
622- )
625+ await self .sys_docker .remove_image (self .image , self .version )
623626
624627 self ._meta = None
625628
@@ -641,18 +644,16 @@ async def check_image(
641644 image_name = f"{ expected_image } :{ version !s} "
642645 if self .image == expected_image :
643646 try :
644- image : Image = await self .sys_run_in_executor (
645- self .sys_docker .images .get , image_name
646- )
647- except (docker .errors .DockerException , requests .RequestException ) as err :
647+ image = await self .sys_docker .images .inspect (image_name )
648+ except (aiodocker .DockerError , requests .RequestException ) as err :
648649 raise DockerError (
649650 f"Could not get { image_name } for check due to: { err !s} " ,
650651 _LOGGER .error ,
651652 ) from err
652653
653- image_arch = f"{ image . attrs ['Os' ]} /{ image . attrs ['Architecture' ]} "
654- if "Variant" in image . attrs :
655- image_arch = f"{ image_arch } /{ image . attrs ['Variant' ]} "
654+ image_arch = f"{ image ['Os' ]} /{ image ['Architecture' ]} "
655+ if "Variant" in image :
656+ image_arch = f"{ image_arch } /{ image ['Variant' ]} "
656657
657658 # If we have an image and its the right arch, all set
658659 # It seems that newer Docker version return a variant for arm64 images.
@@ -714,11 +715,13 @@ async def cleanup(
714715 version : AwesomeVersion | None = None ,
715716 ) -> None :
716717 """Check if old version exists and cleanup."""
717- await self .sys_run_in_executor (
718- self .sys_docker .cleanup_old_images ,
719- image or self .image ,
720- version or self .version ,
721- {old_image } if old_image else None ,
718+ if not (use_image := image or self .image ):
719+ raise DockerError ("Cannot determine image from metadata!" , _LOGGER .error )
720+ if not (use_version := version or self .version ):
721+ raise DockerError ("Cannot determine version from metadata!" , _LOGGER .error )
722+
723+ await self .sys_docker .cleanup_old_images (
724+ use_image , use_version , {old_image } if old_image else None
722725 )
723726
724727 @Job (
@@ -770,10 +773,10 @@ async def get_latest_version(self) -> AwesomeVersion:
770773 """Return latest version of local image."""
771774 available_version : list [AwesomeVersion ] = []
772775 try :
773- for image in await self .sys_run_in_executor (
774- self . sys_docker . images . list , self .image
776+ for image in await self .sys_docker . images . list (
777+ filters = f'{{"reference": [" { self .image } "]}}'
775778 ):
776- for tag in image . tags :
779+ for tag in image [ "RepoTags" ] :
777780 version = AwesomeVersion (tag .partition (":" )[2 ])
778781 if version .strategy == AwesomeVersionStrategy .UNKNOWN :
779782 continue
@@ -782,7 +785,7 @@ async def get_latest_version(self) -> AwesomeVersion:
782785 if not available_version :
783786 raise ValueError ()
784787
785- except (docker . errors . DockerException , ValueError ) as err :
788+ except (aiodocker . DockerError , ValueError ) as err :
786789 raise DockerNotFound (
787790 f"No version found for { self .image } " , _LOGGER .info
788791 ) from err
@@ -821,10 +824,10 @@ async def _validate_trust(self, image_id: str) -> None:
821824 async def check_trust (self ) -> None :
822825 """Check trust of exists Docker image."""
823826 try :
824- image = await self .sys_run_in_executor (
825- self . sys_docker . images . get , f"{ self .image } :{ self .version !s} "
827+ image = await self .sys_docker . images . inspect (
828+ f"{ self .image } :{ self .version !s} "
826829 )
827- except (docker . errors . DockerException , requests .RequestException ):
830+ except (aiodocker . DockerError , requests .RequestException ):
828831 return
829832
830- await self ._validate_trust (cast (str , image . id ))
833+ await self ._validate_trust (cast (str , image [ "Id" ] ))
0 commit comments