Add --exclude-path to enroll diff command
So that you can ignore certain churn from the diff (stuff you still wanted to harvest as a baseline but don't care if it changes day to day)
This commit is contained in:
parent
8be821c494
commit
ca3d958a96
4 changed files with 50 additions and 3 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
* Introduce `enroll validate` - a tool to validate a harvest against the state schema, or check for missing or orphaned obsolete artifacts in a harvest.
|
* Introduce `enroll validate` - a tool to validate a harvest against the state schema, or check for missing or orphaned obsolete artifacts in a harvest.
|
||||||
* Attempt to generate Jinja2 templates of systemd unit files and Postfix main.cf (now that JinjaTurtle supports it)
|
* Attempt to generate Jinja2 templates of systemd unit files and Postfix main.cf (now that JinjaTurtle supports it)
|
||||||
* Update pynacl dependency to resolve CVE-2025-69277
|
* Update pynacl dependency to resolve CVE-2025-69277
|
||||||
|
* Add `--exclude-path` to `enroll diff` command, so that you can ignore certain churn from the diff (stuff you still wanted to harvest as a baseline but don't care if it changes day to day)
|
||||||
|
|
||||||
# 0.3.0
|
# 0.3.0
|
||||||
|
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -131,6 +131,7 @@ Compare two harvest bundles and report what changed.
|
||||||
**Inputs**
|
**Inputs**
|
||||||
- `--old <harvest>` and `--new <harvest>` (directories or `state.json` paths)
|
- `--old <harvest>` and `--new <harvest>` (directories or `state.json` paths)
|
||||||
- `--sops` when comparing SOPS-encrypted harvest bundles
|
- `--sops` when comparing SOPS-encrypted harvest bundles
|
||||||
|
- `--exclude-path` if you want to ignore certain files that changed in the diff
|
||||||
|
|
||||||
**Output formats**
|
**Output formats**
|
||||||
- `--format json` (default for webhooks)
|
- `--format json` (default for webhooks)
|
||||||
|
|
@ -164,8 +165,7 @@ Validates a harvest by checking:
|
||||||
* state.json exists and is valid JSON
|
* state.json exists and is valid JSON
|
||||||
* state.json validates against a JSON Schema (by default the vendored one)
|
* state.json validates against a JSON Schema (by default the vendored one)
|
||||||
* Every `managed_file` entry has a corresponding artifact at: `artifacts/<role_name>/<src_rel>`
|
* Every `managed_file` entry has a corresponding artifact at: `artifacts/<role_name>/<src_rel>`
|
||||||
|
* That there are no **unreferenced files** sitting in `artifacts/` that aren't in the state.
|
||||||
It also warns if there are **unreferenced files** sitting in `artifacts/`.
|
|
||||||
|
|
||||||
#### Schema location + overrides
|
#### Schema location + overrides
|
||||||
|
|
||||||
|
|
@ -400,7 +400,7 @@ enroll single-shot --remote-host myhost.example.com --remote-user myuser --har
|
||||||
|
|
||||||
## Diff
|
## Diff
|
||||||
|
|
||||||
### Compare two harvest directories
|
### Compare two harvest directories, output in json
|
||||||
```bash
|
```bash
|
||||||
enroll diff --old /path/to/harvestA --new /path/to/harvestB --format json
|
enroll diff --old /path/to/harvestA --new /path/to/harvestB --format json
|
||||||
```
|
```
|
||||||
|
|
@ -412,6 +412,11 @@ enroll diff --old /path/to/golden/harvest --new /path/to/new/harvest --web
|
||||||
|
|
||||||
`diff` mode also supports email sending and text or markdown format, as well as `--exit-code` mode to trigger a return code of 2 (useful for crons or CI)
|
`diff` mode also supports email sending and text or markdown format, as well as `--exit-code` mode to trigger a return code of 2 (useful for crons or CI)
|
||||||
|
|
||||||
|
### Ignore a specific directory or file from the diff
|
||||||
|
```bash
|
||||||
|
enroll diff --old /path/to/harvestA --new /path/to/harvestB --exclude-path /var/anacron
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Explain
|
## Explain
|
||||||
|
|
|
||||||
|
|
@ -550,6 +550,16 @@ def main() -> None:
|
||||||
default="text",
|
default="text",
|
||||||
help="Report output format (default: text).",
|
help="Report output format (default: text).",
|
||||||
)
|
)
|
||||||
|
d.add_argument(
|
||||||
|
"--exclude-path",
|
||||||
|
action="append",
|
||||||
|
default=[],
|
||||||
|
metavar="PATTERN",
|
||||||
|
help=(
|
||||||
|
"Exclude file paths from the diff report (repeatable). Supports globs (including '**') and regex via 're:<regex>'. "
|
||||||
|
"This affects file drift reporting only (added/removed/changed files), not package/service/user diffs."
|
||||||
|
),
|
||||||
|
)
|
||||||
d.add_argument(
|
d.add_argument(
|
||||||
"--out",
|
"--out",
|
||||||
help="Write the report to this file instead of stdout.",
|
help="Write the report to this file instead of stdout.",
|
||||||
|
|
@ -827,6 +837,7 @@ def main() -> None:
|
||||||
args.old,
|
args.old,
|
||||||
args.new,
|
args.new,
|
||||||
sops_mode=bool(getattr(args, "sops", False)),
|
sops_mode=bool(getattr(args, "sops", False)),
|
||||||
|
exclude_paths=list(getattr(args, "exclude_path", []) or []),
|
||||||
)
|
)
|
||||||
|
|
||||||
txt = format_report(report, fmt=str(getattr(args, "format", "text")))
|
txt = format_report(report, fmt=str(getattr(args, "format", "text")))
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from pathlib import Path
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
from .remote import _safe_extract_tar
|
from .remote import _safe_extract_tar
|
||||||
|
from .pathfilter import PathFilter
|
||||||
from .sopsutil import decrypt_file_binary_to, require_sops_cmd
|
from .sopsutil import decrypt_file_binary_to, require_sops_cmd
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -289,6 +290,7 @@ def compare_harvests(
|
||||||
new_path: str,
|
new_path: str,
|
||||||
*,
|
*,
|
||||||
sops_mode: bool = False,
|
sops_mode: bool = False,
|
||||||
|
exclude_paths: Optional[List[str]] = None,
|
||||||
) -> Tuple[Dict[str, Any], bool]:
|
) -> Tuple[Dict[str, Any], bool]:
|
||||||
"""Compare two harvests.
|
"""Compare two harvests.
|
||||||
|
|
||||||
|
|
@ -387,6 +389,17 @@ def compare_harvests(
|
||||||
|
|
||||||
old_files = _file_index(old_b.dir, old_state)
|
old_files = _file_index(old_b.dir, old_state)
|
||||||
new_files = _file_index(new_b.dir, new_state)
|
new_files = _file_index(new_b.dir, new_state)
|
||||||
|
|
||||||
|
# Optional user-supplied path exclusions (same semantics as harvest --exclude-path),
|
||||||
|
# applied only to file drift reporting.
|
||||||
|
diff_filter = PathFilter(include=(), exclude=exclude_paths or ())
|
||||||
|
if exclude_paths:
|
||||||
|
old_files = {
|
||||||
|
p: r for p, r in old_files.items() if not diff_filter.is_excluded(p)
|
||||||
|
}
|
||||||
|
new_files = {
|
||||||
|
p: r for p, r in new_files.items() if not diff_filter.is_excluded(p)
|
||||||
|
}
|
||||||
old_paths_set = set(old_files)
|
old_paths_set = set(old_files)
|
||||||
new_paths_set = set(new_files)
|
new_paths_set = set(new_files)
|
||||||
|
|
||||||
|
|
@ -462,6 +475,9 @@ def compare_harvests(
|
||||||
|
|
||||||
report: Dict[str, Any] = {
|
report: Dict[str, Any] = {
|
||||||
"generated_at": _utc_now_iso(),
|
"generated_at": _utc_now_iso(),
|
||||||
|
"filters": {
|
||||||
|
"exclude_paths": list(exclude_paths or []),
|
||||||
|
},
|
||||||
"old": {
|
"old": {
|
||||||
"input": old_path,
|
"input": old_path,
|
||||||
"bundle_dir": str(old_b.dir),
|
"bundle_dir": str(old_b.dir),
|
||||||
|
|
@ -532,6 +548,11 @@ def _report_text(report: Dict[str, Any]) -> str:
|
||||||
f"new: {new.get('input')} (host={new.get('host')}, state_mtime={new.get('state_mtime')})"
|
f"new: {new.get('input')} (host={new.get('host')}, state_mtime={new.get('state_mtime')})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
filt = report.get("filters", {}) or {}
|
||||||
|
ex_paths = filt.get("exclude_paths", []) or []
|
||||||
|
if ex_paths:
|
||||||
|
lines.append(f"file exclude patterns: {', '.join(str(p) for p in ex_paths)}")
|
||||||
|
|
||||||
pk = report.get("packages", {})
|
pk = report.get("packages", {})
|
||||||
lines.append("\nPackages")
|
lines.append("\nPackages")
|
||||||
lines.append(f" added: {len(pk.get('added', []) or [])}")
|
lines.append(f" added: {len(pk.get('added', []) or [])}")
|
||||||
|
|
@ -638,6 +659,15 @@ def _report_markdown(report: Dict[str, Any]) -> str:
|
||||||
f"- **New**: `{new.get('input')}` (host={new.get('host')}, state_mtime={new.get('state_mtime')})\n"
|
f"- **New**: `{new.get('input')}` (host={new.get('host')}, state_mtime={new.get('state_mtime')})\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
filt = report.get("filters", {}) or {}
|
||||||
|
ex_paths = filt.get("exclude_paths", []) or []
|
||||||
|
if ex_paths:
|
||||||
|
out.append(
|
||||||
|
"- **File exclude patterns**: "
|
||||||
|
+ ", ".join(f"`{p}`" for p in ex_paths)
|
||||||
|
+ "\n"
|
||||||
|
)
|
||||||
|
|
||||||
pk = report.get("packages", {})
|
pk = report.get("packages", {})
|
||||||
out.append("## Packages\n")
|
out.append("## Packages\n")
|
||||||
out.append(f"- Added: {len(pk.get('added', []) or [])}\n")
|
out.append(f"- Added: {len(pk.get('added', []) or [])}\n")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue