Skip to content

v0.2.12#58

Open
StranDutton wants to merge 2 commits into
mainfrom
fix/0.2.12
Open

v0.2.12#58
StranDutton wants to merge 2 commits into
mainfrom
fix/0.2.12

Conversation

@StranDutton

@StranDutton StranDutton commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

1. PR #57 by @StranDutton: fix: move non-sensitive data out of secrets.toml - BED-8838

2. PR #56 by @ktstrader: fix: added retry to pipeline.py to mitigate Windows "access denied" error, added log handler for dlt loggers reuse one handler per path - BED-8837(fix: added retry to pipeline.py to mitigate "access denied", added log handler for dlt loggers reuse one handler per path)

Important

Documentation (bloodhound-docs) update should be released at the same time (in relation to #57 OH config updates)

Summary by CodeRabbit

  • New Features

    • Added example configuration for connecting to BloodHound Enterprise with clearer setup guidance.
  • Bug Fixes

    • Improved pipeline resilience by retrying certain transient file-locking errors on Windows.
    • Reduced duplicate log file handles so log rotation behaves more reliably.
  • Documentation

    • Clarified that the BloodHound Enterprise URL is set directly in configuration, while credentials belong in secrets.

StranDutton and others added 2 commits July 7, 2026 09:58
…g handler for dlt loggers reuse one handler per path (#56)

* fix: added retry to pipeline.py to mitigate WinError 5 "access denied", shared RotatingFileHandler cache so root and dlt loggers reuse one handler per path, avoiding rotation "file in use" errors

* improve error surfacing, platform-gate the permission error to Windows, testing

---------

Co-authored-by: Stran Dutton <sdutton@specterops.io>
@StranDutton StranDutton self-assigned this Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR relocates the BloodHound Enterprise destination URL from secrets to config (updating ingest, examples, and docs), adds a shared rotating file-handler cache to CustomLogger to prevent duplicate handlers, and introduces retry logic with backoff for transient filesystem/permission errors in BasePipeline._run, with new tests for both.

Changes

BHE destination URL moved to config

Layer / File(s) Summary
Destination ingest config source
src/openhound/destinations/bloodhound_enterprise/destination.py
The ingest function's url parameter default changes from dlt.secrets.value to dlt.config.value.
Example configs and docs updated
example-configurations/bloodhound-enterprise/.dlt-example/config.toml, .../secrets_github.toml, .../secrets_jamf.toml, .../secrets_okta.toml, example-configurations/bloodhound-enterprise/docker-compose.yml, deployments/helm/openhound/README.md, deployments/helm/values.example.yaml
Config.toml gains a [destination.bloodhoundenterprise] url setting; secrets files drop url and keep only token_id/token_key; docker-compose comments, Helm README, and values.example.yaml comments clarify the URL is non-sensitive.

Logging handler caching and pipeline retry logic

Layer / File(s) Summary
Shared rotating file handler cache
src/openhound/core/logging.py, tests/test_log_handlers.py
CustomLogger caches one RotatingFileHandler per resolved path via _get_file_handler/_build_file_handler; container_handlers, cli_handlers, and service_handlers reuse the cached handler; tests validate caching, shared handler use between loggers, and rotation configuration.
Transient error retry loop in pipeline execution
src/openhound/core/pipeline.py, tests/test_pipeline_retry.py
BasePipeline._run wraps pipeline.run in a bounded retry loop with backoff for transient rename-related OSError/PermissionError causes (Windows-specific handling included), preserving existing exception translation; tests cover retry success, exhaustion, non-transient errors, and non-Windows behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant BasePipeline as BasePipeline._run
    participant DltPipeline as dlt.pipeline.run
    participant Logger

    Caller->>BasePipeline: _run()
    loop up to _MAX_TRANSIENT_RETRIES
        BasePipeline->>DltPipeline: pipeline.run(...)
        alt success
            DltPipeline-->>BasePipeline: result
            BasePipeline-->>Caller: return result
        else transient OSError/PermissionError in cause chain
            DltPipeline-->>BasePipeline: raise error
            BasePipeline->>Logger: log warning, backoff
            BasePipeline->>BasePipeline: sleep + retry
        else non-transient error
            DltPipeline-->>BasePipeline: raise error
            BasePipeline-->>Caller: translate to ConfigException/ParseException/reraise
        end
    end
    BasePipeline-->>Caller: reraise last error (retries exhausted)
Loading

Poem

A rabbit hopped through logs and locks,
Cached each handler, dodged the blocks. 🐇
URLs to config, tokens stay hid,
Retries whisper "try again, kid."
Backoff, backoff, then success blooms—
Off to burrow, past the fixed-up rooms! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is just a version tag and does not describe the pull request’s actual changes. Replace the version label with a short summary of the main change, such as config updates and Windows pipeline retry handling.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/0.2.12

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/openhound/core/logging.py (2)

325-368: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Stale cached handlers on repeated setup().

If setup() runs more than once with a different log_path/name (e.g. reconfiguration), the previously cached handler for the old path is removed from the loggers' handlers lists via .clear() but stays open in self._file_handlers forever — an accumulating, unclosed file descriptor per reconfiguration. Given setup() is normally called once per process lifetime this is low-impact, but consider evicting/closing handlers for paths no longer referenced.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhound/core/logging.py` around lines 325 - 368, The setup() method in
logging.py is leaving stale file handlers open when it is called multiple times
with a changed log_path or name. Update setup() to evict any no-longer-used
handler from self._file_handlers and close it before replacing handlers on
dlt_logger and root_logger, so repeated reconfiguration does not accumulate open
file descriptors. Use the setup(), self._file_handlers, self.handlers, and
log_file_path symbols to locate the cleanup logic.

398-405: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Cache lookup/insert isn't atomic.

Two concurrent calls to _get_file_handler for a new path could both miss the cache and each build a RotatingFileHandler for the same file before either is stored; the last write wins in the dict and the other handler is silently orphaned (open, unclosed, never used) — the exact multi-handler-on-one-file scenario this cache is meant to prevent. Low risk in current single-threaded setup flow, but worth a threading.Lock around the check-and-set for defensive correctness.

🔒 Optional lock-based fix
+import threading
+
 class CustomLogger:
     def __init__(self, ...):
         ...
         self._file_handlers: dict[Path, "RotatingFileHandler"] = {}
+        self._file_handlers_lock = threading.Lock()
 
     def _get_file_handler(self, file_path: Path) -> "RotatingFileHandler":
         key = Path(file_path).resolve()
-        handler = self._file_handlers.get(key)
-        if handler is None:
-            handler = self._build_file_handler(file_path)
-            self._file_handlers[key] = handler
-        return handler
+        with self._file_handlers_lock:
+            handler = self._file_handlers.get(key)
+            if handler is None:
+                handler = self._build_file_handler(file_path)
+                self._file_handlers[key] = handler
+        return handler
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhound/core/logging.py` around lines 398 - 405, The shared handler
cache in _get_file_handler is not atomic, so concurrent calls can create
multiple RotatingFileHandler instances for the same path. Add a threading.Lock
around the cache lookup/build/store sequence in Logging/_get_file_handler so the
check-and-set is performed once per resolved Path key, and keep the existing
_build_file_handler and _file_handlers behavior unchanged.
src/openhound/core/pipeline.py (1)

44-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider dlt's built-in retry_load helper for transient load-step retries.

dlt already ships dlt.pipeline.helpers.retry_load, designed to be used with tenacity.Retrying for retrying run/load on transient errors. Reimplementing bespoke retry/backoff logic here duplicates functionality dlt already provides and misses tenacity's more battle-tested jitter/backoff strategies. Worth evaluating whether retry_load plus a custom retry_if_exception predicate (checking _transient_filesystem_cause) could replace this hand-rolled loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhound/core/pipeline.py` around lines 44 - 91, The transient retry
logic in _run is hand-rolling backoff and retry handling that dlt already
provides. Replace the custom for-loop, warning, and sleep logic in Pipeline._run
with dlt.pipeline.helpers.retry_load wired through tenacity.Retrying, and keep
the existing transient predicate by using a custom retry_if_exception check
based on _transient_filesystem_cause. Preserve the current special handling for
PipelineStepFailed and PermissionError in _run, but let retry_load manage
retry/backoff behavior instead of the bespoke _MAX_TRANSIENT_RETRIES path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/openhound/core/logging.py`:
- Around line 325-368: The setup() method in logging.py is leaving stale file
handlers open when it is called multiple times with a changed log_path or name.
Update setup() to evict any no-longer-used handler from self._file_handlers and
close it before replacing handlers on dlt_logger and root_logger, so repeated
reconfiguration does not accumulate open file descriptors. Use the setup(),
self._file_handlers, self.handlers, and log_file_path symbols to locate the
cleanup logic.
- Around line 398-405: The shared handler cache in _get_file_handler is not
atomic, so concurrent calls can create multiple RotatingFileHandler instances
for the same path. Add a threading.Lock around the cache lookup/build/store
sequence in Logging/_get_file_handler so the check-and-set is performed once per
resolved Path key, and keep the existing _build_file_handler and _file_handlers
behavior unchanged.

In `@src/openhound/core/pipeline.py`:
- Around line 44-91: The transient retry logic in _run is hand-rolling backoff
and retry handling that dlt already provides. Replace the custom for-loop,
warning, and sleep logic in Pipeline._run with dlt.pipeline.helpers.retry_load
wired through tenacity.Retrying, and keep the existing transient predicate by
using a custom retry_if_exception check based on _transient_filesystem_cause.
Preserve the current special handling for PipelineStepFailed and PermissionError
in _run, but let retry_load manage retry/backoff behavior instead of the bespoke
_MAX_TRANSIENT_RETRIES path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3bc677f2-fa12-4f4e-8ff1-7629b28b3a52

📥 Commits

Reviewing files that changed from the base of the PR and between 96443ca and 2cc370a.

📒 Files selected for processing (12)
  • deployments/helm/openhound/README.md
  • deployments/helm/values.example.yaml
  • example-configurations/bloodhound-enterprise/.dlt-example/config.toml
  • example-configurations/bloodhound-enterprise/.dlt-example/secrets_github.toml
  • example-configurations/bloodhound-enterprise/.dlt-example/secrets_jamf.toml
  • example-configurations/bloodhound-enterprise/.dlt-example/secrets_okta.toml
  • example-configurations/bloodhound-enterprise/docker-compose.yml
  • src/openhound/core/logging.py
  • src/openhound/core/pipeline.py
  • src/openhound/destinations/bloodhound_enterprise/destination.py
  • tests/test_log_handlers.py
  • tests/test_pipeline_retry.py
💤 Files with no reviewable changes (3)
  • example-configurations/bloodhound-enterprise/.dlt-example/secrets_okta.toml
  • example-configurations/bloodhound-enterprise/.dlt-example/secrets_github.toml
  • example-configurations/bloodhound-enterprise/.dlt-example/secrets_jamf.toml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants