Skip to content

Commit 1eb86e4

Browse files
author
LittleCoinCoin
committed
fix(host): multi-environment mcp configuration conflict resolution
- Add _cleanup_package_host_conflicts method to detect and remove conflicting configurations - Add _update_target_environment_configuration method for atomic target updates - Enhance update_package_host_configuration to enforce single-environment ownership constraint - Implement automatic conflict cleanup when environments configure same package-host combination - Add user notification when configurations are transferred between environments - Maintain data integrity by ensuring only one environment controls each package-host pair This resolves the critical design flaw where multiple environments could claim ownership of the same package-host configuration, leading to inconsistent metadata and unreliable system state for API consumers.
1 parent 945f66b commit 1eb86e4

File tree

1 file changed

+107
-23
lines changed

1 file changed

+107
-23
lines changed

hatch/environment_manager.py

Lines changed: 107 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,9 @@ def update_package_host_configuration(self, env_name: str, package_name: str,
652652
hostname: str, server_config: dict) -> bool:
653653
"""Update package metadata with host configuration tracking.
654654
655+
Enforces constraint: Only one environment can control a package-host combination.
656+
Automatically cleans up conflicting configurations from other environments.
657+
655658
Args:
656659
env_name (str): Environment name
657660
package_name (str): Package name
@@ -666,37 +669,118 @@ def update_package_host_configuration(self, env_name: str, package_name: str,
666669
self.logger.error(f"Environment {env_name} does not exist")
667670
return False
668671

669-
# Find the package in the environment
670-
packages = self._environments[env_name].get("packages", [])
671-
for i, pkg in enumerate(packages):
672-
if pkg.get("name") == package_name:
673-
# Initialize configured_hosts if it doesn't exist
674-
if "configured_hosts" not in pkg:
675-
pkg["configured_hosts"] = {}
676-
677-
# Add or update host configuration
678-
from datetime import datetime
679-
pkg["configured_hosts"][hostname] = {
680-
"config_path": self._get_host_config_path(hostname),
681-
"configured_at": datetime.now().isoformat(),
682-
"last_synced": datetime.now().isoformat(),
683-
"server_config": server_config
684-
}
672+
# Step 1: Clean up conflicting configurations from other environments
673+
conflicts_removed = self._cleanup_package_host_conflicts(
674+
target_env=env_name,
675+
package_name=package_name,
676+
hostname=hostname
677+
)
685678

686-
# Update the package in the environment
687-
self._environments[env_name]["packages"][i] = pkg
688-
self._save_environments()
679+
# Step 2: Update target environment configuration
680+
success = self._update_target_environment_configuration(
681+
env_name, package_name, hostname, server_config
682+
)
689683

690-
self.logger.info(f"Updated host configuration for package {package_name} on {hostname}")
691-
return True
684+
# Step 3: User notification for conflict resolution
685+
if conflicts_removed > 0 and success:
686+
self.logger.warning(
687+
f"Package '{package_name}' host configuration for '{hostname}' "
688+
f"transferred from {conflicts_removed} other environment(s) to '{env_name}'"
689+
)
692690

693-
self.logger.error(f"Package {package_name} not found in environment {env_name}")
694-
return False
691+
return success
695692

696693
except Exception as e:
697694
self.logger.error(f"Failed to update package host configuration: {e}")
698695
return False
699696

697+
def _cleanup_package_host_conflicts(self, target_env: str, package_name: str, hostname: str) -> int:
698+
"""Remove conflicting package-host configurations from other environments.
699+
700+
This method enforces the constraint that only one environment can control
701+
a package-host combination by removing conflicting configurations from
702+
all environments except the target environment.
703+
704+
Args:
705+
target_env (str): Environment that should control the configuration
706+
package_name (str): Package name
707+
hostname (str): Host identifier
708+
709+
Returns:
710+
int: Number of conflicting configurations removed
711+
"""
712+
conflicts_removed = 0
713+
714+
for env_name, env_data in self._environments.items():
715+
if env_name == target_env:
716+
continue # Skip target environment
717+
718+
packages = env_data.get("packages", [])
719+
for i, pkg in enumerate(packages):
720+
if pkg.get("name") == package_name:
721+
configured_hosts = pkg.get("configured_hosts", {})
722+
if hostname in configured_hosts:
723+
# Remove the conflicting host configuration
724+
del configured_hosts[hostname]
725+
conflicts_removed += 1
726+
727+
# Update package metadata
728+
pkg["configured_hosts"] = configured_hosts
729+
self._environments[env_name]["packages"][i] = pkg
730+
731+
self.logger.info(
732+
f"Removed conflicting '{hostname}' configuration for package '{package_name}' "
733+
f"from environment '{env_name}'"
734+
)
735+
736+
if conflicts_removed > 0:
737+
self._save_environments()
738+
739+
return conflicts_removed
740+
741+
def _update_target_environment_configuration(self, env_name: str, package_name: str,
742+
hostname: str, server_config: dict) -> bool:
743+
"""Update the target environment's package host configuration.
744+
745+
This method handles the actual configuration update for the target environment
746+
after conflicts have been cleaned up.
747+
748+
Args:
749+
env_name (str): Environment name
750+
package_name (str): Package name
751+
hostname (str): Host identifier
752+
server_config (dict): Server configuration data
753+
754+
Returns:
755+
bool: True if update successful, False otherwise
756+
"""
757+
# Find the package in the environment
758+
packages = self._environments[env_name].get("packages", [])
759+
for i, pkg in enumerate(packages):
760+
if pkg.get("name") == package_name:
761+
# Initialize configured_hosts if it doesn't exist
762+
if "configured_hosts" not in pkg:
763+
pkg["configured_hosts"] = {}
764+
765+
# Add or update host configuration
766+
from datetime import datetime
767+
pkg["configured_hosts"][hostname] = {
768+
"config_path": self._get_host_config_path(hostname),
769+
"configured_at": datetime.now().isoformat(),
770+
"last_synced": datetime.now().isoformat(),
771+
"server_config": server_config
772+
}
773+
774+
# Update the package in the environment
775+
self._environments[env_name]["packages"][i] = pkg
776+
self._save_environments()
777+
778+
self.logger.info(f"Updated host configuration for package {package_name} on {hostname}")
779+
return True
780+
781+
self.logger.error(f"Package {package_name} not found in environment {env_name}")
782+
return False
783+
700784
def remove_package_host_configuration(self, env_name: str, package_name: str, hostname: str) -> bool:
701785
"""Remove host configuration tracking for a specific package.
702786

0 commit comments

Comments
 (0)