Skip to content

fix: migration-process#55

Merged
RambokDev merged 20 commits intomainfrom
fix/migration-process
Apr 29, 2026
Merged

fix: migration-process#55
RambokDev merged 20 commits intomainfrom
fix/migration-process

Conversation

@RambokDev
Copy link
Copy Markdown
Contributor

@RambokDev RambokDev commented Apr 26, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved MySQL and MariaDB backup dumps for better compatibility.
    • Restores for MySQL, MariaDB, and PostgreSQL now consistently target the intended database.
    • MongoDB restore now detects source DB from the archive and remaps namespaces to the target database reliably.
  • Chores

    • Updated container configuration to mount a specific host database file path and refreshed an environment key.
  • Other

    • Tweaked Firebird verification command used during seeding.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

Warning

Rate limit exceeded

@RambokDev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minute and 27 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f75690ce-290e-4966-a68b-f466e73b4471

📥 Commits

Reviewing files that changed from the base of the PR and between cc163c2 and e990e3b.

📒 Files selected for processing (1)
  • docker-compose.yml
📝 Walkthrough

Walkthrough

Adjusts Docker Compose rust-app mount and EDGE_KEY; changes backup/restore command arguments across MariaDB/MySQL/Postgres to avoid creating databases and to scope restores explicitly; MongoDB restore now runs a dry-run to detect the archive database name and remaps namespaces for restore.

Changes

Cohort / File(s) Summary
Docker Compose
docker-compose.yml
Switches rust-app volume mount from repo-relative ./databases.json to a host absolute path (old line left commented); updates EDGE_KEY value.
MariaDB
src/domain/mariadb/backup.rs, src/domain/mariadb/restore.rs
Backup: replace --add-drop-database/--databases with --no-create-db and --skip-add-drop-table, pass DB name as positional arg. Restore: quote DB identifier with backticks and add --database flag to the import command.
MySQL
src/domain/mysql/backup.rs, src/domain/mysql/restore.rs
Backup: remove --verbose, use --no-create-db/--skip-add-drop-table, pass DB as positional arg, borrow cfg fields by reference. Restore: quote DB identifier with backticks and add --database cfg.database.
Postgres
src/domain/postgres/restore.rs
Change restore connection URL to use cfg.database instead of /postgres; remove/comment out --create so pg_restore no longer requests DB creation.
MongoDB
src/domain/mongodb/connection.rs, src/domain/mongodb/restore.rs, src/domain/mongodb/backup.rs
Add extract_db_name to parse mongorestore dry-run output; restore now runs a dry-run to detect source DB, then uses --nsInclude/--nsFrom/--nsTo to remap into cfg.database. Removes a pre-run debug log in backup.
Firebird justfile
justfile
Change seed-firebird verification to run a specific SELECT on RDB$RELATIONS piped to isql instead of SHOW TABLES;.
Minor tidy
src/services/status.rs
Remove an extra blank line in StatusService::ping (formatting-only).

Sequence Diagram(s)

sequenceDiagram
    participant Agent as Agent
    participant DryRun as "mongorestore (dry-run)"
    participant Parser as "extract_db_name"
    participant Restore as "mongorestore (actual)"
    Agent->>DryRun: run dry-run against archive (stderr captured)
    DryRun-->>Agent: stderr with "archive prelude <db>..."
    Agent->>Parser: extract_db_name(dry stderr)
    Parser-->>Agent: source_db (optional)
    Agent->>Restore: run actual mongorestore with --nsInclude/--nsFrom/--nsTo remapping from source_db -> cfg.database
    Restore-->>Agent: success / error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
I hopped through flags and mounts so neat,
found DB names in dry-run heat,
remapped namespaces, quoted the rest,
restored each dump to where it's best,
nibble, patch, and skip a beat.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix: migration-process' is vague and does not clearly describe the specific changes made. While it indicates a fix category, it doesn't convey what aspect of the migration process is being addressed. Use a more descriptive title that specifies the actual fix, such as 'fix: update database backup/restore commands' or 'fix: database dump arguments and identifiers' to clearly indicate the primary changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/migration-process

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
Review rate limit: 0/1 reviews remaining, refill in 1 minute and 27 seconds.

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

Caution

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

⚠️ Outside diff range comments (1)
src/domain/postgres/restore.rs (1)

39-56: ⚠️ Potential issue | 🟠 Major

Document the database pre-existence requirement for PostgreSQL restore.

With --create removed and the connection URL pointing to cfg.database, pg_restore cannot create the database and will fail if cfg.database doesn't already exist. Unlike MySQL/MariaDB which explicitly CREATE DATABASE within their restore flow, PostgreSQL expects the target database to be provisioned beforehand. The RestoreService.run_restore() will fail silently at the ping stage if the database is unreachable.

Confirm that your external provisioning (operator action, migration, IaC, or service initialization) always creates cfg.database before restore::run() is invoked, and add a doc comment to the run() function in src/domain/postgres/restore.rs documenting this contract to prevent future connection errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/postgres/restore.rs` around lines 39 - 56, The restore now calls
pg_restore without --create and connects to cfg.database (see
PostgresDumpFormat::Fc block using --dbname &url), so the target database must
exist beforehand; add a clear doc-comment to the run() function (and/or
RestoreService::run_restore()) in this file stating that cfg.database must be
provisioned prior to invocation (operator/IaC/migration must create it), mention
that pg_restore cannot create the DB when --create is removed, and note that
callers should ensure provisioning to avoid silent ping failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docker-compose.yml`:
- Around line 12-13: Replace the developer-specific absolute host path
"/Users/charlesgauthereau/Desktop/test-portabase/databases.json" in the
docker-compose volume mapping with a portable alternative: either restore the
relative mount "./databases.json:/config/config.json" or parameterize via an env
var (e.g., use a DB_CONFIG variable) so contributors and CI can provide their
own host path instead of committing a machine-specific absolute path.
- Line 23: The commit leaks a sensitive EDGE_KEY value in docker-compose.yml
(the EDGE_KEY environment variable); rotate the exposed master key on the server
immediately and remove the hard-coded value from docker-compose.yml, then
reference a runtime variable (e.g., ${EDGE_KEY}) instead and load it from a
gitignored .env or docker-compose.override.yml kept out of source control;
update any deployment docs or dev README to show how to provision EDGE_KEY
locally and ensure docker-compose.yml no longer contains the literal key.

In `@src/domain/mariadb/backup.rs`:
- Around line 33-37: The code computes a version-specific path into the
mariadb_dump variable via select_mariadb_path(&version) but then calls
Command::new("mariadb-dump"), ignoring that path; replace the hardcoded
Command::new("mariadb-dump") with a call that uses the computed mariadb_dump
(e.g., pass mariadb_dump or mariadb_dump.as_os_str() / mariadb_dump.as_path() to
Command::new) so the spawned process uses the version-specific binary selected
by select_mariadb_path and matches the logged path.

In `@src/domain/postgres/restore.rs`:
- Around line 142-144: The FD branch is passing "--dbname={}" literally then a
separate &url which becomes a stray positional arg to pg_restore; update the
pg_restore argument construction so the DB URL is attached correctly — either
split into two .arg calls like .arg("--dbname").arg(&url) or build a single
formatted string via format!("--dbname={}", url); locate the FD branch around
the pg_restore invocation (the .arg("--dbname={}") and .arg(&url) calls near
dump_dir) and replace those two .arg calls accordingly so pg_restore receives
the intended --dbname value.

---

