envs.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import os
  2. import platform
  3. import subprocess
  4. from pathlib import Path
  5. from typing import Dict, List, Optional
  6. def extract_unix_vars_of_source(script_paths: List[Path]) -> Dict[str, str]:
  7. """
  8. Extracts the environment variables from a source-able script on Unix.
  9. On Windows, uses PowerShell to source the script and capture env changes.
  10. """
  11. for script_path in script_paths:
  12. if not script_path.is_file():
  13. raise Exception(
  14. f"The file '{script_path}' does not exist or is not a file."
  15. )
  16. def parse_env(env_str):
  17. env = {}
  18. for line in env_str.splitlines():
  19. if '=' in line:
  20. key, value = line.split('=', 1)
  21. env[key] = value
  22. return env
  23. system = platform.system().lower()
  24. if system == "windows":
  25. return _extract_env_via_powershell(script_paths)
  26. else:
  27. return _extract_env_via_bash(script_paths)
  28. def _extract_env_via_bash(script_paths: List[Path]) -> Dict[str, str]:
  29. def parse_env(env_str):
  30. env = {}
  31. for line in env_str.splitlines():
  32. if '=' in line:
  33. key, value = line.split('=', 1)
  34. env[key] = value
  35. return env
  36. try:
  37. original_env_output = subprocess.check_output(
  38. ['bash', '-c', 'env'],
  39. stderr=subprocess.PIPE,
  40. text=True,
  41. )
  42. original = parse_env(original_env_output)
  43. source_command = ' && '.join(
  44. [f'source {script_path}' for script_path in script_paths]
  45. )
  46. sourced_env_output = subprocess.check_output(
  47. ['bash', '-c', f'{source_command} && env'],
  48. stderr=subprocess.PIPE,
  49. text=True,
  50. )
  51. sourced = parse_env(sourced_env_output)
  52. diff = {
  53. k: v
  54. for k, v in sourced.items()
  55. if k not in original or original.get(k) != v
  56. }
  57. return diff
  58. except subprocess.CalledProcessError as e:
  59. raise Exception(
  60. f"Failed to extract environment variables from [{script_paths}]: {e.stderr}"
  61. )
  62. def _extract_env_via_powershell(script_paths: List[Path]) -> Dict[str, str]:
  63. """
  64. Uses PowerShell to source a script (e.g. .ps1 or env-setup script)
  65. and capture environment variable changes.
  66. """
  67. try:
  68. # Build a PowerShell command that captures env before and after sourcing
  69. script_args = ' '.join(
  70. [f'"{str(p)}"' for p in script_paths]
  71. )
  72. ps_command = (
  73. '$before = Get-ChildItem Env: | ForEach-Object { "$($_.Name)=$($_.Value)" }; '
  74. f'{script_args}; '
  75. '$after = Get-ChildItem Env: | ForEach-Object { "$($_.Name)=$($_.Value)" }; '
  76. 'Write-Output "---BEFORE---"; '
  77. 'Write-Output $before; '
  78. 'Write-Output "---AFTER---"; '
  79. 'Write-Output $after'
  80. )
  81. output = subprocess.check_output(
  82. ['powershell', '-NoProfile', '-NonInteractive', '-Command', ps_command],
  83. stderr=subprocess.PIPE,
  84. text=True,
  85. )
  86. # Split output into before and after sections
  87. parts = output.split('---BEFORE---')
  88. if len(parts) < 2:
  89. return {}
  90. after_section = parts[1].split('---AFTER---')
  91. before_str = after_section[0]
  92. after_str = after_section[1] if len(after_section) > 1 else ''
  93. before = parse_env(before_str)
  94. after = parse_env(after_str)
  95. diff = {
  96. k: v
  97. for k, v in after.items()
  98. if k not in before or before.get(k) != v
  99. }
  100. return diff
  101. except subprocess.CalledProcessError as e:
  102. raise Exception(
  103. f"Failed to extract environment variables from [{script_paths}] via PowerShell: {e.stderr}"
  104. )
  105. def get_gpustack_env(env_var: str) -> Optional[str]:
  106. env_name = "GPUSTACK_" + env_var
  107. return os.getenv(env_name)
  108. def get_gpustack_env_bool(env_var: str) -> Optional[bool]:
  109. env_name = "GPUSTACK_" + env_var
  110. env_value = os.getenv(env_name)
  111. if env_value is not None:
  112. return env_value.lower() in ["true", "1"]
  113. return None
  114. def is_docker_env() -> bool:
  115. return os.path.exists("/.dockerenv")
  116. def sanitize_env(env: Dict[str, str]) -> Dict[str, str]:
  117. """
  118. Sanitize the environment variables by removing any keys that are not valid
  119. environment variable names.
  120. """
  121. prefixes = ("GPUSTACK_",)
  122. suffixes = (
  123. "_KEY",
  124. "_key",
  125. "_TOKEN",
  126. "_token",
  127. "_SECRET",
  128. "_secret",
  129. "_PASSWORD",
  130. "_password",
  131. "_PASS",
  132. "_pass",
  133. )
  134. return {
  135. k: v
  136. for k, v in env.items()
  137. if not k.startswith(prefixes) and not k.endswith(suffixes)
  138. }
  139. def filter_env_vars(env_dict: dict) -> dict:
  140. """
  141. Filter out environment variables that should not be passed to the model instance or container.
  142. """
  143. return {
  144. k: v
  145. for k, v in env_dict.items()
  146. if not (
  147. k.startswith(
  148. (
  149. "GPUSTACK_",
  150. "PIP_",
  151. "PIPX_",
  152. "POETRY_",
  153. "UV_",
  154. "S6_",
  155. "PGCONFIG_",
  156. "POSTGRES_",
  157. )
  158. )
  159. or k.endswith(
  160. (
  161. "_VISIBLE_DEVICES",
  162. "_DISABLE_REQUIRE",
  163. "_DRIVER_CAPABILITIES",
  164. "_PATH",
  165. "_HOME",
  166. )
  167. )
  168. or (
  169. k
  170. in (
  171. "DEBIAN_FRONTEND",
  172. "LANG",
  173. "LANGUAGE",
  174. "LC_ALL",
  175. "PYTHON_VERSION",
  176. "HOME",
  177. "HOSTNAME",
  178. "PWD",
  179. "_",
  180. "TERM",
  181. "SHLVL",
  182. "LS_COLORS",
  183. "PATH",
  184. )
  185. )
  186. )
  187. }