Skip to content

Commit 07e9c1c

Browse files
committed
Migrate images from dockerpy to aiodocker
1 parent 322df15 commit 07e9c1c

File tree

18 files changed

+479
-368
lines changed

18 files changed

+479
-368
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
aiodns==3.5.0
2+
aiodocker==0.24.0
23
aiohttp==3.13.0
34
atomicwrites-homeassistant==1.4.1
45
attrs==25.4.0

supervisor/docker/addon.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pathlib import Path
1010
from typing import TYPE_CHECKING, cast
1111

12+
import aiodocker
1213
from attr import evolve
1314
from awesomeversion import AwesomeVersion
1415
import docker
@@ -717,19 +718,21 @@ def build_image():
717718
error_message = f"Docker build failed for {addon_image_tag} (exit code {result.exit_code}). Build output:\n{logs}"
718719
raise docker.errors.DockerException(error_message)
719720

720-
addon_image = self.sys_docker.images.get(addon_image_tag)
721-
722-
return addon_image, logs
721+
return addon_image_tag, logs
723722

724723
try:
725-
docker_image, log = await self.sys_run_in_executor(build_image)
724+
addon_image_tag, log = await self.sys_run_in_executor(build_image)
726725

727726
_LOGGER.debug("Build %s:%s done: %s", self.image, version, log)
728727

729728
# Update meta data
730-
self._meta = docker_image.attrs
729+
self._meta = await self.sys_docker.images.inspect(addon_image_tag)
731730

732-
except (docker.errors.DockerException, requests.RequestException) as err:
731+
except (
732+
docker.errors.DockerException,
733+
requests.RequestException,
734+
aiodocker.DockerError,
735+
) as err:
733736
_LOGGER.error("Can't build %s:%s: %s", self.image, version, err)
734737
raise DockerError() from err
735738

@@ -751,11 +754,8 @@ def export_image(self, tar_file: Path) -> None:
751754
)
752755
async def import_image(self, tar_file: Path) -> None:
753756
"""Import a tar file as image."""
754-
docker_image = await self.sys_run_in_executor(
755-
self.sys_docker.import_image, tar_file
756-
)
757-
if docker_image:
758-
self._meta = docker_image.attrs
757+
if docker_image := await self.sys_docker.import_image(tar_file):
758+
self._meta = docker_image
759759
_LOGGER.info("Importing image %s and version %s", tar_file, self.version)
760760

761761
with suppress(DockerError):
@@ -769,17 +769,21 @@ async def cleanup(
769769
version: AwesomeVersion | None = None,
770770
) -> None:
771771
"""Check if old version exists and cleanup other versions of image not in use."""
772-
await self.sys_run_in_executor(
773-
self.sys_docker.cleanup_old_images,
774-
(image := image or self.image),
775-
version or self.version,
772+
if not (use_image := image or self.image):
773+
raise DockerError("Cannot determine image from metadata!", _LOGGER.error)
774+
if not (use_version := version or self.version):
775+
raise DockerError("Cannot determine version from metadata!", _LOGGER.error)
776+
777+
await self.sys_docker.cleanup_old_images(
778+
use_image,
779+
use_version,
776780
{old_image} if old_image else None,
777781
keep_images={
778782
f"{addon.image}:{addon.version}"
779783
for addon in self.sys_addons.installed
780784
if addon.slug != self.addon.slug
781785
and addon.image
782-
and addon.image in {old_image, image}
786+
and addon.image in {old_image, use_image}
783787
},
784788
)
785789

supervisor/docker/homeassistant.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Init file for Supervisor Docker object."""
22

3-
from collections.abc import Awaitable
43
from ipaddress import IPv4Address
54
import logging
65
import re
@@ -236,13 +235,12 @@ async def execute_command(self, command: str) -> CommandReturn:
236235
environment={ENV_TIME: self.sys_timezone},
237236
)
238237

239-
def is_initialize(self) -> Awaitable[bool]:
238+
async def is_initialize(self) -> bool:
240239
"""Return True if Docker container exists."""
241-
return self.sys_run_in_executor(
242-
self.sys_docker.container_is_initialized,
243-
self.name,
244-
self.image,
245-
self.sys_homeassistant.version,
240+
if not self.sys_homeassistant.version:
241+
return False
242+
return await self.sys_docker.container_is_initialized(
243+
self.name, self.image, self.sys_homeassistant.version
246244
)
247245

248246
async def _validate_trust(self, image_id: str) -> None:

supervisor/docker/interface.py

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@
66
from collections import defaultdict
77
from collections.abc import Awaitable
88
from contextlib import suppress
9+
from http import HTTPStatus
910
import logging
1011
import re
1112
from time import time
1213
from typing import Any, cast
1314
from uuid import uuid4
1415

16+
import aiodocker
1517
from awesomeversion import AwesomeVersion
1618
from awesomeversion.strategy import AwesomeVersionStrategy
1719
import docker
1820
from docker.models.containers import Container
19-
from docker.models.images import Image
2021
import requests
2122

2223
from ..bus import EventListener
@@ -35,6 +36,7 @@
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

Comments
 (0)