Outside diff comments:
In `@src/domain/postgres/restore.rs`:
- Around line 39-56: The restore now calls pg_restore without --create and
connects to cfg.database (see PostgresDumpFormat::Fc block using --dbname &url),
so the target database must exist beforehand; add a clear doc-comment to the
run() function (and/or RestoreService::run_restore()) in this file stating that
cfg.database must be provisioned prior to invocation (operator/IaC/migration
must create it), mention that pg_restore cannot create the DB when --create is
removed, and note that callers should ensure provisioning to avoid silent ping
failures.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 92bafc5b-79c0-4ca5-97f0-50dd308a9100

📥 Commits

Reviewing files that changed from the base of the PR and between 0a42244 and 60c1d9a.

📒 Files selected for processing (6)
  • docker-compose.yml
  • src/domain/mariadb/backup.rs
  • src/domain/mariadb/restore.rs
  • src/domain/mysql/backup.rs
  • src/domain/mysql/restore.rs
  • src/domain/postgres/restore.rs

Comment thread docker-compose.yml Outdated
Comment thread docker-compose.yml Outdated
LOG: debug
TZ: "Europe/Paris"
EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiNzM0NjU3Y2YtMGQzYy00Y2UwLTkyODQtZDJmOGYyMjI2MzgzIiwibWFzdGVyS2V5QjY0IjoiMUh0djdtWCtYVkJxL0IzUEV2WDlZZjlQeUdVZW5oRHlXemo5THRqNW90WT0ifQ=="
EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiNjIyNWFlMzMtODQwMy00NmE3LWEyNDEtMjU4MTI2MjVlYTA4IiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ=="
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Likely credential leak — rotate immediately.

The new EDGE_KEY base64-decodes to a JSON payload containing serverUrl, agentId, and masterKeyB64. Committing this value into a tracked file in a shared repo is equivalent to publishing the master key. Two actions are required:

  1. Rotate the master key on the server side and reissue a fresh EDGE_KEY for this agent — assume the current value is compromised the moment this PR is pushed.
  2. Move the value out of docker-compose.yml. Use a non-tracked .env (gitignored) or a developer-local override file (docker-compose.override.yml) and reference it via ${EDGE_KEY}.
🔒 Proposed fix
-      EDGE_KEY: "eyJzZXJ2ZXJVcmwi...=="
+      EDGE_KEY: ${EDGE_KEY:?EDGE_KEY must be provided via .env}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiNjIyNWFlMzMtODQwMy00NmE3LWEyNDEtMjU4MTI2MjVlYTA4IiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ=="
EDGE_KEY: ${EDGE_KEY:?EDGE_KEY must be provided via .env}
🧰 Tools
🪛 Betterleaks (1.1.2)

[high] 23-23: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 23-23: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` at line 23, The commit leaks a sensitive EDGE_KEY value
in docker-compose.yml (the EDGE_KEY environment variable); rotate the exposed
master key on the server immediately and remove the hard-coded value from
docker-compose.yml, then reference a runtime variable (e.g., ${EDGE_KEY})
instead and load it from a gitignored .env or docker-compose.override.yml kept
out of source control; update any deployment docs or dev README to show how to
provision EDGE_KEY locally and ensure docker-compose.yml no longer contains the
literal key.

Comment on lines 33 to 37
let mariadb_dump = select_mariadb_path(&version).join("mariadb-dump");
info!("Mariadb dump found: {}", mariadb_dump.display());


let output = Command::new("mariadb-dump")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

mariadb_dump is resolved by version but never used — falls back to $PATH.

Lines 33–34 compute and log a version-specific path, then line 37 spawns the bare string "mariadb-dump", which makes select_mariadb_path effectively dead code and breaks the per-version targeting. If the host has multiple mariadb-dump binaries (or none on $PATH), behavior diverges from what the log line claims.

🔧 Proposed fix
-        let output = Command::new("mariadb-dump")
+        let output = Command::new(&mariadb_dump)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mariadb_dump = select_mariadb_path(&version).join("mariadb-dump");
info!("Mariadb dump found: {}", mariadb_dump.display());
let output = Command::new("mariadb-dump")
let mariadb_dump = select_mariadb_path(&version).join("mariadb-dump");
info!("Mariadb dump found: {}", mariadb_dump.display());
let output = Command::new(&mariadb_dump)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/mariadb/backup.rs` around lines 33 - 37, The code computes a
version-specific path into the mariadb_dump variable via
select_mariadb_path(&version) but then calls Command::new("mariadb-dump"),
ignoring that path; replace the hardcoded Command::new("mariadb-dump") with a
call that uses the computed mariadb_dump (e.g., pass mariadb_dump or
mariadb_dump.as_os_str() / mariadb_dump.as_path() to Command::new) so the
spawned process uses the version-specific binary selected by select_mariadb_path
and matches the logged path.

