nginx-lens 0.2.3__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
commands/cli.py CHANGED
@@ -9,6 +9,7 @@ from commands.include import include_tree
9
9
  from commands.graph import graph
10
10
  from commands.logs import logs
11
11
  from commands.syntax import syntax
12
+ from commands.resolve import resolve
12
13
 
13
14
  app = typer.Typer(help="nginx-lens — анализ и диагностика конфигураций Nginx")
14
15
  console = Console()
@@ -22,6 +23,7 @@ app.command()(include_tree)
22
23
  app.command()(graph)
23
24
  app.command()(logs)
24
25
  app.command()(syntax)
26
+ app.command()(resolve)
25
27
 
26
28
  if __name__ == "__main__":
27
29
  app()
commands/health.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import typer
2
2
  from rich.console import Console
3
3
  from rich.table import Table
4
- from upstream_checker.checker import check_upstreams
4
+ from upstream_checker.checker import check_upstreams, resolve_upstreams
5
5
  from parser.nginx_parser import parse_nginx_config
6
6
 
7
7
  app = typer.Typer()
@@ -12,6 +12,7 @@ def health(
12
12
  timeout: float = typer.Option(2.0, help="Таймаут проверки (сек)"),
13
13
  retries: int = typer.Option(1, help="Количество попыток"),
14
14
  mode: str = typer.Option("tcp", help="Режим проверки: tcp или http", case_sensitive=False),
15
+ resolve: bool = typer.Option(False, "--resolve", "-r", help="Показать резолвленные IP-адреса"),
15
16
  ):
16
17
  """
17
18
  Проверяет доступность upstream-серверов, определённых в nginx.conf. Выводит таблицу.
@@ -19,6 +20,7 @@ def health(
19
20
  Пример:
20
21
  nginx-lens health /etc/nginx/nginx.conf
21
22
  nginx-lens health /etc/nginx/nginx.conf --timeout 5 --retries 3 --mode http
23
+ nginx-lens health /etc/nginx/nginx.conf --resolve
22
24
  """
23
25
  try:
24
26
  tree = parse_nginx_config(config_path)
