sparkforensics-operator 0.2.0__tar.gz → 0.3.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.
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/AGENTS.md +6 -5
- sparkforensics_operator-0.2.0/docs/architecture.md → sparkforensics_operator-0.3.0/ARCHITECTURE.md +60 -26
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/CHANGELOG.md +36 -1
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/CLAUDE.md +4 -4
- sparkforensics_operator-0.3.0/CONTRIBUTING.md +77 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/PKG-INFO +20 -11
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/README.md +19 -10
- sparkforensics_operator-0.3.0/docs/configuration.md +620 -0
- sparkforensics_operator-0.3.0/docs/getting-started.md +227 -0
- sparkforensics_operator-0.3.0/docs/index.md +28 -0
- sparkforensics_operator-0.3.0/docs/troubleshooting.md +406 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/pyproject.toml +1 -1
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/callback.py +16 -1
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/analyze/_cli.py +14 -1
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/analyze/_remote_job.py +16 -3
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/analyze/base.py +69 -9
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/analyze/ssh.py +28 -6
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/analyze/subprocess.py +11 -3
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/operator.py +155 -24
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/report.py +43 -6
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/summary.py +44 -2
- sparkforensics_operator-0.3.0/tests/fixtures/sparkforensics_cli_0_4_0/README.md +17 -0
- sparkforensics_operator-0.3.0/tests/fixtures/sparkforensics_cli_0_4_0/comparison.json +1064 -0
- sparkforensics_operator-0.3.0/tests/fixtures/sparkforensics_cli_0_4_0/comparison.stderr +1 -0
- sparkforensics_operator-0.3.0/tests/fixtures/sparkforensics_cli_0_4_0/incomplete-run.json +691 -0
- sparkforensics_operator-0.3.0/tests/fixtures/sparkforensics_cli_0_4_0/incomplete-run.stderr +1 -0
- sparkforensics_operator-0.3.0/tests/fixtures/sparkforensics_cli_0_4_0/report.json +1209 -0
- sparkforensics_operator-0.3.0/tests/fixtures/sparkforensics_cli_0_4_0/report.stderr +2 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/analyze/_local_ssh.py +8 -1
- sparkforensics_operator-0.3.0/tests/hooks/analyze/test_base.py +108 -0
- sparkforensics_operator-0.3.0/tests/hooks/analyze/test_cli.py +49 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/analyze/test_deferrable_parity.py +64 -9
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/analyze/test_ssh.py +40 -4
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/analyze/test_ssh_deferrable.py +17 -1
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/analyze/test_subprocess.py +41 -3
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_callback.py +16 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_operator.py +228 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_operator_deferrable.py +92 -0
- sparkforensics_operator-0.3.0/tests/test_report.py +180 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_summary.py +81 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_templating.py +31 -0
- sparkforensics_operator-0.2.0/docs/api-reference.md +0 -396
- sparkforensics_operator-0.2.0/docs/glossary.md +0 -58
- sparkforensics_operator-0.2.0/docs/runbook.md +0 -374
- sparkforensics_operator-0.2.0/tests/hooks/analyze/test_base.py +0 -38
- sparkforensics_operator-0.2.0/tests/hooks/analyze/test_cli.py +0 -17
- sparkforensics_operator-0.2.0/tests/test_report.py +0 -128
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/.github/workflows/ci.yml +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/.github/workflows/publish.yml +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/.gitignore +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/LICENSE +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/constraints-airflow-2.6-floor.txt +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/constraints-deferrable-floor.txt +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/coverage-no-ssh.ini +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/__init__.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/_compat.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/exceptions.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/get_provider_info.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/__init__.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/_templating.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/analyze/__init__.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/__init__.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/_dest_root.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/_history_server_args.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/_path_template.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/_rolling_log.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/base.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/filesystem.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/history_server.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/history_server_app.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/remote_path.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/sftp.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/tunnel.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/hooks/log_source/xcom.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/links.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/log_ref.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/notify.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/src/sparkforensics_operator/sinks.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/__init__.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/_render.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/__init__.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/analyze/__init__.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/__init__.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/test_base.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/test_filesystem.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/test_history_server.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/test_history_server_app.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/test_remote_path.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/test_sftp.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/test_tunnel.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/hooks/log_source/test_xcom.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_compat.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_dagbag_integration.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_exceptions.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_links.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_log_ref.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_notify.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_package_exports.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_provider_info.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_serialization.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tests/test_sinks.py +0 -0
- {sparkforensics_operator-0.2.0 → sparkforensics_operator-0.3.0}/tox.ini +0 -0
|
@@ -9,8 +9,8 @@ Python 3.9+, apache-airflow 2.6+ or 3.0+, pytest. Runtime dependency: Node.js
|
|
|
9
9
|
18+ + the `sparkforensics-cli` npm package (`npm install -g sparkforensics-cli`)
|
|
10
10
|
wherever the analysis runs: the worker for `SubprocessAnalyzeHook`, the SSH
|
|
11
11
|
host for `SSHAnalyzeHook`. Log sources resolve an `EventLogRef` (`log_ref.py`)
|
|
12
|
-
that backends consume; see `
|
|
13
|
-
SSH analysis as the SSH provider's detached remote job (
|
|
12
|
+
that backends consume; see `ARCHITECTURE.md`. `deferrable=True` runs the
|
|
13
|
+
SSH analysis as the SSH provider's detached remote job (`ARCHITECTURE.md`'s
|
|
14
14
|
"Deferrable execution").
|
|
15
15
|
|
|
16
16
|
## Commands
|
|
@@ -44,9 +44,10 @@ of going public. The original multi-PR history (42 commits, 4 PRs) lives in
|
|
|
44
44
|
indefinitely). `git blame`/`git log -p` on any line predating that date stop
|
|
45
45
|
at the squash commit; check the archive repo instead.
|
|
46
46
|
|
|
47
|
-
##
|
|
48
|
-
-
|
|
49
|
-
`
|
|
47
|
+
## Docs
|
|
48
|
+
- User docs: `docs/index.md` (getting started, configuration,
|
|
49
|
+
troubleshooting). Contributor docs: `CONTRIBUTING.md` (dev, tox matrix,
|
|
50
|
+
release), `ARCHITECTURE.md` (design notes).
|
|
50
51
|
|
|
51
52
|
## Maintaining this file
|
|
52
53
|
|
sparkforensics_operator-0.2.0/docs/architecture.md → sparkforensics_operator-0.3.0/ARCHITECTURE.md
RENAMED
|
@@ -30,8 +30,22 @@ Each backend declares the reference kinds it can read from where it runs
|
|
|
30
30
|
(`AnalyzeHook.supported_log_refs`); `AnalyzeHook.analyze()` rejects any
|
|
31
31
|
other kind with an error naming what it reads, before running anything, so
|
|
32
32
|
a mismatched pairing (say, `SSHAnalyzeHook` with a log the worker
|
|
33
|
-
downloaded) fails fast instead of analyzing the wrong file.
|
|
34
|
-
|
|
33
|
+
downloaded) fails fast instead of analyzing the wrong file.
|
|
34
|
+
|
|
35
|
+
A run can also be compared against an earlier one: `baseline_log_source`
|
|
36
|
+
is a second `LogSourceHook`, located and cleaned up next to `log_source`,
|
|
37
|
+
and the backend passes its reference to the CLI as `--baseline`. The CLI
|
|
38
|
+
reads a baseline only from a path, never from a History Server, so each
|
|
39
|
+
backend declares the kinds it can pass that way
|
|
40
|
+
(`AnalyzeHook.supported_baseline_refs`: `LocalEventLog` on the worker,
|
|
41
|
+
`RemoteEventLog` on the SSH host) and `analyze()` or the deferrable
|
|
42
|
+
submit step rejects any other before running anything. The comparison
|
|
43
|
+
itself, and the budgets on it, are the CLI's. Which run is the baseline
|
|
44
|
+
is the DAG author's choice, made through that log source: the operator
|
|
45
|
+
keeps no state across DAG runs.
|
|
46
|
+
|
|
47
|
+
Both trigger shapes (the standalone `SparkForensicsOperator` and the
|
|
48
|
+
`spark_forensics_callback` `on_success_callback` factory) converge on one function,
|
|
35
49
|
`operator.run_spark_forensics(context, ...)`, so there is exactly one
|
|
36
50
|
execute-analyze-persist-notify-threshold code path to maintain. Notify
|
|
37
51
|
always runs before the threshold check's fail/warn/ignore branching, so a
|
|
@@ -39,19 +53,24 @@ breach that raises still gets a notification out first.
|
|
|
39
53
|
|
|
40
54
|
## Components
|
|
41
55
|
|
|
42
|
-
- `_compat.py`,
|
|
43
|
-
|
|
56
|
+
- `_compat.py`, where this package imports Airflow's core API from (the
|
|
57
|
+
one exception is `links.py`'s lazy `XCom` import on the Airflow 2
|
|
58
|
+
branch; provider hooks are imported where they are used). `BaseOperator`/`BaseOperatorLink` live in `airflow.sdk` on
|
|
44
59
|
3.x and `airflow.models.*` on 2.x. The Task SDK also re-homed `BaseHook`
|
|
45
60
|
(3.1), and the exceptions, `TaskDeferred` and `conf` (3.2); their old
|
|
46
61
|
paths still work there, as the same objects, behind deprecation shims.
|
|
47
62
|
`_compat.py` tries the SDK path first and falls back to the old one, and
|
|
48
63
|
`tests/test_compat.py` imports every module of the package in a fresh
|
|
49
|
-
interpreter and fails
|
|
64
|
+
interpreter and fails if any import fails or emits a
|
|
65
|
+
`DeprecationWarning`, `PendingDeprecationWarning` or `FutureWarning`
|
|
66
|
+
from inside the package. The repo has
|
|
50
67
|
no ruff configuration, so there are no ruff `AIR3` rules to enable; that
|
|
51
68
|
test covers the same ground.
|
|
52
69
|
- `hooks/_templating.py`, `TemplatedHookMixin`, the base of every hook:
|
|
53
|
-
an empty `template_fields` and a `__repr__` that shows them.
|
|
54
|
-
|
|
70
|
+
an empty `template_fields` and a `__repr__` that shows them. Also
|
|
71
|
+
`task_renderer()` and `render_with_task_env()`, which render a value
|
|
72
|
+
with a task's Jinja environment for the callback and the tunnel hook.
|
|
73
|
+
See "Templated hook arguments" below.
|
|
55
74
|
- `log_ref.py`, the `EventLogRef` kinds (`LocalEventLog`,
|
|
56
75
|
`RemoteEventLog`, `HistoryServerApp`): frozen dataclasses saying where a
|
|
57
76
|
log is, the only thing a log source and a backend share.
|
|
@@ -68,8 +87,9 @@ breach that raises still gets a notification out first.
|
|
|
68
87
|
cleaning up after itself in that case; currently `HistoryServerLogSourceHook`
|
|
69
88
|
and `SFTPLogSourceHook` do this, via their own internal try/except
|
|
70
89
|
around the download/transfer that removes their owned temp dir on any
|
|
71
|
-
exception. See
|
|
72
|
-
hook/config combinations actually
|
|
90
|
+
exception. See the "Disk fills up on a worker" entry in
|
|
91
|
+
`docs/troubleshooting.md` for which hook/config combinations actually
|
|
92
|
+
clean up.
|
|
73
93
|
- `hooks/log_source/{sftp,tunnel}.py`, `SFTPLogSourceHook` and
|
|
74
94
|
`SSHTunneledLogSourceHook`, for on-prem log sources reachable only over
|
|
75
95
|
the SSH connection an `SSHOperator` already has configured (no cloud
|
|
@@ -89,22 +109,23 @@ breach that raises still gets a notification out first.
|
|
|
89
109
|
`HistoryServerApp` and do no I/O. Per-run values come from Jinja, the
|
|
90
110
|
same way as in the fetching hooks (see "Templated hook arguments").
|
|
91
111
|
`hooks/log_source/_path_template.py` checks a rendered path (rendered,
|
|
92
|
-
non-empty, no `..` segment) and `_history_server_args.py` checks a
|
|
112
|
+
non-empty, no `..` segment, no old `{run_id}`-style placeholder) and `_history_server_args.py` checks a
|
|
93
113
|
rendered app id and attempt id; `_rolling_log.py` holds the
|
|
94
114
|
rolling-log-entry-matching logic shared between `history_server.py`
|
|
95
115
|
and `sftp.py`. `_dest_root.py` holds the `dest_root/<basename>` staging
|
|
96
116
|
convention shared by `filesystem.py`/`sftp.py`, and the owned-temp-dir
|
|
97
117
|
bookkeeping (create if `dest_dir` is unset, `rmtree` on cleanup/error if
|
|
98
118
|
owned) shared by `sftp.py`/`history_server.py`.
|
|
99
|
-
- `hooks/analyze/base.py`, `AnalyzeHook`: `analyze(log_ref, thresholds
|
|
100
|
-
checks the reference against `supported_log_refs`
|
|
101
|
-
|
|
119
|
+
- `hooks/analyze/base.py`, `AnalyzeHook`: `analyze(log_ref, thresholds,
|
|
120
|
+
baseline_ref=None)` checks the reference against `supported_log_refs`
|
|
121
|
+
(`check_log_ref()`) and any baseline against `supported_baseline_refs`
|
|
122
|
+
(`check_baseline_ref()`), then calls the backend's `_analyze()`. `DeferrableAnalyzeHook` adds the
|
|
102
123
|
detached-job interface the deferrable mode drives (`submit`,
|
|
103
124
|
`trigger_for`, `defer_timeout`, `collect`, `abandon`); see "Deferrable
|
|
104
125
|
execution" below.
|
|
105
126
|
- `hooks/analyze/_cli.py`, the CLI contract every backend shares: argument
|
|
106
127
|
building for each reference kind (a positional path, or
|
|
107
|
-
`--shs-base-url`/`--app-id`/`--attempt-id`), threshold flags, exit-code
|
|
128
|
+
`--shs-base-url`/`--app-id`/`--attempt-id`), `--baseline`, threshold flags, exit-code
|
|
108
129
|
handling (0/1/3 usable, 2 and anything else raise), and building the
|
|
109
130
|
`Report` from the JSON report and stderr threshold lines. Backends only
|
|
110
131
|
differ in how they run the command and collect its output.
|
|
@@ -139,6 +160,8 @@ breach that raises still gets a notification out first.
|
|
|
139
160
|
threshold-evaluation *logic* lives here: sparkforensics's own CLI decides
|
|
140
161
|
pass/violation/inconclusive, this module only parses what it printed, so
|
|
141
162
|
it can never drift from what the CLI actually enforces.
|
|
163
|
+
- `exceptions.py`, `ThresholdBreached`, an `AirflowFailException` so a
|
|
164
|
+
breach fails the task without consuming its retries.
|
|
142
165
|
- `sinks.py`, persists the report JSON (local path, `file://`, `s3://`).
|
|
143
166
|
- `notify.py`, the `Notifier` base class: subclasses implement `_send()`
|
|
144
167
|
for whatever messaging/paging system they target, and `notify()` makes
|
|
@@ -176,6 +199,7 @@ breach that raises still gets a notification out first.
|
|
|
176
199
|
deferred run's `execute_complete()` calls.
|
|
177
200
|
- `callback.py`, `spark_forensics_callback(**kwargs)`, an
|
|
178
201
|
`on_success_callback` factory sharing the same core.
|
|
202
|
+
- `__init__.py`, the public exports and `__version__`.
|
|
179
203
|
|
|
180
204
|
## Deferrable execution
|
|
181
205
|
|
|
@@ -185,7 +209,7 @@ worker slot while the CLI runs on the SSH host:
|
|
|
185
209
|
```
|
|
186
210
|
worker: execute() locate log_ref -> backend.submit() -> defer(trigger_for(job), kwargs)
|
|
187
211
|
triggerer: SSHRemoteJobTrigger polls exit_code, streams stdout.log
|
|
188
|
-
worker: execute_complete(event, job, log_ref, report_dest, thresholds)
|
|
212
|
+
worker: execute_complete(event, job, log_ref, report_dest, thresholds, baseline_log_ref)
|
|
189
213
|
-> backend.collect() read stderr + report.json, remove the job dir
|
|
190
214
|
-> handle_report() same persist/notify/threshold code as a sync run
|
|
191
215
|
```
|
|
@@ -207,8 +231,10 @@ from the DAG file, so nothing set on the instance before `defer()`
|
|
|
207
231
|
survives. Everything `execute_complete()` needs travels in the resume
|
|
208
232
|
kwargs, as JSON-native values that Airflow 2's `BaseSerialization` and
|
|
209
233
|
Airflow 3's serde both round-trip: the job dict (connection id, CLI
|
|
210
|
-
binary, `timeout`,
|
|
211
|
-
|
|
234
|
+
binary, `timeout`, `poll_interval`, the job id, the submission time and
|
|
235
|
+
every remote path), the log reference as a dict (and the baseline's, when
|
|
236
|
+
there is one), the rendered `report_dest` and the thresholds the job was
|
|
237
|
+
submitted with. The
|
|
212
238
|
job dict and the log reference are built from the rendered hooks, so
|
|
213
239
|
templated hook arguments cross the deferral as the values the job ran
|
|
214
240
|
with. The notifier, `on_threshold_breach` and `report_url_template` come
|
|
@@ -243,9 +269,10 @@ session ids in `/proc/<pid>/stat`. A pid is only signalled while its
|
|
|
243
269
|
command line still names its job directory, so a pid reused after a
|
|
244
270
|
reboot is left alone.
|
|
245
271
|
|
|
246
|
-
Failure paths. A trigger error event
|
|
247
|
-
the job before raising. A deferral that times
|
|
248
|
-
`defer_timeout`, `timeout` plus 120 seconds
|
|
272
|
+
Failure paths. A trigger error event without an exit code makes
|
|
273
|
+
`collect()` stop and remove the job before raising. A deferral that times
|
|
274
|
+
out (the backend's `defer_timeout`, `timeout` plus 120 seconds counted
|
|
275
|
+
from submission, or the task's
|
|
249
276
|
`execution_timeout`) resumes with `next_method="__fail__"`, which never
|
|
250
277
|
reaches `execute_complete()`; `resume_execution()` is overridden to call
|
|
251
278
|
`backend.abandon(context, job)` first, with the job from XCom. Airflow 2
|
|
@@ -256,7 +283,8 @@ anything on the resumed operator, then calls `on_kill()`, which is why
|
|
|
256
283
|
between `submit()` and `defer()` (building the trigger, the timeout, the
|
|
257
284
|
deferral itself) abandons the job before the error propagates, and a
|
|
258
285
|
task timeout or kill there reaches `on_kill()`, which does the same. The
|
|
259
|
-
|
|
286
|
+
"Deferred runs" section of `docs/troubleshooting.md` lists the behaviour
|
|
287
|
+
for each case.
|
|
260
288
|
|
|
261
289
|
Killing a task. `SparkForensicsOperator.on_kill()` uses the context of the
|
|
262
290
|
`execute()` or `execute_complete()` running in the same process, or,
|
|
@@ -306,7 +334,7 @@ synchronous.
|
|
|
306
334
|
Hook arguments are Jinja templates, rendered by Airflow's own nested
|
|
307
335
|
template-field mechanism, the same one used for any object assigned to a
|
|
308
336
|
templated operator field. `SparkForensicsOperator.template_fields` is
|
|
309
|
-
`("report_dest", "log_source", "backend")`, and each hook class declares
|
|
337
|
+
`("report_dest", "log_source", "backend", "baseline_log_source")`, and each hook class declares
|
|
310
338
|
the attributes it templates in its own `template_fields`; Airflow renders
|
|
311
339
|
those in place, on Airflow 2 and 3 alike. There is one mechanism, and no
|
|
312
340
|
hook formats a string itself. The earlier `{run_id}`-style `str.format`
|
|
@@ -316,7 +344,7 @@ one is rejected with an error naming the Jinja replacement.
|
|
|
316
344
|
Three details keep this safe.
|
|
317
345
|
|
|
318
346
|
- `render_template_fields()` gives the task its own shallow copy of
|
|
319
|
-
`log_source` and `backend` before rendering, because Airflow renders in
|
|
347
|
+
`log_source`, `baseline_log_source` and `backend` before rendering, because Airflow renders in
|
|
320
348
|
place and the same hook instance is often shared by several tasks or by
|
|
321
349
|
every mapped instance.
|
|
322
350
|
- The log-source method is `locate()`, not `resolve()`. Airflow 3's
|
|
@@ -335,7 +363,8 @@ Three details keep this safe.
|
|
|
335
363
|
when Airflow renders the task. `spark_forensics_callback` runs outside
|
|
336
364
|
any template rendering, so it renders `report_dest`, `log_source` and
|
|
337
365
|
`backend` itself, on copies, with the upstream task's Jinja environment.
|
|
338
|
-
Both render through a copy of
|
|
366
|
+
Both render through the task, or a copy of it with an empty
|
|
367
|
+
`template_ext` when it has one, so a
|
|
339
368
|
value ending in `.json` is rendered as a string, not looked up as a
|
|
340
369
|
template file on an EMR or Spark-on-Kubernetes upstream.
|
|
341
370
|
|
|
@@ -345,7 +374,12 @@ Before notifying and before any threshold breach raises,
|
|
|
345
374
|
`handle_report()` pushes a summary under the `sparkforensics_summary`
|
|
346
375
|
XCom key: the schema version, the destination, the report URL, the
|
|
347
376
|
impact-band counts, whether the run violated a threshold, the breached
|
|
348
|
-
and inconclusive threshold names, and the CLI exit code.
|
|
377
|
+
and inconclusive threshold names, and the CLI exit code. A run compared
|
|
378
|
+
against a baseline adds a `comparison` entry: the direction the CLI
|
|
379
|
+
computed for one metric (`regression_metric`, wall-clock by default) as
|
|
380
|
+
the verdict, the comparison's confidence and reason, and that metric's
|
|
381
|
+
values and change in percent, taken from the CLI's comparison section.
|
|
382
|
+
The full section is persisted with the report. Downstream tasks
|
|
349
383
|
can branch on it without reading the report. It is built from the same
|
|
350
384
|
`Report` on both the synchronous and the deferred path, and
|
|
351
385
|
`return_value` stays the destination.
|
|
@@ -358,7 +392,7 @@ presigns anything: the URL points at a place the viewer's own
|
|
|
358
392
|
credentials open. `ReportLink` shows that URL when it is set and the raw
|
|
359
393
|
destination when it is not.
|
|
360
394
|
|
|
361
|
-
##
|
|
395
|
+
## Not implemented
|
|
362
396
|
|
|
363
397
|
**http/MCP AnalyzeHook backend.** The spec originally described an "http"
|
|
364
398
|
backend calling a deployed sparkforensics `server/` instance. `server/`
|
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-09-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Run comparison: `baseline_log_source` on `SparkForensicsOperator` and
|
|
15
|
+
`spark_forensics_callback` resolves an earlier run's event log, the same
|
|
16
|
+
way `log_source` resolves the run under analysis, and passes it to
|
|
17
|
+
`sparkforensics-analyze --baseline`. It must resolve to a file path
|
|
18
|
+
readable where the analysis runs: a `LocalEventLog` for
|
|
19
|
+
`SubprocessAnalyzeHook`, a `RemoteEventLog` on the same SSH connection
|
|
20
|
+
for `SSHAnalyzeHook`. A History Server application is rejected, since
|
|
21
|
+
the CLI reads a baseline only from a path. A baseline that is, contains
|
|
22
|
+
or sits inside the log under analysis fails the task before analysis.
|
|
23
|
+
Works with `deferrable=True`.
|
|
24
|
+
- Comparison thresholds `max_regression_pct`, `regression_metric` and
|
|
25
|
+
`fail_on_introduced`, mirroring the CLI's flags and its rules: all need
|
|
26
|
+
`baseline_log_source`, and `regression_metric` needs
|
|
27
|
+
`max_regression_pct`. A breach raises `ThresholdBreached` like any other
|
|
28
|
+
threshold.
|
|
29
|
+
- The `sparkforensics_summary` XCom gains a `comparison` key when a
|
|
30
|
+
baseline ran: the checked metric's verdict, the comparison's confidence
|
|
31
|
+
and reason, and the metric's baseline and candidate values and change in
|
|
32
|
+
percent. The persisted report carries the CLI's full comparison section.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- The operator reads report schema 5, where a finding's text figure is in
|
|
37
|
+
`valueText` and its `value` is always a number or `null`. A report with
|
|
38
|
+
an older `schemaVersion` fails the task.
|
|
39
|
+
- The persisted report and `Report` carry the CLI's `verdict` and
|
|
40
|
+
`notRunChecks` sections (`Report.verdict`, `Report.not_run_checks`).
|
|
41
|
+
Before this, the operator persisted only the sections it listed, so
|
|
42
|
+
these two would have been dropped.
|
|
43
|
+
|
|
10
44
|
## [0.2.0] - 2026-09-25
|
|
11
45
|
|
|
12
46
|
### Added
|
|
@@ -154,7 +188,8 @@ Initial release.
|
|
|
154
188
|
- Generic `Notifier` interface for threshold-breach alerting.
|
|
155
189
|
- PyPI trusted-publishing GitHub Actions workflow (OIDC, no stored token).
|
|
156
190
|
|
|
157
|
-
[Unreleased]: https://github.com/shuffle-works/sparkforensics-operator/compare/v0.
|
|
191
|
+
[Unreleased]: https://github.com/shuffle-works/sparkforensics-operator/compare/v0.3.0...HEAD
|
|
192
|
+
[0.3.0]: https://github.com/shuffle-works/sparkforensics-operator/releases/tag/v0.3.0
|
|
158
193
|
[0.2.0]: https://github.com/shuffle-works/sparkforensics-operator/releases/tag/v0.2.0
|
|
159
194
|
[0.1.1]: https://github.com/shuffle-works/sparkforensics-operator/releases/tag/v0.1.1
|
|
160
195
|
[0.1.0]: https://github.com/shuffle-works/sparkforensics-operator/releases/tag/v0.1.0
|
|
@@ -9,7 +9,7 @@ Python 3.9+, apache-airflow 2.6+ or 3.0+, pytest. Runtime dependency: Node.js
|
|
|
9
9
|
18+ + the `sparkforensics-cli` npm package (`npm install -g sparkforensics-cli`)
|
|
10
10
|
wherever the analysis runs: the worker for `SubprocessAnalyzeHook`, the SSH
|
|
11
11
|
host for `SSHAnalyzeHook`. Log sources resolve an `EventLogRef` (`log_ref.py`)
|
|
12
|
-
that backends consume; see `
|
|
12
|
+
that backends consume; see `ARCHITECTURE.md`.
|
|
13
13
|
|
|
14
14
|
## Commands
|
|
15
15
|
- `pip install -e ".[test,s3,ssh]"`, install for local dev.
|
|
@@ -18,6 +18,6 @@ that backends consume; see `docs/architecture.md`.
|
|
|
18
18
|
- `tox -e py311-airflow2` / `tox -e py311-airflow3`, run the suite against a
|
|
19
19
|
specific Airflow major version.
|
|
20
20
|
|
|
21
|
-
##
|
|
22
|
-
-
|
|
23
|
-
`
|
|
21
|
+
## Docs
|
|
22
|
+
- User docs: `docs/index.md` (getting started, configuration,
|
|
23
|
+
troubleshooting). Contributor docs: `CONTRIBUTING.md`, `ARCHITECTURE.md`.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Design notes and the component map are in [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
4
|
+
User documentation lives under [docs/](docs/index.md).
|
|
5
|
+
|
|
6
|
+
## Development install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install -e ".[test,s3,ssh]"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Running the tests
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pytest
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The suite needs no live Spark, Airflow or Node.js. The one real process is
|
|
19
|
+
a shell: the deferred-mode tests run the SSH provider's real shell wrapper against a local `sh` standing in
|
|
20
|
+
for the SSH host (`tests/hooks/analyze/_local_ssh.py`), so they need
|
|
21
|
+
`bash`, `setsid` and coreutils `timeout` on the machine running the tests.
|
|
22
|
+
The kill and sweep tests also run with procps hidden
|
|
23
|
+
(`LocalHost.hide_procps()`), as on slim images.
|
|
24
|
+
|
|
25
|
+
## The tox Airflow matrix
|
|
26
|
+
|
|
27
|
+
`tox.ini` runs the suite against each supported Airflow major version:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
tox -e py311-airflow2
|
|
31
|
+
tox -e py311-airflow3
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The full env list is `py{39,310,311,312}-airflow2`,
|
|
35
|
+
`py{310,311,312}-airflow3`, and three floor envs:
|
|
36
|
+
|
|
37
|
+
- `py39-airflow2min` pins Airflow 2.6.3 without the `ssh` extra, and
|
|
38
|
+
omits the SSH-only modules from its coverage (`coverage-no-ssh.ini`).
|
|
39
|
+
- `py39-airflow2sshmin` runs the `ssh` extra's floor
|
|
40
|
+
(`apache-airflow-providers-ssh` 3.7.1) on Airflow 2.6.3. The deferrable
|
|
41
|
+
tests skip there, since that mode needs a newer provider.
|
|
42
|
+
- `py311-airflow2deferrablemin` pins the deferrable mode's floors
|
|
43
|
+
(`constraints-deferrable-floor.txt`).
|
|
44
|
+
|
|
45
|
+
The two Airflow 2.6.3 envs install through
|
|
46
|
+
`constraints-airflow-2.6-floor.txt` so the extras step can't upgrade
|
|
47
|
+
Airflow past the floor; `tox.ini` explains why.
|
|
48
|
+
|
|
49
|
+
CI (`.github/workflows/ci.yml`) runs a subset of these envs on every push
|
|
50
|
+
to `main` and every pull request. Each env writes coverage to
|
|
51
|
+
`coverage-<envname>.lcov`; CI uploads one file per env to Coveralls and
|
|
52
|
+
merges them in a `finish` job. `usedevelop = true` in `tox.ini` keeps the
|
|
53
|
+
lcov `SF:` paths pointing at `src/sparkforensics_operator/...` instead of
|
|
54
|
+
a `.tox` virtualenv, which Coveralls needs to attribute lines.
|
|
55
|
+
|
|
56
|
+
## Releasing to PyPI
|
|
57
|
+
|
|
58
|
+
`.github/workflows/publish.yml` builds the sdist and wheel and publishes
|
|
59
|
+
them on every GitHub Release (`release: published`). It uses PyPI trusted
|
|
60
|
+
publishing (OIDC), so no API token is stored in the repository.
|
|
61
|
+
|
|
62
|
+
One-time setup before the first release:
|
|
63
|
+
|
|
64
|
+
- On PyPI, add a trusted publisher for this project: owner
|
|
65
|
+
`shuffle-works`, repo `sparkforensics-operator`, workflow `publish.yml`,
|
|
66
|
+
environment `pypi`.
|
|
67
|
+
- In the GitHub repository settings, create an environment named `pypi`
|
|
68
|
+
(optionally with required reviewers) to match.
|
|
69
|
+
|
|
70
|
+
To cut a release:
|
|
71
|
+
|
|
72
|
+
1. Bump `version` in `pyproject.toml` and move the `[Unreleased]` entries
|
|
73
|
+
in `CHANGELOG.md` under the new version. The Airflow provider metadata
|
|
74
|
+
reads the version from the installed package, so nothing else needs
|
|
75
|
+
changing.
|
|
76
|
+
2. Tag the release commit (for example `v0.2.0`).
|
|
77
|
+
3. Publish a GitHub Release from that tag. The workflow does the rest.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sparkforensics-operator
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Airflow operator that runs sparkforensics analysis on a Spark job's event log and acts on the result.
|
|
5
5
|
Project-URL: Homepage, https://github.com/shuffle-works/sparkforensics-operator
|
|
6
6
|
Project-URL: Repository, https://github.com/shuffle-works/sparkforensics-operator
|
|
@@ -71,6 +71,14 @@ runs as part of the DAG, right where the Spark task just ran.
|
|
|
71
71
|
you configure are enforced, and a breach raises `ThresholdBreached`
|
|
72
72
|
without consuming the task's retries, since re-running would just reach
|
|
73
73
|
the same verdict.
|
|
74
|
+
- Compare a run against an earlier one. `baseline_log_source` takes any
|
|
75
|
+
log source that resolves to an event log path the analysis can read
|
|
76
|
+
(`path_template="/spark-logs/{{ prev_ds }}/eventlog"`, an XCom pull),
|
|
77
|
+
and `max_regression_pct` fails the DAG when runtime, or the metric
|
|
78
|
+
`regression_metric` names, regressed past that budget.
|
|
79
|
+
`fail_on_introduced` fails it when the run gained new findings. The
|
|
80
|
+
comparison's verdict, confidence and measured regression land in the
|
|
81
|
+
summary XCom.
|
|
74
82
|
- Reports persist to a local path, `file://`, or `s3://`, and a clickable
|
|
75
83
|
"SparkForensics report" link shows up on the task in the Airflow UI. Set
|
|
76
84
|
`report_url_template` (an S3 console URL, say) to make it open in a
|
|
@@ -85,8 +93,8 @@ runs as part of the DAG, right where the Spark task just ran.
|
|
|
85
93
|
- An optional `Notifier` you implement (Slack, MS Teams, email, PagerDuty,
|
|
86
94
|
ZenDuty, whatever you use) gets a best-effort pass/fail summary; a
|
|
87
95
|
delivery failure never fails the task itself.
|
|
88
|
-
- Tested against both Airflow 2.6+ and Airflow 3
|
|
89
|
-
3.
|
|
96
|
+
- Tested against both Airflow 2.6+ and Airflow 3 (CI runs Python 3.9,
|
|
97
|
+
3.11 and 3.12; the tox matrix also covers 3.10). No live Spark, Airflow or Node is needed to run the tests:
|
|
90
98
|
everything but the shell on the stand-in SSH host is mocked.
|
|
91
99
|
|
|
92
100
|
## Quick start
|
|
@@ -162,16 +170,17 @@ the analysis runs: the CLI runs as a detached job on the SSH host and the
|
|
|
162
170
|
triggerer polls it, which needs a running triggerer with
|
|
163
171
|
`sparkforensics-operator[ssh]` installed and
|
|
164
172
|
`apache-airflow-providers-ssh>=6.0.1`, so Airflow 2.11 or newer. See
|
|
165
|
-
[Deferrable execution](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/
|
|
173
|
+
[Deferrable execution](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/configuration.md#deferrable-execution).
|
|
166
174
|
|
|
167
175
|
Whichever host runs the analysis (the worker for `SubprocessAnalyzeHook`,
|
|
168
176
|
the SSH host for `SSHAnalyzeHook`) needs Node.js 18+ and the
|
|
169
|
-
`sparkforensics-cli` npm package
|
|
177
|
+
`sparkforensics-cli` npm package, version 0.4.0 or newer
|
|
178
|
+
(`npm install -g "sparkforensics-cli@>=0.4.0"`), so
|
|
170
179
|
`sparkforensics-analyze` is resolvable on `PATH`. Add the `ssh` extra for
|
|
171
180
|
the SSH hooks (it works from Airflow 2.6, like the package; only
|
|
172
181
|
`deferrable=True` needs Airflow 2.11+), and the `s3` extra if
|
|
173
182
|
`report_dest` is `s3://...`.
|
|
174
|
-
Full details in [
|
|
183
|
+
Full details in [Getting started](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/getting-started.md).
|
|
175
184
|
|
|
176
185
|
## Development
|
|
177
186
|
|
|
@@ -189,11 +198,11 @@ tox -e py311-airflow3
|
|
|
189
198
|
|
|
190
199
|
## Learn more
|
|
191
200
|
|
|
192
|
-
- [
|
|
193
|
-
- [
|
|
194
|
-
|
|
195
|
-
- [
|
|
196
|
-
- [
|
|
201
|
+
- [Getting started](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/getting-started.md), install and host prerequisites, and DAG wiring examples
|
|
202
|
+
- [Configuration](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/configuration.md), the full operator/hook/threshold surface
|
|
203
|
+
- [Troubleshooting](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/troubleshooting.md), error messages and deferred-run failure modes
|
|
204
|
+
- [Contributing](https://github.com/shuffle-works/sparkforensics-operator/blob/main/CONTRIBUTING.md), development setup and the release procedure
|
|
205
|
+
- [Architecture](https://github.com/shuffle-works/sparkforensics-operator/blob/main/ARCHITECTURE.md), components and design decisions
|
|
197
206
|
|
|
198
207
|
## License
|
|
199
208
|
|
|
@@ -36,6 +36,14 @@ runs as part of the DAG, right where the Spark task just ran.
|
|
|
36
36
|
you configure are enforced, and a breach raises `ThresholdBreached`
|
|
37
37
|
without consuming the task's retries, since re-running would just reach
|
|
38
38
|
the same verdict.
|
|
39
|
+
- Compare a run against an earlier one. `baseline_log_source` takes any
|
|
40
|
+
log source that resolves to an event log path the analysis can read
|
|
41
|
+
(`path_template="/spark-logs/{{ prev_ds }}/eventlog"`, an XCom pull),
|
|
42
|
+
and `max_regression_pct` fails the DAG when runtime, or the metric
|
|
43
|
+
`regression_metric` names, regressed past that budget.
|
|
44
|
+
`fail_on_introduced` fails it when the run gained new findings. The
|
|
45
|
+
comparison's verdict, confidence and measured regression land in the
|
|
46
|
+
summary XCom.
|
|
39
47
|
- Reports persist to a local path, `file://`, or `s3://`, and a clickable
|
|
40
48
|
"SparkForensics report" link shows up on the task in the Airflow UI. Set
|
|
41
49
|
`report_url_template` (an S3 console URL, say) to make it open in a
|
|
@@ -50,8 +58,8 @@ runs as part of the DAG, right where the Spark task just ran.
|
|
|
50
58
|
- An optional `Notifier` you implement (Slack, MS Teams, email, PagerDuty,
|
|
51
59
|
ZenDuty, whatever you use) gets a best-effort pass/fail summary; a
|
|
52
60
|
delivery failure never fails the task itself.
|
|
53
|
-
- Tested against both Airflow 2.6+ and Airflow 3
|
|
54
|
-
3.
|
|
61
|
+
- Tested against both Airflow 2.6+ and Airflow 3 (CI runs Python 3.9,
|
|
62
|
+
3.11 and 3.12; the tox matrix also covers 3.10). No live Spark, Airflow or Node is needed to run the tests:
|
|
55
63
|
everything but the shell on the stand-in SSH host is mocked.
|
|
56
64
|
|
|
57
65
|
## Quick start
|
|
@@ -127,16 +135,17 @@ the analysis runs: the CLI runs as a detached job on the SSH host and the
|
|
|
127
135
|
triggerer polls it, which needs a running triggerer with
|
|
128
136
|
`sparkforensics-operator[ssh]` installed and
|
|
129
137
|
`apache-airflow-providers-ssh>=6.0.1`, so Airflow 2.11 or newer. See
|
|
130
|
-
[Deferrable execution](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/
|
|
138
|
+
[Deferrable execution](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/configuration.md#deferrable-execution).
|
|
131
139
|
|
|
132
140
|
Whichever host runs the analysis (the worker for `SubprocessAnalyzeHook`,
|
|
133
141
|
the SSH host for `SSHAnalyzeHook`) needs Node.js 18+ and the
|
|
134
|
-
`sparkforensics-cli` npm package
|
|
142
|
+
`sparkforensics-cli` npm package, version 0.4.0 or newer
|
|
143
|
+
(`npm install -g "sparkforensics-cli@>=0.4.0"`), so
|
|
135
144
|
`sparkforensics-analyze` is resolvable on `PATH`. Add the `ssh` extra for
|
|
136
145
|
the SSH hooks (it works from Airflow 2.6, like the package; only
|
|
137
146
|
`deferrable=True` needs Airflow 2.11+), and the `s3` extra if
|
|
138
147
|
`report_dest` is `s3://...`.
|
|
139
|
-
Full details in [
|
|
148
|
+
Full details in [Getting started](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/getting-started.md).
|
|
140
149
|
|
|
141
150
|
## Development
|
|
142
151
|
|
|
@@ -154,11 +163,11 @@ tox -e py311-airflow3
|
|
|
154
163
|
|
|
155
164
|
## Learn more
|
|
156
165
|
|
|
157
|
-
- [
|
|
158
|
-
- [
|
|
159
|
-
|
|
160
|
-
- [
|
|
161
|
-
- [
|
|
166
|
+
- [Getting started](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/getting-started.md), install and host prerequisites, and DAG wiring examples
|
|
167
|
+
- [Configuration](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/configuration.md), the full operator/hook/threshold surface
|
|
168
|
+
- [Troubleshooting](https://github.com/shuffle-works/sparkforensics-operator/blob/main/docs/troubleshooting.md), error messages and deferred-run failure modes
|
|
169
|
+
- [Contributing](https://github.com/shuffle-works/sparkforensics-operator/blob/main/CONTRIBUTING.md), development setup and the release procedure
|
|
170
|
+
- [Architecture](https://github.com/shuffle-works/sparkforensics-operator/blob/main/ARCHITECTURE.md), components and design decisions
|
|
162
171
|
|
|
163
172
|
## License
|
|
164
173
|
|