Comment thread src/domain/postgres/restore.rs
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 26, 2026

Codecov Report

❌ Patch coverage is 84.37500% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/domain/postgres/restore.rs 33.33% 2 Missing ⚠️
src/utils/tus.rs 0.00% 2 Missing ⚠️
src/settings.rs 88.88% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

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.

🧹 Nitpick comments (1)
src/domain/mysql/restore.rs (1)

19-22: Backtick quoting helps, but consider validating cfg.database.

Wrapping the identifier in backticks fixes the common case (reserved words, hyphens). However, if cfg.database ever contains a backtick (e.g. from a misconfigured config file), the DROP/CREATE statement will break out of the quoted identifier. Since this string is also passed via -e to a privileged mysql session, it's worth either validating the database name against an allow-list pattern (e.g. ^[A-Za-z0-9_]+$) at config load time, or escaping embedded backticks by doubling them (` ``).

Not a blocker given the value originates from trusted config, but the same hardening should apply symmetrically here and in src/domain/mariadb/restore.rs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/mysql/restore.rs` around lines 19 - 22, The DROP/CREATE SQL string
built into drop_create_cmd using cfg.database is vulnerable if cfg.database
contains backticks; either validate the database name against a tight allow-list
regex like ^[A-Za-z0-9_]+$ (reject/return error at config load) or sanitize by
escaping any backticks (replace ` with ``) before interpolation; apply the same
fix symmetrically in src/domain/mariadb/restore.rs where the same pattern is
used so both drop_create_cmd usages are safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/domain/mysql/restore.rs`:
- Around line 19-22: The DROP/CREATE SQL string built into drop_create_cmd using
cfg.database is vulnerable if cfg.database contains backticks; either validate
the database name against a tight allow-list regex like ^[A-Za-z0-9_]+$
(reject/return error at config load) or sanitize by escaping any backticks
(replace ` with ``) before interpolation; apply the same fix symmetrically in
src/domain/mariadb/restore.rs where the same pattern is used so both
drop_create_cmd usages are safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 58cc19d0-03a9-447e-af02-23ed341ad333

📥 Commits

Reviewing files that changed from the base of the PR and between 60c1d9a and 14caae8.

