gpustack-runtime 0.1.39.post1__py3-none-any.whl → 0.1.39.post3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1020,11 +1020,13 @@ class WorkloadPlan(WorkloadSecurity):
1020
1020
  c.execution.command_script = None
1021
1021
  # Add default registry if needed.
1022
1022
  if (
1023
- envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_IMAGE_REGISTRY
1024
- and envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_IMAGE_REGISTRY
1023
+ envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY
1024
+ and envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY
1025
1025
  not in ["docker.io", "index.docker.io"]
1026
1026
  ):
1027
- image_registry = envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_IMAGE_NAMESPACE
1027
+ image_registry = (
1028
+ envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_NAMESPACE
1029
+ )
1028
1030
  image_split = c.image.split("/")
1029
1031
  if len(image_split) == 1:
1030
1032
  c.image = f"{image_registry}/library/{c.image}"
@@ -1245,6 +1247,25 @@ class WorkloadExecStream(ABC):
1245
1247
  raise NotImplementedError
1246
1248
 
1247
1249
 
1250
+ def _default_args(func):
1251
+ """
1252
+ Decorator to set default args for deployer methods.
1253
+
1254
+ """
1255
+
1256
+ def wrapper(self, *args, async_mode=None, **kwargs):
1257
+ if async_mode is None:
1258
+ async_mode = envs.GPUSTACK_RUNTIME_DEPLOY_ASYNC
1259
+ return func(
1260
+ self,
1261
+ *args,
1262
+ async_mode=async_mode,
1263
+ **kwargs,
1264
+ )
1265
+
1266
+ return wrapper
1267
+
1268
+
1248
1269
  class Deployer(ABC):
1249
1270
  """
1250
1271
  Base class for all deployers.
@@ -1326,20 +1347,6 @@ class Deployer(ABC):
1326
1347
  """
1327
1348
  raise NotImplementedError
1328
1349
 
1329
- @staticmethod
1330
- def _default_args(func):
1331
- def wrapper(self, *args, async_mode=None, **kwargs):
1332
- if async_mode is None:
1333
- async_mode = envs.GPUSTACK_RUNTIME_DEPLOY_ASYNC
1334
- return func(
1335
- self,
1336
- *args,
1337
- async_mode=async_mode,
1338
- **kwargs,
1339
- )
1340
-
1341
- return wrapper
1342
-
1343
1350
  def __init__(self, name: str):
1344
1351
  self._name = name
1345
1352
 
@@ -2130,3 +2137,361 @@ class Deployer(ABC):
2130
2137
 
