From 120874e408515f5bdf91d5915f9c813c96183ad4 Mon Sep 17 00:00:00 2001 From: Chris K Date: Fri, 5 Apr 2019 11:00:02 +0100 Subject: [PATCH 1/4] Feature: AWS Secrets Manager list-secrets --- moto/secretsmanager/models.py | 4 ++++ moto/secretsmanager/responses.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 1350ab469..0d276c944 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -188,6 +188,10 @@ class SecretsManagerBackend(BaseBackend): return response + def list_secrets(self, max_results, next_token): + # implement here + return secret_list, next_token + available_regions = ( boto3.session.Session().get_available_regions("secretsmanager") diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py index 932e7bfd7..73921a11a 100644 --- a/moto/secretsmanager/responses.py +++ b/moto/secretsmanager/responses.py @@ -64,3 +64,13 @@ class SecretsManagerResponse(BaseResponse): rotation_lambda_arn=rotation_lambda_arn, rotation_rules=rotation_rules ) + + def list_secrets(self): + max_results = self._get_int_param("MaxResults") + next_token = self._get_param("NextToken") + secret_list, next_token = self.secretsmanager_backend.list_secrets( + max_results=max_results, + next_token=next_token, + ) + # TODO: adjust response + return json.dumps(dict(secretList=secret_list, nextToken=next_token)) From 89e4ab93eeefa07e827a0653dd775472c34d3c48 Mon Sep 17 00:00:00 2001 From: Chris K Date: Fri, 5 Apr 2019 13:33:28 +0100 Subject: [PATCH 2/4] Implement ListSecrets --- IMPLEMENTATION_COVERAGE.md | 2 +- moto/secretsmanager/models.py | 25 +++++++++++-- moto/secretsmanager/responses.py | 7 ++-- .../test_secretsmanager.py | 36 +++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index a5650f572..e206467b5 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3659,7 +3659,7 @@ - [X] get_random_password - [X] get_secret_value - [ ] list_secret_version_ids -- [ ] list_secrets +- [x] list_secrets - [ ] put_secret_value - [ ] restore_secret - [X] rotate_secret diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 0d276c944..a6aec8b81 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -189,9 +189,30 @@ class SecretsManagerBackend(BaseBackend): return response def list_secrets(self, max_results, next_token): - # implement here + # TODO implement pagination + + secret_list = [{ + "ARN": secret_arn(self.region, secret['secret_id']), + "DeletedDate": None, + "Description": "", + "KmsKeyId": "", + "LastAccessedDate": None, + "LastChangedDate": None, + "LastRotatedDate": None, + "Name": secret['name'], + "RotationEnabled": secret['rotation_enabled'], + "RotationLambdaARN": secret['rotation_lambda_arn'], + "RotationRules": { + "AutomaticallyAfterDays": secret['auto_rotate_after_days'] + }, + "SecretVersionsToStages": { + secret['version_id']: ["AWSCURRENT"] + }, + "Tags": secret['tags'] + } for secret in self.secrets.values()] + return secret_list, next_token - + available_regions = ( boto3.session.Session().get_available_regions("secretsmanager") diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py index 73921a11a..ab1fe0d9d 100644 --- a/moto/secretsmanager/responses.py +++ b/moto/secretsmanager/responses.py @@ -4,6 +4,8 @@ from moto.core.responses import BaseResponse from .models import secretsmanager_backends +import json + class SecretsManagerResponse(BaseResponse): @@ -68,9 +70,8 @@ class SecretsManagerResponse(BaseResponse): def list_secrets(self): max_results = self._get_int_param("MaxResults") next_token = self._get_param("NextToken") - secret_list, next_token = self.secretsmanager_backend.list_secrets( + secret_list, next_token = secretsmanager_backends[self.region].list_secrets( max_results=max_results, next_token=next_token, ) - # TODO: adjust response - return json.dumps(dict(secretList=secret_list, nextToken=next_token)) + return json.dumps(dict(SecretList=secret_list, NextToken=next_token)) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 169282421..6146698d9 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -203,6 +203,42 @@ def test_describe_secret_that_does_not_match(): with assert_raises(ClientError): result = conn.get_secret_value(SecretId='i-dont-match') + +@mock_secretsmanager +def test_list_secrets_empty(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + + secrets = conn.list_secrets() + + assert secrets['SecretList'] == [] + + +@mock_secretsmanager +def test_list_secrets(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + + conn.create_secret(Name='test-secret', + SecretString='foosecret') + + conn.create_secret(Name='test-secret-2', + SecretString='barsecret', + Tags=[{ + 'Key': 'a', + 'Value': '1' + }]) + + secrets = conn.list_secrets() + + assert secrets['SecretList'][0]['ARN'] is not None + assert secrets['SecretList'][0]['Name'] == 'test-secret' + assert secrets['SecretList'][1]['ARN'] is not None + assert secrets['SecretList'][1]['Name'] == 'test-secret-2' + assert secrets['SecretList'][1]['Tags'] == [{ + 'Key': 'a', + 'Value': '1' + }] + + @mock_secretsmanager def test_rotate_secret(): secret_name = 'test-secret' From 2d6be24ffcc94a69c8340a7f3c1bf0d584429a1e Mon Sep 17 00:00:00 2001 From: Chris K Date: Fri, 5 Apr 2019 13:54:11 +0100 Subject: [PATCH 3/4] Fix lint error --- moto/secretsmanager/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py index ab1fe0d9d..f017ec0dc 100644 --- a/moto/secretsmanager/responses.py +++ b/moto/secretsmanager/responses.py @@ -66,7 +66,7 @@ class SecretsManagerResponse(BaseResponse): rotation_lambda_arn=rotation_lambda_arn, rotation_rules=rotation_rules ) - + def list_secrets(self): max_results = self._get_int_param("MaxResults") next_token = self._get_param("NextToken") From 7fcedcb783d8bd5fc0e944029ccaf25c647aadef Mon Sep 17 00:00:00 2001 From: Chris K Date: Fri, 5 Apr 2019 15:59:38 +0100 Subject: [PATCH 4/4] Fix: Ensure the returned next_token is None (avoid client going round in a loop) --- moto/secretsmanager/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index a6aec8b81..74cf89039 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -189,7 +189,7 @@ class SecretsManagerBackend(BaseBackend): return response def list_secrets(self, max_results, next_token): - # TODO implement pagination + # TODO implement pagination and limits secret_list = [{ "ARN": secret_arn(self.region, secret['secret_id']), @@ -211,7 +211,7 @@ class SecretsManagerBackend(BaseBackend): "Tags": secret['tags'] } for secret in self.secrets.values()] - return secret_list, next_token + return secret_list, None available_regions = (