Skip to content

Commit

Permalink
Add support for 'docker version' and 'podman version' (#593)
Browse files Browse the repository at this point in the history
  • Loading branch information
LewisGaul authored May 29, 2024
1 parent 268bd63 commit fd18ff4
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 19 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ markers = [
"podman: marks tests as needing a podman engine to run"
]
xfail_strict = true
pythonpath = ["./"]
3 changes: 2 additions & 1 deletion python_on_whales/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .components.system.cli_wrapper import SystemInfo
from .components.task.cli_wrapper import Task
from .components.volume.cli_wrapper import Volume
from .docker_client import DockerClient
from .docker_client import DockerClient, Version
from .exceptions import DockerException

# alias
Expand All @@ -43,5 +43,6 @@
"Stack",
"SystemInfo",
"Task",
"Version",
"Volume",
]
104 changes: 99 additions & 5 deletions python_on_whales/docker_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import base64
import json
import warnings
from typing import List, Literal, Optional
from typing import Dict, List, Literal, Optional

import pydantic
from typing_extensions import Annotated

from python_on_whales.client_config import ClientConfig, DockerCLICaller
from python_on_whales.components.buildx.cli_wrapper import BuildxCLI
Expand All @@ -22,7 +26,78 @@
from python_on_whales.components.trust.cli_wrapper import TrustCLI
from python_on_whales.components.volume.cli_wrapper import VolumeCLI

from .utils import ValidPath, run
from .utils import DockerCamelModel, ValidPath, run


class ClientVersion(DockerCamelModel):
platform: Optional[Dict[str, str]] = None
version: Optional[str] = None
if pydantic.VERSION.startswith("1."):
api_version: Optional[str] = None
else:
api_version: Annotated[
Optional[str],
pydantic.Field(
validation_alias=pydantic.AliasChoices("APIVersion", "ApiVersion")
),
] = None
default_api_version: Optional[str] = None
git_commit: Optional[str] = None
go_version: Optional[str] = None
os: Optional[str] = None
arch: Optional[str] = None
if pydantic.VERSION.startswith("1."):
build_time: Optional[str] = None
else:
build_time: Annotated[
Optional[str],
pydantic.Field(
validation_alias=pydantic.AliasChoices("BuildTime", "BuiltTime")
),
] = None
context: Optional[str] = None
experimental: Optional[bool] = None


class ServerVersionComponent(DockerCamelModel):
name: Optional[str] = None
version: Optional[str] = None
details: Optional[Dict[str, str]] = None


class ServerVersion(DockerCamelModel):
platform: Optional[Dict[str, str]] = None
components: Optional[List[ServerVersionComponent]] = None
version: Optional[str] = None
if pydantic.VERSION.startswith("1."):
api_version: Optional[str] = None
else:
api_version: Annotated[
Optional[str],
pydantic.Field(
validation_alias=pydantic.AliasChoices("APIVersion", "ApiVersion")
),
] = None
min_api_version: Optional[str] = None
git_commit: Optional[str] = None
go_version: Optional[str] = None
os: Optional[str] = None
arch: Optional[str] = None
kernel_version: Optional[str] = None
if pydantic.VERSION.startswith("1."):
build_time: Optional[str] = None
else:
build_time: Annotated[
Optional[str],
pydantic.Field(
validation_alias=pydantic.AliasChoices("BuildTime", "BuiltTime")
),
] = None


class Version(DockerCamelModel):
client: Optional[ClientVersion] = None
server: Optional[ServerVersion] = None


class DockerClient(DockerCLICaller):
Expand Down Expand Up @@ -198,9 +273,28 @@ def __init__(
self.update = self.container.update
self.wait = self.container.wait

def version(self):
"""Not yet implemented"""
raise NotImplementedError
def version(self) -> Version:
"""
Get version information about the container client and server.
# Returns
A `python_on_whales.Version` object
As an example:
```python
from python_on_whales import docker
version_info = docker.version()
print(version_info.client.version)
# 3.4.2
print(version_info.server.kernel_version)
# 5.15.133.1-microsoft-standard-WSL2
...
```
"""
full_cmd = self.docker_cmd + ["version", "-f", "{{json .}}"]
return Version(**json.loads(run(full_cmd)))

def login(
self,
Expand Down
2 changes: 2 additions & 0 deletions python_on_whales/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ def to_docker_camel(string):
"ipam": "IPAM",
"tls_info": "TLSInfo",
"virtual_ips": "VirtualIPs",
"default_api_version": "DefaultAPIVersion",
"min_api_version": "MinAPIVersion",
}
return special_cases[string]
except KeyError:
Expand Down
37 changes: 24 additions & 13 deletions tests/python_on_whales/test_docker_client.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
import re
import time
from pathlib import Path

import pytest

from python_on_whales import DockerClient, docker
from python_on_whales import DockerClient
from python_on_whales.exceptions import DockerException


def test_login_logout(docker_registry_without_login):
busybox_image = docker.pull("busybox")
@pytest.mark.parametrize("ctr_client", ["docker", "podman"], indirect=True)
def test_version(ctr_client: DockerClient):
version_info = ctr_client.version()
assert re.match(r"\d+\.\d+", version_info.client.version)
if ctr_client.client_config.client_type == "docker":
assert re.match(r"\d+\.\d+", version_info.server.version)


def test_login_logout(docker_client: DockerClient, docker_registry_without_login: str):
busybox_image = docker_client.pull("busybox")
busybox_image.tag(f"{docker_registry_without_login}/my_busybox")
with pytest.raises(DockerException):
docker.push(f"{docker_registry_without_login}/my_busybox")
docker.login(
docker_client.push(f"{docker_registry_without_login}/my_busybox")
docker_client.login(
docker_registry_without_login, username="my_user", password="my_password"
)
assert (
docker_registry_without_login
in (Path.home() / ".docker" / "config.json").read_text()
)
docker.push(f"{docker_registry_without_login}/my_busybox")
docker.push([f"{docker_registry_without_login}/my_busybox" for _ in range(2)])
docker.pull(f"{docker_registry_without_login}/my_busybox")
docker.logout(docker_registry_without_login)
docker_client.push(f"{docker_registry_without_login}/my_busybox")
docker_client.push(
[f"{docker_registry_without_login}/my_busybox" for _ in range(2)]
)
docker_client.pull(f"{docker_registry_without_login}/my_busybox")
docker_client.logout(docker_registry_without_login)
assert (
docker_registry_without_login
not in (Path.home() / ".docker" / "config.json").read_text()
)


@pytest.mark.skipif(True, reason="It doesn't work in the ci")
def test_docker_client_options():
if docker.container.exists("test_dind_container"):
docker.container.remove("test_dind_container", force=True, volumes=True)
def test_docker_client_options(docker_client: DockerClient):
if docker_client.container.exists("test_dind_container"):
docker_client.container.remove("test_dind_container", force=True, volumes=True)

with docker.run(
with docker_client.run(
"docker:20.10.16-dind",
["dockerd", "--host=tcp://0.0.0.0:2375", "--tls=false"],
remove=True,
Expand Down

0 comments on commit fd18ff4

Please sign in to comment.