nginx-lens 0.3.4__tar.gz → 0.5.0__tar.gz
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.
- {nginx_lens-0.3.4/nginx_lens.egg-info → nginx_lens-0.5.0}/PKG-INFO +6 -1
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/analyze.py +9 -3
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/cli.py +6 -0
- nginx_lens-0.5.0/commands/completion.py +174 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/diff.py +3 -2
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/graph.py +3 -2
- nginx_lens-0.5.0/commands/health.py +144 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/include.py +3 -2
- nginx_lens-0.5.0/commands/logs.py +345 -0
- nginx_lens-0.5.0/commands/metrics.py +495 -0
- nginx_lens-0.5.0/commands/resolve.py +120 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/route.py +1 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/syntax.py +1 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/tree.py +3 -2
- nginx_lens-0.5.0/commands/validate.py +451 -0
- nginx_lens-0.5.0/config/__init__.py +4 -0
- nginx_lens-0.5.0/config/config_loader.py +200 -0
- nginx_lens-0.5.0/exporter/csv.py +87 -0
- nginx_lens-0.5.0/exporter/json_yaml.py +361 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0/nginx_lens.egg-info}/PKG-INFO +6 -1
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/nginx_lens.egg-info/SOURCES.txt +18 -1
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/nginx_lens.egg-info/requires.txt +5 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/nginx_lens.egg-info/top_level.txt +2 -0
- nginx_lens-0.5.0/pyproject.toml +10 -0
- nginx_lens-0.5.0/setup.py +54 -0
- nginx_lens-0.5.0/tests/test_diff.py +68 -0
- nginx_lens-0.5.0/tests/test_graph.py +64 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/tests/test_health.py +3 -3
- nginx_lens-0.5.0/tests/test_integration.py +199 -0
- nginx_lens-0.5.0/tests/test_logs.py +74 -0
- nginx_lens-0.5.0/tests/test_performance.py +80 -0
- nginx_lens-0.5.0/tests/test_resolve.py +67 -0
- nginx_lens-0.5.0/tests/test_route.py +85 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/upstream_checker/checker.py +47 -7
- nginx_lens-0.5.0/upstream_checker/dns_cache.py +216 -0
- nginx_lens-0.5.0/utils/__init__.py +4 -0
- nginx_lens-0.5.0/utils/progress.py +120 -0
- nginx_lens-0.3.4/commands/health.py +0 -87
- nginx_lens-0.3.4/commands/logs.py +0 -97
- nginx_lens-0.3.4/commands/resolve.py +0 -65
- nginx_lens-0.3.4/pyproject.toml +0 -3
- nginx_lens-0.3.4/setup.py +0 -23
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/LICENSE +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/README.md +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/__init__.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/base.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/conflicts.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/dead_locations.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/diff.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/duplicates.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/empty_blocks.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/include.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/rewrite.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/route.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/unused.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/analyzer/warnings.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/commands/__init__.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/exporter/__init__.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/exporter/graph.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/exporter/html.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/exporter/markdown.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/nginx_lens.egg-info/dependency_links.txt +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/nginx_lens.egg-info/entry_points.txt +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/parser/__init__.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/parser/nginx_parser.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/setup.cfg +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/tests/test_conflicts.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/tests/test_duplicates.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/tests/test_empty_blocks.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/tests/test_parser.py +0 -0
- {nginx_lens-0.3.4 → nginx_lens-0.5.0}/upstream_checker/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nginx-lens
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: CLI-инструмент для анализа, визуализации и диагностики конфигураций Nginx
|
|
5
5
|
Author: Daniil Astrouski
|
|
6
6
|
Author-email: shelovesuastra@gmail.com
|
|
@@ -10,9 +10,14 @@ Requires-Dist: typer[all]>=0.9.0
|
|
|
10
10
|
Requires-Dist: rich>=13.0.0
|
|
11
11
|
Requires-Dist: requests>=2.25.0
|
|
12
12
|
Requires-Dist: dnspython>=2.0.0
|
|
13
|
+
Requires-Dist: pyyaml>=6.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
13
17
|
Dynamic: author
|
|
14
18
|
Dynamic: author-email
|
|
15
19
|
Dynamic: license-file
|
|
20
|
+
Dynamic: provides-extra
|
|
16
21
|
Dynamic: requires-dist
|
|
17
22
|
Dynamic: requires-python
|
|
18
23
|
Dynamic: summary
|
|
@@ -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
|
|
@@ -9,6 +10,7 @@ from analyzer.unused import find_unused_variables
|
|
|
9
10
|
from parser.nginx_parser import parse_nginx_config
|
|
10
11
|
from analyzer.rewrite import find_rewrite_issues
|
|
11
12
|
from analyzer.dead_locations import find_dead_locations
|
|
13
|
+
from exporter.json_yaml import format_analyze_results, print_export
|
|
12
14
|
|
|
13
15
|
app = typer.Typer()
|
|
14
16
|
console = Console()
|
|
@@ -41,7 +43,11 @@ ISSUE_META = {
|
|
|
41
43
|
}
|
|
42
44
|
SEVERITY_COLOR = {"high": "red", "medium": "orange3", "low": "yellow"}
|
|
43
45
|
|
|
44
|
-
def analyze(
|
|
46
|
+
def analyze(
|
|
47
|
+
config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
|
|
48
|
+
json: bool = typer.Option(False, "--json", help="Экспортировать результаты в JSON"),
|
|
49
|
+
yaml: bool = typer.Option(False, "--yaml", help="Экспортировать результаты в YAML"),
|
|
50
|
+
):
|
|
45
51
|
"""
|
|
46
52
|
Анализирует конфигурацию Nginx на типовые проблемы и best practices.
|
|
47
53
|
|
|
@@ -62,10 +68,10 @@ def analyze(config_path: str = typer.Argument(..., help="Путь к nginx.conf"
|
|
|
62
68
|
tree = parse_nginx_config(config_path)
|
|
63
69
|
except FileNotFoundError:
|
|
64
70
|
console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
|
|
65
|
-
|
|
71
|
+
sys.exit(1)
|
|
66
72
|
except Exception as e:
|
|
67
73
|
console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
|
|
68
|
-
|
|
74
|
+
sys.exit(1)
|
|
69
75
|
conflicts = find_location_conflicts(tree)
|
|
70
76
|
dups = find_duplicate_directives(tree)
|
|
71
77
|
empties = find_empty_blocks(tree)
|
|
@@ -10,6 +10,9 @@ from commands.graph import graph
|
|
|
10
10
|
from commands.logs import logs
|
|
11
11
|
from commands.syntax import syntax
|
|
12
12
|
from commands.resolve import resolve
|
|
13
|
+
from commands.validate import validate
|
|
14
|
+
from commands.metrics import metrics
|
|
15
|
+
from commands.completion import app as completion_app
|
|
13
16
|
|
|
14
17
|
app = typer.Typer(help="nginx-lens — анализ и диагностика конфигураций Nginx")
|
|
15
18
|
console = Console()
|
|
@@ -24,6 +27,9 @@ app.command()(graph)
|
|
|
24
27
|
app.command()(logs)
|
|
25
28
|
app.command()(syntax)
|
|
26
29
|
app.command()(resolve)
|
|
30
|
+
app.command()(validate)
|
|
31
|
+
app.command()(metrics)
|
|
32
|
+
app.add_typer(completion_app, name="completion", help="Генерация скриптов автодополнения")
|
|
27
33
|
|
|
28
34
|
if __name__ == "__main__":
|
|
29
35
|
app()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Команда для генерации скриптов автодополнения.
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich import box
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _generate_completion_script(shell: str) -> str:
|
|
15
|
+
"""
|
|
16
|
+
Генерирует базовый completion скрипт для указанного shell.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
shell: Тип shell (bash, zsh, fish, powershell)
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Строка с completion скриптом
|
|
23
|
+
"""
|
|
24
|
+
commands = [
|
|
25
|
+
"health", "analyze", "tree", "diff", "route", "include",
|
|
26
|
+
"graph", "logs", "syntax", "resolve", "validate", "metrics"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
if shell == "bash":
|
|
30
|
+
script = f"""# Bash completion for nginx-lens
|
|
31
|
+
_nginx_lens_completion() {{
|
|
32
|
+
local cur="${{COMP_WORDS[COMP_CWORD]}}"
|
|
33
|
+
local commands="{' '.join(commands)}"
|
|
34
|
+
|
|
35
|
+
if [[ ${{COMP_CWORD}} -eq 1 ]]; then
|
|
36
|
+
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
37
|
+
else
|
|
38
|
+
COMPREPLY=($(compgen -f -- "$cur"))
|
|
39
|
+
fi
|
|
40
|
+
}}
|
|
41
|
+
|
|
42
|
+
complete -F _nginx_lens_completion nginx-lens
|
|
43
|
+
"""
|
|
44
|
+
elif shell == "zsh":
|
|
45
|
+
script = f"""# Zsh completion for nginx-lens
|
|
46
|
+
#compdef nginx-lens
|
|
47
|
+
|
|
48
|
+
_nginx_lens() {{
|
|
49
|
+
local -a commands
|
|
50
|
+
commands=(
|
|
51
|
+
{' '.join(f'"{cmd}":{cmd}' for cmd in commands)}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
_describe 'command' commands
|
|
55
|
+
}}
|
|
56
|
+
|
|
57
|
+
_nginx_lens "$@"
|
|
58
|
+
"""
|
|
59
|
+
elif shell == "fish":
|
|
60
|
+
script = f"""# Fish completion for nginx-lens
|
|
61
|
+
complete -c nginx-lens -f -a "{' '.join(commands)}"
|
|
62
|
+
"""
|
|
63
|
+
elif shell == "powershell":
|
|
64
|
+
script = f"""# PowerShell completion for nginx-lens
|
|
65
|
+
Register-ArgumentCompleter -Native -CommandName nginx-lens -ScriptBlock {{
|
|
66
|
+
param($commandName, $wordToComplete, $cursorPosition)
|
|
67
|
+
|
|
68
|
+
$commands = @({', '.join(f'"{cmd}"' for cmd in commands)})
|
|
69
|
+
|
|
70
|
+
$commands | Where-Object {{ $_ -like "$wordToComplete*" }} | ForEach-Object {{
|
|
71
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
72
|
+
}}
|
|
73
|
+
}}
|
|
74
|
+
"""
|
|
75
|
+
else:
|
|
76
|
+
script = f"# Completion script for {shell}\n# Use: typer commands.cli app {shell}\n"
|
|
77
|
+
|
|
78
|
+
return script
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@app.command()
|
|
82
|
+
def install(
|
|
83
|
+
shell: str = typer.Argument(..., help="Тип shell: bash, zsh, fish, powershell"),
|
|
84
|
+
output: str = typer.Option(None, "--output", "-o", help="Путь для сохранения скрипта (по умолчанию выводится в stdout)"),
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Генерирует скрипт автодополнения для указанного shell.
|
|
88
|
+
|
|
89
|
+
После генерации нужно добавить его в конфигурацию shell:
|
|
90
|
+
|
|
91
|
+
Bash:
|
|
92
|
+
nginx-lens completion install bash >> ~/.bashrc
|
|
93
|
+
|
|
94
|
+
Zsh:
|
|
95
|
+
nginx-lens completion install zsh >> ~/.zshrc
|
|
96
|
+
|
|
97
|
+
Fish:
|
|
98
|
+
nginx-lens completion install fish > ~/.config/fish/completions/nginx-lens.fish
|
|
99
|
+
|
|
100
|
+
PowerShell:
|
|
101
|
+
nginx-lens completion install powershell > nginx-lens-completion.ps1
|
|
102
|
+
# Затем выполните: . .\nginx-lens-completion.ps1
|
|
103
|
+
"""
|
|
104
|
+
import subprocess
|
|
105
|
+
import sys
|
|
106
|
+
|
|
107
|
+
# Пробуем использовать typer CLI для генерации completion
|
|
108
|
+
completion_script = None
|
|
109
|
+
try:
|
|
110
|
+
result = subprocess.run(
|
|
111
|
+
[sys.executable, "-m", "typer", "commands.cli", "app", shell],
|
|
112
|
+
capture_output=True,
|
|
113
|
+
text=True,
|
|
114
|
+
check=False,
|
|
115
|
+
timeout=10
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if result.returncode == 0 and result.stdout and len(result.stdout.strip()) > 0:
|
|
119
|
+
completion_script = result.stdout
|
|
120
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
# Если typer CLI не сработал, используем базовый скрипт
|
|
124
|
+
if not completion_script:
|
|
125
|
+
completion_script = _generate_completion_script(shell)
|
|
126
|
+
|
|
127
|
+
if shell not in ["bash", "zsh", "fish", "powershell"]:
|
|
128
|
+
console.print(f"[red]Неподдерживаемый shell: {shell}[/red]")
|
|
129
|
+
console.print("[yellow]Поддерживаемые shell: bash, zsh, fish, powershell[/yellow]")
|
|
130
|
+
raise typer.Exit(1)
|
|
131
|
+
|
|
132
|
+
if output:
|
|
133
|
+
try:
|
|
134
|
+
with open(output, 'w') as f:
|
|
135
|
+
f.write(completion_script)
|
|
136
|
+
console.print(f"[green]✓ Скрипт автодополнения сохранен в {output}[/green]")
|
|
137
|
+
console.print(f"[yellow]Не забудьте добавить его в конфигурацию {shell}[/yellow]")
|
|
138
|
+
except Exception as e:
|
|
139
|
+
console.print(f"[red]Ошибка при сохранении файла: {e}[/red]")
|
|
140
|
+
raise typer.Exit(1)
|
|
141
|
+
else:
|
|
142
|
+
typer.echo(completion_script)
|
|
143
|
+
# Выводим инструкцию в stderr, чтобы не мешать скрипту
|
|
144
|
+
console.print("\n[yellow]Добавьте этот скрипт в конфигурацию вашего shell[/yellow]", file=sys.stderr)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@app.command()
|
|
148
|
+
def show_instructions():
|
|
149
|
+
"""
|
|
150
|
+
Показывает инструкции по установке автодополнения для всех поддерживаемых shell.
|
|
151
|
+
"""
|
|
152
|
+
console.print(Panel("[bold blue]Инструкции по установке автодополнения[/bold blue]", box=box.ROUNDED))
|
|
153
|
+
|
|
154
|
+
console.print("\n[bold]Bash:[/bold]")
|
|
155
|
+
console.print(" nginx-lens completion install bash >> ~/.bashrc")
|
|
156
|
+
console.print(" source ~/.bashrc")
|
|
157
|
+
|
|
158
|
+
console.print("\n[bold]Zsh:[/bold]")
|
|
159
|
+
console.print(" nginx-lens completion install zsh >> ~/.zshrc")
|
|
160
|
+
console.print(" source ~/.zshrc")
|
|
161
|
+
|
|
162
|
+
console.print("\n[bold]Fish:[/bold]")
|
|
163
|
+
console.print(" mkdir -p ~/.config/fish/completions")
|
|
164
|
+
console.print(" nginx-lens completion install fish > ~/.config/fish/completions/nginx-lens.fish")
|
|
165
|
+
|
|
166
|
+
console.print("\n[bold]PowerShell:[/bold]")
|
|
167
|
+
console.print(" nginx-lens completion install powershell > nginx-lens-completion.ps1")
|
|
168
|
+
console.print(" . .\\nginx-lens-completion.ps1")
|
|
169
|
+
console.print(" # Или добавьте в профиль PowerShell: $PROFILE")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
app()
|
|
174
|
+
|
|
@@ -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
|
|
@@ -24,10 +25,10 @@ def diff(
|
|
|
24
25
|
lines2 = f2.readlines()
|
|
25
26
|
except FileNotFoundError as e:
|
|
26
27
|
console.print(f"[red]Файл {e.filename} не найден. Проверьте путь к конфигу.[/red]")
|
|
27
|
-
|
|
28
|
+
sys.exit(1)
|
|
28
29
|
except Exception as e:
|
|
29
30
|
console.print(f"[red]Ошибка при чтении файлов: {e}[/red]")
|
|
30
|
-
|
|
31
|
+
sys.exit(1)
|
|
31
32
|
maxlen = max(len(lines1), len(lines2))
|
|
32
33
|
# Выравниваем длины
|
|
33
34
|
lines1 += [''] * (maxlen - len(lines1))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
import typer
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
from parser.nginx_parser import parse_nginx_config
|
|
@@ -21,10 +22,10 @@ def graph(
|
|
|
21
22
|
tree = parse_nginx_config(config_path)
|
|
22
23
|
except FileNotFoundError:
|
|
23
24
|
console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
|
|
24
|
-
|
|
25
|
+
sys.exit(1)
|
|
25
26
|
except Exception as e:
|
|
26
27
|
console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
|
|
27
|
-
|
|
28
|
+
sys.exit(1)
|
|
28
29
|
routes = []
|
|
29
30
|
# Для каждого server/location строим маршрут
|
|
30
31
|
def walk(d, chain, upstreams):
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from upstream_checker.checker import check_upstreams, resolve_upstreams
|
|
7
|
+
from upstream_checker.dns_cache import disable_cache, enable_cache
|
|
8
|
+
from parser.nginx_parser import parse_nginx_config
|
|
9
|
+
from exporter.json_yaml import format_health_results, print_export
|
|
10
|
+
from config.config_loader import get_config
|
|
11
|
+
from utils.progress import ProgressManager
|
|
12
|
+
|
|
13
|
+
app = typer.Typer()
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
def health(
|
|
17
|
+
config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
|
|
18
|
+
timeout: Optional[float] = typer.Option(None, help="Таймаут проверки (сек)"),
|
|
19
|
+
retries: Optional[int] = typer.Option(None, help="Количество попыток"),
|
|
20
|
+
mode: Optional[str] = typer.Option(None, help="Режим проверки: tcp или http", case_sensitive=False),
|
|
21
|
+
resolve: bool = typer.Option(False, "--resolve", "-r", help="Показать резолвленные IP-адреса"),
|
|
22
|
+
max_workers: Optional[int] = typer.Option(None, "--max-workers", "-w", help="Максимальное количество потоков для параллельной обработки"),
|
|
23
|
+
json: bool = typer.Option(False, "--json", help="Экспортировать результаты в JSON"),
|
|
24
|
+
yaml: bool = typer.Option(False, "--yaml", help="Экспортировать результаты в YAML"),
|
|
25
|
+
no_cache: bool = typer.Option(False, "--no-cache", help="Отключить кэширование DNS резолвинга"),
|
|
26
|
+
cache_ttl: Optional[int] = typer.Option(None, "--cache-ttl", help="Время жизни кэша в секундах"),
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
Проверяет доступность upstream-серверов, определённых в nginx.conf. Выводит таблицу.
|
|
30
|
+
Использует параллельную обработку для ускорения проверки множества upstream серверов.
|
|
31
|
+
Поддерживает кэширование результатов DNS резолвинга для ускорения повторных запусков.
|
|
32
|
+
|
|
33
|
+
Пример:
|
|
34
|
+
nginx-lens health /etc/nginx/nginx.conf
|
|
35
|
+
nginx-lens health /etc/nginx/nginx.conf --timeout 5 --retries 3 --mode http
|
|
36
|
+
nginx-lens health /etc/nginx/nginx.conf --resolve
|
|
37
|
+
nginx-lens health /etc/nginx/nginx.conf --max-workers 20
|
|
38
|
+
nginx-lens health /etc/nginx/nginx.conf --resolve --no-cache
|
|
39
|
+
nginx-lens health /etc/nginx/nginx.conf --resolve --cache-ttl 600
|
|
40
|
+
"""
|
|
41
|
+
exit_code = 0
|
|
42
|
+
|
|
43
|
+
# Загружаем конфигурацию
|
|
44
|
+
config = get_config()
|
|
45
|
+
defaults = config.get_defaults()
|
|
46
|
+
cache_config = config.get_cache_config()
|
|
47
|
+
|
|
48
|
+
# Применяем значения из конфига, если не указаны через CLI
|
|
49
|
+
timeout = timeout if timeout is not None else defaults.get("timeout", 2.0)
|
|
50
|
+
retries = retries if retries is not None else defaults.get("retries", 1)
|
|
51
|
+
mode = mode if mode is not None else defaults.get("mode", "tcp")
|
|
52
|
+
max_workers = max_workers if max_workers is not None else defaults.get("max_workers", 10)
|
|
53
|
+
cache_ttl = cache_ttl if cache_ttl is not None else cache_config.get("ttl", defaults.get("dns_cache_ttl", 300))
|
|
54
|
+
|
|
55
|
+
# Управление кэшем
|
|
56
|
+
use_cache = not no_cache and cache_config.get("enabled", True)
|
|
57
|
+
if no_cache:
|
|
58
|
+
disable_cache()
|
|
59
|
+
else:
|
|
60
|
+
enable_cache()
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
tree = parse_nginx_config(config_path)
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
upstreams = tree.get_upstreams()
|
|
72
|
+
|
|
73
|
+
# Подсчитываем общее количество серверов для прогресс-бара
|
|
74
|
+
total_servers = sum(len(servers) for servers in upstreams.values())
|
|
75
|
+
|
|
76
|
+
# Проверка upstream с прогресс-баром
|
|
77
|
+
with ProgressManager(description="Проверка upstream серверов", show_progress=total_servers > 5) as pm:
|
|
78
|
+
results = check_upstreams(upstreams, timeout=timeout, retries=retries, mode=mode.lower(), max_workers=max_workers, progress_manager=pm)
|
|
79
|
+
|
|
80
|
+
# Если нужно показать резолвленные IP-адреса
|
|
81
|
+
resolved_info = {}
|
|
82
|
+
if resolve:
|
|
83
|
+
with ProgressManager(description="Резолвинг DNS", show_progress=total_servers > 5) as pm:
|
|
84
|
+
resolved_info = resolve_upstreams(upstreams, max_workers=max_workers, use_cache=use_cache, cache_ttl=cache_ttl, progress_manager=pm)
|
|
85
|
+
|
|
86
|
+
# Экспорт в JSON/YAML
|
|
87
|
+
if json or yaml:
|
|
88
|
+
export_data = format_health_results(results, resolved_info if resolve else None)
|
|
89
|
+
format_type = 'json' if json else 'yaml'
|
|
90
|
+
print_export(export_data, format_type)
|
|
91
|
+
# Exit code остается прежним
|
|
92
|
+
for name, servers in results.items():
|
|
93
|
+
for srv in servers:
|
|
94
|
+
if not srv["healthy"]:
|
|
95
|
+
exit_code = 1
|
|
96
|
+
if resolve and name in resolved_info:
|
|
97
|
+
for resolved_srv in resolved_info[name]:
|
|
98
|
+
if resolved_srv["address"] == srv["address"]:
|
|
99
|
+
if any("invalid resolve" in r for r in resolved_srv["resolved"]):
|
|
100
|
+
exit_code = 1
|
|
101
|
+
break
|
|
102
|
+
sys.exit(exit_code)
|
|
103
|
+
|
|
104
|
+
# Обычный вывод в таблицу
|
|
105
|
+
table = Table(show_header=True, header_style="bold blue")
|
|
106
|
+
table.add_column("Address")
|
|
107
|
+
table.add_column("Status")
|
|
108
|
+
if resolve:
|
|
109
|
+
table.add_column("Resolved IP")
|
|
110
|
+
|
|
111
|
+
for name, servers in results.items():
|
|
112
|
+
for srv in servers:
|
|
113
|
+
status = "Healthy" if srv["healthy"] else "Unhealthy"
|
|
114
|
+
color = "green" if srv["healthy"] else "red"
|
|
115
|
+
|
|
116
|
+
# Проверяем статус здоровья
|
|
117
|
+
if not srv["healthy"]:
|
|
118
|
+
exit_code = 1
|
|
119
|
+
|
|
120
|
+
if resolve:
|
|
121
|
+
resolved_list = []
|
|
122
|
+
if name in resolved_info:
|
|
123
|
+
for resolved_srv in resolved_info[name]:
|
|
124
|
+
if resolved_srv["address"] == srv["address"]:
|
|
125
|
+
resolved_list = resolved_srv["resolved"]
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
if resolved_list:
|
|
129
|
+
# Показываем все IP-адреса через запятую
|
|
130
|
+
resolved_str = ", ".join(resolved_list)
|
|
131
|
+
# Если есть "invalid resolve", показываем красным, иначе зеленым
|
|
132
|
+
if any("invalid resolve" in r for r in resolved_list):
|
|
133
|
+
table.add_row(srv["address"], f"[{color}]{status}[/{color}]", f"[red]{resolved_str}[/red]")
|
|
134
|
+
exit_code = 1
|
|
135
|
+
else:
|
|
136
|
+
table.add_row(srv["address"], f"[{color}]{status}[/{color}]", f"[green]{resolved_str}[/green]")
|
|
137
|
+
else:
|
|
138
|
+
table.add_row(srv["address"], f"[{color}]{status}[/{color}]", "[yellow]Failed to resolve[/yellow]")
|
|
139
|
+
exit_code = 1
|
|
140
|
+
else:
|
|
141
|
+
table.add_row(srv["address"], f"[{color}]{status}[/{color}]")
|
|
142
|
+
|
|
143
|
+
console.print(table)
|
|
144
|
+
sys.exit(exit_code)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
import typer
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
from rich.tree import Tree
|
|
@@ -22,10 +23,10 @@ def include_tree(
|
|
|
22
23
|
tree = build_include_tree(config_path)
|
|
23
24
|
except FileNotFoundError:
|
|
24
25
|
console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
|
|
25
|
-
|
|
26
|
+
sys.exit(1)
|
|
26
27
|
except Exception as e:
|
|
27
28
|
console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
|
|
28
|
-
|
|
29
|
+
sys.exit(1)
|
|
29
30
|
rich_tree = Tree(f"[bold blue]{config_path}[/bold blue]")
|
|
30
31
|
def _add(node, t):
|
|
31
32
|
for k, v in t.items():
|