@@ -31,15 +33,38 @@ def health(
31
33
 
32
34
  upstreams = tree.get_upstreams()
33
35
  results = check_upstreams(upstreams, timeout=timeout, retries=retries, mode=mode.lower())
36
+
37
+ # Если нужно показать резолвленные IP-адреса
38
+ resolved_info = {}
39
+ if resolve:
40
+ resolved_info = resolve_upstreams(upstreams)
34
41
 
35
42
  table = Table(show_header=True, header_style="bold blue")
36
- table.add_column("upstream_name")
37
- table.add_column("upstream_status")
43
+ table.add_column("Address")
44
+ table.add_column("Status")
45
+ if resolve:
46
+ table.add_column("Resolved IP")
38
47
 
39
48
  for name, servers in results.items():
40
49
  for srv in servers:
41
50
  status = "Healthy" if srv["healthy"] else "Unhealthy"
42
51
  color = "green" if srv["healthy"] else "red"
43
- table.add_row(srv["address"], f"[{color}]{status}[/{color}]")
52
+
53
+ if resolve:
54
+ resolved_list = []
55
+ if name in resolved_info:
56
+ for resolved_srv in resolved_info[name]:
57
+ if resolved_srv["address"] == srv["address"]:
58
+ resolved_list = resolved_srv["resolved"]
59
+ break
60
+
61
+ if resolved_list:
62
+ # Показываем все IP-адреса через запятую
63
+ resolved_str = ", ".join(resolved_list)
64
+ table.add_row(srv["address"], f"[{color}]{status}[/{color}]", f"[green]{resolved_str}[/green]")
65
+ else:
66
+ table.add_row(srv["address"], f"[{color}]{status}[/{color}]", "[yellow]Failed to resolve[/yellow]")
67
+ else:
68
+ table.add_row(srv["address"], f"[{color}]{status}[/{color}]")
44
69
 
45
70
  console.print(table)
commands/resolve.py ADDED
@@ -0,0 +1,52 @@
1
+ import typer
2
+ from rich.console import Console
3
+ from rich.table import Table
4
+ from upstream_checker.checker import resolve_upstreams
5
+ from parser.nginx_parser import parse_nginx_config
6
+
7
+ app = typer.Typer()
8
+ console = Console()
9
+
10
+ def resolve(
11
+ config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
12
+ ):
13
+ """
14
+ Резолвит DNS имена upstream-серверов в IP-адреса.
15
+
16
+ Пример:
17
+ nginx-lens resolve /etc/nginx/nginx.conf
18
+ """
19
+ try:
20
+ tree = parse_nginx_config(config_path)
21
+ except FileNotFoundError:
22
+ console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
23
+ return
24
+ except Exception as e:
25
+ console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
26
+ return
27
+
28
+ upstreams = tree.get_upstreams()
29
+ if not upstreams:
30
+ console.print("[yellow]Не найдено ни одного upstream в конфигурации.[/yellow]")
31
+ return
32
+
33
+ results = resolve_upstreams(upstreams)
34
+
35
+ table = Table(show_header=True, header_style="bold blue")
36
+ table.add_column("Upstream Name")
37
+ table.add_column("Address")
38
+ table.add_column("Resolved IP")
39
+
40
+ for name, servers in results.items():
41
+ for idx, srv in enumerate(servers):
42
+ upstream_name = name if idx == 0 else ""
43
+ resolved_list = srv["resolved"]
44
+ if resolved_list:
45
+ # Показываем все IP-адреса через запятую
46
+ resolved_str = ", ".join(resolved_list)
47
+ table.add_row(upstream_name, srv["address"], f"[green]{resolved_str}[/green]")
48
+ else:
49
+ table.add_row(upstream_name, srv["address"], "[red]Failed to resolve[/red]")
50
+
51
+ console.print(table)
52
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nginx-lens
3
- Version: 0.2.3
3
+ Version: 0.3.1
4
4
  Summary: CLI-инструмент для анализа, визуализации и диагностики конфигураций Nginx
5
5
  Author: Daniil Astrouski
6
6
  Author-email: shelovesuastra@gmail.com
@@ -12,12 +12,13 @@ analyzer/unused.py,sha256=Ixzv0bPsw9IafblVwLiAOgugdg2dGu1MJDtuoqzPZiY,1066
12
12
  analyzer/warnings.py,sha256=zC36QMvegA2eQPvZ-P1eysrX_kXHx5A1MUKHKKNvG5c,5784
13
13
  commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  commands/analyze.py,sha256=W6begSgXNjgKJGoGeguR3WKgHPLkClWXxxpDcqvsJdc,8343
15
- commands/cli.py,sha256=9HwDJ-po5al0ceb4Wkyw5F2wzqxbJTo0CbHQ2AQ8obo,722
15
+ commands/cli.py,sha256=brzp6xDDWIrm7ibaoT4x94hgAdBB2DVWniXoK8dRylE,782
16
16
  commands/diff.py,sha256=C7gRIWh6DNWHzjiQBPVTn-rZ40m2KCY75Zd6Q4URJIE,2076
17
17
  commands/graph.py,sha256=xB6KjXBkLmm5gII3e-5BMRGO7WeTwc7EFxRGzYgnme4,5947
18
- commands/health.py,sha256=EV2M2Gt_LnA8WI_0gOBlRYr2u-fxpjq6FBd-tlQOSuI,1830
18
+ commands/health.py,sha256=ZGc42Ak4xoZt9r9DJ_84AvgQ8yXCO-CzWppvsOww1Yo,3086
19
19
  commands/include.py,sha256=5PTYG5C00-AlWfIgpQXLq9E7C9yTFSv7HrZkM5ogDps,2224
20
20
  commands/logs.py,sha256=RkPUdIpbO9dOVL56lemreYRuAjMjcqqMxRCKOFv2gC4,3691
21
+ commands/resolve.py,sha256=hdbvB88bgivNt1v5LMyhf--2mwfI_IUXyi4b1IUB62A,1841
21
22
  commands/route.py,sha256=-x_71u6ENl3iO-oxK3bdE8v5eZKf4xRCydeUyXMFVrY,3163
22
23
  commands/syntax.py,sha256=ZWFdaL8LVv9S694wlk2aV3HJKb0OSKjw3wNgTlNvFR8,3418
23
24
  commands/tree.py,sha256=mDfx0Aeg1EDQSYQoJ2nJIkSd_uP7ZR7pEqy7Cw3clQ0,2161
@@ -25,13 +26,13 @@ exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
26
  exporter/graph.py,sha256=WYUrqUgCaK6KihgxAcRHaQn4oMo6b7ybC8yb_36ZIsA,3995
26
27
  exporter/html.py,sha256=uquEM-WvBt2aV9GshgaI3UVhYd8sD0QQ-OmuNtvYUdU,798
27
28
  exporter/markdown.py,sha256=_0mXQIhurGEZ0dO-eq9DbsuKNrgEDIblgtL3DAgYNo8,724
28
- nginx_lens-0.2.3.dist-info/licenses/LICENSE,sha256=g8QXKdvZZC56rU8E12vIeYF6R4jeTWOsblOnYAda3K4,1073
29
+ nginx_lens-0.3.1.dist-info/licenses/LICENSE,sha256=g8QXKdvZZC56rU8E12vIeYF6R4jeTWOsblOnYAda3K4,1073
29
30
  parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
31
  parser/nginx_parser.py,sha256=Sa9FtGAkvTqNzoehBvgLUWPJHLLIZYWH9ugSHW50X8s,3699
31
32
  upstream_checker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- upstream_checker/checker.py,sha256=5aTU3-ejUlEOKX9lYxD8PuGTBYyQENwiDknSh168lFE,2336
33
- nginx_lens-0.2.3.dist-info/METADATA,sha256=ue5Bb9dQNf6RSCxxS7k718glb6-ujAY8yqLlTU7b27w,520
34
- nginx_lens-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
- nginx_lens-0.2.3.dist-info/entry_points.txt,sha256=qEcecjSyLqcJjbIVlNlTpqAhPqDyaujUV5ZcBTAr3po,48
36
- nginx_lens-0.2.3.dist-info/top_level.txt,sha256=mxLJO4rZg0rbixVGhplF3fUNFs8vxDIL25ronZNvRy4,51
37
- nginx_lens-0.2.3.dist-info/RECORD,,
33
+ upstream_checker/checker.py,sha256=Th49U6OF-egTH145XOyrfIUUHtygBJiOtqqD9ZT1lKo,5261
34
+ nginx_lens-0.3.1.dist-info/METADATA,sha256=Z-8Ke06GaHt1iVb4zvhX5G3RiJMHwW7bdHRuwKi9Wd8,520
35
+ nginx_lens-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ nginx_lens-0.3.1.dist-info/entry_points.txt,sha256=qEcecjSyLqcJjbIVlNlTpqAhPqDyaujUV5ZcBTAr3po,48
37
+ nginx_lens-0.3.1.dist-info/top_level.txt,sha256=mxLJO4rZg0rbixVGhplF3fUNFs8vxDIL25ronZNvRy4,51
38
+ nginx_lens-0.3.1.dist-info/RECORD,,
@@ -49,6 +49,91 @@ def check_http(address: str, timeout: float, retries: int) -> bool:
49
49
  return False
50
50
 
51
51
 
52
+ def resolve_address(address: str) -> List[str]:
53
+ """
54
+ Резолвит адрес upstream сервера в IP-адреса.
55
+
56
+ Args:
57
+ address: Адрес в формате "host:port" или "host:port параметры"
58
+
59
+ Returns:
60
+ Список IP-адресов в формате "ip:port" или пустой список, если резолвинг не удался
61
+ """
62
+ try:
63
+ host_port = address.split()[0]
64
+
65
+ if ":" not in host_port:
66
+ return []
67
+
68
+ parts = host_port.rsplit(":", 1)
69
+ if len(parts) != 2:
70
+ return []
71
+ host, port = parts
72
+
73
+ # Если это уже IPv4 адрес, возвращаем как есть
74
+ try:
75
+ socket.inet_aton(host)
76
+ return [host_port]
77
+ except socket.error:
78
+ pass
79
+
80
+ # Проверяем IPv6 (в квадратных скобках)
81
+ if host.startswith("[") and host.endswith("]"):
82
+ ipv6_host = host[1:-1]
83
+ try:
84
+ socket.inet_pton(socket.AF_INET6, ipv6_host)
85
+ return [host_port]
86
+ except (socket.error, OSError):
87
+ pass
88
+
89
+ # Пытаемся резолвить DNS имя - получаем все IP-адреса
90
+ try:
91
+ # gethostbyname_ex возвращает (hostname, aliaslist, ipaddrlist)
92
+ _, _, ipaddrlist = socket.gethostbyname_ex(host)
93
+ # Фильтруем только IPv4 адреса (IPv6 обрабатываются отдельно)
94
+ resolved_ips = []
95
+ for ip in ipaddrlist:
96
+ try:
97
+ # Проверяем, что это IPv4
98
+ socket.inet_aton(ip)
99
+ resolved_ips.append(f"{ip}:{port}")
100
+ except socket.error:
101
+ pass
102
+ return resolved_ips if resolved_ips else []
103
+ except (socket.gaierror, OSError):
104
+ return []
105
+ except (ValueError, IndexError, AttributeError):
106
+ return []
107
+
108
+
109
+ def resolve_upstreams(
110
+ upstreams: Dict[str, List[str]]
111
+ ) -> Dict[str, List[dict]]:
112
+ """
113
+ Резолвит DNS имена upstream-серверов в IP-адреса.
114
+
115
+ Возвращает:
116
+ {
117
+ "backend": [
118
+ {"address": "example.com:8080", "resolved": ["192.168.1.1:8080", "192.168.1.2:8080"]},
119
+ {"address": "127.0.0.1:8080", "resolved": ["127.0.0.1:8080"]},
120
+ {"address": "badhost:80", "resolved": []},
121
+ ...
122
+ ]
123
+ }
124
+ """
125
+ results = {}
126
+ for name, servers in upstreams.items():
127
+ results[name] = []
128
+ for srv in servers:
129
+ resolved = resolve_address(srv)
130
+ results[name].append({
131
+ "address": srv,
132
+ "resolved": resolved
133
+ })
134
+ return results
135
+
136
+
52
137
  def check_upstreams(
53
138
  upstreams: Dict[str, List[str]],
54
139
  timeout: float = 2.0,