nginx-lens 0.4.0__py3-none-any.whl → 0.5.0__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 +6 -0
- commands/completion.py +174 -0
- commands/health.py +41 -6
- commands/logs.py +10 -2
- commands/metrics.py +495 -0
- commands/resolve.py +32 -2
- commands/validate.py +451 -0
- config/__init__.py +4 -0
- config/config_loader.py +200 -0
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/METADATA +1 -1
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/RECORD +19 -11
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/top_level.txt +2 -0
- upstream_checker/checker.py +47 -7
- upstream_checker/dns_cache.py +216 -0
- utils/__init__.py +4 -0
- utils/progress.py +120 -0
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/WHEEL +0 -0
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/entry_points.txt +0 -0
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/licenses/LICENSE +0 -0
commands/cli.py
CHANGED
|
@@ -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()
|
commands/completion.py
ADDED
|
@@ -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
|
+
|
commands/health.py
CHANGED
|
@@ -1,36 +1,64 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from typing import Optional
|
|
2
3
|
import typer
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
from rich.table import Table
|
|
5
6
|
from upstream_checker.checker import check_upstreams, resolve_upstreams
|
|
7
|
+
from upstream_checker.dns_cache import disable_cache, enable_cache
|
|
6
8
|
from parser.nginx_parser import parse_nginx_config
|
|
7
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
|
|
8
12
|
|
|
9
13
|
app = typer.Typer()
|
|
10
14
|
console = Console()
|
|
11
15
|
|
|
12
16
|
def health(
|
|
13
17
|
config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
|
|
14
|
-
timeout: float = typer.Option(
|
|
15
|
-
retries: int = typer.Option(
|
|
16
|
-
mode: str = typer.Option(
|
|
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),
|
|
17
21
|
resolve: bool = typer.Option(False, "--resolve", "-r", help="Показать резолвленные IP-адреса"),
|
|
18
|
-
max_workers: int = typer.Option(
|
|
22
|
+
max_workers: Optional[int] = typer.Option(None, "--max-workers", "-w", help="Максимальное количество потоков для параллельной обработки"),
|
|
19
23
|
json: bool = typer.Option(False, "--json", help="Экспортировать результаты в JSON"),
|
|
20
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="Время жизни кэша в секундах"),
|
|
21
27
|
):
|
|
22
28
|
"""
|
|
23
29
|
Проверяет доступность upstream-серверов, определённых в nginx.conf. Выводит таблицу.
|
|
24
30
|
Использует параллельную обработку для ускорения проверки множества upstream серверов.
|
|
31
|
+
Поддерживает кэширование результатов DNS резолвинга для ускорения повторных запусков.
|
|
25
32
|
|
|
26
33
|
Пример:
|
|
27
34
|
nginx-lens health /etc/nginx/nginx.conf
|
|
28
35
|
nginx-lens health /etc/nginx/nginx.conf --timeout 5 --retries 3 --mode http
|
|
29
36
|
nginx-lens health /etc/nginx/nginx.conf --resolve
|
|
30
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
|
|
31
40
|
"""
|
|
32
41
|
exit_code = 0
|
|
33
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
|
+
|
|
34
62
|
try:
|
|
35
63
|
tree = parse_nginx_config(config_path)
|
|
36
64
|
except FileNotFoundError:
|
|
@@ -41,12 +69,19 @@ def health(
|
|
|
41
69
|
sys.exit(1)
|
|
42
70
|
|
|
43
71
|
upstreams = tree.get_upstreams()
|
|
44
|
-
|
|
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)
|
|
45
79
|
|
|
46
80
|
# Если нужно показать резолвленные IP-адреса
|
|
47
81
|
resolved_info = {}
|
|
48
82
|
if resolve:
|
|
49
|
-
|
|
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)
|
|
50
85
|
|
|
51
86
|
# Экспорт в JSON/YAML
|
|
52
87
|
if json or yaml:
|
commands/logs.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from typing import Optional, List, Dict, Any
|
|
2
3
|
import typer
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
from rich.table import Table
|
|
@@ -6,9 +7,9 @@ import re
|
|
|
6
7
|
import gzip
|
|
7
8
|
from datetime import datetime, timedelta
|
|
8
9
|
from collections import Counter, defaultdict
|
|
9
|
-
from typing import Optional, List, Dict, Any
|
|
10
10
|
from exporter.json_yaml import format_logs_results, print_export
|
|
11
11
|
from exporter.csv import export_logs_to_csv
|
|
12
|
+
from config.config_loader import get_config
|
|
12
13
|
|
|
13
14
|
app = typer.Typer(help="Анализ access.log/error.log: топ-статусы, пути, IP, User-Agent, ошибки.")
|
|
14
15
|
console = Console()
|
|
@@ -23,7 +24,7 @@ log_line_re = re.compile(
|
|
|
23
24
|
|
|
24
25
|
def logs(
|
|
25
26
|
log_path: str = typer.Argument(..., help="Путь к access.log или error.log"),
|
|
26
|
-
top: int = typer.Option(
|
|
27
|
+
top: Optional[int] = typer.Option(None, help="Сколько топ-значений выводить"),
|
|
27
28
|
json: bool = typer.Option(False, "--json", help="Экспортировать результаты в JSON"),
|
|
28
29
|
yaml: bool = typer.Option(False, "--yaml", help="Экспортировать результаты в YAML"),
|
|
29
30
|
csv: bool = typer.Option(False, "--csv", help="Экспортировать результаты в CSV"),
|
|
@@ -49,6 +50,13 @@ def logs(
|
|
|
49
50
|
nginx-lens logs /var/log/nginx/access.log --since "2024-01-01" --status 404,500
|
|
50
51
|
nginx-lens logs /var/log/nginx/access.log.gz --detect-anomalies --json
|
|
51
52
|
"""
|
|
53
|
+
# Загружаем конфигурацию
|
|
54
|
+
config = get_config()
|
|
55
|
+
defaults = config.get_defaults()
|
|
56
|
+
|
|
57
|
+
# Применяем значения из конфига, если не указаны через CLI
|
|
58
|
+
top = top if top is not None else defaults.get("top", 10)
|
|
59
|
+
|
|
52
60
|
# Парсинг фильтров
|
|
53
61
|
status_filter = None
|
|
54
62
|
if status:
|