📒 Files selected for processing (3)
  • src/domain/mariadb/restore.rs
  • src/domain/mysql/restore.rs
  • src/domain/postgres/restore.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/domain/postgres/restore.rs
  • src/domain/mariadb/restore.rs

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/domain/mongodb/connection.rs`:
- Around line 34-45: The current logic collects candidate DB names into the
HashSet `dbs` and then uses `dbs.into_iter().next()` which is nondeterministic
when multiple source DBs exist; change this to explicitly check `dbs.len()` and
fail closed if it is not exactly 1 (e.g., return None or an Err) so a multi-DB
archive doesn't randomly pick a DB; replace the final `dbs.into_iter().next()`
with a branch that returns the single DB when `dbs.len() == 1` and returns an
error/None when `dbs.is_empty()` or `dbs.len() > 1`, referencing the `dbs`
variable and the parsing loop that finds `"archive prelude "` to locate the
code.

In `@src/domain/mongodb/restore.rs`:
- Around line 29-31: The dry-run subprocess result stored in dry_run must have
its exit status verified before attempting to parse stderr; after running the
dry-run (dry_run variable) check dry_run.status.success() and return an error
(with context like "mongorestore --dryRun failed") if it failed, so that
failures (auth/network/archive) are reported instead of being masked by
extract_db_name(&dry_output) parsing errors; update the logic around the dry_run
handling to mirror the later restore output.status.success() check and only call
extract_db_name when the dry_run succeeded.
- Around line 15-22: The dry-run Command construction currently in the block
that defines dry_run and builds a "--uri=..." string should use the same logic
as get_mongo_uri() to avoid inserting empty "username:password" and
"authSource=admin" for no-auth configs; update the dry-run URI construction (the
Command::new(&mongorestore) invocation that sets .arg(format!("--uri={}", ...)))
to call or reimplement the get_mongo_uri() logic (checking
cfg.username/cfg.password and conditionally including credentials and
authSource) so the dry-run URI matches the actual restore URI returned by
get_mongo_uri().
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 86d42ed2-5f99-41b5-a385-df711a27b781

📥 Commits

Reviewing files that changed from the base of the PR and between 14caae8 and cc163c2.

📒 Files selected for processing (5)
  • justfile
  • src/domain/mongodb/backup.rs
  • src/domain/mongodb/connection.rs
  • src/domain/mongodb/restore.rs
  • src/services/status.rs
💤 Files with no reviewable changes (2)
  • src/services/status.rs
  • src/domain/mongodb/backup.rs

Comment on lines +34 to +45
let mut dbs = std::collections::HashSet::new();
for line in dry_output.lines() {
if let Some(pos) = line.find("archive prelude ") {
let rest = &line[pos + "archive prelude ".len()..];
if let Some(dot) = rest.find('.') {
let db = &rest[..dot];
dbs.insert(db.to_string());
}
}
}
dbs.into_iter().next()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail closed when multiple source DBs are detected.

HashSet::into_iter().next() at Line 44 is nondeterministic for multi-DB archives, so restore target mapping can randomly pick the wrong source DB.

💡 Proposed fix
 pub fn extract_db_name(dry_output: &str) -> Option<String> {
-    let mut dbs = std::collections::HashSet::new();
+    let mut dbs = std::collections::BTreeSet::new();
     for line in dry_output.lines() {
         if let Some(pos) = line.find("archive prelude ") {
             let rest = &line[pos + "archive prelude ".len()..];
             if let Some(dot) = rest.find('.') {
                 let db = &rest[..dot];
-                dbs.insert(db.to_string());
+                if !db.is_empty() {
+                    dbs.insert(db.to_string());
+                }
             }
         }
     }
-    dbs.into_iter().next()
+    if dbs.len() == 1 {
+        dbs.into_iter().next()
+    } else {
+        None
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/mongodb/connection.rs` around lines 34 - 45, The current logic
collects candidate DB names into the HashSet `dbs` and then uses
`dbs.into_iter().next()` which is nondeterministic when multiple source DBs
exist; change this to explicitly check `dbs.len()` and fail closed if it is not
exactly 1 (e.g., return None or an Err) so a multi-DB archive doesn't randomly
pick a DB; replace the final `dbs.into_iter().next()` with a branch that returns
the single DB when `dbs.len() == 1` and returns an error/None when
`dbs.is_empty()` or `dbs.len() > 1`, referencing the `dbs` variable and the
parsing loop that finds `"archive prelude "` to locate the code.

Comment on lines +15 to +22
let dry_run = Command::new(&mongorestore)
.arg(format!(
"--uri={}",
format!(
"mongodb://{}:{}@{}:{}/?authSource=admin",
cfg.username, cfg.password, cfg.host, cfg.port
)
))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For mongorestore, is URI mongodb://:@host:port/?authSource=adminvalid when authentication is disabled, or should anonymous connections usemongodb://host:port/?

