mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 08:11:08 -05:00
Refactor test utilities and enhance ASGI settings
- Cleaned up and standardized assertions in ApiTestMixin for API response validation. - Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE. - Removed unused imports and improved formatting in settings.py. - Refactored URL patterns in urls.py for better readability and organization. - Enhanced view functions in views.py for consistency and clarity. - Added .flake8 configuration for linting and style enforcement. - Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
@@ -6,11 +6,8 @@ Uses pre-built template VMs for fast deployment instead of autoinstall.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Import our modular components
|
||||
from template_manager import TemplateVMManager
|
||||
@@ -19,90 +16,92 @@ from vm_manager_template import UnraidTemplateVMManager
|
||||
|
||||
class ConfigLoader:
|
||||
"""Dynamic configuration loader that reads environment variables when needed."""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
# Try to load ***REMOVED***.unraid if it exists to ensure we have the latest config
|
||||
# Try to load ***REMOVED***.unraid if it exists to ensure we have the
|
||||
# latest config
|
||||
self._load_env_file()
|
||||
|
||||
|
||||
def _load_env_file(self):
|
||||
"""Load ***REMOVED***.unraid file if it exists."""
|
||||
# Find the project directory (two levels up from this script)
|
||||
script_dir = Path(__file__).parent
|
||||
project_dir = script_dir.parent.parent
|
||||
env_file = project_dir / "***REMOVED***.unraid"
|
||||
|
||||
|
||||
if env_file.exists():
|
||||
try:
|
||||
with open(env_file, 'r') as f:
|
||||
with open(env_file, "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#') and '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, value = line.split("=", 1)
|
||||
# Remove quotes if present
|
||||
value = value.strip('"\'')
|
||||
# Only set if not already in environment (env vars take precedence)
|
||||
if key not in os***REMOVED***iron:
|
||||
os***REMOVED***iron[key] = value
|
||||
|
||||
value = value.strip("\"'")
|
||||
# Only set if not already in environment (env vars
|
||||
# take precedence)
|
||||
if key not in os.environ:
|
||||
os.environ[key] = value
|
||||
|
||||
logging.info(f"📝 Loaded configuration from {env_file}")
|
||||
except Exception as e:
|
||||
logging.warning(f"⚠️ Could not load ***REMOVED***.unraid: {e}")
|
||||
|
||||
|
||||
@property
|
||||
def UNRAID_HOST(self):
|
||||
return os***REMOVED***iron.get("UNRAID_HOST", "localhost")
|
||||
|
||||
return os.environ.get("UNRAID_HOST", "localhost")
|
||||
|
||||
@property
|
||||
def UNRAID_USER(self):
|
||||
return os***REMOVED***iron.get("UNRAID_USER", "root")
|
||||
|
||||
return os.environ.get("UNRAID_USER", "root")
|
||||
|
||||
@property
|
||||
def VM_NAME(self):
|
||||
return os***REMOVED***iron.get("VM_NAME", "thrillwiki-vm")
|
||||
|
||||
return os.environ.get("VM_NAME", "thrillwiki-vm")
|
||||
|
||||
@property
|
||||
def VM_MEMORY(self):
|
||||
return int(os***REMOVED***iron.get("VM_MEMORY", 4096))
|
||||
|
||||
return int(os.environ.get("VM_MEMORY", 4096))
|
||||
|
||||
@property
|
||||
def VM_VCPUS(self):
|
||||
return int(os***REMOVED***iron.get("VM_VCPUS", 2))
|
||||
|
||||
return int(os.environ.get("VM_VCPUS", 2))
|
||||
|
||||
@property
|
||||
def VM_DISK_SIZE(self):
|
||||
return int(os***REMOVED***iron.get("VM_DISK_SIZE", 50))
|
||||
|
||||
return int(os.environ.get("VM_DISK_SIZE", 50))
|
||||
|
||||
@property
|
||||
def SSH_PUBLIC_KEY(self):
|
||||
return os***REMOVED***iron.get("SSH_PUBLIC_KEY", "")
|
||||
|
||||
return os.environ.get("SSH_PUBLIC_KEY", "")
|
||||
|
||||
@property
|
||||
def VM_IP(self):
|
||||
return os***REMOVED***iron.get("VM_IP", "dhcp")
|
||||
|
||||
return os.environ.get("VM_IP", "dhcp")
|
||||
|
||||
@property
|
||||
def VM_GATEWAY(self):
|
||||
return os***REMOVED***iron.get("VM_GATEWAY", "192.168.20.1")
|
||||
|
||||
return os.environ.get("VM_GATEWAY", "192.168.20.1")
|
||||
|
||||
@property
|
||||
def VM_NETMASK(self):
|
||||
return os***REMOVED***iron.get("VM_NETMASK", "255.255.255.0")
|
||||
|
||||
return os.environ.get("VM_NETMASK", "255.255.255.0")
|
||||
|
||||
@property
|
||||
def VM_NETWORK(self):
|
||||
return os***REMOVED***iron.get("VM_NETWORK", "192.168.20.0/24")
|
||||
|
||||
return os.environ.get("VM_NETWORK", "192.168.20.0/24")
|
||||
|
||||
@property
|
||||
def REPO_URL(self):
|
||||
return os***REMOVED***iron.get("REPO_URL", "")
|
||||
|
||||
return os.environ.get("REPO_URL", "")
|
||||
|
||||
@property
|
||||
def GITHUB_USERNAME(self):
|
||||
return os***REMOVED***iron.get("GITHUB_USERNAME", "")
|
||||
|
||||
return os.environ.get("GITHUB_USERNAME", "")
|
||||
|
||||
@property
|
||||
def GITHUB_TOKEN(self):
|
||||
return os***REMOVED***iron.get("GITHUB_TOKEN", "")
|
||||
return os.environ.get("GITHUB_TOKEN", "")
|
||||
|
||||
|
||||
# Create a global configuration instance
|
||||
@@ -114,14 +113,18 @@ os.makedirs("logs", exist_ok=True)
|
||||
# Configure console handler with line buffering
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
||||
console_handler.setFormatter(
|
||||
logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
||||
)
|
||||
# Force flush after each log message
|
||||
console_handler.flush = lambda: sys.stdout.flush()
|
||||
|
||||
# Configure file handler
|
||||
file_handler = logging.FileHandler("logs/unraid-vm.log")
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
||||
file_handler.setFormatter(
|
||||
logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
||||
)
|
||||
|
||||
# Set up basic config with both handlers
|
||||
logging.basicConfig(
|
||||
@@ -136,76 +139,91 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class ThrillWikiTemplateVMOrchestrator:
|
||||
"""Main orchestrator for template-based ThrillWiki VM deployment."""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
# Log current configuration for debugging
|
||||
logger.info(f"🔧 Using configuration: UNRAID_HOST={config.UNRAID_HOST}, UNRAID_USER={config.UNRAID_USER}, VM_NAME={config.VM_NAME}")
|
||||
|
||||
self.template_manager = TemplateVMManager(config.UNRAID_HOST, config.UNRAID_USER)
|
||||
self.vm_manager = UnraidTemplateVMManager(config.VM_NAME, config.UNRAID_HOST, config.UNRAID_USER)
|
||||
|
||||
logger.info(
|
||||
f"🔧 Using configuration: UNRAID_HOST={
|
||||
config.UNRAID_HOST}, UNRAID_USER={
|
||||
config.UNRAID_USER}, VM_NAME={
|
||||
config.VM_NAME}"
|
||||
)
|
||||
|
||||
self.template_manager = TemplateVMManager(
|
||||
config.UNRAID_HOST, config.UNRAID_USER
|
||||
)
|
||||
self.vm_manager = UnraidTemplateVMManager(
|
||||
config.VM_NAME, config.UNRAID_HOST, config.UNRAID_USER
|
||||
)
|
||||
|
||||
def check_template_ready(self) -> bool:
|
||||
"""Check if template VM is ready for use."""
|
||||
logger.info("🔍 Checking template VM availability...")
|
||||
|
||||
|
||||
if not self.template_manager.check_template_exists():
|
||||
logger.error("❌ Template VM disk not found!")
|
||||
logger.error("Please ensure 'thrillwiki-template-ubuntu' VM exists and is properly configured")
|
||||
logger.error("Template should be located at: /mnt/user/domains/thrillwiki-template-ubuntu/vdisk1.qcow2")
|
||||
logger.error(
|
||||
"Please ensure 'thrillwiki-template-ubuntu' VM exists and is properly configured"
|
||||
)
|
||||
logger.error(
|
||||
"Template should be located at: /mnt/user/domains/thrillwiki-template-ubuntu/vdisk1.qcow2"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
# Check template status
|
||||
if not self.template_manager.update_template():
|
||||
logger.warning("⚠️ Template VM may be running - this could cause issues")
|
||||
logger.warning("Ensure the template VM is stopped before creating new instances")
|
||||
|
||||
logger.warning(
|
||||
"Ensure the template VM is stopped before creating new instances"
|
||||
)
|
||||
|
||||
info = self.template_manager.get_template_info()
|
||||
if info:
|
||||
logger.info(f"📋 Template Info:")
|
||||
logger.info(f" Virtual Size: {info['virtual_size']}")
|
||||
logger.info(f" File Size: {info['file_size']}")
|
||||
logger.info(f" Last Modified: {info['last_modified']}")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def deploy_vm_from_template(self) -> bool:
|
||||
"""Complete template-based VM deployment process."""
|
||||
try:
|
||||
logger.info("🚀 Starting ThrillWiki template-based VM deployment...")
|
||||
|
||||
|
||||
# Step 1: Check SSH connectivity
|
||||
logger.info("📡 Testing Unraid connectivity...")
|
||||
if not self.vm_manager.authenticate():
|
||||
logger.error("❌ Cannot connect to Unraid server")
|
||||
return False
|
||||
|
||||
|
||||
# Step 2: Check template availability
|
||||
logger.info("🔍 Verifying template VM...")
|
||||
if not self.check_template_ready():
|
||||
logger.error("❌ Template VM not ready")
|
||||
return False
|
||||
|
||||
|
||||
# Step 3: Create VM from template
|
||||
logger.info("⚙️ Creating VM from template...")
|
||||
success = self.vm_manager.create_vm_from_template(
|
||||
vm_memory=config.VM_MEMORY,
|
||||
vm_vcpus=config.VM_VCPUS,
|
||||
vm_vcpus=config.VM_VCPUS,
|
||||
vm_disk_size=config.VM_DISK_SIZE,
|
||||
vm_ip=config.VM_IP
|
||||
vm_ip=config.VM_IP,
|
||||
)
|
||||
|
||||
|
||||
if not success:
|
||||
logger.error("❌ Failed to create VM from template")
|
||||
return False
|
||||
|
||||
|
||||
# Step 4: Start VM
|
||||
logger.info("🟢 Starting VM...")
|
||||
success = self.vm_manager.start_vm()
|
||||
|
||||
|
||||
if not success:
|
||||
logger.error("❌ Failed to start VM")
|
||||
return False
|
||||
|
||||
|
||||
logger.info("🎉 Template-based VM deployment completed successfully!")
|
||||
logger.info("")
|
||||
logger.info("📋 Next Steps:")
|
||||
@@ -214,44 +232,54 @@ class ThrillWikiTemplateVMOrchestrator:
|
||||
logger.info("3. Use 'python main_template.py ip' to get VM IP when ready")
|
||||
logger.info("4. SSH to VM and run deployment commands")
|
||||
logger.info("")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Template VM deployment failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def deploy_and_configure_thrillwiki(self) -> bool:
|
||||
"""Deploy VM from template and configure ThrillWiki."""
|
||||
try:
|
||||
logger.info("🚀 Starting complete ThrillWiki deployment from template...")
|
||||
|
||||
|
||||
# Step 1: Deploy VM from template
|
||||
if not self.deploy_vm_from_template():
|
||||
return False
|
||||
|
||||
|
||||
# Step 2: Wait for VM to be accessible and configure ThrillWiki
|
||||
if config.REPO_URL:
|
||||
logger.info("🔧 Configuring ThrillWiki on VM...")
|
||||
success = self.vm_manager.customize_vm_for_thrillwiki(config.REPO_URL, config.GITHUB_TOKEN)
|
||||
|
||||
success = self.vm_manager.customize_vm_for_thrillwiki(
|
||||
config.REPO_URL, config.GITHUB_TOKEN
|
||||
)
|
||||
|
||||
if success:
|
||||
vm_ip = self.vm_manager.get_vm_ip()
|
||||
logger.info("🎉 Complete ThrillWiki deployment successful!")
|
||||
logger.info(f"🌐 ThrillWiki is available at: http://{vm_ip}:8000")
|
||||
else:
|
||||
logger.warning("⚠️ VM deployed but ThrillWiki configuration may have failed")
|
||||
logger.info("You can manually configure ThrillWiki by SSH'ing to the VM")
|
||||
logger.warning(
|
||||
"⚠️ VM deployed but ThrillWiki configuration may have failed"
|
||||
)
|
||||
logger.info(
|
||||
"You can manually configure ThrillWiki by SSH'ing to the VM"
|
||||
)
|
||||
else:
|
||||
logger.info("📝 No repository URL provided - VM deployed but ThrillWiki not configured")
|
||||
logger.info("Set REPO_URL environment variable to auto-configure ThrillWiki")
|
||||
|
||||
logger.info(
|
||||
"📝 No repository URL provided - VM deployed but ThrillWiki not configured"
|
||||
)
|
||||
logger.info(
|
||||
"Set REPO_URL environment variable to auto-configure ThrillWiki"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Complete deployment failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_vm_info(self) -> dict:
|
||||
"""Get VM information."""
|
||||
return {
|
||||
@@ -261,7 +289,7 @@ class ThrillWikiTemplateVMOrchestrator:
|
||||
"memory": config.VM_MEMORY,
|
||||
"vcpus": config.VM_VCPUS,
|
||||
"disk_size": config.VM_DISK_SIZE,
|
||||
"deployment_type": "template-based"
|
||||
"deployment_type": "template-based",
|
||||
}
|
||||
|
||||
|
||||
@@ -281,24 +309,35 @@ Examples:
|
||||
python main_template.py delete # Remove VM completely
|
||||
python main_template.py template # Manage template VM
|
||||
""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
|
||||
|
||||
parser.add_argument(
|
||||
"action",
|
||||
choices=["setup", "deploy", "create", "start", "stop", "status", "ip", "delete", "info", "template"],
|
||||
help="Action to perform"
|
||||
choices=[
|
||||
"setup",
|
||||
"deploy",
|
||||
"create",
|
||||
"start",
|
||||
"stop",
|
||||
"status",
|
||||
"ip",
|
||||
"delete",
|
||||
"info",
|
||||
"template",
|
||||
],
|
||||
help="Action to perform",
|
||||
)
|
||||
|
||||
|
||||
parser.add_argument(
|
||||
"template_action",
|
||||
nargs="?",
|
||||
choices=["info", "check", "update", "list"],
|
||||
help="Template management action (used with 'template' action)"
|
||||
help="Template management action (used with 'template' action)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
# Create orchestrator
|
||||
orchestrator = ThrillWikiTemplateVMOrchestrator()
|
||||
|
||||
@@ -314,7 +353,12 @@ Examples:
|
||||
|
||||
elif args.action == "create":
|
||||
logger.info("⚙️ Creating VM from template...")
|
||||
success = orchestrator.vm_manager.create_vm_from_template(config.VM_MEMORY, config.VM_VCPUS, config.VM_DISK_SIZE, config.VM_IP)
|
||||
success = orchestrator.vm_manager.create_vm_from_template(
|
||||
config.VM_MEMORY,
|
||||
config.VM_VCPUS,
|
||||
config.VM_DISK_SIZE,
|
||||
config.VM_IP,
|
||||
)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.action == "start":
|
||||
@@ -362,7 +406,7 @@ Examples:
|
||||
|
||||
elif args.action == "template":
|
||||
template_action = args.template_action or "info"
|
||||
|
||||
|
||||
if template_action == "info":
|
||||
logger.info("📋 Template VM Information")
|
||||
info = orchestrator.template_manager.get_template_info()
|
||||
@@ -374,7 +418,7 @@ Examples:
|
||||
else:
|
||||
print("❌ Failed to get template information")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
elif template_action == "check":
|
||||
if orchestrator.template_manager.check_template_exists():
|
||||
logger.info("✅ Template VM disk exists and is ready to use")
|
||||
@@ -382,21 +426,29 @@ Examples:
|
||||
else:
|
||||
logger.error("❌ Template VM disk not found")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
elif template_action == "update":
|
||||
success = orchestrator.template_manager.update_template()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
elif template_action == "list":
|
||||
logger.info("📋 Template-based VM Instances")
|
||||
instances = orchestrator.template_manager.list_template_instances()
|
||||
if instances:
|
||||
for instance in instances:
|
||||
status_emoji = "🟢" if instance["status"] == "running" else "🔴" if instance["status"] == "shut off" else "🟡"
|
||||
print(f"{status_emoji} {instance['name']} ({instance['status']})")
|
||||
status_emoji = (
|
||||
"🟢"
|
||||
if instance["status"] == "running"
|
||||
else "🔴" if instance["status"] == "shut off" else "🟡"
|
||||
)
|
||||
print(
|
||||
f"{status_emoji} {
|
||||
instance['name']} ({
|
||||
instance['status']})"
|
||||
)
|
||||
else:
|
||||
print("No template instances found")
|
||||
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user