Repository: getmoto/moto
Pull request #7392 (2024-02-25 12:54:58)

RDS: Allow copying snapshots of deleted DB instances
Fix a regression introduced in #6444, in which copying a snapshot of an already deleted DB instance results in raising a `DBInstanceNotFoundError`.
The expected behavior is that copying a snapshot is supported regardless whether its source database exists or not.

Change `create_db_snapshot` to accept a `Database` object (instead of an identifier), and use this flow in the `copy_db_snapshot` implementation. This works because the actual `Database` object is not deleted (as it is referred to by the snapshot), but rather it is already removed from the `databases` dictionary.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7392?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.77%. Comparing base [(`9aef694`)](https://app.codecov.io/gh/getmoto/moto/commit/9aef6944da6a6f1b84be99cefb34f1724e6ca58f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`09c5b28`)](https://app.codecov.io/gh/getmoto/moto/pull/7392?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 51 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #7392      +/-   ##
==========================================
- Coverage   95.88%   95.77%   -0.11%     
==========================================
  Files         843      843              
  Lines       82578    83051     +473     
==========================================
+ Hits        79178    79544     +366     
- Misses       3400     3507     +107     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7392/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/7392/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `32.50% <0.00%> (-0.03%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7392/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `95.74% <100.00%> (-0.11%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/7392?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


Patch:
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 18ffab32f..ace7eaf05 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -1782,14 +1782,18 @@ class RDSBackend(BaseBackend):
 
     def create_db_snapshot(
         self,
-        db_instance_identifier: str,
+        db_instance: Union[str, Database],
         db_snapshot_identifier: str,
         snapshot_type: str = "manual",
         tags: Optional[List[Dict[str, str]]] = None,
     ) -> DatabaseSnapshot:
-        database = self.databases.get(db_instance_identifier)
-        if not database:
-            raise DBInstanceNotFoundError(db_instance_identifier)
+        if isinstance(db_instance, str):
+            database = self.databases.get(db_instance)
+            if not database:
+                raise DBInstanceNotFoundError(db_instance)
+        else:
+            database = db_instance
+
         if db_snapshot_identifier in self.database_snapshots:
             raise DBSnapshotAlreadyExistsError(db_snapshot_identifier)
         if len(self.database_snapshots) >= int(
@@ -1821,7 +1825,7 @@ class RDSBackend(BaseBackend):
         else:
             tags = self._merge_tags(source_snapshot.tags, tags)
         return self.create_db_snapshot(
-            db_instance_identifier=source_snapshot.database.db_instance_identifier,  # type: ignore
+            db_instance=source_snapshot.database,
             db_snapshot_identifier=target_snapshot_identifier,
             tags=tags,
         )


Test patch:
diff --git a/tests/test_rds/test_rds.py b/tests/test_rds/test_rds.py
index 4f0aa02ae..bf06b5645 100644
--- a/tests/test_rds/test_rds.py
+++ b/tests/test_rds/test_rds.py
@@ -1039,8 +1039,9 @@ def test_create_db_snapshots_with_tags():
     ]
 
 
+@pytest.mark.parametrize("delete_db_instance", [True, False])
 @mock_aws
-def test_copy_db_snapshots():
+def test_copy_db_snapshots(delete_db_instance: bool):
     conn = boto3.client("rds", region_name=DEFAULT_REGION)
 
     conn.create_db_instance(
@@ -1059,6 +1060,10 @@ def test_copy_db_snapshots():
         DBInstanceIdentifier="db-primary-1", DBSnapshotIdentifier="snapshot-1"
     ).get("DBSnapshot")
 
+    if delete_db_instance:
+        # Delete the original instance, but the copy snapshot operation should still succeed.
+        conn.delete_db_instance(DBInstanceIdentifier="db-primary-1")
+
     target_snapshot = conn.copy_db_snapshot(
         SourceDBSnapshotIdentifier="snapshot-1", TargetDBSnapshotIdentifier="snapshot-2"
     ).get("DBSnapshot")