Skip to content

Commit fbd8d3d

Browse files
authored
default to 1.35, fall back to 1.44 (aws#8439)
1 parent fac5b20 commit fbd8d3d

3 files changed

Lines changed: 66 additions & 25 deletions

File tree

samcli/lib/constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
DOCKER_MIN_API_VERSION = "1.44"
1+
DOCKER_MIN_API_VERSION = "1.35"
2+
DOCKER_MIN_API_VERSION_FALLBACK = "1.44"

samcli/local/docker/container_client.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import docker
3333
from docker.utils import kwargs_from_env
3434

35-
from samcli.lib.constants import DOCKER_MIN_API_VERSION
35+
from samcli.lib.constants import DOCKER_MIN_API_VERSION, DOCKER_MIN_API_VERSION_FALLBACK
3636
from samcli.local.docker.exceptions import ContainerArchiveImageLoadFailedException, ContainerInvalidSocketPathException
3737
from samcli.local.docker.platform_config import get_finch_socket_path
3838

@@ -62,7 +62,7 @@ class ContainerClient(docker.DockerClient, ABC):
6262
# Initialize socket_path
6363
socket_path: Optional[str] = None
6464

65-
def __init__(self, client_version, base_url=None):
65+
def __init__(self, base_url=None):
6666
"""
6767
Initialize the container client with environment variable processing and overrides.
6868
@@ -87,19 +87,20 @@ def __init__(self, client_version, base_url=None):
8787

8888
# Always start with environment variables
8989
current_env = os.environ.copy()
90-
client_params = kwargs_from_env(environment=current_env)
90+
self.client_params = kwargs_from_env(environment=current_env)
9191

9292
# Override base_url if explicitly provided
9393
if base_url is not None:
94-
client_params["base_url"] = base_url
94+
self.client_params["base_url"] = base_url
9595

9696
# Specify minimum version
97-
client_params["version"] = client_version
97+
self.client_params["version"] = DOCKER_MIN_API_VERSION
9898

9999
# Initialize DockerClient with processed parameters
100-
LOG.debug(f"Creating container client with parameters: {client_params}")
101-
super().__init__(**client_params)
100+
LOG.debug(f"Creating container client with parameters: {self.client_params}")
101+
super().__init__(**self.client_params)
102102

103+
@abstractmethod
103104
def is_available(self) -> bool:
104105
"""
105106
Check if this client instance is available and can connect.
@@ -111,12 +112,7 @@ def is_available(self) -> bool:
111112
Returns:
112113
bool: True if the client can successfully connect to the runtime
113114
"""
114-
try:
115-
self.ping()
116-
return True
117-
except Exception as e:
118-
LOG.debug(f"Container daemon availability check failed: {e}")
119-
return False
115+
pass
120116

121117
@abstractmethod
122118
def get_socket_path(self) -> str:
@@ -292,10 +288,27 @@ def __init__(self):
292288

293289
if socket_path:
294290
LOG.debug(f"Creating Docker container client with base_url={socket_path}.")
295-
super().__init__(base_url=socket_path, client_version=DOCKER_MIN_API_VERSION)
291+
super().__init__(base_url=socket_path)
296292
else:
297293
LOG.debug("Creating Docker container client from environment variable.")
298-
super().__init__(client_version=DOCKER_MIN_API_VERSION)
294+
super().__init__()
295+
296+
def is_available(self):
297+
try:
298+
self.ping()
299+
return True
300+
except Exception as e:
301+
try:
302+
# docker engine > 29 requires 1.44 as minimum api version
303+
LOG.debug(f"Fall back docker api version to {DOCKER_MIN_API_VERSION_FALLBACK}: {e}")
304+
self.client_params["version"] = DOCKER_MIN_API_VERSION_FALLBACK
305+
self.api = docker.APIClient(**self.client_params)
306+
self.ping()
307+
LOG.debug(f"Docker daemon check succeeded with fallback: {e}")
308+
return True
309+
except Exception as e:
310+
LOG.debug(f"Docker daemon availability check failed with fallback: {e}")
311+
return False
299312

300313
def get_runtime_type(self) -> str:
301314
"""
@@ -504,9 +517,15 @@ def __init__(self):
504517
return None
505518

506519
LOG.debug(f"Creating Finch container client with base_url={socket_path}")
507-
super().__init__(
508-
base_url=socket_path, client_version="1.35"
509-
) # TODO: Placeholder until Finch updates to Docker's min latest version
520+
super().__init__(base_url=socket_path) # TODO: Placeholder until Finch updates to Docker's min latest version
521+
522+
def is_available(self):
523+
try:
524+
self.ping()
525+
return True
526+
except Exception as e:
527+
LOG.debug(f"Finch daemon availability check failed: {e}")
528+
return False
510529

511530
def get_socket_path(self) -> str:
512531
"""

tests/unit/local/docker/test_container_client.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def setUp(self):
3333
self.finch_socket = "unix:///tmp/finch.sock"
3434
self.docker_socket = "unix:///var/run/docker.sock"
3535
self.docker_version = DOCKER_MIN_API_VERSION
36-
self.finch_version = "1.35" # TODO: Update when Finch updates to latest Docker API version
36+
self.finch_version = DOCKER_MIN_API_VERSION # TODO: Update when Finch updates to latest Docker API version
3737

3838
def create_mock_container_client(self, client_class, methods_to_bind=None):
3939
"""Create a mock container client with bound methods for testing."""
@@ -95,6 +95,9 @@ def create_mock_containers(self, container_configs):
9595
class ConcreteContainerClient(ContainerClient):
9696
"""Concrete implementation of ContainerClient for testing purposes"""
9797

98+
def is_available(self):
99+
return True
100+
98101
def get_socket_path(self):
99102
return "unix:///var/run/docker.sock"
100103

@@ -135,6 +138,7 @@ def test_inherits_from_docker_client(self):
135138
def test_abstract_methods_defined(self):
136139
"""Test that all required abstract methods are defined"""
137140
abstract_methods = {
141+
"is_available",
138142
"get_runtime_type",
139143
"get_socket_path",
140144
"load_image_from_archive",
@@ -721,7 +725,7 @@ class TestContainerClientBaseInit(BaseContainerClientTestCase):
721725
def test_init_no_overrides(self, mock_log, mock_docker_init):
722726
"""Test ContainerClient init with no environment overrides"""
723727
with patch.dict("os.environ", {}, clear=True):
724-
client = ConcreteContainerClient(client_version=self.docker_version)
728+
client = ConcreteContainerClient()
725729

726730
# Verify DockerClient.__init__ was called with expected parameters
727731
mock_docker_init.assert_called_once()
@@ -736,7 +740,7 @@ def test_init_with_base_url_override(self, mock_log, mock_docker_init):
736740
override_url = "unix:///tmp/finch.sock"
737741

738742
with patch.dict("os.environ", {}, clear=True):
739-
client = ConcreteContainerClient(client_version=self.docker_version, base_url=override_url)
743+
client = ConcreteContainerClient(base_url=override_url)
740744

741745
# Verify DockerClient.__init__ was called with expected parameters
742746
mock_docker_init.assert_called_once()
@@ -755,26 +759,43 @@ def setUp(self):
755759
self.client = Mock(spec=ContainerClient)
756760

757761
# Set up the methods we need to test
758-
self.client.is_available = ContainerClient.is_available.__get__(self.client)
759762
self.client.is_finch = ContainerClient.is_finch.__get__(self.client)
760763
self.client.is_docker = ContainerClient.is_docker.__get__(self.client)
761764

762765
# Set up the mocked docker client attributes
763766
self.client.ping = Mock(return_value=True)
764767
self.client.get_runtime_type = Mock(return_value="docker")
765768

766-
def test_is_available_success(self):
769+
def test_is_available_finch_success(self):
770+
"""Test is_available returns True when ping succeeds"""
771+
self.client.ping.return_value = True
772+
# check finch
773+
self.client.is_available = FinchContainerClient.is_available.__get__(self.client)
774+
self.assertTrue(self.client.is_available())
775+
776+
def test_is_available_docker_success(self):
767777
"""Test is_available returns True when ping succeeds"""
768778
self.client.ping.return_value = True
779+
# check docker
780+
self.client.is_available = DockerContainerClient.is_available.__get__(self.client)
769781
self.assertTrue(self.client.is_available())
770782

771783
@patch("samcli.local.docker.container_client.LOG")
772-
def test_is_available_failure(self, mock_log):
784+
def test_is_available_finch_failure(self, mock_log):
773785
"""Test is_available returns False when ping fails and logs debug"""
786+
self.client.is_available = FinchContainerClient.is_available.__get__(self.client)
774787
self.client.ping.side_effect = Exception("Connection failed")
775788
self.assertFalse(self.client.is_available())
776789
mock_log.debug.assert_called_once()
777790

791+
@patch("samcli.local.docker.container_client.LOG")
792+
def test_is_available_docker_failure(self, mock_log):
793+
"""Test is_available returns False when ping fails and logs debug"""
794+
self.client.is_available = DockerContainerClient.is_available.__get__(self.client)
795+
self.client.ping.side_effect = Exception("Connection failed")
796+
self.assertFalse(self.client.is_available())
797+
mock_log.debug.assert_called()
798+
778799
@parameterized.expand(
779800
[
780801
("is_finch", "finch", True),

0 commit comments

Comments
 (0)