Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions autobot-slm-backend/api/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ async def create_node(
node = Node(
node_id=node_id,
hostname=node_data.hostname,
ansible_name=node_data.ansible_name, # Issue #1814
ip_address=node_data.ip_address,
roles=node_data.roles,
ssh_user=node_data.ssh_user,
Expand Down Expand Up @@ -1395,6 +1396,7 @@ async def replace_node(
new_node = Node(
node_id=new_node_id,
hostname=node_data.hostname,
ansible_name=node_data.ansible_name, # Issue #1814
ip_address=node_data.ip_address,
roles=node_data.roles,
ssh_user=node_data.ssh_user,
Expand Down
5 changes: 3 additions & 2 deletions autobot-slm-backend/api/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,10 @@ async def scan_node_services(
from services.playbook_executor import get_playbook_executor

executor = get_playbook_executor()
ansible_target = node.ansible_target # #1814
result = await executor.execute_playbook(
playbook_name="discover-services.yml",
limit=[node.hostname],
limit=[ansible_target],
)

if not result.get("success"):
Expand All @@ -388,7 +389,7 @@ async def scan_node_services(
# Extract service facts from Ansible result
# service_facts returns ansible_facts.services as a dict
services_data = (
result.get("facts", {}).get(node.hostname, {}).get("services", {})
result.get("facts", {}).get(ansible_target, {}).get("services", {})
)

if not services_data:
Expand Down
3 changes: 2 additions & 1 deletion autobot-slm-backend/api/setup_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ async def _generate_dynamic_inventory(
}
if node.ssh_port and node.ssh_port != 22:
host_vars["ansible_port"] = node.ssh_port
hosts[node.hostname] = host_vars
inventory_name = node.ansible_target # #1814
hosts[inventory_name] = host_vars
node_id_to_hostname[node.node_id] = node.hostname
node_id_to_ip[node.node_id] = node.ip_address

Expand Down
3 changes: 2 additions & 1 deletion autobot-slm-backend/api/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -1017,9 +1017,10 @@ async def _execute_cert_deployment(
if chain_path:
extra_vars["chain_file"] = chain_path

ansible_target = node.ansible_target # #1814
result = await executor.execute_playbook(
playbook_name="deploy-certificate.yml",
limit=[node.hostname],
limit=[ansible_target],
extra_vars=extra_vars,
)

Expand Down
29 changes: 29 additions & 0 deletions autobot-slm-backend/migrations/add_node_ansible_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# AutoBot - AI-Powered Automation Platform
# Copyright (c) 2025 mrveiss
# Author: mrveiss
"""
Migration: Add ansible_name column to nodes table (#1814).

The hostname column stores user-facing display names (e.g., '00-SLM-Manager')
which are unsuitable for Ansible --limit and SSH operations. This adds an
explicit ansible_name column for Ansible inventory host targeting, with
ip_address fallback for nodes where it is not set.
"""

import logging

from migrations.utils import add_column_if_not_exists, get_connection

logger = logging.getLogger(__name__)


def migrate(db_url: str) -> None:
"""Add ansible_name column to nodes table (#1814)."""
conn = get_connection(db_url)
cursor = conn.cursor()

add_column_if_not_exists(cursor, "nodes", "ansible_name", "VARCHAR(255)")

conn.commit()
conn.close()
logger.info("Migration: added ansible_name column to nodes")
2 changes: 2 additions & 0 deletions autobot-slm-backend/migrations/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
# Issue #1900: consolidate slm_users (integer PK) into users (UUID PK)
# and drop the now-orphaned slm_users table.
"consolidate_slm_users_to_uuid",
# Issue #1814: add ansible_name column for proper Ansible targeting
"add_node_ansible_name",
]


Expand Down
6 changes: 6 additions & 0 deletions autobot-slm-backend/models/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class Node(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
node_id = Column(String(64), unique=True, nullable=False, index=True)
hostname = Column(String(255), nullable=False)
ansible_name = Column(String(255), nullable=True) # Issue #1814
ip_address = Column(String(45), nullable=False)
status = Column(String(20), default=NodeStatus.PENDING.value)
roles = Column(JSON, default=list)
Expand Down Expand Up @@ -129,6 +130,11 @@ class Node(Base):
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

@property
def ansible_target(self) -> str:
"""Best identifier for Ansible --limit targeting (#1814)."""
return self.ansible_name or self.ip_address


class Deployment(Base):
"""Deployment model for tracking role deployments."""
Expand Down
3 changes: 3 additions & 0 deletions autobot-slm-backend/models/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class NodeCreate(BaseModel):
"""Node registration request."""

hostname: str
ansible_name: Optional[str] = None # Ansible inventory name (#1814)
ip_address: str
node_id: Optional[
str
Expand All @@ -153,6 +154,7 @@ class NodeUpdate(BaseModel):
"""Node update request."""

hostname: Optional[str] = None
ansible_name: Optional[str] = None # Ansible inventory name (#1814)
ip_address: Optional[str] = None
status: Optional[NodeStatus] = None
roles: Optional[List[str]] = None
Expand All @@ -164,6 +166,7 @@ class NodeResponse(BaseModel):
id: int
node_id: str
hostname: str
ansible_name: Optional[str] = None # Ansible inventory name (#1814)
ip_address: str
status: str
roles: Optional[List[str]] = []
Expand Down
13 changes: 7 additions & 6 deletions autobot-slm-backend/services/reconciler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from datetime import datetime, timedelta
from typing import Dict, List, Optional

from config import settings
from models.database import (
Deployment,
DeploymentStatus,
Expand All @@ -33,8 +34,6 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from config import settings

logger = logging.getLogger(__name__)

# Role to systemd service mapping
Expand Down Expand Up @@ -471,9 +470,10 @@ async def _remediate_node(self, db: AsyncSession, node: Node) -> bool:
message=f"Attempting to restart SLM agent on {node.hostname}",
)

# Try to restart the SLM agent via Ansible
# Try to restart the SLM agent via Ansible (#1814: prefer ansible_name)
ansible_target = node.ansible_target
success = await self._restart_service_via_ansible(
node.hostname,
ansible_target,
"slm-agent",
)

Expand Down Expand Up @@ -757,9 +757,10 @@ async def _remediate_failed_service(
message=f"Attempting to restart {service.service_name} on {node.hostname}",
)

# Try to restart via Ansible
# Try to restart via Ansible (#1814: prefer ansible_name)
ansible_target = node.ansible_target
success = await self._restart_service_via_ansible(
node.hostname,
ansible_target,
service.service_name,
)

Expand Down
Loading