scan-missing-env-vars.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. #!/usr/bin/env python3
  2. # Only use stdlib to avoid needing to install dependencies
  3. import ast
  4. import sys
  5. import urllib.request
  6. import os
  7. def find_env_vars(code):
  8. tree = ast.parse(code)
  9. class EnvVarVisitor(ast.NodeVisitor):
  10. def __init__(self):
  11. self.env_vars = set()
  12. def visit_Subscript(self, node):
  13. if isinstance(node.value, ast.Attribute):
  14. if (
  15. isinstance(node.value.value, ast.Name)
  16. and node.value.value.id == "os"
  17. and node.value.attr == "environ"
  18. ):
  19. if isinstance(node.slice, ast.Constant):
  20. self.env_vars.add(node.slice.value)
  21. elif isinstance(node.slice, ast.BinOp):
  22. # Handle dynamically constructed env var names like os.environ["VAR_" + "NAME"]
  23. self.env_vars.add(ast.unparse(node.slice))
  24. self.generic_visit(node)
  25. def visit_Call(self, node):
  26. if isinstance(node.func, ast.Attribute):
  27. if (
  28. isinstance(node.func.value, ast.Name)
  29. and node.func.value.id == "os"
  30. and node.func.attr in ("getenv", "get")
  31. ) or (
  32. isinstance(node.func.value, ast.Attribute)
  33. and isinstance(node.func.value.value, ast.Name)
  34. and node.func.value.value.id == "os"
  35. and node.func.value.attr == "environ"
  36. and node.func.attr == "get"
  37. ):
  38. if isinstance(node.args[0], ast.Constant):
  39. self.env_vars.add(node.args[0].value)
  40. self.generic_visit(node)
  41. visitor = EnvVarVisitor()
  42. visitor.visit(tree)
  43. return visitor.env_vars
  44. def main():
  45. if len(sys.argv) < 2:
  46. print("Usage: scan-missing-env-vars.py <git_ref>")
  47. print("Example: scan-missing-env-vars.py main")
  48. sys.exit(0)
  49. git_ref = sys.argv[1]
  50. print(f"Scanning git ref: {git_ref}")
  51. urls = [
  52. f"https://raw.githubusercontent.com/open-webui/open-webui/{git_ref}/backend/open_webui/config.py",
  53. f"https://raw.githubusercontent.com/open-webui/open-webui/{git_ref}/backend/open_webui/env.py",
  54. f"https://raw.githubusercontent.com/open-webui/open-webui/{git_ref}/backend/open_webui/migrations/env.py",
  55. ]
  56. filenames = ["config.py", "env.py", "migrations/env.py"]
  57. all_env_vars = set()
  58. try:
  59. for url, filename in zip(urls, filenames):
  60. with urllib.request.urlopen(url) as response:
  61. contents = response.read().decode("utf-8")
  62. for env_var in find_env_vars(contents):
  63. all_env_vars.add(env_var)
  64. except urllib.error.URLError as e:
  65. print(f"Failed to open URL: {e}")
  66. sys.exit(1)
  67. ignored_env_vars = {
  68. "FROM_INIT_PY",
  69. "GLOBAL_LOG_LEVEL",
  70. "http_proxy",
  71. "https_proxy",
  72. "no_proxy",
  73. "PORT",
  74. "WEBUI_JWT_SECRET_KEY",
  75. }
  76. documented_env_vars = set()
  77. script_dir = os.path.dirname(os.path.abspath(__file__))
  78. docs_file = os.path.join(
  79. script_dir, *[part for part in ["..", "docs", "getting-started", "advanced-topics", "env-configuration.md"]]
  80. )
  81. try:
  82. with open(docs_file, "r", encoding="utf-8", errors="ignore") as f:
  83. for line in f:
  84. if line.startswith("#### `"):
  85. env_var = line.split("`")[1]
  86. documented_env_vars.add(env_var)
  87. except FileNotFoundError as e:
  88. print(f"Failed to open file: {e}")
  89. sys.exit(1)
  90. print("\nEnvironment variables accessed but not documented:")
  91. not_documented_env_vars = all_env_vars - documented_env_vars - ignored_env_vars
  92. for env_var in sorted(not_documented_env_vars):
  93. print(env_var)
  94. if not not_documented_env_vars:
  95. print("None")
  96. print("\nEnvironment variables documented but not accessed:")
  97. diff = documented_env_vars - all_env_vars - ignored_env_vars
  98. for env_var in sorted(diff):
  99. print(env_var)
  100. if not diff:
  101. print("None")
  102. if __name__ == "__main__":
  103. main()