fix(discovery): prevent WORDPRESS_ADVANCED_ prefix collision with WORDPRESS_

ProjectManager and SiteManager now skip env vars that belong to a more
specific plugin type (e.g. WORDPRESS_ADVANCED_*) when discovering sites
for a shorter prefix (WORDPRESS_). This prevents ghost projects, ERROR
logs, and false health alerts on startup.

Also downgrades Docker socket errors in WP-CLI to warnings — if Docker
socket is not mounted, WP-CLI features are silently unavailable instead
of logging ERROR and triggering health alerts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
airano
2026-02-18 17:08:34 +03:30
parent 85379ec36c
commit 3b87c01d78
3 changed files with 41 additions and 23 deletions

View File

@@ -61,11 +61,24 @@ class ProjectManager:
""" """
prefix = plugin_type.upper() + "_" prefix = plugin_type.upper() + "_"
# Build list of longer prefixes from other plugin types to avoid collisions.
# e.g. WORDPRESS_ must not match WORDPRESS_ADVANCED_ env vars.
plugin_types = registry.get_registered_types()
longer_prefixes = [
pt.upper() + "_"
for pt in plugin_types
if pt != plugin_type and pt.upper().startswith(plugin_type.upper() + "_")
]
# Find all project IDs for this plugin type # Find all project IDs for this plugin type
project_ids = set() project_ids = set()
env_pattern = re.compile(f"^{prefix}([A-Z0-9_]+?)_(.+)$") env_pattern = re.compile(f"^{prefix}([A-Z0-9_]+?)_(.+)$")
for env_key in os.environ.keys(): for env_key in os.environ.keys():
# Skip env vars that belong to a more specific plugin type
if any(env_key.startswith(lp) for lp in longer_prefixes):
continue
match = env_pattern.match(env_key) match = env_pattern.match(env_key)
if match: if match:
project_id = match.group(1).lower() project_id = match.group(1).lower()

View File

@@ -211,12 +211,27 @@ class SiteManager:
""" """
prefix = plugin_type.upper() + "_" prefix = plugin_type.upper() + "_"
# Build list of longer prefixes from other plugin types to avoid collisions.
# e.g. WORDPRESS_ must not match WORDPRESS_ADVANCED_ env vars.
from plugins import registry as plugin_registry
all_plugin_types = plugin_registry.get_registered_types()
longer_prefixes = [
pt.upper() + "_"
for pt in all_plugin_types
if pt != plugin_type and pt.upper().startswith(plugin_type.upper() + "_")
]
# Pattern to match: WORDPRESS_SITE1_URL, WORDPRESS_SITE2_USERNAME, etc. # Pattern to match: WORDPRESS_SITE1_URL, WORDPRESS_SITE2_USERNAME, etc.
env_pattern = re.compile(f"^{prefix}([A-Z0-9_]+?)_(.+)$") env_pattern = re.compile(f"^{prefix}([A-Z0-9_]+?)_(.+)$")
# Find all unique site IDs # Find all unique site IDs
site_ids = set() site_ids = set()
for env_key in os.environ.keys(): for env_key in os.environ.keys():
# Skip env vars that belong to a more specific plugin type
if any(env_key.startswith(lp) for lp in longer_prefixes):
continue
match = env_pattern.match(env_key) match = env_pattern.match(env_key)
if match: if match:
site_id = match.group(1).lower() site_id = match.group(1).lower()

View File

@@ -10,9 +10,6 @@ Security:
- WP-CLI installation check - WP-CLI installation check
- Timeout protection (30s default) - Timeout protection (30s default)
- Graceful error handling - Graceful error handling
Phase 5.1: Cache Management (4 tools)
Phase 5.2: Database & Plugin/Theme Info (7 tools)
""" """
import asyncio import asyncio
@@ -75,10 +72,7 @@ class WPCLIManager:
Check if the Docker container exists and is running. Check if the Docker container exists and is running.
Returns: Returns:
bool: True if container exists and is running bool: True if container exists and is running, False otherwise
Raises:
Exception: If docker command fails or container not found
""" """
try: try:
# First, test if we have Docker socket access # First, test if we have Docker socket access
@@ -93,15 +87,12 @@ class WPCLIManager:
if test_process.returncode != 0: if test_process.returncode != 0:
error_msg = test_stderr.decode().strip() error_msg = test_stderr.decode().strip()
self.logger.error(f"Cannot access Docker daemon: {error_msg}") self.logger.warning(
raise Exception( f"Docker daemon not accessible for container '{self.container_name}': "
f"Cannot access Docker daemon. This is likely a permissions issue. " f"{error_msg}. WP-CLI features will be unavailable. "
f"Error: {error_msg}. " f"Mount /var/run/docker.sock to enable WP-CLI."
f"Please ensure:\n"
f"1. Docker socket is mounted: /var/run/docker.sock\n"
f"2. User has permission to access Docker (member of docker group)\n"
f"3. Docker daemon is running on the host"
) )
return False
docker_version = test_stdout.decode().strip() docker_version = test_stdout.decode().strip()
self.logger.debug(f"Docker access OK - Server version: {docker_version}") self.logger.debug(f"Docker access OK - Server version: {docker_version}")
@@ -151,15 +142,14 @@ class WPCLIManager:
return True return True
except TimeoutError: except TimeoutError:
self.logger.error("Docker command timed out") self.logger.warning(
raise Exception("Docker command timed out after 5 seconds") f"Docker command timed out for container '{self.container_name}'. "
f"WP-CLI features will be unavailable."
)
return False
except Exception as e: except Exception as e:
# Re-raise exceptions with full context self.logger.warning(f"Docker check failed for '{self.container_name}': {e}")
if "Cannot access Docker" in str(e) or "not found" in str(e): return False
raise
else:
self.logger.error(f"Error checking container: {e}")
raise Exception(f"Failed to check container existence: {str(e)}")
async def _check_wp_cli_available(self) -> bool: async def _check_wp_cli_available(self) -> bool:
""" """