mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
fix(monitor): keep Docker web links and schemes accurate
- Reuse the Find snapshot for web links only after a complete search - Skip unassigned or ranged host ports instead of dropping the container - Prefill the https scheme of published HTTPS ports in the editor - Run the Docker discovery tests when they change - Translate the Docker search texts
This commit is contained in:
@@ -13,6 +13,8 @@ on:
|
||||
- 'AppImage/scripts/*.py'
|
||||
- 'AppImage/scripts/tests/test_health_persistence_recurrence.py'
|
||||
- 'AppImage/scripts/tests/test_native_app_discovery.py'
|
||||
- 'AppImage/scripts/tests/test_docker_app_discovery.py'
|
||||
- 'AppImage/scripts/tests/fixtures/docker_discovery_inspect.json'
|
||||
- 'AppImage/app/**'
|
||||
- 'AppImage/components/**'
|
||||
- 'AppImage/hooks/**'
|
||||
@@ -34,6 +36,8 @@ on:
|
||||
- 'AppImage/scripts/*.py'
|
||||
- 'AppImage/scripts/tests/test_health_persistence_recurrence.py'
|
||||
- 'AppImage/scripts/tests/test_native_app_discovery.py'
|
||||
- 'AppImage/scripts/tests/test_docker_app_discovery.py'
|
||||
- 'AppImage/scripts/tests/fixtures/docker_discovery_inspect.json'
|
||||
- 'AppImage/app/**'
|
||||
- 'AppImage/components/**'
|
||||
- 'AppImage/hooks/**'
|
||||
|
||||
@@ -1748,7 +1748,14 @@
|
||||
"nativeScanPartial": "Die Anwendungssuche ist unvollständig. Prüfen Sie die gefundenen Hinweise und versuchen Sie es erneut.",
|
||||
"nativeVersionUnknown": "Installierte Version unbekannt",
|
||||
"nativeEvidenceStale": "Die Hinweise sind möglicherweise veraltet oder unvollständig.",
|
||||
"nativeRegisterOnly": "Bestätigen Sie die Registrierung im Editor. Eine bekannte Version wird als manueller Wert gespeichert; es wird kein Updater eingerichtet."
|
||||
"nativeRegisterOnly": "Bestätigen Sie die Registrierung im Editor. Eine bekannte Version wird als manueller Wert gespeichert; es wird kein Updater eingerichtet.",
|
||||
"dockerScanFailed": "Die Docker-Suche konnte nicht abgeschlossen werden. Frühere Ergebnisse sind möglicherweise veraltet; versuchen Sie es erneut.",
|
||||
"dockerScanPartial": "Die Docker-Suche ist unvollständig. Einige Container konnten nicht geprüft werden.",
|
||||
"dockerEvidenceContainer": "Docker-Container",
|
||||
"dockerEvidenceImage": "Image",
|
||||
"dockerEvidencePort": "Veröffentlichter TCP-Port",
|
||||
"dockerEvidenceProject": "Compose-Projekt",
|
||||
"dockerEndpointReview": "Container mit veröffentlichten TCP-Ports. Bestätigen Sie Namen und Webadresse vor dem Speichern. Die Registrierung richtet keine Updates ein."
|
||||
},
|
||||
"statusFilter": {
|
||||
"ariaLabel": "Virtuelle Maschinen und Container filtern",
|
||||
|
||||
@@ -1734,6 +1734,13 @@
|
||||
"nativeVersionUnknown": "Versión instalada desconocida",
|
||||
"nativeEvidenceStale": "Lo detectado puede estar desactualizado o incompleto.",
|
||||
"nativeRegisterOnly": "Confirma el registro en el editor. Una versión conocida se guarda como valor manual; no se configura ningún actualizador.",
|
||||
"dockerScanFailed": "La búsqueda en Docker no pudo terminar. Los resultados anteriores pueden estar desactualizados; vuelve a intentarlo.",
|
||||
"dockerScanPartial": "La búsqueda en Docker está incompleta. No se pudieron revisar algunos contenedores.",
|
||||
"dockerEvidenceContainer": "Contenedor Docker",
|
||||
"dockerEvidenceImage": "Imagen",
|
||||
"dockerEvidencePort": "Puerto TCP publicado",
|
||||
"dockerEvidenceProject": "Proyecto Compose",
|
||||
"dockerEndpointReview": "Contenedor con puertos TCP publicados. Confirma el nombre y la dirección web antes de guardar. El registro no configura actualizaciones.",
|
||||
"noNewApplicationsDetected": "No se han detectado aplicaciones nuevas.",
|
||||
"oneNewApplicationDetected": "Se ha detectado una aplicación nueva.",
|
||||
"newApplicationsDetected": "Se han detectado {count} aplicaciones nuevas.",
|
||||
|
||||
@@ -1748,7 +1748,14 @@
|
||||
"nativeScanPartial": "La recherche d'applications est incomplète. Vérifiez les éléments trouvés et réessayez.",
|
||||
"nativeVersionUnknown": "Version installée inconnue",
|
||||
"nativeEvidenceStale": "Les éléments trouvés peuvent être obsolètes ou incomplets.",
|
||||
"nativeRegisterOnly": "Confirmez l'enregistrement dans l'éditeur. Une version connue est enregistrée comme valeur manuelle ; aucun outil de mise à jour n'est configuré."
|
||||
"nativeRegisterOnly": "Confirmez l'enregistrement dans l'éditeur. Une version connue est enregistrée comme valeur manuelle ; aucun outil de mise à jour n'est configuré.",
|
||||
"dockerScanFailed": "La recherche Docker n'a pas pu se terminer. Les résultats précédents peuvent être obsolètes ; réessayez.",
|
||||
"dockerScanPartial": "La recherche Docker est incomplète. Certains conteneurs n'ont pas pu être vérifiés.",
|
||||
"dockerEvidenceContainer": "Conteneur Docker",
|
||||
"dockerEvidenceImage": "Image",
|
||||
"dockerEvidencePort": "Port TCP publié",
|
||||
"dockerEvidenceProject": "Projet Compose",
|
||||
"dockerEndpointReview": "Conteneur avec des ports TCP publiés. Confirmez le nom et l'adresse web avant d'enregistrer. L'enregistrement ne configure pas les mises à jour."
|
||||
},
|
||||
"statusFilter": {
|
||||
"ariaLabel": "Filtrer les machines virtuelles et les conteneurs",
|
||||
|
||||
@@ -1748,7 +1748,14 @@
|
||||
"nativeScanPartial": "La ricerca delle applicazioni è incompleta. Controlla gli elementi trovati e riprova.",
|
||||
"nativeVersionUnknown": "Versione installata sconosciuta",
|
||||
"nativeEvidenceStale": "Gli elementi trovati potrebbero non essere aggiornati o essere incompleti.",
|
||||
"nativeRegisterOnly": "Conferma la registrazione nell'editor. Una versione nota viene salvata come valore manuale; non viene configurato alcun aggiornamento automatico."
|
||||
"nativeRegisterOnly": "Conferma la registrazione nell'editor. Una versione nota viene salvata come valore manuale; non viene configurato alcun aggiornamento automatico.",
|
||||
"dockerScanFailed": "La ricerca Docker non è stata completata. I risultati precedenti potrebbero non essere aggiornati; riprova.",
|
||||
"dockerScanPartial": "La ricerca Docker è incompleta. Non è stato possibile controllare alcuni container.",
|
||||
"dockerEvidenceContainer": "Container Docker",
|
||||
"dockerEvidenceImage": "Immagine",
|
||||
"dockerEvidencePort": "Porta TCP pubblicata",
|
||||
"dockerEvidenceProject": "Progetto Compose",
|
||||
"dockerEndpointReview": "Container con porte TCP pubblicate. Conferma il nome e l'indirizzo web prima di salvare. La registrazione non configura gli aggiornamenti."
|
||||
},
|
||||
"statusFilter": {
|
||||
"ariaLabel": "Filtra macchine virtuali e container",
|
||||
|
||||
@@ -1748,7 +1748,14 @@
|
||||
"nativeScanPartial": "A pesquisa de aplicativos está incompleta. Revise os itens encontrados e tente novamente.",
|
||||
"nativeVersionUnknown": "Versão instalada desconhecida",
|
||||
"nativeEvidenceStale": "Os itens encontrados podem estar desatualizados ou incompletos.",
|
||||
"nativeRegisterOnly": "Confirme o registro no editor. Uma versão conhecida é salva como valor manual; nenhum atualizador é configurado."
|
||||
"nativeRegisterOnly": "Confirme o registro no editor. Uma versão conhecida é salva como valor manual; nenhum atualizador é configurado.",
|
||||
"dockerScanFailed": "A pesquisa no Docker não pôde ser concluída. Os resultados anteriores podem estar desatualizados; tente novamente.",
|
||||
"dockerScanPartial": "A pesquisa no Docker está incompleta. Não foi possível verificar alguns contentores.",
|
||||
"dockerEvidenceContainer": "Contentor Docker",
|
||||
"dockerEvidenceImage": "Imagem",
|
||||
"dockerEvidencePort": "Porta TCP publicada",
|
||||
"dockerEvidenceProject": "Projeto Compose",
|
||||
"dockerEndpointReview": "Contentor com portas TCP publicadas. Confirme o nome e o endereço web antes de guardar. O registo não configura atualizações."
|
||||
},
|
||||
"statusFilter": {
|
||||
"ariaLabel": "Filtrar máquinas virtuais e contêineres",
|
||||
|
||||
@@ -1748,7 +1748,14 @@
|
||||
"nativeScanPartial": "Programsökningen är ofullständig. Granska det som hittades och försök igen.",
|
||||
"nativeVersionUnknown": "Installerad version okänd",
|
||||
"nativeEvidenceStale": "Det som hittades kan vara inaktuellt eller ofullständigt.",
|
||||
"nativeRegisterOnly": "Bekräfta registreringen i redigeraren. En känd version sparas som ett manuellt värde; ingen uppdatering konfigureras."
|
||||
"nativeRegisterOnly": "Bekräfta registreringen i redigeraren. En känd version sparas som ett manuellt värde; ingen uppdatering konfigureras.",
|
||||
"dockerScanFailed": "Docker-sökningen kunde inte slutföras. Tidigare resultat kan vara inaktuella; försök igen.",
|
||||
"dockerScanPartial": "Docker-sökningen är ofullständig. Vissa containrar kunde inte kontrolleras.",
|
||||
"dockerEvidenceContainer": "Docker-container",
|
||||
"dockerEvidenceImage": "Avbild",
|
||||
"dockerEvidencePort": "Publicerad TCP-port",
|
||||
"dockerEvidenceProject": "Compose-projekt",
|
||||
"dockerEndpointReview": "Container med publicerade TCP-portar. Bekräfta namnet och webbadressen innan du sparar. Registreringen konfigurerar inga uppdateringar."
|
||||
},
|
||||
"statusFilter": {
|
||||
"ariaLabel": "Filtrera virtuella maskiner och containrar",
|
||||
|
||||
@@ -116,18 +116,19 @@ def public_ports(record):
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError('invalid binding')
|
||||
address = value.get('HostIp') or '0.0.0.0'
|
||||
raw = value.get('HostPort')
|
||||
# An unassigned ("") or ranged host port is not a single address.
|
||||
if not isinstance(raw, str) or not raw.isdecimal():
|
||||
continue
|
||||
try:
|
||||
ip = ipaddress.ip_address(address)
|
||||
if ip.is_loopback or getattr(ip, 'ipv4_mapped', None) and ip.ipv4_mapped.is_loopback:
|
||||
continue
|
||||
raw = value.get('HostPort')
|
||||
if not isinstance(raw, str) or not raw.isdecimal():
|
||||
raise ValueError('invalid port')
|
||||
outer = int(raw)
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError('invalid binding') from None
|
||||
continue
|
||||
if ip.is_loopback or getattr(ip, 'ipv4_mapped', None) and ip.ipv4_mapped.is_loopback:
|
||||
continue
|
||||
outer = int(raw)
|
||||
if not 1 <= outer <= 65535:
|
||||
raise ValueError('invalid port')
|
||||
continue
|
||||
result[outer] = {'host_port': outer, 'container_port': inner,
|
||||
'scheme': 'https' if inner in {443, 4443, 8443, 9443} or outer in {443, 4443, 8443, 9443} else 'http'}
|
||||
return sorted(result.values(), key=lambda item: item['host_port'])
|
||||
@@ -167,6 +168,9 @@ def _candidate(vmid, record):
|
||||
'container_name': record['name'], 'name': name,
|
||||
'installed_version': record['version'], 'evidence': evidence,
|
||||
'default_ports': [port['host_port'] for port in record['endpoints'] if port['container_port'] in WEB_PORTS],
|
||||
'port_details': [{'port': port['host_port'], 'scheme': port['scheme'], 'path': None,
|
||||
'description': '', 'logo_url': ''}
|
||||
for port in record['endpoints'] if port['container_port'] in WEB_PORTS] or None,
|
||||
'tracking_suggestion': None}
|
||||
|
||||
|
||||
@@ -272,7 +276,9 @@ def cached_web_links(vmid, lookup):
|
||||
"""Reuse a Find snapshot in existing web-link suggestions without another scan."""
|
||||
with _mutex:
|
||||
value = copy.deepcopy(_cache.get(int(vmid)))
|
||||
if not value or value.get('_retired') or time.time() - value['_attempt_at'] >= SNAPSHOT_SECONDS:
|
||||
# Only a complete search lists every container; otherwise probe as before.
|
||||
if (not value or value.get('_retired') or value.get('status') != 'complete'
|
||||
or time.time() - value['_attempt_at'] >= SNAPSHOT_SECONDS):
|
||||
return None
|
||||
result = []
|
||||
for record in value.get('_containers', []):
|
||||
|
||||
@@ -100,6 +100,21 @@ class Scan(unittest.TestCase):
|
||||
self.assertEqual(item['default_ports'],[8080])
|
||||
self.assertIn({'kind':'tcp_listener','value':'21115'},item['evidence'])
|
||||
|
||||
def test_unassigned_or_ranged_host_ports_are_skipped_not_invalid(self):
|
||||
app = record(running=False, bindings={'8080/tcp': [{'HostIp': '', 'HostPort': ''}],
|
||||
'9000/tcp': [{'HostIp': '', 'HostPort': '9000-9010'}],
|
||||
'443/tcp': [{'HostIp': '', 'HostPort': '8444'}]})
|
||||
result = self.scan(records=[app])
|
||||
self.assertEqual(result['status'], 'complete')
|
||||
endpoints = result['_containers'][0]['endpoints']
|
||||
self.assertEqual([e['host_port'] for e in endpoints], [8444])
|
||||
|
||||
def test_https_container_port_prefills_its_scheme(self):
|
||||
app = record(bindings={'443/tcp': [{'HostIp': '0.0.0.0', 'HostPort': '8444'}]})
|
||||
item, = self.scan(records=[app])['candidates']
|
||||
self.assertEqual(item['default_ports'], [8444])
|
||||
self.assertEqual([(d['port'], d['scheme']) for d in item['port_details']], [(8444, 'https')])
|
||||
|
||||
def test_empty_engine_is_complete_and_missing_docker_never_calls_docker(self):
|
||||
self.assertEqual(self.scan(ids=[])['status'], 'complete')
|
||||
self.calls.clear()
|
||||
@@ -202,6 +217,20 @@ class Cache(unittest.TestCase):
|
||||
self.assertEqual(lxc_apps._probe_docker_web_links(101)[0]['host_port'], 8080)
|
||||
execute.assert_not_called()
|
||||
|
||||
def test_incomplete_search_never_replaces_the_web_link_probe(self):
|
||||
lookup = Mock(return_value={'slug': None, 'name': 'web', 'logo_url': None})
|
||||
partial = {**copy.deepcopy(self.value), 'status': 'partial', 'issues': ['inspect_failed']}
|
||||
with patch.object(docker, 'scan', return_value=partial), patch.object(docker.time, 'time', return_value=1000):
|
||||
docker.discover(101)
|
||||
with patch.object(docker.time, 'time', return_value=1001):
|
||||
self.assertIsNone(docker.cached_web_links(101, lookup))
|
||||
docker._cache.clear()
|
||||
with patch.object(docker, 'scan', side_effect=native.ProbeError('timeout')), \
|
||||
patch.object(docker.time, 'time', return_value=1000):
|
||||
docker.discover(101)
|
||||
with patch.object(docker.time, 'time', return_value=1001):
|
||||
self.assertIsNone(docker.cached_web_links(101, lookup))
|
||||
|
||||
def test_shared_links_keep_existing_name_then_port_order(self):
|
||||
value = copy.deepcopy(self.value)
|
||||
value['_containers'][0]['name'] = 'z-last'
|
||||
|
||||
Reference in New Issue
Block a user