💡 Result:

When MongoDB authentication is truly disabled, you should use an anonymous connection string that does not include username/password material. So, use: mongodb://host:port/ The URI you proposed, mongodb://:@host:port/?authSource=admin, is not the right form for an anonymous (no-credentials) connection. In MongoDB connection strings, the "username:password@" portion is optional authentication credentials; the client only attempts authentication when credentials are provided. The authSource option is defined as the database associated with user credentials, and MongoDB ignores authSource when no username is provided. Given that, providing empty credentials while also specifying authSource is at best unnecessary and may be rejected or mis-parsed by drivers. Recommended for mongorestore (no auth): mongorestore --uri "mongodb://host:port/" ./dump If you want to restore a specific database, include it in the path (without auth options), e.g.: mongorestore --uri "mongodb://host:port/testdb" ./dump

Citations:


🏁 Script executed:

cat -n src/domain/mongodb/restore.rs | head -50

Repository: Portabase/agent

Length of output: 2471


🏁 Script executed:

cat -n src/domain/mongodb/connection.rs

Repository: Portabase/agent

Length of output: 1832


Use consistent URI construction for no-auth MongoDB configs in dry-run command.

At lines 15–22, the dry-run command always injects username:password and authSource=admin into the URI. For no-auth configurations (empty username or password), this produces an invalid URI like mongodb://:@host:port/?authSource=admin. Meanwhile, the actual restore command at line 36 uses get_mongo_uri(), which correctly handles no-auth by returning mongodb://host:port/database. This inconsistency causes dry-run to fail on valid no-auth setups while the actual restore would succeed.

Apply the same conditional logic from get_mongo_uri() to construct the dry-run URI:

Proposed fix
-        let dry_run = Command::new(&mongorestore)
+        let dry_uri = if cfg.username.is_empty() || cfg.password.is_empty() {
+            format!("mongodb://{}:{}/", cfg.host, cfg.port)
+        } else {
+            format!(
+                "mongodb://{}:{}@{}:{}/?authSource=admin",
+                cfg.username, cfg.password, cfg.host, cfg.port
+            )
+        };
+
+        let dry_run = Command::new(&mongorestore)
             .arg(format!(
                 "--uri={}",
-                format!(
-                    "mongodb://{}:{}@{}:{}/?authSource=admin",
-                    cfg.username, cfg.password, cfg.host, cfg.port
-                )
+                dry_uri
             ))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let dry_run = Command::new(&mongorestore)
