|
50 | 50 | import socket |
51 | 51 | import sys |
52 | 52 | from time import time |
53 | | -from typing import List, Optional, Tuple |
| 53 | +from typing import ( |
| 54 | + Dict, |
| 55 | + List, |
| 56 | + Optional, |
| 57 | + Tuple, |
| 58 | +) |
54 | 59 |
|
55 | 60 | from cylc.flow.cfgspec.glbl_cfg import glbl_cfg |
56 | 61 |
|
@@ -81,13 +86,21 @@ def get_inst(cls, new=False, expire=None): |
81 | 86 | cls._instance = cls(expire) |
82 | 87 | return cls._instance |
83 | 88 |
|
84 | | - def __init__(self, expire): |
| 89 | + def __init__(self, expire: float): |
85 | 90 | self.expire_time = time() + expire |
86 | | - self._host = None # preferred name of localhost |
87 | | - self._host_exs = {} # host: socket.gethostbyname_ex(host), ... |
88 | | - self._remote_hosts = {} # host: is_remote, ... |
89 | | - self.user_pwent = None |
90 | | - self.remote_users = {} |
| 91 | + self._host: Optional[str] = None # preferred name of localhost |
| 92 | + self._host_exs: Dict[ # host: socket.gethostbyname_ex(host), ... |
| 93 | + str, Tuple[str, List[str], List[str]] |
| 94 | + ] = {} |
| 95 | + self._remote_hosts: Dict[str, bool] = {} # host: is_remote, ... |
| 96 | + self.user_pwent: Optional[pwd.struct_passwd] = None |
| 97 | + self.remote_users: Dict[str, bool] = {} |
| 98 | + |
| 99 | + # On MacOS we have seen different results of socket.gethostbyname_ex() |
| 100 | + # before and after calling socket.getfqdn() for the 1st time. See |
| 101 | + # https://github.com/actions/runner-images/issues/8649#issuecomment-1855919367 |
| 102 | + # Call it here at init to ensure we get consistent results from now on. |
| 103 | + socket.getfqdn() |
91 | 104 |
|
92 | 105 | @staticmethod |
93 | 106 | def get_local_ip_address(target): |
@@ -117,23 +130,31 @@ def get_host_ip_by_name(target): |
117 | 130 | def _get_host_info( |
118 | 131 | self, target: Optional[str] = None |
119 | 132 | ) -> Tuple[str, List[str], List[str]]: |
120 | | - """Return the extended info of the current host.""" |
| 133 | + """Return the extended info of the current host. |
| 134 | +
|
| 135 | + This should return the same result for all possible names or |
| 136 | + IP addresses of the same host, as well as caching the results, |
| 137 | + unlike socket.gethostbyname_ex() alone. |
| 138 | + """ |
121 | 139 | if target is None: |
122 | 140 | target = socket.getfqdn() |
123 | | - if IS_MAC_OS and target in { |
124 | | - '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.' |
125 | | - '0.0.0.0.0.0.ip6.arpa', |
126 | | - '1.0.0.127.in-addr.arpa', |
127 | | - }: |
128 | | - # Python's socket bindings don't play nicely with mac os |
129 | | - # so by default we get the above ip6.arpa address from |
130 | | - # socket.getfqdn, note this does *not* match `hostname -f`. |
131 | | - # https://github.com/cylc/cylc-flow/issues/2689 |
132 | | - # https://github.com/cylc/cylc-flow/issues/3595 |
133 | | - target = socket.gethostname() |
134 | 141 | if target not in self._host_exs: |
135 | 142 | try: |
136 | | - self._host_exs[target] = socket.gethostbyname_ex(target) |
| 143 | + if IS_MAC_OS and target in { |
| 144 | + '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.' |
| 145 | + '0.0.0.0.0.0.ip6.arpa', |
| 146 | + '1.0.0.127.in-addr.arpa', |
| 147 | + }: |
| 148 | + # Python's socket bindings don't play nicely with mac os |
| 149 | + # so by default we get the above ip6.arpa address from |
| 150 | + # socket.getfqdn, note this does *not* match `hostname -f`. |
| 151 | + # https://github.com/cylc/cylc-flow/issues/2689 |
| 152 | + # https://github.com/cylc/cylc-flow/issues/3595 |
| 153 | + name = socket.gethostname() |
| 154 | + else: |
| 155 | + # Normalise the name or IP address to a FQDN |
| 156 | + name = socket.getfqdn(socket.gethostbyaddr(target)[0]) |
| 157 | + self._host_exs[target] = socket.gethostbyname_ex(name) |
137 | 158 | except IOError as exc: |
138 | 159 | if exc.filename is None: |
139 | 160 | exc.filename = target |
@@ -190,26 +211,32 @@ def _get_user_pwent(self): |
190 | 211 | self.remote_users.update(((self.user_pwent.pw_name, False),)) |
191 | 212 | return self.user_pwent |
192 | 213 |
|
193 | | - def is_remote_host(self, name): |
194 | | - """Return True if name has different IP address than the current host. |
| 214 | + def is_remote_host(self, host: Optional[str]) -> bool: |
| 215 | + """Return True if the host is not the current host. |
| 216 | +
|
| 217 | + If the given host's primary name does not match the current host's or |
| 218 | + 'localhost', the host is considered remote. |
| 219 | +
|
| 220 | + Args: |
| 221 | + host: Either a host name or an IP address. |
195 | 222 |
|
196 | 223 | Return False if name is None. |
197 | 224 | Return True if host is unknown. |
198 | 225 |
|
199 | 226 | """ |
200 | | - if name not in self._remote_hosts: |
201 | | - if not name or name.startswith("localhost"): |
202 | | - # e.g. localhost4.localdomain4 |
203 | | - self._remote_hosts[name] = False |
| 227 | + if not host: |
| 228 | + return False |
| 229 | + if host not in self._remote_hosts: |
| 230 | + try: |
| 231 | + host_name = self._get_host_info(host)[0].lower() |
| 232 | + except IOError: |
| 233 | + self._remote_hosts[host] = True |
204 | 234 | else: |
205 | | - try: |
206 | | - host_info = self._get_host_info(name) |
207 | | - except IOError: |
208 | | - self._remote_hosts[name] = True |
209 | | - else: |
210 | | - self._remote_hosts[name] = ( |
211 | | - host_info != self._get_host_info()) |
212 | | - return self._remote_hosts[name] |
| 235 | + this_name = self._get_host_info()[0].lower() |
| 236 | + self._remote_hosts[host] = ( |
| 237 | + host_name not in {this_name, 'localhost'} |
| 238 | + ) |
| 239 | + return self._remote_hosts[host] |
213 | 240 |
|
214 | 241 | def is_remote_user(self, name): |
215 | 242 | """Return True if name is not a name of the current user. |
@@ -281,9 +308,9 @@ def is_remote_platform(platform): |
281 | 308 | return HostUtil.get_inst()._is_remote_platform(platform) |
282 | 309 |
|
283 | 310 |
|
284 | | -def is_remote_host(name): |
| 311 | +def is_remote_host(host: Optional[str]) -> bool: |
285 | 312 | """Shorthand for HostUtil.get_inst().is_remote_host(name).""" |
286 | | - return HostUtil.get_inst().is_remote_host(name) |
| 313 | + return HostUtil.get_inst().is_remote_host(host) |
287 | 314 |
|
288 | 315 |
|
289 | 316 | def is_remote_user(name): |
|
0 commit comments