Skip to content

Commit cb5eb87

Browse files
committed
feat(status): Add web URL to status and fix port management
Add web URL display in status message for easier access to Concourse web interface. Units now show the http://IP:PORT in their status to improve usability. Fix port leak when web-port config changes. The _manage_web_port() method now closes previously opened ports before opening the new one, ensuring clean configuration transitions without leaving stale ports open. Implementation adds two new methods: - _get_web_url(): Constructs the web URL from unit IP and configured port - _manage_web_port(): Closes old ports and opens the desired port to handle config changes Both web-only and auto-scaling modes updated to use these methods. Tested with bidirectional port changes (8080↔9090) and verified URLs display correctly in unit status.
1 parent 59911d4 commit cb5eb87

File tree

1 file changed

+75
-14
lines changed

1 file changed

+75
-14
lines changed

src/charm.py

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,14 +1722,16 @@ def _update_status(self):
17221722
version_info = f" (v{installed_version})" if installed_version else ""
17231723

17241724
if mode == "web":
1725-
# Open the web port for external access
1725+
# Manage web port (close old, open new)
17261726
web_port = self.config.get("web-port", 8080)
1727-
try:
1728-
self.unit.open_port("tcp", web_port)
1729-
logger.info(f"Opened port {web_port}/tcp")
1730-
except Exception as e:
1731-
logger.warning(f"Failed to open port {web_port}: {e}")
1732-
self.unit.status = ActiveStatus(f"Web server ready{version_info}")
1727+
self._manage_web_port(web_port)
1728+
1729+
# Get web URL for status message
1730+
web_url = self._get_web_url()
1731+
url_info = f" {web_url}" if web_url else ""
1732+
self.unit.status = ActiveStatus(
1733+
f"Web server ready{version_info}{url_info}"
1734+
)
17331735
elif mode == "worker":
17341736
gpu_status = self.worker_helper.get_gpu_status_message()
17351737
_, discovery_status = self._check_folder_discovery_status()
@@ -1739,15 +1741,15 @@ def _update_status(self):
17391741
else:
17401742
gpu_status = self.worker_helper.get_gpu_status_message()
17411743
_, discovery_status = self._check_folder_discovery_status()
1742-
# Open web port when running both
1744+
# Manage web port when running both (close old, open new)
17431745
web_port = self.config.get("web-port", 8080)
1744-
try:
1745-
self.unit.open_port("tcp", web_port)
1746-
logger.info(f"Opened port {web_port}/tcp")
1747-
except Exception as e:
1748-
logger.warning(f"Failed to open port {web_port}: {e}")
1746+
self._manage_web_port(web_port)
1747+
1748+
# Get web URL for status message
1749+
web_url = self._get_web_url()
1750+
url_info = f" {web_url}" if web_url else ""
17491751
self.unit.status = ActiveStatus(
1750-
f"Ready{version_info}{gpu_status}{discovery_status}"
1752+
f"Ready{version_info}{url_info}{gpu_status}{discovery_status}"
17511753
)
17521754

17531755
except Exception as e:
@@ -1789,6 +1791,65 @@ def _get_installed_concourse_version(self) -> Optional[str]:
17891791
logger.debug(f"Could not detect installed version: {e}")
17901792
return None
17911793

1794+
def _get_web_url(self) -> Optional[str]:
1795+
"""Get the web server URL (http://IP:PORT) for status display"""
1796+
try:
1797+
# Get the unit's IP address from network binding
1798+
# Try multiple bindings in order of preference
1799+
binding = None
1800+
for binding_name in ["tsa", "peers", ""]:
1801+
try:
1802+
binding = self.model.get_binding(binding_name)
1803+
if binding and binding.network and binding.network.bind_address:
1804+
break
1805+
except Exception:
1806+
continue
1807+
1808+
if not binding or not binding.network or not binding.network.bind_address:
1809+
logger.debug("Could not determine unit IP address for web URL")
1810+
return None
1811+
1812+
unit_ip = str(binding.network.bind_address)
1813+
web_port = self.config.get("web-port", 8080)
1814+
1815+
return f"http://{unit_ip}:{web_port}"
1816+
except Exception as e:
1817+
logger.debug(f"Could not construct web URL: {e}")
1818+
return None
1819+
1820+
def _manage_web_port(self, desired_port: int):
1821+
"""
1822+
Manage web server port - close old ports and open the desired port.
1823+
1824+
This ensures only the configured web port is open, closing any
1825+
previously opened ports to handle configuration changes cleanly.
1826+
1827+
Args:
1828+
desired_port: The port number to open for the web server
1829+
"""
1830+
try:
1831+
# Get currently opened TCP ports
1832+
opened_ports = self.unit.opened_ports()
1833+
1834+
# Find TCP ports that should be closed (all TCP ports except the desired one)
1835+
for port_obj in opened_ports:
1836+
if port_obj.protocol == "tcp" and port_obj.port != desired_port:
1837+
try:
1838+
self.unit.close_port("tcp", port_obj.port)
1839+
logger.info(f"Closed old port {port_obj.port}/tcp")
1840+
except Exception as e:
1841+
logger.warning(f"Failed to close port {port_obj.port}/tcp: {e}")
1842+
1843+
# Open the desired port
1844+
try:
1845+
self.unit.open_port("tcp", desired_port)
1846+
logger.info(f"Opened port {desired_port}/tcp")
1847+
except Exception as e:
1848+
logger.warning(f"Failed to open port {desired_port}/tcp: {e}")
1849+
1850+
except Exception as e:
1851+
logger.error(f"Port management failed: {e}", exc_info=True)
1852+
17921853
def _orchestrate_coordinated_upgrade(self, event, version: str):
17931854
"""Orchestrate coordinated upgrade across multiple units (T045)"""
17941855
try:

0 commit comments

Comments
 (0)