Skip to content

chore: Update the fork to support paramiko v3 to v5#1

Merged
m1so merged 19 commits into
masterfrom
init-fork
May 12, 2026
Merged

chore: Update the fork to support paramiko v3 to v5#1
m1so merged 19 commits into
masterfrom
init-fork

Conversation

@m1so
Copy link
Copy Markdown

@m1so m1so commented May 12, 2026

Summary by CodeRabbit

  • Chores

    • Migrated CI/CD to GitHub Actions (removed legacy CI configs), modernized packaging (Hatchling + dynamic versioning), updated tooling/config and ignore/manifest files.
  • Documentation

    • README and changelog rewritten to reflect the fork, updated install/compatibility and badges.
  • New Features

    • Reworked end-to-end test suite with ephemeral SSH/key infrastructure and multi-database tunnel tests; added hang/context-manager tests.
  • Bug Fixes

    • Host-key handling restricted to RSA, ECDSA, and Ed25519.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: efca2c1e-f1c2-4ea2-a131-104c702a6664

📥 Commits

Reviewing files that changed from the base of the PR and between 6f9bc4b and ca0ac09.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • changelog.rst
  • pyproject.toml

📝 Walkthrough

Walkthrough

Removed CircleCI and AppVeyor configs; added GitHub Actions workflows for CI and release. Migrated packaging from setuptools/setup.py/tox/MANIFEST.in to pyproject.toml with Hatch/uv dynamic versioning and a nox-based test/e2e/lint setup. Repository rebranded to deepnote-sshtunnel (import name remains sshtunnel); sshtunnel.py dropped Python‑2 compatibility and restricted host-key support to RSA/ECDSA/Ed25519. Tests modernized: ephemeral SSH key fixtures, generated ssh config, session-scoped Docker-based e2e infrastructure (SSH + PostgreSQL/MySQL/MongoDB), and new e2e tests exercising SSHTunnelForwarder. Removed static e2e artifacts and older test keys.

Sequence Diagram(s)

sequenceDiagram
  participant Runner as Test runner
  participant KeyGen as Key generator (paramiko)
  participant Docker as Docker engine
  participant SSH as SSH container
  participant Postgres as PostgreSQL container
  participant MySQL as MySQL container
  participant Mongo as MongoDB container
  participant Tunnel as SSHTunnelForwarder

  Runner->>KeyGen: create ephemeral RSA host key + sshd_config
  Runner->>Docker: create shared network
  Docker->>Postgres: start postgres (alias: pg)
  Docker->>MySQL: start mysql (alias: mysql)
  Docker->>Mongo: start mongo (alias: mongo)
  Docker->>SSH: start sshd container mounting keys/config
  SSH-->>Runner: emit readiness logs
  Runner->>Tunnel: construct tunnel with SSH host/port/key and remote binds
  Tunnel->>SSH: establish SSH connection and remote port forwards
  Runner->>Postgres: connect via local forwarded port and run query
  Runner->>MySQL: connect via local forwarded port and run query
  Runner->>Mongo: connect via local forwarded port and run query
  Runner->>Tunnel: stop tunnel
  Docker->>SSH: stop sshd container
  Docker->>Postgres: stop postgres container
  Docker->>MySQL: stop mysql container
  Docker->>Mongo: stop mongo container
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Updates Docs ❓ Inconclusive In-repo docs (README, changelog) are well updated. Cannot verify primary docs in deepnote/deepnote OSS or roadmap in deepnote/deepnote-internal private repos. Confirm documentation/roadmap updates in external repos: deepnote/deepnote (OSS) and deepnote/deepnote-internal (private).
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately reflects the main change: modernizing the codebase to support Paramiko v3-v5, including dependency updates and Python 2 removal.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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.

