Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Code quality - Ruff formatter
run: ruff format --check .

- name: Run health test suite
run: |
pytest tests/unit/health/ tests/integration/test_health_*.py -q
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine black mypy pip-audit types-requests types-PyYAML types-setuptools
pip install build twine ruff mypy pip-audit types-requests types-PyYAML types-setuptools

- name: Set up Node.js
uses: actions/setup-node@v4
Expand All @@ -40,8 +40,8 @@ jobs:
npm ci
npm run build

- name: Code Quality - Black Formatter
run: black --check .
- name: Code Quality - Ruff Formatter
run: ruff format --check .

- name: Code Quality - Type Safety (Mypy)
run: mypy .
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ pip install -e ".[dev]"
We enforce strict quality gates. All checks must pass before merging.

### Formatting & Linting
We use **Black** (formatter) and **Isort** (import sorter).
We use **Ruff** for formatting and linting.
```bash
black .
isort .
ruff format .
ruff check --fix .
```

### Static Type Checking
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Code style: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)


A comprehensive Python module for managing Elasticsearch operations within pipeline processes.
Expand Down
125 changes: 125 additions & 0 deletions elastro-logloom.ndjson

Large diffs are not rendered by default.

56 changes: 42 additions & 14 deletions elastro/cli/commands/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,22 +158,38 @@ def health_group() -> None:
default=False,
help="With --fix, show planned API calls without executing",
)
@click.option("--yes", is_flag=True, default=False, help="With --fix, auto-confirm CONFIRM actions")
@click.option(
"--yes",
is_flag=True,
default=False,
help="With --fix, auto-confirm CONFIRM actions",
)
@click.option(
"--force",
is_flag=True,
default=False,
help="With --fix and --yes, allow DESTRUCTIVE actions in non-interactive mode",
)
@click.option("--index", "index_pattern", type=str, default=None, help="Limit fixes to an index pattern")
@click.option(
"--index",
"index_pattern",
type=str,
default=None,
help="Limit fixes to an index pattern",
)
@click.option(
"--action",
"action_filter",
type=click.Choice(CATALOG_ACTION_IDS, case_sensitive=False),
default=None,
help="Limit fixes to a single remediation action",
)
@click.option("--target-replicas", type=int, default=None, help="Explicit replica target for reduce_replicas")
@click.option(
"--target-replicas",
type=int,
default=None,
help="Explicit replica target for reduce_replicas",
)
@click.option(
"--history/--no-history",
default=False,
Expand Down Expand Up @@ -386,7 +402,13 @@ def _render_health_fix(
default=False,
help="With --yes, allow DESTRUCTIVE actions in non-interactive mode",
)
@click.option("--index", "index_pattern", type=str, default=None, help="Limit fixes to an index pattern")
@click.option(
"--index",
"index_pattern",
type=str,
default=None,
help="Limit fixes to an index pattern",
)
@click.option(
"--action",
"action_filter",
Expand Down Expand Up @@ -846,7 +868,9 @@ def health_nodes(


@health_group.command("shards")
@click.option("--index", type=str, default=None, help="Limit to a specific index pattern")
@click.option(
"--index", type=str, default=None, help="Limit to a specific index pattern"
)
@click.option(
"--analyze",
is_flag=True,
Expand Down Expand Up @@ -977,7 +1001,9 @@ def health_shards(
default=False,
help="Show only indices with failed or stuck ILM lifecycle steps",
)
@click.option("--index", "index_pattern", type=str, default=None, help="Limit to an index pattern")
@click.option(
"--index", "index_pattern", type=str, default=None, help="Limit to an index pattern"
)
@click.pass_context
def health_ilm(
ctx: click.Context,
Expand Down Expand Up @@ -1203,9 +1229,7 @@ def health_rollback_list(ctx: click.Context, last: int) -> None:
f"{record.action_id} {record.applied_at.isoformat()}"
)
else:
payload = {
"rollbacks": [record.model_dump(mode="json") for record in records]
}
payload = {"rollbacks": [record.model_dump(mode="json") for record in records]}
click.echo(format_output(payload, output_format=output_fmt))


Expand Down Expand Up @@ -1251,10 +1275,14 @@ def _run_health_rollback(
rollback_id,
dry_run,
)
audit = None if dry_run else HealthAuditLogger(
client,
profile=_cli_profile(ctx),
host=_client_host(client),
audit = (
None
if dry_run
else HealthAuditLogger(
client,
profile=_cli_profile(ctx),
host=_client_host(client),
)
)
executor = RemediationExecutor(
client,
Expand Down Expand Up @@ -1395,4 +1423,4 @@ def health_report(
click.echo(format_output(result, output_format="json"))
except OperationError as e:
click.echo(f"Error fetching health report: {str(e)}", err=True)
raise SystemExit(1) from e
raise SystemExit(1) from e
18 changes: 14 additions & 4 deletions elastro/cli/commands/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ def update_index(client: ElasticsearchClient, name: str, settings: Any) -> None:
help="Preview deletion without executing (scriptable with -o json)",
)
@click.pass_obj
def delete_index(client: ElasticsearchClient, name: str, force: bool, dry_run: bool) -> None:
def delete_index(
client: ElasticsearchClient, name: str, force: bool, dry_run: bool
) -> None:
"""
Delete an index.

Expand All @@ -210,7 +212,11 @@ def delete_index(client: ElasticsearchClient, name: str, force: bool, dry_run: b
elastro index delete my-logs --force
```
"""
from elastro.cli.deletion import emit_delete_preview, preview_index_delete, should_prompt_for_delete
from elastro.cli.deletion import (
emit_delete_preview,
preview_index_delete,
should_prompt_for_delete,
)

if dry_run:
emit_delete_preview(preview_index_delete(client, name))
Expand Down Expand Up @@ -337,7 +343,9 @@ def list_indices(client: ElasticsearchClient, pattern: str) -> None:
color = (
"green"
if health == "green"
else "yellow" if health == "yellow" else "red"
else "yellow"
if health == "yellow"
else "red"
)

table.add_row(
Expand Down Expand Up @@ -603,7 +611,9 @@ def fix_indices(client: ElasticsearchClient) -> None:
fix_result = run_health_fix(
client,
interactive=True,
confirm=lambda prompt, default: Confirm.ask(f" {prompt}", default=default),
confirm=lambda prompt, default: Confirm.ask(
f" {prompt}", default=default
),
prompt=lambda message: Prompt.ask(f" {message}"),
)
render_fix_run_result(fix_result, output_format="table")
Expand Down
9 changes: 6 additions & 3 deletions elastro/cli/commands/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ def delete_repository(
elastro -o json snapshot repo delete my_backup --dry-run
```
"""
from elastro.cli.deletion import emit_delete_preview, preview_snapshot_repository_delete
from elastro.cli.deletion import (
emit_delete_preview,
preview_snapshot_repository_delete,
)

if dry_run:
emit_delete_preview(preview_snapshot_repository_delete(client, name))
Expand Down Expand Up @@ -307,7 +310,7 @@ def restore_snapshot(
if not repository:
console.print("\nAvailable Repositories:")
for i, r in enumerate(repo_names):
console.print(f" {i+1}. {r}")
console.print(f" {i + 1}. {r}")

repo_idx = int(Prompt.ask("Select Repository", default="1")) - 1
repository = repo_names[repo_idx]
Expand All @@ -334,7 +337,7 @@ def restore_snapshot(
for i in range(limit):
s = snaps[i]
console.print(
f" {i+1}. {s['snapshot']} ({s['state']}, {len(s['indices'])} indices)"
f" {i + 1}. {s['snapshot']} ({s['state']}, {len(s['indices'])} indices)"
)

snap_idx = int(Prompt.ask("Select Snapshot", default="1")) - 1
Expand Down
15 changes: 11 additions & 4 deletions elastro/cli/deletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,15 @@ def emit_delete_preview(preview: DeletePreview) -> None:
click.echo(f" {key}: {value}")
return

click.echo(format_output(delete_preview_payload(preview), output_format=output_fmt), nl=False)
click.echo(
format_output(delete_preview_payload(preview), output_format=output_fmt),
nl=False,
)


def should_prompt_for_delete(*, dry_run: bool, force: bool = False, yes: bool = False) -> bool:
def should_prompt_for_delete(
*, dry_run: bool, force: bool = False, yes: bool = False
) -> bool:
"""Return True when interactive confirmation is required."""
if dry_run:
return False
Expand Down Expand Up @@ -325,7 +330,9 @@ def preview_script_delete(client: ElasticsearchClient, script_id: str) -> Delete
)


def preview_pipeline_delete(client: ElasticsearchClient, pipeline_id: str) -> DeletePreview:
def preview_pipeline_delete(
client: ElasticsearchClient, pipeline_id: str
) -> DeletePreview:
exists = False
try:
client._ensure_connected()
Expand All @@ -344,4 +351,4 @@ def preview_pipeline_delete(client: ElasticsearchClient, pipeline_id: str) -> De
if exists
else f"Ingest pipeline '{pipeline_id}' does not exist; delete would fail."
),
)
)
4 changes: 4 additions & 0 deletions elastro/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class ElasticIndexError(OperationError):
pass


# Backward-compatible alias used by tests and docs.
IndexError = ElasticIndexError


class DocumentError(OperationError):
"""Raised when a document operation fails."""

Expand Down
4 changes: 3 additions & 1 deletion elastro/core/ilm.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,6 @@ def retry_lifecycle(self, index: str) -> bool:
return True
except Exception as e:
logger.error("Failed to retry ILM lifecycle for '%s': %s", index, e)
raise OperationError(f"Failed to retry ILM lifecycle for '{index}': {e}") from e
raise OperationError(
f"Failed to retry ILM lifecycle for '{index}': {e}"
) from e
6 changes: 2 additions & 4 deletions elastro/core/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,7 @@ def configure_logging(log_level: Optional[str] = None) -> logging.Logger:
)
package_logger.addHandler(file_handler)
except Exception:
sys.stderr.write(
f"Warning: Could not set up file logging to {LOG_FILE_PATH}\n"
)
sys.stderr.write(f"Warning: Could not set up file logging to {LOG_FILE_PATH}\n")

if _logloom_handler is not None:
try:
Expand Down Expand Up @@ -158,4 +156,4 @@ def get_logger(name: str, log_level: Optional[str] = None) -> logging.Logger:
logger.handlers.clear()
if log_level is not None:
logger.setLevel(_resolve_level(log_level))
return logger
return logger
7 changes: 5 additions & 2 deletions elastro/health/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
Severity,
)
from elastro.health.assessor import HealthAssessor
from elastro.health.collectors.health_report import HealthReportCollector, map_indicators
from elastro.health.collectors.health_report import (
HealthReportCollector,
map_indicators,
)
from elastro.health.manager import HealthManager
from elastro.health.remediation import RemediationCatalog, RemediationExecutor

Expand All @@ -26,4 +29,4 @@
"RemediationExecutor",
"RemediationSafety",
"Severity",
]
]
13 changes: 7 additions & 6 deletions elastro/health/assessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ def run(
title="Health report unavailable",
status=FindingStatus.SKIPPED,
severity=Severity.LOW,
summary=result.error or "Health report collector was skipped.",
summary=result.error
or "Health report collector was skipped.",
source="collector",
)
)
Expand All @@ -131,7 +132,9 @@ def run(
if result.name == "health_report":
report_data = result.data
raw_health_report = report_data.get("report")
cluster_name = report_data.get("cluster_name", cluster_name) or cluster_name
cluster_name = (
report_data.get("cluster_name", cluster_name) or cluster_name
)
indicators = report_data.get("indicators", {})
overall_score = compute_weighted_score(indicators)
findings.extend(non_passing_findings(report_data.get("findings", [])))
Expand Down Expand Up @@ -225,9 +228,7 @@ def run(
overall_score = max(0, overall_score - deduction)

collector_data = {
result.name: result.data
for result in results
if result.status == "ok"
result.name: result.data for result in results if result.status == "ok"
}
disk_data = collector_data.get("disk") or {}
if isinstance(disk_data, dict) and disk_data.get("cluster_settings"):
Expand Down Expand Up @@ -395,4 +396,4 @@ def _run_collectors(

results.append(result)

return results
return results
2 changes: 1 addition & 1 deletion elastro/health/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,4 @@ def log_rollback(
record.session_id,
payload,
cluster_name=record.cluster_name,
)
)
2 changes: 1 addition & 1 deletion elastro/health/collectors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
"Collector",
"CollectorRegistry",
"CollectorResult",
]
]
2 changes: 1 addition & 1 deletion elastro/health/collectors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@ def run(
)
results.append(result)

return results
return results
Loading
Loading