Repository: getmoto/moto
Pull request #8824 (2025-04-17 14:09:38)

RDS: do not throw an error if multiple modify requests are sent with ManageMasterUserPassword=False
I've stumbled across this while updating moto in my project. Tests which sent two modify requests with `ManageMasterUserPassword=False` are started failing afterwards.
Even though I don't remember why I wrote those tests this way and make them work again, I think it is more convenient if moto does not fail when multiple modify requests are sent with the same parameter. What do you think?

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8824?dropdown=coverage&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 92.82%. Comparing base [(`ec3224f`)](https://app.codecov.io/gh/getmoto/moto/commit/ec3224f772c8c3b46830f99e8dbc31944124e552?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`aeec668`)](https://app.codecov.io/gh/getmoto/moto/commit/aeec66875ed8dd120844a5a56b72bcce255cff75?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 8 commits behind head on master.

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


```diff
@@           Coverage Diff           @@
##           master    #8824   +/-   ##
=======================================
  Coverage   92.82%   92.82%           
=======================================
  Files        1266     1266           
  Lines      110461   110521   +60     
=======================================
+ Hits       102533   102589   +56     
- Misses       7928     7932    +4     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8824/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/8824/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.16% <0.00%> (+0.04%)` | :arrow_up: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8824/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `92.79% <100.00%> (+<0.01%)` | :arrow_up: |

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/8824?dropdown=coverage&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).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
bpandola: @lukaalba The real AWS backend requires that `MasterUserPassword` be passed in whenever `ManageMasterUserPassword` is set to `False`, but `moto` doesn't validate that currently, so... I'm happy to merge this fix in for now just because it obviously shouldn't fail for the case you've identified.

Patch:
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 3a291b6d6..de0855cab 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -1454,7 +1454,9 @@ class DBInstance(EventMixin, CloudFormationModel, RDSBaseModel):
             self.master_user_secret = MasterUserSecret(
                 self, master_user_secret_kms_key_id
             )
-        elif manage_master_user_password is False:
+        elif manage_master_user_password is False and hasattr(
+            self, "master_user_secret"
+        ):
             self.master_user_secret.delete_secret()
             del self.master_user_secret
 
@@ -3141,7 +3143,9 @@ class RDSBackend(BaseBackend):
             if manage_master_user_password:
                 kms_key_id = kwargs.pop("master_user_secret_kms_key_id", None)
                 cluster.master_user_secret = MasterUserSecret(cluster, kms_key_id)
-            else:
+            elif not manage_master_user_password and hasattr(
+                cluster, "master_user_secret"
+            ):
                 cluster.master_user_secret.delete_secret()
                 del cluster.master_user_secret
 


Test patch:
diff --git a/tests/test_rds/test_rds.py b/tests/test_rds/test_rds.py
index 7fdf0446c..a9650033e 100644
--- a/tests/test_rds/test_rds.py
+++ b/tests/test_rds/test_rds.py
@@ -486,6 +486,10 @@ def test_modify_db_instance_manage_master_user_password(
         DBInstanceIdentifier=db_id, ManageMasterUserPassword=False
     )
 
+    retry_revert_modification_response = client.modify_db_instance(
+        DBInstanceIdentifier=db_id, ManageMasterUserPassword=False
+    )
+
     assert db_instance.get("MasterUserSecret") is None
     master_user_secret = modify_response["DBInstance"]["MasterUserSecret"]
     assert len(master_user_secret.keys()) == 3
@@ -510,6 +514,9 @@ def test_modify_db_instance_manage_master_user_password(
         == describe_response["DBInstances"][0]["MasterUserSecret"]["SecretArn"]
     )
     assert revert_modification_response["DBInstance"].get("MasterUserSecret") is None
+    assert (
+        retry_revert_modification_response["DBInstance"].get("MasterUserSecret") is None
+    )
 
 
 @pytest.mark.parametrize("with_apply_immediately", [True, False])
diff --git a/tests/test_rds/test_rds_clusters.py b/tests/test_rds/test_rds_clusters.py
index dd30ab133..1570656c2 100644
--- a/tests/test_rds/test_rds_clusters.py
+++ b/tests/test_rds/test_rds_clusters.py
@@ -189,6 +189,10 @@ def test_modify_db_cluster_manage_master_user_password(client, with_custom_kms_k
         DBClusterIdentifier=cluster_id, ManageMasterUserPassword=False
     )
 
+    retry_revert_modification_response = client.modify_db_cluster(
+        DBClusterIdentifier=cluster_id, ManageMasterUserPassword=False
+    )
+
     assert create_response["DBCluster"].get("MasterUserSecret") is None
     master_user_secret = modify_response["DBCluster"]["MasterUserSecret"]
     assert len(master_user_secret.keys()) == 3
@@ -213,6 +217,9 @@ def test_modify_db_cluster_manage_master_user_password(client, with_custom_kms_k
         == describe_response["DBClusters"][0]["MasterUserSecret"]["SecretArn"]
     )
     assert revert_modification_response["DBCluster"].get("MasterUserSecret") is None
+    assert (
+        retry_revert_modification_response["DBCluster"].get("MasterUserSecret") is None
+    )
 
 
 @pytest.mark.parametrize("with_apply_immediately", [True, False])