nginx-lens 0.3.2__py3-none-any.whl → 0.3.3__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/health.py CHANGED
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  import typer
2
3
  from rich.console import Console
3
4
  from rich.table import Table
@@ -22,14 +23,16 @@ def health(
22
23
  nginx-lens health /etc/nginx/nginx.conf --timeout 5 --retries 3 --mode http
23
24
  nginx-lens health /etc/nginx/nginx.conf --resolve
24
25
  """
26
+ exit_code = 0
27
+
25
28
  try:
26
29
  tree = parse_nginx_config(config_path)
27
30
  except FileNotFoundError:
28
31
  console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
29
- return
32
+ sys.exit(1)
30
33
  except Exception as e:
31
34
  console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
32
- return
35
+ sys.exit(1)
33
36
 
34
37
  upstreams = tree.get_upstreams()
35
38
  results = check_upstreams(upstreams, timeout=timeout, retries=retries, mode=mode.lower())
@@ -50,6 +53,10 @@ def health(
50
53
  status = "Healthy" if srv["healthy"] else "Unhealthy"
51
54
  color = "green" if srv["healthy"] else "red"
52
55
 
56
+ # Проверяем статус здоровья
57
+ if not srv["healthy"]:
58
+ exit_code = 1
59
+
53
60
  if resolve:
54
61
  resolved_list = []
55
62
  if name in resolved_info:
@@ -64,11 +71,14 @@ def health(
64
71
  # Если есть "invalid resolve", показываем красным, иначе зеленым
65
72
  if any("invalid resolve" in r for r in resolved_list):
66
73
  table.add_row(srv["address"], f"[{color}]{status}[/{color}]", f"[red]{resolved_str}[/red]")
74
+ exit_code = 1
67
75
  else:
68
76
  table.add_row(srv["address"], f"[{color}]{status}[/{color}]", f"[green]{resolved_str}[/green]")
69
77
  else:
70
78
  table.add_row(srv["address"], f"[{color}]{status}[/{color}]", "[yellow]Failed to resolve[/yellow]")
79
+ exit_code = 1
71
80
  else:
72
81
  table.add_row(srv["address"], f"[{color}]{status}[/{color}]")
73
82
 
74
83
  console.print(table)
84
+ sys.exit(exit_code)
commands/resolve.py CHANGED
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  import typer
2
3
  from rich.console import Console
3
4
  from rich.table import Table
@@ -16,19 +17,21 @@ def resolve(
16
17
  Пример:
17
18
  nginx-lens resolve /etc/nginx/nginx.conf
18
19
  """
20
+ exit_code = 0
21
+
19
22
  try:
20
23
  tree = parse_nginx_config(config_path)
21
24
  except FileNotFoundError:
22
25
  console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
23
- return
26
+ sys.exit(1)
24
27
  except Exception as e:
25
28
  console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
26
- return
29
+ sys.exit(1)
27
30
 
28
31
  upstreams = tree.get_upstreams()
29
32
  if not upstreams:
30
33
  console.print("[yellow]Не найдено ни одного upstream в конфигурации.[/yellow]")
31
- return
34
+ sys.exit(0) # Нет upstream - это не ошибка, просто нет чего проверять
32
35
 
33
36
  results = resolve_upstreams(upstreams)
34
37
 
@@ -47,10 +50,13 @@ def resolve(
47
50
  # Если есть "invalid resolve", показываем красным, иначе зеленым
48
51
  if any("invalid resolve" in r for r in resolved_list):
49
52
  table.add_row(upstream_name, srv["address"], f"[red]{resolved_str}[/red]")
53
+ exit_code = 1
50
54
  else:
51
55
  table.add_row(upstream_name, srv["address"], f"[green]{resolved_str}[/green]")
52
56
  else:
53
57
  table.add_row(upstream_name, srv["address"], "[red]Failed to resolve[/red]")
58
+ exit_code = 1
54
59
 
55
60
  console.print(table)
61
+ sys.exit(exit_code)
56
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nginx-lens
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: CLI-инструмент для анализа, визуализации и диагностики конфигураций Nginx
5
5
  Author: Daniil Astrouski
6
6
  Author-email: shelovesuastra@gmail.com
@@ -15,10 +15,10 @@ commands/analyze.py,sha256=W6begSgXNjgKJGoGeguR3WKgHPLkClWXxxpDcqvsJdc,8343
15
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=JAlUKx2kXhi2eigSQXG7GxuQMPydYzojgV2qaRcoLWw,3429
18
+ commands/health.py,sha256=MIFORCxH9wFH6954h7pmYhbBiqDUVMU6BDHBqWmuSLI,3710
19
19
  commands/include.py,sha256=5PTYG5C00-AlWfIgpQXLq9E7C9yTFSv7HrZkM5ogDps,2224
20
20
  commands/logs.py,sha256=RkPUdIpbO9dOVL56lemreYRuAjMjcqqMxRCKOFv2gC4,3691
21
- commands/resolve.py,sha256=LVfp-cva-XlKOOIS5Da908mBGgjq1DtZlsahMUylAsk,2151
21
+ commands/resolve.py,sha256=sRsEFgGmqS0WBg9VZBiLXB4KjaiFj8kKRprHYq-f9lI,2383
22
22
  commands/route.py,sha256=-x_71u6ENl3iO-oxK3bdE8v5eZKf4xRCydeUyXMFVrY,3163
23
23
  commands/syntax.py,sha256=ZWFdaL8LVv9S694wlk2aV3HJKb0OSKjw3wNgTlNvFR8,3418
24
24
  commands/tree.py,sha256=mDfx0Aeg1EDQSYQoJ2nJIkSd_uP7ZR7pEqy7Cw3clQ0,2161
@@ -26,13 +26,13 @@ exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  exporter/graph.py,sha256=WYUrqUgCaK6KihgxAcRHaQn4oMo6b7ybC8yb_36ZIsA,3995
27
27
  exporter/html.py,sha256=uquEM-WvBt2aV9GshgaI3UVhYd8sD0QQ-OmuNtvYUdU,798
28
28
  exporter/markdown.py,sha256=_0mXQIhurGEZ0dO-eq9DbsuKNrgEDIblgtL3DAgYNo8,724
29
- nginx_lens-0.3.2.dist-info/licenses/LICENSE,sha256=g8QXKdvZZC56rU8E12vIeYF6R4jeTWOsblOnYAda3K4,1073
29
+ nginx_lens-0.3.3.dist-info/licenses/LICENSE,sha256=g8QXKdvZZC56rU8E12vIeYF6R4jeTWOsblOnYAda3K4,1073
30
30
  parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  parser/nginx_parser.py,sha256=Sa9FtGAkvTqNzoehBvgLUWPJHLLIZYWH9ugSHW50X8s,3699
32
32
  upstream_checker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- upstream_checker/checker.py,sha256=ZwAlIW6yh0OVdeI44dpZUeSUbByE04gBxTUsRMRUgU8,9496
34
- nginx_lens-0.3.2.dist-info/METADATA,sha256=DanBz8o77g84wsKtgPzMKCa2IrX8VVrq78DOFuJeTdA,552
35
- nginx_lens-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
- nginx_lens-0.3.2.dist-info/entry_points.txt,sha256=qEcecjSyLqcJjbIVlNlTpqAhPqDyaujUV5ZcBTAr3po,48
37
- nginx_lens-0.3.2.dist-info/top_level.txt,sha256=mxLJO4rZg0rbixVGhplF3fUNFs8vxDIL25ronZNvRy4,51
38
- nginx_lens-0.3.2.dist-info/RECORD,,
33
+ upstream_checker/checker.py,sha256=tyv6bVjoFdeGFJzASiamj4EoBppqyvM3gGc9aQSzwXo,8271
34
+ nginx_lens-0.3.3.dist-info/METADATA,sha256=EW_qvvG4Qz_ercZXKVX6xVGC4dBVNVvtYuGm41h7ZQ4,552
35
+ nginx_lens-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ nginx_lens-0.3.3.dist-info/entry_points.txt,sha256=qEcecjSyLqcJjbIVlNlTpqAhPqDyaujUV5ZcBTAr3po,48
37
+ nginx_lens-0.3.3.dist-info/top_level.txt,sha256=mxLJO4rZg0rbixVGhplF3fUNFs8vxDIL25ronZNvRy4,51
38
+ nginx_lens-0.3.3.dist-info/RECORD,,
@@ -80,14 +80,12 @@ def resolve_address(address: str) -> List[str]:
80
80
  return []
81
81
  host, port = parts
82
82
 
83
- # Если это уже IPv4 адрес, возвращаем как есть
84
83
  try:
85
84
  socket.inet_aton(host)
86
85
  return [host_port]
87
86
  except socket.error:
88
87
  pass
89
88
 
90
- # Проверяем IPv6 (в квадратных скобках)
91
89
  if host.startswith("[") and host.endswith("]"):
92
90
  ipv6_host = host[1:-1]
93
91
  try:
@@ -96,11 +94,9 @@ def resolve_address(address: str) -> List[str]:
96
94
  except (socket.error, OSError):
97
95
  pass
98
96
 
99
- # Используем dnspython для детального DNS резолвинга
100
97
  if DNS_AVAILABLE:
101
98
  return _resolve_with_dns(host, port)
102
99
  else:
103
- # Fallback на стандартный socket, если dnspython недоступен
104
100
  return _resolve_with_socket(host, port)
105
101
  except (ValueError, IndexError, AttributeError):
106
102
  return []
@@ -112,25 +108,21 @@ def _resolve_with_dns(host: str, port: str) -> List[str]:
112
108
  cname_info = None
113
109
  invalid_type = None
114
110
 
115
- # Сначала проверяем CNAME для исходного хоста
116
111
  try:
117
112
  cname_answer = dns.resolver.resolve(host, 'CNAME', raise_on_no_answer=False)
118
113
  if cname_answer:
119
114
  cname_target = str(cname_answer[0].target).rstrip('.')
120
115
  cname_info = cname_target
121
116
 
122
- # Если есть CNAME, проверяем A записи для CNAME цели (не для исходного хоста)
123
117
  try:
124
118
  a_answer = dns.resolver.resolve(cname_target, 'A', raise_on_no_answer=False)
125
119
  if a_answer:
126
- # CNAME ведет на валидные A записи
127
120
  resolved_ips = []
128
121
  for rdata in a_answer:
129
122
  ip = str(rdata.address)
130
123
  resolved_ips.append(f"{ip}:{port} (via {cname_info})")
131
124
  return resolved_ips
132
125
  else:
133
- # Нет A записей для CNAME цели, проверяем другие типы
134
126
  try:
135
127
  txt_answer = dns.resolver.resolve(cname_target, 'TXT', raise_on_no_answer=False)
136
128
  if txt_answer:
@@ -145,7 +137,6 @@ def _resolve_with_dns(host: str, port: str) -> List[str]:
145
137
  except:
146
138
  pass
147
139
  if not invalid_type:
148
- # Проверяем другие невалидные типы
149
140
  try:
150
141
  ns_answer = dns.resolver.resolve(cname_target, 'NS', raise_on_no_answer=False)
151
142
  if ns_answer:
@@ -161,7 +152,6 @@ def _resolve_with_dns(host: str, port: str) -> List[str]:
161
152
  except Exception:
162
153
  pass
163
154
 
164
- # Если нет CNAME, получаем A записи для исходного хоста
165
155
  try:
166
156
  a_answer = dns.resolver.resolve(host, 'A', raise_on_no_answer=False)
167
157
  if a_answer:
@@ -181,13 +171,10 @@ def _resolve_with_dns(host: str, port: str) -> List[str]:
181
171
  def _resolve_with_socket(host: str, port: str) -> List[str]:
182
172
  """Fallback резолвинг через socket (без информации о CNAME)."""
183
173
  try:
184
- # gethostbyname_ex возвращает (hostname, aliaslist, ipaddrlist)
185
174
  _, _, ipaddrlist = socket.gethostbyname_ex(host)
186
- # Фильтруем только IPv4 адреса (IPv6 обрабатываются отдельно)
187
175
  resolved_ips = []
188
176
  for ip in ipaddrlist:
189
177
  try:
190
- # Проверяем, что это IPv4
191
178
  socket.inet_aton(ip)
192
179
  resolved_ips.append(f"{ip}:{port}")
193
180
  except socket.error: