From 381e7b165fdd18c7aebc7cfd1379d44468e87861 Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Tue, 15 Oct 2019 21:57:16 +1100 Subject: [PATCH 1/8] Raise appropriate error when secret exists but has no value --- moto/secretsmanager/exceptions.py | 4 ++-- moto/secretsmanager/models.py | 20 ++++++++++++------- .../test_secretsmanager.py | 17 +++++++++++++++- tests/test_secretsmanager/test_server.py | 20 +++++++++++++++++++ 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/moto/secretsmanager/exceptions.py b/moto/secretsmanager/exceptions.py index fa81b6d8b..746368a6b 100644 --- a/moto/secretsmanager/exceptions.py +++ b/moto/secretsmanager/exceptions.py @@ -7,11 +7,11 @@ class SecretsManagerClientError(JsonRESTError): class ResourceNotFoundException(SecretsManagerClientError): - def __init__(self): + def __init__(self, message): self.code = 404 super(ResourceNotFoundException, self).__init__( "ResourceNotFoundException", - "Secrets Manager can't find the specified secret" + message, ) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 63d847c49..f7cb855eb 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -46,7 +46,7 @@ class SecretsManagerBackend(BaseBackend): def get_secret_value(self, secret_id, version_id, version_stage): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException() + raise ResourceNotFoundException("Secrets Manager can't find the specified secret") if not version_id and version_stage: # set version_id to match version_stage @@ -56,7 +56,7 @@ class SecretsManagerBackend(BaseBackend): version_id = ver_id break if not version_id: - raise ResourceNotFoundException() + raise ResourceNotFoundException("Secrets Manager can't find the specified secret") # TODO check this part if 'deleted_date' in self.secrets[secret_id]: @@ -84,6 +84,12 @@ class SecretsManagerBackend(BaseBackend): if 'secret_binary' in secret_version: response_data["SecretBinary"] = secret_version['secret_binary'] + if 'secret_string' not in secret_version and 'secret_binary' not in secret_version: + raise ResourceNotFoundException( + "Secrets Manager can’t find the specified secret value for staging label: %s" % + (version_stage or "AWSCURRENT") + ) + response = json.dumps(response_data) return response @@ -169,7 +175,7 @@ class SecretsManagerBackend(BaseBackend): def describe_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException + raise ResourceNotFoundException("Secrets Manager can't find the specified secret") secret = self.secrets[secret_id] @@ -198,7 +204,7 @@ class SecretsManagerBackend(BaseBackend): rotation_days = 'AutomaticallyAfterDays' if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException + raise ResourceNotFoundException("Secrets Manager can't find the specified secret") if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -340,7 +346,7 @@ class SecretsManagerBackend(BaseBackend): def delete_secret(self, secret_id, recovery_window_in_days, force_delete_without_recovery): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException + raise ResourceNotFoundException("Secrets Manager can't find the specified secret") if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -370,7 +376,7 @@ class SecretsManagerBackend(BaseBackend): secret = self.secrets.get(secret_id, None) if not secret: - raise ResourceNotFoundException + raise ResourceNotFoundException("Secrets Manager can't find the specified secret") arn = secret_arn(self.region, secret['secret_id']) name = secret['name'] @@ -380,7 +386,7 @@ class SecretsManagerBackend(BaseBackend): def restore_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException + raise ResourceNotFoundException("Secrets Manager can't find the specified secret") self.secrets[secret_id].pop('deleted_date', None) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 62de93bab..b77fa70d0 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -8,7 +8,7 @@ import string import pytz from datetime import datetime import sure # noqa -from nose.tools import assert_raises +from nose.tools import assert_raises, assert_raises_regexp from six import b DEFAULT_SECRET_NAME = 'test-secret' @@ -65,6 +65,21 @@ def test_get_secret_value_that_is_marked_deleted(): result = conn.get_secret_value(SecretId='test-secret') +@mock_secretsmanager +def test_get_secret_that_has_no_value(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + + create_secret = conn.create_secret(Name="java-util-test-password") + + with assert_raises_regexp( + ClientError, + r"An error occurred \(ResourceNotFoundException\) when calling the GetSecretValue " + r"operation: Secrets Manager can’t find the specified secret value for staging label: " + r"AWSCURRENT" + ): + result = conn.get_secret_value(SecretId='java-util-test-password') + + @mock_secretsmanager def test_create_secret(): conn = boto3.client('secretsmanager', region_name='us-east-1') diff --git a/tests/test_secretsmanager/test_server.py b/tests/test_secretsmanager/test_server.py index 23d823239..25fc1676b 100644 --- a/tests/test_secretsmanager/test_server.py +++ b/tests/test_secretsmanager/test_server.py @@ -73,6 +73,26 @@ def test_get_secret_that_does_not_match(): assert json_data['message'] == "Secrets Manager can't find the specified secret" assert json_data['__type'] == 'ResourceNotFoundException' +@mock_secretsmanager +def test_get_secret_that_has_no_value(): + backend = server.create_backend_app('secretsmanager') + test_client = backend.test_client() + + create_secret = test_client.post('/', + data={"Name": DEFAULT_SECRET_NAME}, + headers={ + "X-Amz-Target": "secretsmanager.CreateSecret"}, + ) + get_secret = test_client.post('/', + data={"SecretId": DEFAULT_SECRET_NAME}, + headers={ + "X-Amz-Target": "secretsmanager.GetSecretValue"}, + ) + + json_data = json.loads(get_secret.data.decode("utf-8")) + assert json_data['message'] == "Secrets Manager can’t find the specified secret value for staging label: AWSCURRENT" + assert json_data['__type'] == 'ResourceNotFoundException' + @mock_secretsmanager def test_create_secret(): From 9d6a1ca81d3296a465cbe038ce4e12eeb57f2c7a Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Wed, 16 Oct 2019 10:58:59 +1100 Subject: [PATCH 2/8] Fix slightly incorrect message for some errors --- moto/secretsmanager/models.py | 14 +++++++------- tests/test_secretsmanager/test_secretsmanager.py | 12 ++++++++++-- tests/test_secretsmanager/test_server.py | 12 ++++++------ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index f7cb855eb..b46e04d6c 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -46,7 +46,7 @@ class SecretsManagerBackend(BaseBackend): def get_secret_value(self, secret_id, version_id, version_stage): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can't find the specified secret") + raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") if not version_id and version_stage: # set version_id to match version_stage @@ -56,7 +56,7 @@ class SecretsManagerBackend(BaseBackend): version_id = ver_id break if not version_id: - raise ResourceNotFoundException("Secrets Manager can't find the specified secret") + raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") # TODO check this part if 'deleted_date' in self.secrets[secret_id]: @@ -175,7 +175,7 @@ class SecretsManagerBackend(BaseBackend): def describe_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can't find the specified secret") + raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") secret = self.secrets[secret_id] @@ -204,7 +204,7 @@ class SecretsManagerBackend(BaseBackend): rotation_days = 'AutomaticallyAfterDays' if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can't find the specified secret") + raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -346,7 +346,7 @@ class SecretsManagerBackend(BaseBackend): def delete_secret(self, secret_id, recovery_window_in_days, force_delete_without_recovery): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can't find the specified secret") + raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -376,7 +376,7 @@ class SecretsManagerBackend(BaseBackend): secret = self.secrets.get(secret_id, None) if not secret: - raise ResourceNotFoundException("Secrets Manager can't find the specified secret") + raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") arn = secret_arn(self.region, secret['secret_id']) name = secret['name'] @@ -386,7 +386,7 @@ class SecretsManagerBackend(BaseBackend): def restore_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can't find the specified secret") + raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") self.secrets[secret_id].pop('deleted_date', None) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index b77fa70d0..7273b5c21 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -38,7 +38,11 @@ def test_get_secret_value_binary(): def test_get_secret_that_does_not_exist(): conn = boto3.client('secretsmanager', region_name='us-west-2') - with assert_raises(ClientError): + with assert_raises_regexp( + ClientError, + r"An error occurred \(ResourceNotFoundException\) when calling the GetSecretValue " + r"operation: Secrets Manager can’t find the specified secret." + ): result = conn.get_secret_value(SecretId='i-dont-exist') @@ -48,7 +52,11 @@ def test_get_secret_that_does_not_match(): create_secret = conn.create_secret(Name='java-util-test-password', SecretString="foosecret") - with assert_raises(ClientError): + with assert_raises_regexp( + ClientError, + r"An error occurred \(ResourceNotFoundException\) when calling the GetSecretValue " + r"operation: Secrets Manager can’t find the specified secret." + ): result = conn.get_secret_value(SecretId='i-dont-match') diff --git a/tests/test_secretsmanager/test_server.py b/tests/test_secretsmanager/test_server.py index 25fc1676b..a1491dc8f 100644 --- a/tests/test_secretsmanager/test_server.py +++ b/tests/test_secretsmanager/test_server.py @@ -49,7 +49,7 @@ def test_get_secret_that_does_not_exist(): "X-Amz-Target": "secretsmanager.GetSecretValue"}, ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == "Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -70,7 +70,7 @@ def test_get_secret_that_does_not_match(): "X-Amz-Target": "secretsmanager.GetSecretValue"}, ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == "Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -178,7 +178,7 @@ def test_describe_secret_that_does_not_exist(): ) json_data = json.loads(describe_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == "Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -202,7 +202,7 @@ def test_describe_secret_that_does_not_match(): ) json_data = json.loads(describe_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == "Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -303,7 +303,7 @@ def test_rotate_secret_that_does_not_exist(): ) json_data = json.loads(rotate_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == "Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -327,7 +327,7 @@ def test_rotate_secret_that_does_not_match(): ) json_data = json.loads(rotate_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == "Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager From d74f9e47c834de7e3527f25619ed6326f5686ba5 Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Wed, 16 Oct 2019 12:44:30 +1100 Subject: [PATCH 3/8] Add coding hint for python2 compatibility --- moto/secretsmanager/models.py | 1 + tests/test_secretsmanager/test_secretsmanager.py | 1 + tests/test_secretsmanager/test_server.py | 1 + 3 files changed, 3 insertions(+) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index b46e04d6c..7040d4136 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import time diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 7273b5c21..606f775b2 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import boto3 diff --git a/tests/test_secretsmanager/test_server.py b/tests/test_secretsmanager/test_server.py index a1491dc8f..bc8b9700d 100644 --- a/tests/test_secretsmanager/test_server.py +++ b/tests/test_secretsmanager/test_server.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import json From 9a54cea4f1fbd66710ec428f45aaaadd30a2d746 Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Wed, 16 Oct 2019 14:44:41 +1100 Subject: [PATCH 4/8] Work around python2 unicode exception str() issues --- moto/secretsmanager/models.py | 18 +++++++++--------- tests/test_secretsmanager/test_server.py | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 7040d4136..6ecc32935 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -47,7 +47,7 @@ class SecretsManagerBackend(BaseBackend): def get_secret_value(self, secret_id, version_id, version_stage): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") + raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") if not version_id and version_stage: # set version_id to match version_stage @@ -57,7 +57,7 @@ class SecretsManagerBackend(BaseBackend): version_id = ver_id break if not version_id: - raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") + raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") # TODO check this part if 'deleted_date' in self.secrets[secret_id]: @@ -87,8 +87,8 @@ class SecretsManagerBackend(BaseBackend): if 'secret_string' not in secret_version and 'secret_binary' not in secret_version: raise ResourceNotFoundException( - "Secrets Manager can’t find the specified secret value for staging label: %s" % - (version_stage or "AWSCURRENT") + u"Secrets Manager can’t find the specified secret value for staging label: %s" % + (version_stage or u"AWSCURRENT") ) response = json.dumps(response_data) @@ -176,7 +176,7 @@ class SecretsManagerBackend(BaseBackend): def describe_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") + raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") secret = self.secrets[secret_id] @@ -205,7 +205,7 @@ class SecretsManagerBackend(BaseBackend): rotation_days = 'AutomaticallyAfterDays' if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") + raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -347,7 +347,7 @@ class SecretsManagerBackend(BaseBackend): def delete_secret(self, secret_id, recovery_window_in_days, force_delete_without_recovery): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") + raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -377,7 +377,7 @@ class SecretsManagerBackend(BaseBackend): secret = self.secrets.get(secret_id, None) if not secret: - raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") + raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") arn = secret_arn(self.region, secret['secret_id']) name = secret['name'] @@ -387,7 +387,7 @@ class SecretsManagerBackend(BaseBackend): def restore_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException("Secrets Manager can’t find the specified secret.") + raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") self.secrets[secret_id].pop('deleted_date', None) diff --git a/tests/test_secretsmanager/test_server.py b/tests/test_secretsmanager/test_server.py index bc8b9700d..552ed6a92 100644 --- a/tests/test_secretsmanager/test_server.py +++ b/tests/test_secretsmanager/test_server.py @@ -50,7 +50,7 @@ def test_get_secret_that_does_not_exist(): "X-Amz-Target": "secretsmanager.GetSecretValue"}, ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -71,7 +71,7 @@ def test_get_secret_that_does_not_match(): "X-Amz-Target": "secretsmanager.GetSecretValue"}, ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -91,7 +91,7 @@ def test_get_secret_that_has_no_value(): ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can’t find the specified secret value for staging label: AWSCURRENT" + assert json_data['message'] == u"Secrets Manager can’t find the specified secret value for staging label: AWSCURRENT" assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -179,7 +179,7 @@ def test_describe_secret_that_does_not_exist(): ) json_data = json.loads(describe_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -203,7 +203,7 @@ def test_describe_secret_that_does_not_match(): ) json_data = json.loads(describe_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -304,7 +304,7 @@ def test_rotate_secret_that_does_not_exist(): ) json_data = json.loads(rotate_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -328,7 +328,7 @@ def test_rotate_secret_that_does_not_match(): ) json_data = json.loads(rotate_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can’t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager From 8eba88d1afd314581705cb02b4fcc1ebcdb7eca5 Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Wed, 16 Oct 2019 15:16:38 +1100 Subject: [PATCH 5/8] Fix python2 unicode-in-exceptions issue for tests --- .../test_secretsmanager.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 606f775b2..7319241d3 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -9,7 +9,7 @@ import string import pytz from datetime import datetime import sure # noqa -from nose.tools import assert_raises, assert_raises_regexp +from nose.tools import assert_raises, assert_equal from six import b DEFAULT_SECRET_NAME = 'test-secret' @@ -39,13 +39,14 @@ def test_get_secret_value_binary(): def test_get_secret_that_does_not_exist(): conn = boto3.client('secretsmanager', region_name='us-west-2') - with assert_raises_regexp( - ClientError, - r"An error occurred \(ResourceNotFoundException\) when calling the GetSecretValue " - r"operation: Secrets Manager can’t find the specified secret." - ): + with assert_raises(ClientError) as cm: result = conn.get_secret_value(SecretId='i-dont-exist') + assert_equal( + u"Secrets Manager can’t find the specified secret.", + cm.exception.response['Error']['Message'] + ) + @mock_secretsmanager def test_get_secret_that_does_not_match(): @@ -53,13 +54,13 @@ def test_get_secret_that_does_not_match(): create_secret = conn.create_secret(Name='java-util-test-password', SecretString="foosecret") - with assert_raises_regexp( - ClientError, - r"An error occurred \(ResourceNotFoundException\) when calling the GetSecretValue " - r"operation: Secrets Manager can’t find the specified secret." - ): + with assert_raises(ClientError) as cm: result = conn.get_secret_value(SecretId='i-dont-match') + assert_equal( + u"Secrets Manager can’t find the specified secret.", + cm.exception.response['Error']['Message'] + ) @mock_secretsmanager def test_get_secret_value_that_is_marked_deleted(): @@ -80,14 +81,14 @@ def test_get_secret_that_has_no_value(): create_secret = conn.create_secret(Name="java-util-test-password") - with assert_raises_regexp( - ClientError, - r"An error occurred \(ResourceNotFoundException\) when calling the GetSecretValue " - r"operation: Secrets Manager can’t find the specified secret value for staging label: " - r"AWSCURRENT" - ): + with assert_raises(ClientError) as cm: result = conn.get_secret_value(SecretId='java-util-test-password') + assert_equal( + u"Secrets Manager can’t find the specified secret value for staging label: AWSCURRENT", + cm.exception.response['Error']['Message'] + ) + @mock_secretsmanager def test_create_secret(): From 4eb921480eaafffa33ac010d268d6a5e4f12c301 Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Fri, 18 Oct 2019 10:09:16 +1100 Subject: [PATCH 6/8] Use specific exception to prevent repetition --- moto/secretsmanager/exceptions.py | 9 +++++++++ moto/secretsmanager/models.py | 15 ++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/moto/secretsmanager/exceptions.py b/moto/secretsmanager/exceptions.py index 746368a6b..d4563dc42 100644 --- a/moto/secretsmanager/exceptions.py +++ b/moto/secretsmanager/exceptions.py @@ -15,6 +15,15 @@ class ResourceNotFoundException(SecretsManagerClientError): ) +class SecretNotFoundException(SecretsManagerClientError): + def __init__(self): + self.code = 404 + super(SecretNotFoundException, self).__init__( + "ResourceNotFoundException", + message=u"Secrets Manager can\u2019t find the specified secret." + ) + + class ClientError(SecretsManagerClientError): def __init__(self, message): super(ClientError, self).__init__( diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 6ecc32935..2a3e7ede4 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -11,6 +11,7 @@ import boto3 from moto.core import BaseBackend, BaseModel from .exceptions import ( ResourceNotFoundException, + SecretNotFoundException, InvalidParameterException, ResourceExistsException, InvalidRequestException, @@ -47,7 +48,7 @@ class SecretsManagerBackend(BaseBackend): def get_secret_value(self, secret_id, version_id, version_stage): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") + raise SecretNotFoundException() if not version_id and version_stage: # set version_id to match version_stage @@ -57,7 +58,7 @@ class SecretsManagerBackend(BaseBackend): version_id = ver_id break if not version_id: - raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") + raise SecretNotFoundException() # TODO check this part if 'deleted_date' in self.secrets[secret_id]: @@ -176,7 +177,7 @@ class SecretsManagerBackend(BaseBackend): def describe_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") + raise SecretNotFoundException() secret = self.secrets[secret_id] @@ -205,7 +206,7 @@ class SecretsManagerBackend(BaseBackend): rotation_days = 'AutomaticallyAfterDays' if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") + raise SecretNotFoundException() if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -347,7 +348,7 @@ class SecretsManagerBackend(BaseBackend): def delete_secret(self, secret_id, recovery_window_in_days, force_delete_without_recovery): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") + raise SecretNotFoundException() if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -377,7 +378,7 @@ class SecretsManagerBackend(BaseBackend): secret = self.secrets.get(secret_id, None) if not secret: - raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") + raise SecretNotFoundException() arn = secret_arn(self.region, secret['secret_id']) name = secret['name'] @@ -387,7 +388,7 @@ class SecretsManagerBackend(BaseBackend): def restore_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException(u"Secrets Manager can’t find the specified secret.") + raise SecretNotFoundException() self.secrets[secret_id].pop('deleted_date', None) From 6120a60263b215862941dfc0e2b5fd132bd00261 Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Fri, 18 Oct 2019 10:13:22 +1100 Subject: [PATCH 7/8] Use escape sequence to express non-ASCII character --- moto/secretsmanager/models.py | 2 +- tests/test_secretsmanager/test_secretsmanager.py | 7 ++++--- tests/test_secretsmanager/test_server.py | 14 +++++++------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 2a3e7ede4..1e9d6a518 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -88,7 +88,7 @@ class SecretsManagerBackend(BaseBackend): if 'secret_string' not in secret_version and 'secret_binary' not in secret_version: raise ResourceNotFoundException( - u"Secrets Manager can’t find the specified secret value for staging label: %s" % + u"Secrets Manager can\u2019t find the specified secret value for staging label: %s" % (version_stage or u"AWSCURRENT") ) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 7319241d3..e2fc266ea 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -43,7 +43,7 @@ def test_get_secret_that_does_not_exist(): result = conn.get_secret_value(SecretId='i-dont-exist') assert_equal( - u"Secrets Manager can’t find the specified secret.", + u"Secrets Manager can\u2019t find the specified secret.", cm.exception.response['Error']['Message'] ) @@ -58,10 +58,11 @@ def test_get_secret_that_does_not_match(): result = conn.get_secret_value(SecretId='i-dont-match') assert_equal( - u"Secrets Manager can’t find the specified secret.", + u"Secrets Manager can\u2019t find the specified secret.", cm.exception.response['Error']['Message'] ) + @mock_secretsmanager def test_get_secret_value_that_is_marked_deleted(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -85,7 +86,7 @@ def test_get_secret_that_has_no_value(): result = conn.get_secret_value(SecretId='java-util-test-password') assert_equal( - u"Secrets Manager can’t find the specified secret value for staging label: AWSCURRENT", + u"Secrets Manager can\u2019t find the specified secret value for staging label: AWSCURRENT", cm.exception.response['Error']['Message'] ) diff --git a/tests/test_secretsmanager/test_server.py b/tests/test_secretsmanager/test_server.py index 552ed6a92..6955d8232 100644 --- a/tests/test_secretsmanager/test_server.py +++ b/tests/test_secretsmanager/test_server.py @@ -50,7 +50,7 @@ def test_get_secret_that_does_not_exist(): "X-Amz-Target": "secretsmanager.GetSecretValue"}, ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == u"Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -71,7 +71,7 @@ def test_get_secret_that_does_not_match(): "X-Amz-Target": "secretsmanager.GetSecretValue"}, ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == u"Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -91,7 +91,7 @@ def test_get_secret_that_has_no_value(): ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == u"Secrets Manager can’t find the specified secret value for staging label: AWSCURRENT" + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret value for staging label: AWSCURRENT" assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -179,7 +179,7 @@ def test_describe_secret_that_does_not_exist(): ) json_data = json.loads(describe_secret.data.decode("utf-8")) - assert json_data['message'] == u"Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -203,7 +203,7 @@ def test_describe_secret_that_does_not_match(): ) json_data = json.loads(describe_secret.data.decode("utf-8")) - assert json_data['message'] == u"Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -304,7 +304,7 @@ def test_rotate_secret_that_does_not_exist(): ) json_data = json.loads(rotate_secret.data.decode("utf-8")) - assert json_data['message'] == u"Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -328,7 +328,7 @@ def test_rotate_secret_that_does_not_match(): ) json_data = json.loads(rotate_secret.data.decode("utf-8")) - assert json_data['message'] == u"Secrets Manager can’t find the specified secret." + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager From 30853a0b5cde01495adc371c9612574c5e640d2d Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Fri, 18 Oct 2019 12:06:12 +1100 Subject: [PATCH 8/8] Use specialised exception for "secret has no value" scenario --- moto/secretsmanager/exceptions.py | 12 ++++++++++++ moto/secretsmanager/models.py | 7 ++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/moto/secretsmanager/exceptions.py b/moto/secretsmanager/exceptions.py index d4563dc42..7ef1a9239 100644 --- a/moto/secretsmanager/exceptions.py +++ b/moto/secretsmanager/exceptions.py @@ -15,6 +15,7 @@ class ResourceNotFoundException(SecretsManagerClientError): ) +# Using specialised exception due to the use of a non-ASCII character class SecretNotFoundException(SecretsManagerClientError): def __init__(self): self.code = 404 @@ -24,6 +25,17 @@ class SecretNotFoundException(SecretsManagerClientError): ) +# Using specialised exception due to the use of a non-ASCII character +class SecretHasNoValueException(SecretsManagerClientError): + def __init__(self, version_stage): + self.code = 404 + super(SecretHasNoValueException, self).__init__( + "ResourceNotFoundException", + message=u"Secrets Manager can\u2019t find the specified secret " + u"value for staging label: {}".format(version_stage) + ) + + class ClientError(SecretsManagerClientError): def __init__(self, message): super(ClientError, self).__init__( diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 1e9d6a518..e1a380c39 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -10,8 +10,8 @@ import boto3 from moto.core import BaseBackend, BaseModel from .exceptions import ( - ResourceNotFoundException, SecretNotFoundException, + SecretHasNoValueException, InvalidParameterException, ResourceExistsException, InvalidRequestException, @@ -87,10 +87,7 @@ class SecretsManagerBackend(BaseBackend): response_data["SecretBinary"] = secret_version['secret_binary'] if 'secret_string' not in secret_version and 'secret_binary' not in secret_version: - raise ResourceNotFoundException( - u"Secrets Manager can\u2019t find the specified secret value for staging label: %s" % - (version_stage or u"AWSCURRENT") - ) + raise SecretHasNoValueException(version_stage or u"AWSCURRENT") response = json.dumps(response_data)