mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-17 08:05:09 +00:00
47cc79dcf7
Five confirmed findings from an adversarial review of the branch, four of them regressions introduced by it. Fall through to the next source when a stored URL cannot be resolved. Stopping at the first non-empty candidate emitted a backend section with no `server` line: the section exists so `haproxy -c` passes and Apply succeeds, then every challenge request 503s from an empty backend with nothing to show for it. Scheme-less values are common — the settings field was free text until this branch — so this was reachable on real installs. Selection moved into `select_acme_backend_source()` so it is testable and the skipped candidates are logged rather than silently dropped. Report a challenge backend with no server line. `extract_acme_backend_target` returns None for that section, and the loopback filter skipped falsy targets, so the case above would have been reported as "challenge route present in applied config" — the new check confirming the very state it exists to catch. Do not narrow the row set feeding the routing check's `fail` branch. Adding a mode filter to the WHERE clause turned a tcp-only port-80 cluster from "ok" into "fail", and the site wizard blocks submit on any failing check, so those installs would have been locked on upgrade day. Mode is now examined in Python and only downgrades to `warn`, using an expression that is character-for- character the renderer's normalisation. Match the agent's config selector. The applied-config lookup omitted `is_active = TRUE`, so it could read a superseded row and report on a config the nodes never received. Extraction now happens in SQL rather than pulling whole configs — these run to hundreds of KB. Select `acme_backend_url` when loading the existing cluster. It was absent, so the entity snapshot recorded old_values as NULL unconditionally and rejecting the pending version wiped the operator's per-cluster URL back to the global loopback default — re-creating the exact failure this branch removes. Also carry `acme_enabled` and `acme_backend_url` through cluster creation. The create model declared neither and the INSERT wrote neither, so a cluster created with ACME switched on came back switched off with no error shown. Refuted and deliberately not changed: settings PUT re-validating a stored loopback value (it validates only what is submitted), an apply-path connection leak (the 422 propagates to a handler that closes it), and the modal discarding backend rejection reasons (the envelope matches).
94 lines
3.3 KiB
Python
94 lines
3.3 KiB
Python
from pydantic import BaseModel, field_validator
|
|
from typing import Optional, List
|
|
|
|
from utils.acme_backend_url import AcmeBackendUrlError, validate_acme_backend_url
|
|
|
|
|
|
def _validated_acme_backend_url(value: Optional[str]) -> Optional[str]:
|
|
"""Shared field validator body for `acme_backend_url`.
|
|
|
|
Pydantic turns the raised ValueError into a 422 with this message attached, so
|
|
the operator sees why the value was refused instead of discovering months later
|
|
that HTTP-01 never worked. Returns the normalised value — callers persist THIS,
|
|
not the raw input, so surrounding whitespace never reaches haproxy.cfg.
|
|
"""
|
|
try:
|
|
return validate_acme_backend_url(value)
|
|
except AcmeBackendUrlError as exc:
|
|
raise ValueError(str(exc)) from None
|
|
|
|
class HAProxyClusterCreate(BaseModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
connection_type: str = "agent" # Only "agent" supported now
|
|
stats_socket_path: str = "/run/haproxy/admin.sock"
|
|
haproxy_config_path: str = "/etc/haproxy/haproxy.cfg"
|
|
haproxy_bin_path: str = "/usr/sbin/haproxy" # HAProxy binary path
|
|
keepalived_config_path: str = "/etc/keepalived/keepalived.conf" # HA/VIP: keepalived.conf path (Issue #27)
|
|
pool_id: Optional[int] = None # Which pool this cluster belongs to
|
|
# The create form submits both of these. Until they were declared here pydantic
|
|
# dropped them and the INSERT never carried them, so a cluster created with ACME
|
|
# switched on came back switched off with no error shown — the same silent-success
|
|
# failure this work exists to remove.
|
|
acme_enabled: Optional[bool] = None
|
|
acme_backend_url: Optional[str] = None
|
|
|
|
_validate_acme_backend_url = field_validator("acme_backend_url")(
|
|
_validated_acme_backend_url
|
|
)
|
|
|
|
class HAProxyClusterUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
connection_type: Optional[str] = None
|
|
stats_socket_path: Optional[str] = None
|
|
haproxy_config_path: Optional[str] = None
|
|
haproxy_bin_path: Optional[str] = None
|
|
keepalived_config_path: Optional[str] = None
|
|
pool_id: Optional[int] = None
|
|
is_active: Optional[bool] = None
|
|
acme_enabled: Optional[bool] = None
|
|
acme_backend_url: Optional[str] = None
|
|
|
|
_validate_acme_backend_url = field_validator("acme_backend_url")(
|
|
_validated_acme_backend_url
|
|
)
|
|
|
|
class HAProxyClusterResponse(BaseModel):
|
|
id: int
|
|
name: str
|
|
description: Optional[str] = None
|
|
|
|
# Installation options
|
|
installation_type: str
|
|
deployment_type: str
|
|
|
|
# Connection details
|
|
host: str
|
|
port: int
|
|
connection_type: str
|
|
|
|
# Status info
|
|
is_active: bool
|
|
is_default: bool
|
|
last_connected_at: Optional[str] = None
|
|
connection_status: str
|
|
connection_error: Optional[str] = None
|
|
haproxy_version: Optional[str] = None
|
|
created_at: str
|
|
|
|
class HAProxyGlobalConfig(BaseModel):
|
|
max_connections: int = 4096
|
|
timeout_connect: int = 10000
|
|
timeout_client: int = 60000
|
|
timeout_server: int = 60000
|
|
log_level: str = "info"
|
|
stats_enabled: bool = True
|
|
stats_uri: str = "/stats"
|
|
stats_port: int = 8404
|
|
|
|
class ConfigVersion(BaseModel):
|
|
version_name: str
|
|
description: Optional[str] = None
|
|
config_content: Optional[str] = None
|
|
cluster_id: Optional[int] = None |