Inline comments:
In `@e2e_tests/conftest.py`:
- Around line 120-125: The extra time.sleep(2) after wait_for_logs indicates the
LogMessageWaitStrategy may not guarantee full readiness; remove the
unconditional sleep and instead perform explicit readiness checks: keep using
wait_for_logs(postgres/mysql/mongo, LogMessageWaitStrategy(...)) but follow each
with an active health check—e.g., attempt a TCP connect to ssh's port for the
ssh container (reference: ssh), and simple service-level checks for
postgres/mysql/mongo (attempt a DB TCP connection or run a lightweight query) to
confirm readiness; alternatively, if those checks are not feasible, add a clear
comment explaining why time.sleep(2) is required and what exact condition it’s
compensating for.
- Line 137: The shutil.rmtree call using ignore_errors=True can silently swallow
permission or filesystem errors for tmp_key_dir; change this to explicitly
handle failures by removing ignore_errors=True and wrapping the call in a
try/except around shutil.rmtree(tmp_key_dir) (or keep the call but remove the
flag), log the exception using the test/module logger or call pytest.fail with
the error message, and finally assert or check that tmp_key_dir no longer exists
(e.g., os.path.exists(tmp_key_dir) is False) so CI surfaces cleanup failures;
reference the shutil.rmtree(tmp_key_dir) invocation and tmp_key_dir variable
when making the change.

In `@e2e_tests/test_db_tunnel.py`:
- Around line 13-18: SSHTunnelForwarder is currently created without specifying
local_bind_addresses so it defaults to 0.0.0.0; update the SSHTunnelForwarder
instantiation to include local_bind_addresses set to loopback (e.g.,
local_bind_addresses=('127.0.0.1',) or [ ('127.0.0.1',) ]) alongside the
existing remote_bind_addresses to ensure the forwarded ports are bound to
127.0.0.1 only.
- Around line 29-35: Add client-side timeouts to the DB connections to avoid
long hangs: for the psycopg2 connection call (the psycopg2.connect invocation in
e2e_tests/test_db_tunnel.py) add connect_timeout=10; for the Mongo clients (the
pymongo.MongoClient constructions in this file) add
serverSelectionTimeoutMS=10000, connectTimeoutMS=10000 and socketTimeoutMS=10000
so they mirror the MySQL behavior and fail fast on connection issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94a6221d-4adb-4495-80a5-5101e7212e94

📥 Commits

Reviewing files that changed from the base of the PR and between dc07328 and ebc703f.

