File size: 6,316 Bytes
a42b16d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
"""Environment variable validation and suggestions."""
from __future__ import annotations
import os
import re
from pathlib import Path
from typing import Any, Dict, List, Optional
class EnvironmentValidator:
"""Validates and suggests environment variables."""
def __init__(self):
self.common_vars = {
"next.js": ["NEXT_PUBLIC_API_URL", "DATABASE_URL", "NEXTAUTH_SECRET", "NEXTAUTH_URL"],
"django": ["SECRET_KEY", "DEBUG", "DATABASE_URL", "ALLOWED_HOSTS"],
"fastapi": ["DATABASE_URL", "SECRET_KEY", "CORS_ORIGINS", "ENVIRONMENT"],
"react": ["REACT_APP_API_URL", "REACT_APP_ENV"],
"express": ["PORT", "NODE_ENV", "DATABASE_URL", "JWT_SECRET"],
"nestjs": ["PORT", "DATABASE_URL", "JWT_SECRET", "NODE_ENV"],
}
def validate_env_file(self, folder_path: str, framework: Optional[str] = None) -> Dict[str, Any]:
"""Validate .env files in codebase."""
path = Path(folder_path)
env_files = list(path.rglob(".env*"))
found_vars = []
missing_vars = []
issues = []
# Read all env files
for env_file in env_files:
if env_file.is_file() and ".git" not in str(env_file):
try:
content = env_file.read_text()
# Extract variable names
var_pattern = r'^([A-Z_][A-Z0-9_]*)\s*='
vars_in_file = re.findall(var_pattern, content, re.MULTILINE)
found_vars.extend(vars_in_file)
except Exception:
pass
# Check for framework-specific requirements
if framework:
required_vars = self.common_vars.get(framework.lower(), [])
for var in required_vars:
if var not in found_vars:
missing_vars.append({
"variable": var,
"required": True,
"description": self._get_var_description(var, framework)
})
# Check for common issues
for env_file in env_files:
try:
content = env_file.read_text()
# Check for hardcoded secrets
if re.search(r'(password|secret|key|token)\s*=\s*["\'][^"\']+["\']', content, re.IGNORECASE):
issues.append({
"file": str(env_file.relative_to(path)),
"severity": "high",
"issue": "Hardcoded secrets detected - use environment variables or secrets manager"
})
# Check for .env in git
gitignore = path / ".gitignore"
if gitignore.exists():
gitignore_content = gitignore.read_text()
if ".env" not in gitignore_content:
issues.append({
"file": ".gitignore",
"severity": "medium",
"issue": ".env files should be in .gitignore"
})
except Exception:
pass
return {
"env_files_found": len(env_files),
"variables_found": len(set(found_vars)),
"missing_required": missing_vars,
"issues": issues,
"status": "valid" if not missing_vars and not issues else "needs_attention",
"recommendations": self._get_recommendations(framework, missing_vars, issues)
}
def suggest_env_vars(self, framework: str, platform: str) -> List[Dict[str, str]]:
"""Suggest environment variables based on framework and platform."""
suggestions = []
# Framework-specific
framework_vars = self.common_vars.get(framework.lower(), [])
for var in framework_vars:
suggestions.append({
"variable": var,
"required": True,
"description": self._get_var_description(var, framework),
"category": "framework"
})
# Platform-specific
platform_vars = {
"vercel": ["VERCEL_URL", "VERCEL_ENV"],
"netlify": ["NETLIFY", "CONTEXT"],
"aws": ["AWS_REGION", "AWS_ACCESS_KEY_ID"],
"gcp": ["GOOGLE_CLOUD_PROJECT", "GCP_REGION"],
"azure": ["AZURE_REGION", "AZURE_SUBSCRIPTION_ID"],
}
platform_vars_list = platform_vars.get(platform.lower(), [])
for var in platform_vars_list:
suggestions.append({
"variable": var,
"required": False,
"description": f"Platform-specific variable for {platform}",
"category": "platform"
})
return suggestions
def _get_var_description(self, var: str, framework: str) -> str:
"""Get description for a variable."""
descriptions = {
"DATABASE_URL": "Database connection string",
"SECRET_KEY": "Secret key for encryption/signing",
"API_URL": "API endpoint URL",
"NODE_ENV": "Node.js environment (development/production)",
"PORT": "Application port number",
"DEBUG": "Debug mode flag",
}
return descriptions.get(var, f"Required for {framework}")
def _get_recommendations(
self,
framework: Optional[str],
missing_vars: List[Dict],
issues: List[Dict]
) -> List[str]:
"""Get recommendations based on validation results."""
recommendations = []
if missing_vars:
recommendations.append(f"Add {len(missing_vars)} missing required environment variables")
if issues:
recommendations.append("Review security issues in environment files")
recommendations.append("Use a secrets manager (AWS Secrets Manager, HashiCorp Vault) for production")
recommendations.append("Never commit .env files to version control")
if framework:
recommendations.append(f"Follow {framework} best practices for environment configuration")
return recommendations
|