Files
connpy/connpy/services/profile_service.py
T

135 lines
5.8 KiB
Python

from .base import BaseService
from .exceptions import ProfileNotFoundError, ProfileAlreadyExistsError, InvalidConfigurationError
class ProfileService(BaseService):
"""Business logic for node profiles management."""
def list_profiles(self, filter_str=None):
"""List all profile names, optionally filtered."""
profiles = list(self.config.profiles.keys())
case_sensitive = self.config.config.get("case", False)
if filter_str:
if not case_sensitive:
f_str = filter_str.lower()
return [p for p in profiles if f_str in p.lower()]
else:
return [p for p in profiles if filter_str in p]
return profiles
def get_profile(self, name, resolve=True):
"""Get the profile dictionary, optionally resolved."""
profile = self.config.profiles.get(name)
if not profile:
raise ProfileNotFoundError(f"Profile '{name}' not found.")
if resolve:
return self.resolve_node_data(profile)
return profile
def add_profile(self, name, data):
"""Add a new profile."""
if name in self.config.profiles:
raise ProfileAlreadyExistsError(f"Profile '{name}' already exists.")
# Filter data to match _profiles_add signature and ensure id is passed
allowed_keys = {"host", "options", "logs", "password", "port", "protocol", "user", "tags", "jumphost"}
filtered_data = {k: v for k, v in data.items() if k in allowed_keys}
self.config._profiles_add(id=name, **filtered_data)
self.config._saveconfig(self.config.file)
def resolve_node_data(self, node_data):
"""Resolve profile references (@profile) in node data and handle inheritance."""
resolved = node_data.copy()
# 1. Identify all referenced profiles to support inheritance
referenced_profiles = []
for value in resolved.values():
if isinstance(value, str) and value.startswith("@"):
referenced_profiles.append(value[1:])
elif isinstance(value, list):
for item in value:
if isinstance(item, str) and item.startswith("@"):
referenced_profiles.append(item[1:])
# 2. Resolve explicit references
for key, value in resolved.items():
if isinstance(value, str) and value.startswith("@"):
profile_name = value[1:]
try:
profile = self.get_profile(profile_name, resolve=True)
resolved[key] = profile.get(key, "")
except ProfileNotFoundError:
resolved[key] = ""
elif isinstance(value, list):
resolved_list = []
for item in value:
if isinstance(item, str) and item.startswith("@"):
profile_name = item[1:]
try:
profile = self.get_profile(profile_name, resolve=True)
if "password" in profile:
resolved_list.append(profile["password"])
except ProfileNotFoundError:
pass
else:
resolved_list.append(item)
resolved[key] = resolved_list
# 3. Inheritance: Fill empty keys from the first referenced profile
if referenced_profiles:
base_profile_name = referenced_profiles[0]
try:
base_profile = self.get_profile(base_profile_name, resolve=True)
for key, value in base_profile.items():
# Fill if key is missing or empty
if key not in resolved or resolved[key] == "" or resolved[key] == [] or resolved[key] is None:
resolved[key] = value
except ProfileNotFoundError:
pass
# 4. Handle default protocol
if resolved.get("protocol") == "" or resolved.get("protocol") is None:
try:
default_profile = self.get_profile("default", resolve=True)
resolved["protocol"] = default_profile.get("protocol", "ssh")
except ProfileNotFoundError:
resolved["protocol"] = "ssh"
return resolved
def delete_profile(self, name):
"""Delete an existing profile, with safety checks."""
if name not in self.config.profiles:
raise ProfileNotFoundError(f"Profile '{name}' not found.")
if name == "default":
raise InvalidConfigurationError("Cannot delete the 'default' profile.")
used_by = self.config._profileused(name)
if used_by:
# We return the list of nodes using it so the UI can inform the user
raise InvalidConfigurationError(f"Profile '{name}' is used by nodes: {', '.join(used_by)}")
self.config._profiles_del(id=name)
self.config._saveconfig(self.config.file)
def update_profile(self, name, data):
"""Update an existing profile."""
if name not in self.config.profiles:
raise ProfileNotFoundError(f"Profile '{name}' not found.")
# Merge with existing data
existing = self.get_profile(name, resolve=False)
updated_data = existing.copy()
updated_data.update(data)
# Filter data to match _profiles_add signature
allowed_keys = {"host", "options", "logs", "password", "port", "protocol", "user", "tags", "jumphost"}
filtered_data = {k: v for k, v in updated_data.items() if k in allowed_keys}
self.config._profiles_add(id=name, **filtered_data)
self.config._saveconfig(self.config.file)