⛔ Files ignored due to path filters (1)
  • e2e_tests/ssh-server-config/ssh_host_rsa_key.pub is excluded by !**/*.pub
📒 Files selected for processing (30)
  • .circleci/config.yml
  • .github/workflows/ci.yml
  • .github/workflows/database.yml
  • .github/workflows/release.yml
  • .gitignore
  • MANIFEST.in
  • README.rst
  • appveyor.yml
  • e2e_tests/__init__.py
  • e2e_tests/conftest.py
  • e2e_tests/docker-compose.yaml
  • e2e_tests/run_docker_e2e_db_tests.py
  • e2e_tests/run_docker_e2e_hangs_tests.py
  • e2e_tests/ssh-server-config/ssh_host_rsa_key
  • e2e_tests/ssh-server-config/sshd_config
  • e2e_tests/test_db_tunnel.py
  • e2e_tests/test_hang.py
  • noxfile.py
  • pyproject.toml
  • setup.cfg
  • setup.py
  • sshtunnel.py
  • tests/conftest.py
  • tests/requirements-syntax.txt
  • tests/requirements.txt
  • tests/test_forwarder.py
  • tests/testconfig
  • tests/testrsa.key
  • tests/testrsa_encrypted.key
  • tox.ini
💤 Files with no reviewable changes (17)
  • tests/requirements.txt
  • tox.ini
  • MANIFEST.in
  • .github/workflows/database.yml
  • e2e_tests/ssh-server-config/ssh_host_rsa_key
  • e2e_tests/docker-compose.yaml
  • tests/requirements-syntax.txt
  • e2e_tests/ssh-server-config/sshd_config
  • tests/testrsa.key
  • tests/testconfig
  • appveyor.yml
  • e2e_tests/run_docker_e2e_db_tests.py
  • tests/testrsa_encrypted.key
  • setup.py
  • e2e_tests/run_docker_e2e_hangs_tests.py
  • setup.cfg
  • .circleci/config.yml

Comment thread e2e_tests/conftest.py Outdated
Comment thread e2e_tests/conftest.py Outdated
Comment thread e2e_tests/test_db_tunnel.py
Comment thread e2e_tests/test_db_tunnel.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 35-40: Replace direct use of a globally-installed nox from "uv
tool install 'nox[uv]'" + plain "nox -s ..." invocations with uvx one-off
invocations to ensure deterministic execution; specifically, in the steps named
"Install nox" and "Run tests (paramiko ${{ matrix.paramiko-version }})" (and the
analogous step at lines 58–63) stop relying on PATH and invoke uvx --from
'nox[uv]' to run nox (e.g., use uvx --from 'nox[uv]' nox -s "tests-${{
matrix.python-version }}(paramiko='${{ matrix.paramiko-version }}')" instead of
the current two-step install + plain nox call).

In `@pyproject.toml`:
- Around line 74-77: The pyproject build target includes an invalid packages =
["."] for a single-file module; remove the packages = ["."] entry and rely only
on include = ["sshtunnel.py"] in the [tool.hatch.build.targets.wheel] section so
the wheel builds the top-level module correctly (keep the include line and
delete the packages line).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8c5b439-8db6-483a-948b-f2b7215da800

📥 Commits

Reviewing files that changed from the base of the PR and between ebc703f and 969085b.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • pyproject.toml
  • tests/conftest.py
  • tests/test_forwarder.py

Comment thread .github/workflows/ci.yml Outdated
Comment thread pyproject.toml
@m1so m1so deployed to testpypi May 12, 2026 08:44 — with GitHub Actions Active
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@e2e_tests/conftest.py`:
- Line 114: The bind mount at .with_volume_mapping(str(tmp_key_dir),
"/config/ssh_host_keys") causes the container entrypoint chown to fail because
the host-owned files are not writable by container UID; fix by either making the
host dir owned by UID 1000 before mounting (e.g., call os.chown(tmp_key_dir,
1000, 1000) / ensure permissions permit container chown) or change the mount to
a writable bind (use the explicit bind mode string like
.with_volume_mapping(f"{str(tmp_key_dir)}:rw", "/config/ssh_host_keys") or use a
tmpfs instead) so the SSH container can successfully chown the keys.

In `@sshtunnel.py`:
- Around line 34-37: The except block that sets the package fallback version
should match pyproject.toml's fallback-version; update the except handler so
that when _get_version("deepnote-sshtunnel") fails it assigns __version__ =
"0.0.1" (instead of "0.0.0"), keeping the version source consistent; locate the
try/except around _get_version and change only the fallback string for
__version__.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ecab7134-7f8b-4d3a-bd38-c5e3e65ecdac

📥 Commits

Reviewing files that changed from the base of the PR and between 969085b and 2bca6cb.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • e2e_tests/conftest.py
  • e2e_tests/test_db_tunnel.py
  • pyproject.toml
  • sshtunnel.py
  • tests/conftest.py
  • tests/test_forwarder.py

Comment thread e2e_tests/conftest.py Outdated
Comment thread sshtunnel.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In @.github/workflows/release.yml:
- Line 6: The tag pattern in the release workflow is using regex-style
characters (`[0-9]+.[0-9]+.[0-9]+*`) which GitHub Actions treats as a glob, so
update the tag pattern in .github/workflows/release.yml to a proper glob that
matches semantic version tags (for example use a glob like v*.*.* or *.*.*) so
tags like 1.2.3 or v1.2.3 will trigger the workflow; modify the value under the
tags key in the release workflow accordingly.

In `@e2e_tests/conftest.py`:
- Around line 79-152: Temp key directory tmp_key_dir may not be removed if an
exception occurs before the yield; wrap the container setup and waits in a
try/finally (or use a context manager like TemporaryDirectory) so that
shutil.rmtree(tmp_key_dir, ignore_errors=True) is executed in the finally block;
ensure the cleanup runs regardless of failures when calling
_generate_ssh_keypair, _generate_sshd_config, entering Network(), or while
waiting on containers (references: tmp_key_dir, _generate_ssh_keypair,
_generate_sshd_config, Network(), yield, shutil.rmtree).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: dc6c06b2-e7d2-463b-b2da-2d3dfa20679e

📥 Commits

Reviewing files that changed from the base of the PR and between 2bca6cb and 75d1e7f.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • e2e_tests/conftest.py

Comment thread .github/workflows/release.yml Outdated
Comment thread e2e_tests/conftest.py Outdated
@m1so m1so marked this pull request as ready for review May 12, 2026 09:10
@m1so m1so requested a review from mfranczel May 12, 2026 09:11
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sshtunnel.py (1)

1608-1625: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

_bindlist breaks documented UNIX-socket -L inputs.

The parser always returns (ip, port), so /tmp/foo.sock becomes ('/tmp/foo.sock', 22) instead of a socket path string.

Suggested fix
 def _bindlist(input_str):
     """Define type of data expected for remote and local bind address lists
-    Returns a tuple (ip_address, port) whose elements are (str, int)
+    Returns either:
+      - tuple (ip_address, port), or
+      - UNIX socket path (str) for local bind usage
     """
     try:
+        if "/" in input_str and ":" not in input_str:
+            return input_str
         ip_port = input_str.split(":")
         if len(ip_port) == 1:
             _ip = ip_port[0]
             _port = None
🤖 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 `@sshtunnel.py` around lines 1608 - 1625, _bindlist currently always parses
inputs into (ip, port) tuples which misinterprets UNIX-socket paths like
"/tmp/foo.sock"; modify _bindlist to detect socket-path inputs (e.g., input_str
contains '/' or startswith '/') and return the raw path string instead of a (ip,
port) tuple, otherwise keep existing parsing logic (splitting on ":" and
defaulting port to "22") and the argparse.ArgumentTypeError on invalid numeric
ports; update the return type behavior in _bindlist so callers can receive
either a str (UNIX socket path) or a (str, int) tuple for host:port.
🤖 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.

Inline comments:
In `@sshtunnel.py`:
- Around line 1467-1473: The __str__ implementation is including raw
self.ssh_password in the credentials dict which can leak secrets; update the
code that builds credentials (the credentials dict created alongside calls to
_remove_none_values) to never insert the plaintext password—either remove the
"password" key or replace its value with a redacted token (e.g., "REDACTED" or a
masked string) before calling _remove_none_values so logs/str output cannot
expose self.ssh_password; keep the pkeys behavior unchanged and ensure the
change is applied inside the same method that constructs credentials for
__str__.
- Around line 191-195: The current code unconditionally extends
pywarnings.handlers with logger.handlers
(pywarnings.handlers.extend(logger.handlers)), which can duplicate handlers on
repeated setup; replace this with a safe add that avoids duplicates—iterate over
logger.handlers and for each handler call pywarnings.addHandler(handler) only if
that handler is not already present in pywarnings.handlers (or compare by
identity) so repeated calls to the capture_warnings/capture_warnings block won’t
register the same handler multiple times; update the block that references
capture_warnings, pywarnings, and logger to perform this guarded add instead of
extend.

---

Outside diff comments:
In `@sshtunnel.py`:
- Around line 1608-1625: _bindlist currently always parses inputs into (ip,
port) tuples which misinterprets UNIX-socket paths like "/tmp/foo.sock"; modify
_bindlist to detect socket-path inputs (e.g., input_str contains '/' or
startswith '/') and return the raw path string instead of a (ip, port) tuple,
otherwise keep existing parsing logic (splitting on ":" and defaulting port to
"22") and the argparse.ArgumentTypeError on invalid numeric ports; update the
return type behavior in _bindlist so callers can receive either a str (UNIX
socket path) or a (str, int) tuple for host:port.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2e695faf-e83f-4b50-a9ee-9d3c85bbecd4

📥 Commits

Reviewing files that changed from the base of the PR and between 75d1e7f and 6f9bc4b.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • e2e_tests/conftest.py
  • sshtunnel.py

Comment thread sshtunnel.py
Comment thread sshtunnel.py
Copy link
Copy Markdown

@mfranczel mfranczel left a comment

Choose a reason for hiding this comment

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

Please consider updating changelog as well

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread pyproject.toml
@m1so m1so merged commit 6d1fb3d into master May 12, 2026
16 checks passed
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