Repository: getmoto/moto

Issue:
Support for secretsmanager update_secret
I would like to explore the possibility of adding update_secret support to the secretsmanager endpoint and am filing this issue per the contributor guidelines.

As of moto 1.3.14 support for update_secret is not yet implemented. The following is raised when the secretsmanager client is used in my personal code

```
E.   NotImplementedError: The update_secret action has not been implemented
.tox/unit/lib/python3.7/site-packages/moto/core/responses.py:393: NotImplementedError
```

I would like to take a stab at adding this so that I can use moto to test in-house code that calls this method.

I'm new to the moto process though, so want to have a maintainer +1 this work before I put any serious effort into it. Preliminary toying with a fix seems relatively straightforward.


Discussion:
Hi @caphrim007, that's a very useful feature! A PR would be welcome.

Let me know if you need any help.

Resolving patch:
diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py
--- a/moto/secretsmanager/models.py
+++ b/moto/secretsmanager/models.py
@@ -107,6 +107,34 @@ def get_secret_value(self, secret_id, version_id, version_stage):
 
         return response
 
+    def update_secret(
+        self, secret_id, secret_string=None, secret_binary=None, **kwargs
+    ):
+
+        # error if secret does not exist
+        if secret_id not in self.secrets.keys():
+            raise SecretNotFoundException()
+
+        if "deleted_date" in self.secrets[secret_id]:
+            raise InvalidRequestException(
+                "An error occurred (InvalidRequestException) when calling the UpdateSecret operation: "
+                "You can't perform this operation on the secret because it was marked for deletion."
+            )
+
+        version_id = self._add_secret(
+            secret_id, secret_string=secret_string, secret_binary=secret_binary
+        )
+
+        response = json.dumps(
+            {
+                "ARN": secret_arn(self.region, secret_id),
+                "Name": secret_id,
+                "VersionId": version_id,
+            }
+        )
+
+        return response
+
     def create_secret(
         self, name, secret_string=None, secret_binary=None, tags=[], **kwargs
     ):
diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py
--- a/moto/secretsmanager/responses.py
+++ b/moto/secretsmanager/responses.py
@@ -29,6 +29,16 @@ def create_secret(self):
             tags=tags,
         )
 
+    def update_secret(self):
+        secret_id = self._get_param("SecretId")
+        secret_string = self._get_param("SecretString")
+        secret_binary = self._get_param("SecretBinary")
+        return secretsmanager_backends[self.region].update_secret(
+            secret_id=secret_id,
+            secret_string=secret_string,
+            secret_binary=secret_binary,
+        )
+
     def get_random_password(self):
         password_length = self._get_param("PasswordLength", if_none=32)
         exclude_characters = self._get_param("ExcludeCharacters", if_none="")


Test patch:
diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py
--- a/tests/test_secretsmanager/test_secretsmanager.py
+++ b/tests/test_secretsmanager/test_secretsmanager.py
@@ -711,3 +711,79 @@ def test_can_list_secret_version_ids():
     returned_version_ids = [v["VersionId"] for v in versions_list["Versions"]]
 
     assert [first_version_id, second_version_id].sort() == returned_version_ids.sort()
+
+
+@mock_secretsmanager
+def test_update_secret():
+    conn = boto3.client("secretsmanager", region_name="us-west-2")
+
+    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
+
+    assert created_secret["ARN"]
+    assert created_secret["Name"] == "test-secret"
+    assert created_secret["VersionId"] != ""
+
+    secret = conn.get_secret_value(SecretId="test-secret")
+    assert secret["SecretString"] == "foosecret"
+
+    updated_secret = conn.update_secret(
+        SecretId="test-secret", SecretString="barsecret"
+    )
+
+    assert updated_secret["ARN"]
+    assert updated_secret["Name"] == "test-secret"
+    assert updated_secret["VersionId"] != ""
+
+    secret = conn.get_secret_value(SecretId="test-secret")
+    assert secret["SecretString"] == "barsecret"
+    assert created_secret["VersionId"] != updated_secret["VersionId"]
+
+
+@mock_secretsmanager
+def test_update_secret_which_does_not_exit():
+    conn = boto3.client("secretsmanager", region_name="us-west-2")
+
+    with assert_raises(ClientError) as cm:
+        updated_secret = conn.update_secret(
+            SecretId="test-secret", SecretString="barsecret"
+        )
+
+    assert_equal(
+        "Secrets Manager can't find the specified secret.",
+        cm.exception.response["Error"]["Message"],
+    )
+
+
+@mock_secretsmanager
+def test_update_secret_marked_as_deleted():
+    conn = boto3.client("secretsmanager", region_name="us-west-2")
+
+    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
+    deleted_secret = conn.delete_secret(SecretId="test-secret")
+
+    with assert_raises(ClientError) as cm:
+        updated_secret = conn.update_secret(
+            SecretId="test-secret", SecretString="barsecret"
+        )
+
+    assert (
+        "because it was marked for deletion."
+        in cm.exception.response["Error"]["Message"]
+    )
+
+
+@mock_secretsmanager
+def test_update_secret_marked_as_deleted_after_restoring():
+    conn = boto3.client("secretsmanager", region_name="us-west-2")
+
+    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
+    deleted_secret = conn.delete_secret(SecretId="test-secret")
+    restored_secret = conn.restore_secret(SecretId="test-secret")
+
+    updated_secret = conn.update_secret(
+        SecretId="test-secret", SecretString="barsecret"
+    )
+
+    assert updated_secret["ARN"]
+    assert updated_secret["Name"] == "test-secret"
+    assert updated_secret["VersionId"] != ""