fix(health): resolve "Site not found" for wordpress_advanced health checks

The health monitor's _basic_site_health_check() used split("_", 1) to parse
project IDs like "wordpress_advanced_site1", incorrectly yielding
plugin_type="wordpress" and site_id="advanced_site1". Now uses SiteManager's
list_all_sites() for proper full_id lookup, and creates a temporary plugin
instance for proper health checks (REST API, WP-CLI) instead of basic HTTP.

Also fixes the same split bug in SiteManager.get_effective_path_suffix().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
airano
2026-02-19 03:29:05 +03:30
parent d74594524a
commit cafcd64eff
2 changed files with 57 additions and 29 deletions

View File

@@ -411,9 +411,9 @@ class HealthMonitor:
# Perform health check via plugin instance # Perform health check via plugin instance
health_result = await plugin.health_check() health_result = await plugin.health_check()
elif self.site_manager: elif self.site_manager:
# Fallback: site exists in SiteManager but not ProjectManager # Site exists in SiteManager but not legacy ProjectManager
# (e.g. WooCommerce uses CONSUMER_KEY, not USERNAME/APP_PASSWORD) # Create a temporary plugin instance for a proper health check
health_result = await self._basic_site_health_check(project_id) health_result = await self._site_manager_health_check(project_id)
else: else:
return ProjectHealthStatus( return ProjectHealthStatus(
project_id=project_id, project_id=project_id,
@@ -503,27 +503,62 @@ class HealthMonitor:
alerts=[f"CRITICAL: Health check failed - {error_msg}"], alerts=[f"CRITICAL: Health check failed - {error_msg}"],
) )
async def _basic_site_health_check(self, project_id: str) -> dict[str, Any]: def _find_site_info(self, project_id: str) -> dict[str, Any] | None:
"""Basic HTTP health check for sites not in ProjectManager.""" """Find site info from SiteManager by full_id."""
import aiohttp if not self.site_manager:
return None
for info in self.site_manager.list_all_sites():
if info["full_id"] == project_id:
return info
return None
async def _site_manager_health_check(self, project_id: str) -> dict[str, Any]:
"""
Health check for sites managed by SiteManager (not in legacy ProjectManager).
Creates a temporary plugin instance and calls its health_check() method,
falling back to a basic HTTP check if plugin instantiation fails.
"""
if not self.site_manager: if not self.site_manager:
return {"healthy": False, "message": "SiteManager not available"} return {"healthy": False, "message": "SiteManager not available"}
# Parse plugin_type and site_id from full_id (e.g. "woocommerce_site1") # Look up site info by full_id (handles multi-word plugin types like wordpress_advanced)
parts = project_id.split("_", 1) site_info = self._find_site_info(project_id)
if len(parts) < 2: if not site_info:
return {"healthy": False, "message": f"Invalid project_id format: {project_id}"} return {"healthy": False, "message": f"Site not found: {project_id}"}
plugin_type = parts[0] plugin_type = site_info["plugin_type"]
site_id = parts[1] site_id = site_info["site_id"]
try: try:
config = self.site_manager.get_site_config(plugin_type, site_id) config = self.site_manager.get_site_config(plugin_type, site_id)
except (KeyError, ValueError): except (KeyError, ValueError):
return {"healthy": False, "message": f"Site not found: {project_id}"} return {"healthy": False, "message": f"Site config not found: {project_id}"}
# Try to create a temporary plugin instance for a proper health check
try:
from plugins import registry as plugin_registry
config_dict = config.to_dict()
plugin_instance = plugin_registry.create_instance(
plugin_type, site_id, config_dict
)
return await plugin_instance.health_check()
except Exception as e:
logger.debug(
f"Could not create plugin instance for {project_id}, "
f"falling back to basic HTTP check: {e}"
)
# Fallback: basic HTTP check if plugin instantiation fails
return await self._basic_http_health_check(config.url, project_id)
async def _basic_http_health_check(
self, url: str | None, project_id: str
) -> dict[str, Any]:
"""Basic HTTP health check as a last-resort fallback."""
import aiohttp
url = config.url
if not url: if not url:
return {"healthy": False, "message": "No URL configured for site"} return {"healthy": False, "message": "No URL configured for site"}

View File

@@ -501,22 +501,15 @@ class SiteManager:
>>> suffix = manager.get_effective_path_suffix('wordpress_site1') >>> suffix = manager.get_effective_path_suffix('wordpress_site1')
>>> print(suffix) # 'myblog' or 'wordpress_site1' >>> print(suffix) # 'myblog' or 'wordpress_site1'
""" """
# Parse full_id to get plugin_type and site_id # Look up by full_id from registered sites (handles multi-word plugin types)
parts = full_id.split("_", 1) for info in self.list_all_sites():
if len(parts) != 2: if info["full_id"] == full_id:
return full_id config = self.sites[info["plugin_type"]].get(info["site_id"])
if config and config.alias and config.alias != config.site_id:
return config.alias
return full_id
plugin_type, site_id = parts return full_id
# Try to get config
try:
config = self.get_site_config(plugin_type, site_id)
# If alias exists and is different from site_id, use alias
if config.alias and config.alias != config.site_id:
return config.alias
return full_id
except ValueError:
return full_id
def get_alias_conflicts(self) -> dict[str, list[str]]: def get_alias_conflicts(self) -> dict[str, list[str]]:
""" """