.arg(format!(
"--uri={}",
format!(
"mongodb://{}:{}@{}:{}/?authSource=admin",
cfg.username, cfg.password, cfg.host, cfg.port
)
))
let dry_uri = if cfg.username.is_empty() || cfg.password.is_empty() {
format!("mongodb://{}:{}/", cfg.host, cfg.port)
} else {
format!(
"mongodb://{}:{}@{}:{}/?authSource=admin",
cfg.username, cfg.password, cfg.host, cfg.port
)
};
let dry_run = Command::new(&mongorestore)
.arg(format!(
"--uri={}",
dry_uri
))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/mongodb/restore.rs` around lines 15 - 22, The dry-run Command
construction currently in the block that defines dry_run and builds a
"--uri=..." string should use the same logic as get_mongo_uri() to avoid
inserting empty "username:password" and "authSource=admin" for no-auth configs;
update the dry-run URI construction (the Command::new(&mongorestore) invocation
that sets .arg(format!("--uri={}", ...))) to call or reimplement the
get_mongo_uri() logic (checking cfg.username/cfg.password and conditionally
including credentials and authSource) so the dry-run URI matches the actual
restore URI returned by get_mongo_uri().

Comment on lines +29 to +31
let dry_output = String::from_utf8_lossy(&dry_run.stderr);
let source_db = extract_db_name(&dry_output)
.context("Could not detect source database name from archive")?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In mongorestore --dryRun --verbose mode, are archive prelude details guaranteed on stderr only, and should callers treat non-zero exit as authoritative failure before parsing output?

💡 Result:

Non-zero exit status: treat it as authoritative failure. Don’t base correctness on whether archive “prelude” details appear on stderr vs stdout. stderr vs stdout for archive prelude details - MongoDB’s docs state that the database tools (including mongorestore) print logs to stderr. - The documentation for --dryRun + --verbose describes verbosity affecting “internal reporting returned on standard output or in log files” (i.e., you should not assume anything about where specific messages land). - In the mongorestore code, archive prelude metadata is parsed/used internally (and failures in parsing/reading return an error), rather than being something a caller should infer from stderr-only output. Should callers parse output and ignore exit code? - No: callers should key off mongorestore’s process exit status. If mongorestore encounters errors such as inability to parse/validate options or read archive/prelude metadata, the tool returns an error result and exits non-zero. - In general, callers should not treat “message seen on stderr” as the only signal. Use the exit code to decide whether it is safe to parse any summary output. Practical guidance 1) Run mongorestore with --dryRun --verbose and capture both streams if you need logs. 2) If the command’s exit code is non-zero, treat the run as failed and do not trust/parse success summaries that may still have been printed. 3) Only parse success/summary output when exit code is 0. Grounding notes: This applies to archive prelude handling because prelude metadata is not merely informational; mongorestore reads it as part of establishing the archive/dump format and may abort the restore when it can’t be processed, independent of whether any particular detail was emitted on stderr.

Citations:


🏁 Script executed:

cat -n src/domain/mongodb/restore.rs | sed -n '1,50p'

Repository: Portabase/agent

Length of output: 2471


🏁 Script executed:

wc -l src/domain/mongodb/restore.rs

Repository: Portabase/agent

Length of output: 92


🏁 Script executed:

cat -n src/domain/mongodb/restore.rs | sed -n '15,45p'

Repository: Portabase/agent

Length of output: 1501


Check dry-run exit status before parsing stderr.

The dry-run command exits at line 27 without verifying success. If mongorestore --dryRun fails due to authentication, network, or archive issues, line 30 currently masks the real error by reporting a parse failure instead. Add a status check immediately after line 27:

Fix
         let dry_output = String::from_utf8_lossy(&dry_run.stderr);
+        if !dry_run.status.success() {
+            anyhow::bail!("mongorestore dry-run failed for {}: {}", cfg.name, dry_output);
+        }
         let source_db = extract_db_name(&dry_output)
             .context("Could not detect source database name from archive")?;

The actual restore command at line 46 correctly checks output.status.success() — apply the same pattern here.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let dry_output = String::from_utf8_lossy(&dry_run.stderr);
let source_db = extract_db_name(&dry_output)
.context("Could not detect source database name from archive")?;
let dry_output = String::from_utf8_lossy(&dry_run.stderr);
if !dry_run.status.success() {
anyhow::bail!("mongorestore dry-run failed for {}: {}", cfg.name, dry_output);
}
let source_db = extract_db_name(&dry_output)
.context("Could not detect source database name from archive")?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/mongodb/restore.rs` around lines 29 - 31, The dry-run subprocess
result stored in dry_run must have its exit status verified before attempting to
parse stderr; after running the dry-run (dry_run variable) check
dry_run.status.success() and return an error (with context like "mongorestore
--dryRun failed") if it failed, so that failures (auth/network/archive) are
reported instead of being masked by extract_db_name(&dry_output) parsing errors;
update the logic around the dry_run handling to mirror the later restore
output.status.success() check and only call extract_db_name when the dry_run
succeeded.

@RambokDev RambokDev merged commit 1406884 into main Apr 29, 2026
1 of 2 checks passed
@RambokDev RambokDev deleted the fix/migration-process branch April 29, 2026 20:40
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.

1 participant