2131
2138
  """
2132
2139
  raise NotImplementedError
2140
+
2141
+ @_default_args
2142
+ def inspect(
2143
+ self,
2144
+ name: WorkloadName,
2145
+ namespace: WorkloadNamespace | None = None,
2146
+ async_mode: bool | None = None,
2147
+ ) -> str | None:
2148
+ """
2149
+ Inspect a workload.
2150
+
2151
+ Args:
2152
+ name:
2153
+ The name of the workload.
2154
+ namespace:
2155
+ The namespace of the workload.
2156
+ async_mode:
2157
+ Whether to execute in a separate thread.
2158
+
2159
+ Returns:
2160
+ The inspection result. None if not found.
2161
+
2162
+ Raises:
2163
+ UnsupportedError:
2164
+ If the deployer is not supported in the current environment.
2165
+ OperationError:
2166
+ If the workload fails to inspect.
2167
+
2168
+ """
2169
+ if async_mode:
2170
+ try:
2171
+ future = self.pool.submit(
2172
+ self._inspect,
2173
+ name,
2174
+ namespace,
2175
+ )
2176
+ return future.result()
2177
+ except OperationError:
2178
+ raise
2179
+ except Exception as e:
2180
+ msg = "Asynchronous workload inspect failed."
2181
+ raise OperationError(msg) from e
2182
+ else:
2183
+ return self._inspect(name, namespace)
2184
+
2185
+ @abstractmethod
2186
+ def _inspect(
2187
+ self,
2188
+ name: WorkloadName,
2189
+ namespace: WorkloadNamespace | None = None,
2190
+ ) -> str | None:
2191
+ """
2192
+ Inspect a workload.
2193
+
2194
+ Args:
2195
+ name:
2196
+ The name of the workload.
2197
+ namespace:
2198
+ The namespace of the workload.
2199
+
2200
+ Returns:
2201
+ The inspection result. None if not found.
2202
+
2203
+ Raises:
2204
+ UnsupportedError:
2205
+ If the deployer is not supported in the current environment.
2206
+ OperationError:
2207
+ If the workload fails to inspect.
2208
+
2209
+ """
2210
+ raise NotImplementedError
2211
+
2212
+
2213
+ class EndoscopicDeployer(Deployer):
2214
+ """
2215
+ Base class for endoscopic deployers.
2216
+ """
2217
+
2218
+ async def async_endoscopic_logs(
2219
+ self,
2220
+ timestamps: bool = False,
2221
+ tail: int | None = None,
2222
+ since: int | None = None,
2223
+ follow: bool = False,
2224
+ async_mode: bool | None = None,
2225
+ ) -> AsyncGenerator[bytes | str, None, None] | bytes | str:
2226
+ """
2227
+ Asynchronously get the logs of the deployer itself.
2228
+ Only works in mirrored deployment mode.
2229
+
2230
+ Args:
2231
+ timestamps:
2232
+ Show timestamps in the logs.
2233
+ tail:
2234
+ Number of lines to show from the end of the logs.
2235
+ since:
2236
+ Show logs since the given epoch in seconds.
2237
+ follow:
2238
+ Whether to follow the logs.
2239
+ async_mode:
2240
+ Whether to execute in a separate thread.
2241
+
2242
+ Returns:
2243
+ The logs as a byte string or a generator yielding byte strings if follow is True.
2244
+
2245
+ Raises:
2246
+ UnsupportedError:
2247
+ If the deployer is not supported in the current environment.
2248
+ OperationError:
2249
+ If the deployer fails to get logs.
2250
+
2251
+ """
2252
+
2253
+ def run_sync_gen(f: bool) -> Generator[bytes | str, None, None] | bytes | str:
2254
+ return self.endoscopic_logs(
2255
+ timestamps=timestamps,
2256
+ tail=tail,
2257
+ since=since,
2258
+ follow=f,
2259
+ async_mode=async_mode,
2260
+ )
2261
+
2262
+ if not follow:
2263
+ return run_sync_gen(False)
2264
+
2265
+ async def async_gen() -> AsyncGenerator[bytes | str, None, None]:
2266
+ loop = asyncio.get_event_loop()
2267
+ sync_gen = await loop.run_in_executor(self.pool, run_sync_gen, True)
2268
+ with contextlib.closing(sync_gen) as gen:
2269
+ for chunk in gen:
2270
+ yield chunk
2271
+
2272
+ return async_gen()
2273
+
2274
+ @_default_args
2275
+ def endoscopic_logs(
2276
+ self,
2277
+ timestamps: bool = False,
2278
+ tail: int | None = None,
2279
+ since: int | None = None,
2280
+ follow: bool = False,
2281
+ async_mode: bool | None = None,
2282
+ ) -> Generator[bytes | str, None, None] | bytes | str:
2283
+ """
2284
+ Get the logs of the deployer itself.
2285
+ Only works in mirrored deployment mode.
2286
+
2287
+ Args:
2288
+ timestamps:
2289
+ Show timestamps in the logs.
2290
+ tail:
2291
+ Number of lines to show from the end of the logs.
2292
+ since:
2293
+ Show logs since the given epoch in seconds.
2294
+ follow:
2295
+ Whether to follow the logs.
2296
+ async_mode:
2297
+ Whether to execute in a separate thread.
2298
+
2299
+ Returns:
2300
+ The logs as a byte string or a generator yielding byte strings if follow is True.
2301
+
2302
+ Raises:
2303
+ UnsupportedError:
2304
+ If the deployer is not supported in the current environment.
2305
+ OperationError:
2306
+ If the deployer fails to get logs.
2307
+
2308
+ """
2309
+ if async_mode:
2310
+ try:
2311
+ future = self.pool.submit(
2312
+ self._endoscopic_logs,
2313
+ timestamps,
2314
+ tail,
2315
+ since,
2316
+ follow,
2317
+ )
2318
+ return future.result()
2319
+ except OperationError:
2320
+ raise
2321
+ except Exception as e:
2322
+ msg = "Asynchronous endoscopic logs failed."
2323
+ raise OperationError(msg) from e
2324
+ else:
2325
+ return self._endoscopic_logs(timestamps, tail, since, follow)
2326
+
2327
+ @abstractmethod
2328
+ def _endoscopic_logs(
2329
+ self,
2330
+ timestamps: bool = False,
2331
+ tail: int | None = None,
2332
+ since: int | None = None,
2333
+ follow: bool = False,
2334
+ ) -> Generator[bytes | str, None, None] | bytes | str:
2335
+ """
2336
+ Get the logs of the deployer itself.
2337
+ Only works in mirrored deployment mode.
2338
+
2339
+ Args:
2340
+ timestamps:
2341
+ Show timestamps in the logs.
2342
+ tail:
2343
+ Number of lines to show from the end of the logs.
2344
+ since:
2345
+ Show logs since the given epoch in seconds.
2346
+ follow:
2347
+ Whether to follow the logs.
2348
+
2349
+ Returns:
2350
+ The logs as a byte string or a generator yielding byte strings if follow is True.
2351
+
2352
+ Raises:
2353
+ UnsupportedError:
2354
+ If the deployer is not supported in the current environment.
2355
+ OperationError:
2356
+ If the deployer fails to get logs.
2357
+
2358
+ """
2359
+ raise NotImplementedError
2360
+
2361
+ @_default_args
2362
+ def endoscopic_exec(
2363
+ self,
2364
+ detach: bool = True,
2365
+ command: list[str] | None = None,
2366
+ args: list[str] | None = None,
2367
+ async_mode: bool | None = None,
2368
+ ) -> WorkloadExecStream | bytes | str:
2369
+ """
2370
+ Execute a command in the deployer itself.
2371
+ Only works in mirrored deployment mode.
2372
+
2373
+ Args:
2374
+ detach:
2375
+ Whether to detach from the command.
2376
+ command:
2377
+ The command to execute.
2378
+ If not specified, use /bin/sh and implicitly attach.
2379
+ args:
2380
+ The arguments to pass to the command.
2381
+ async_mode:
2382
+ Whether to execute in a separate thread.
2383
+
2384
+ Returns:
2385
+ If detach is False, return a WorkloadExecStream.
2386
+ otherwise, return the output of the command as a byte string or string.
2387
+
2388
+ Raises:
2389
+ UnsupportedError:
2390
+ If the deployer is not supported in the current environment.
2391
+ OperationError:
2392
+ If the deployer fails to execute the command.
2393
+
2394
+ """
2395
+ if async_mode:
2396
+ try:
2397
+ future = self.pool.submit(
2398
+ self._endoscopic_exec,
2399
+ detach,
2400
+ command,
2401
+ args,
2402
+ )
2403
+ return future.result()
2404
+ except OperationError:
2405
+ raise
2406
+ except Exception as e:
2407
+ msg = "Asynchronous endoscopic exec failed."
2408
+ raise OperationError(msg) from e
2409
+ else:
2410
+ return self._endoscopic_exec(detach, command, args)
2411
+
2412
+ @abstractmethod
2413
+ def _endoscopic_exec(
2414
+ self,
2415
+ detach: bool = True,
2416
+ command: list[str] | None = None,
2417
+ args: list[str] | None = None,
2418
+ ) -> WorkloadExecStream | bytes | str:
2419
+ """
2420
+ Execute a command in the deployer itself.
2421
+ Only works in mirrored deployment mode.
2422
+
2423
+ Args:
2424
+ detach:
2425
+ Whether to detach from the command.
2426
+ command:
2427
+ The command to execute.
2428
+ If not specified, use /bin/sh and implicitly attach.
2429
+ args:
2430
+ The arguments to pass to the command.
2431
+
2432
+ Returns:
2433
+ If detach is False, return a WorkloadExecStream.
2434
+ otherwise, return the output of the command as a byte string or string.
2435
+
2436
+ Raises:
2437
+ UnsupportedError:
2438
+ If the deployer is not supported in the current environment.
2439
+ OperationError:
2440
+ If the deployer fails to execute the command.
2441
+
2442
+ """
2443
+ raise NotImplementedError
2444
+
2445
+ def endoscopic_inspect(
2446
+ self,
2447
+ async_mode: bool | None = None,
2448
+ ) -> str:
2449
+ """
2450
+ Inspect the deployer itself.
2451
+ Only works in mirrored deployment mode.
2452
+
2453
+ Args:
2454
+ async_mode:
2455
+ Whether to execute in a separate thread.
2456
+
2457
+ Returns:
2458
+ The inspection result.
2459
+
2460
+ Raises:
2461
+ UnsupportedError:
2462
+ If the deployer is not supported in the current environment.
2463
+ OperationError:
2464
+ If the deployer fails to execute the command.
2465
+
2466
+ """
2467
+ if async_mode:
2468
+ try:
2469
+ future = self.pool.submit(
2470
+ self._endoscopic_inspect,
2471
+ )
2472
+ return future.result()
2473
+ except OperationError:
2474
+ raise
2475
+ except Exception as e:
2476
+ msg = "Asynchronous endoscopic inspect failed."
2477
+ raise OperationError(msg) from e
2478
+ else:
2479
+ return self._endoscopic_inspect()
2480
+
2481
+ @abstractmethod
2482
+ def _endoscopic_inspect(self) -> str:
2483
+ """
2484
+ Inspect the deployer itself.
2485
+ Only works in mirrored deployment mode.
2486
+
2487
+ Returns:
2488
+ The inspection result.
2489
+
2490
+ Raises:
2491
+ UnsupportedError:
2492
+ If the deployer is not supported in the current environment.
2493
+ OperationError:
2494
+ If the deployer fails to execute the command.
2495
+
2496
+ """
2497
+ raise NotImplementedError
@@ -42,6 +42,25 @@ Regex for RFC1035 domain name, which must:
42
42
  - start with an alphabetic character
43
43
  - end with an alphanumeric character
44
44
  """
45
+ _SENSITIVE_ENVS_SUFFIX = (
46
+ "key",
47
+ "keys",
48
+ "token",
49
+ "tokens",
50
+ "secret",
51
+ "secrets",
52
+ "password",
53
+ "passwords",
54
+ "passwd",
55
+ "pass",
56
+ "credential",
57
+ "credentials",
58
+ "cred",
59
+ "auth",
60
+ )
61
+ """
62
+ Suffixes of environment variable names that are considered sensitive.
63
+ """
45
64
 
46
65
 
47
66
  @lru_cache
@@ -775,3 +794,18 @@ def make_image_with(
775
794
  else:
776
795
  image += f":{tag}"
777
796
  return image
797
+
798
+
799
+ def sensitive_env_var(name: str) -> bool:
800
+ """
801
+ Check if the given environment variable name is considered sensitive.
802
+
803
+ Args:
804
+ name:
805
+ The environment variable name to check.
806
+
807
+ Returns:
808
+ True if the name is considered sensitive, False otherwise.
809
+
810
+ """
811
+ return name.lower().endswith(_SENSITIVE_ENVS_SUFFIX)