diff --git a/samcli/local/docker/platform_config.py b/samcli/local/docker/platform_config.py index c44c873a11..3e5361cb90 100644 --- a/samcli/local/docker/platform_config.py +++ b/samcli/local/docker/platform_config.py @@ -123,11 +123,61 @@ def _read_config(self) -> Optional[str]: def get_finch_socket_path(self) -> Optional[str]: """ - Returns the socket path for Linux. - """ - - # Default fallback to system socket - return "unix:///var/run/finch.sock" + Returns the socket path for Linux, checking multiple locations. + + On Linux, Finch can use either a Finch-specific socket or the underlying + containerd socket via nerdctl. + + Priority order: + 1. XDG_RUNTIME_DIR/finch.sock (Finch-specific socket) + 2. ~/.finch/finch.sock (user home directory) + 3. /var/run/finch.sock (system-wide) + 4. XDG_RUNTIME_DIR/containerd/containerd.sock (rootless containerd fallback) + + Note: The containerd socket is checked last as a fallback since it may be + shared by multiple container tools. Finch-specific sockets are preferred + for accurate telemetry reporting. + + Returns: + Optional[str]: Socket path if found, None otherwise + """ + + # Check XDG_RUNTIME_DIR for Finch-specific socket first + xdg_runtime_dir = os.environ.get("XDG_RUNTIME_DIR") + if xdg_runtime_dir: + # Finch-specific socket in XDG_RUNTIME_DIR + finch_sock = os.path.join(xdg_runtime_dir, "finch.sock") + if os.path.exists(finch_sock): + LOG.debug(f"Found Finch socket at XDG_RUNTIME_DIR: {finch_sock}") + return f"unix://{finch_sock}" + + # Check user home directory for Finch VM socket + home_dir = os.path.expanduser("~") + home_finch_sock = os.path.join(home_dir, ".finch", "finch.sock") + if os.path.exists(home_finch_sock): + LOG.debug(f"Found Finch socket in home directory: {home_finch_sock}") + return f"unix://{home_finch_sock}" + + # System-wide socket + system_sock = "/var/run/finch.sock" + if os.path.exists(system_sock): + LOG.debug(f"Found Finch socket at system location: {system_sock}") + return f"unix://{system_sock}" + + # Fallback: Check for rootless containerd socket + # This is checked last since containerd may be used by other tools + if xdg_runtime_dir: + containerd_sock = os.path.join(xdg_runtime_dir, "containerd", "containerd.sock") + if os.path.exists(containerd_sock): + LOG.debug( + f"Found containerd socket at XDG_RUNTIME_DIR (fallback): {containerd_sock}. " + "Note: This socket may be shared with other containerd-based tools." + ) + return f"unix://{containerd_sock}" + + # No socket found - return None to enable future CLI fallback + LOG.warning("No Finch socket found in standard locations") + return None def supports_finch(self) -> bool: """ diff --git a/tests/unit/local/docker/test_platform_config.py b/tests/unit/local/docker/test_platform_config.py index c235c1c6ec..a0ae21bfae 100644 --- a/tests/unit/local/docker/test_platform_config.py +++ b/tests/unit/local/docker/test_platform_config.py @@ -159,11 +159,81 @@ def test_read_config_not_implemented(self): result = self.handler.read_config() self.assertIsNone(result) - def test_get_finch_socket_path(self): - """Test Linux Finch socket path""" + @patch("os.path.exists") + @patch.dict("os.environ", {"XDG_RUNTIME_DIR": "/run/user/1001"}) + def test_get_finch_socket_path_xdg_containerd(self, mock_exists): + """Test Linux Finch socket path with XDG_RUNTIME_DIR containerd socket""" + + # Mock that containerd socket exists + def exists_side_effect(path): + return path == "/run/user/1001/containerd/containerd.sock" + + mock_exists.side_effect = exists_side_effect + result = self.handler.get_finch_socket_path() + self.assertEqual(result, "unix:///run/user/1001/containerd/containerd.sock") + + @patch("os.path.exists") + @patch.dict("os.environ", {"XDG_RUNTIME_DIR": "/run/user/1001"}) + def test_get_finch_socket_path_xdg_finch(self, mock_exists): + """Test Linux Finch socket path with XDG_RUNTIME_DIR finch socket""" + + # Mock that finch socket exists (but not containerd) + def exists_side_effect(path): + return path == "/run/user/1001/finch.sock" + + mock_exists.side_effect = exists_side_effect + result = self.handler.get_finch_socket_path() + self.assertEqual(result, "unix:///run/user/1001/finch.sock") + + @patch("os.path.exists") + @patch.dict("os.environ", {"XDG_RUNTIME_DIR": "/run/user/1001"}) + def test_get_finch_socket_path_priority_finch_over_containerd(self, mock_exists): + """Test that finch.sock is preferred over containerd.sock when both exist""" + + # Mock that BOTH sockets exist + def exists_side_effect(path): + return path in ["/run/user/1001/finch.sock", "/run/user/1001/containerd/containerd.sock"] + + mock_exists.side_effect = exists_side_effect + result = self.handler.get_finch_socket_path() + # Should prefer finch.sock for accurate telemetry + self.assertEqual(result, "unix:///run/user/1001/finch.sock") + + @patch("os.path.exists") + @patch("os.path.expanduser") + @patch.dict("os.environ", {}, clear=True) + def test_get_finch_socket_path_home_directory(self, mock_expanduser, mock_exists): + """Test Linux Finch socket path in home directory""" + mock_expanduser.return_value = "/home/testuser" + + # Mock that home finch socket exists + def exists_side_effect(path): + return path == "/home/testuser/.finch/finch.sock" + + mock_exists.side_effect = exists_side_effect + result = self.handler.get_finch_socket_path() + self.assertEqual(result, "unix:///home/testuser/.finch/finch.sock") + + @patch("os.path.exists") + @patch.dict("os.environ", {}, clear=True) + def test_get_finch_socket_path_system_socket(self, mock_exists): + """Test Linux Finch socket path at system location""" + + # Mock that system socket exists + def exists_side_effect(path): + return path == "/var/run/finch.sock" + + mock_exists.side_effect = exists_side_effect result = self.handler.get_finch_socket_path() self.assertEqual(result, "unix:///var/run/finch.sock") + @patch("os.path.exists", return_value=False) + @patch.dict("os.environ", {}, clear=True) + def test_get_finch_socket_path_not_found(self, mock_exists): + """Test Linux Finch socket path when no socket exists""" + result = self.handler.get_finch_socket_path() + self.assertIsNone(result) + def test_supports_finch(self): """Test that Linux supports Finch""" self.assertTrue(self.handler.supports_finch()) @@ -268,21 +338,45 @@ def test_returns_none_for_unsupported_platform(self, mock_system): class TestGetFinchSocketPath(unittest.TestCase): """Tests for get_finch_socket_path utility function""" - @parameterized.expand( - [ - ("Linux", "unix:///var/run/finch.sock"), - ("Darwin", "unix:////Applications/Finch/lima/data/finch/sock/finch.sock"), - ("Windows", None), - ] - ) + @patch("os.path.exists") @patch("samcli.local.docker.platform_config.platform.system") - def test_get_finch_socket_path_returns_correct_path(self, platform_name, expected_path, mock_system): - """Test that get_finch_socket_path returns the correct path based on platform""" - mock_system.return_value = platform_name + def test_get_finch_socket_path_linux_with_system_socket(self, mock_system, mock_exists): + """Test that get_finch_socket_path returns system socket path on Linux when it exists""" + mock_system.return_value = "Linux" + + # Mock that system socket exists + def exists_side_effect(path): + return path == "/var/run/finch.sock" + + mock_exists.side_effect = exists_side_effect result = get_finch_socket_path() - self.assertEqual(result, expected_path) - mock_system.assert_called_once() + self.assertEqual(result, "unix:///var/run/finch.sock") + + @patch("os.path.exists", return_value=False) + @patch("samcli.local.docker.platform_config.platform.system") + def test_get_finch_socket_path_linux_no_socket(self, mock_system, mock_exists): + """Test that get_finch_socket_path returns None on Linux when no socket exists""" + mock_system.return_value = "Linux" + + result = get_finch_socket_path() + self.assertIsNone(result) + + @patch("samcli.local.docker.platform_config.platform.system") + def test_get_finch_socket_path_macos(self, mock_system): + """Test that get_finch_socket_path returns correct path on macOS""" + mock_system.return_value = "Darwin" + + result = get_finch_socket_path() + self.assertEqual(result, "unix:////Applications/Finch/lima/data/finch/sock/finch.sock") + + @patch("samcli.local.docker.platform_config.platform.system") + def test_get_finch_socket_path_windows(self, mock_system): + """Test that get_finch_socket_path returns None on Windows""" + mock_system.return_value = "Windows" + + result = get_finch_socket_path() + self.assertIsNone(result) @patch("samcli.local.docker.platform_config.get_platform_handler") def test_get_finch_socket_path_returns_none_when_no_handler(self, mock_get_handler):