From 3f534119f4569522fa9097d310ffa5590bfa9907 Mon Sep 17 00:00:00 2001 From: Daniel Fangl Date: Tue, 25 Jan 2022 11:24:26 +0100 Subject: [PATCH] Add CreatedDate and LastChangedDate in secretsmanager responses (#4770) --- moto/secretsmanager/models.py | 18 ++++++-- .../test_secretsmanager/test_list_secrets.py | 7 +++ .../test_secretsmanager.py | 43 ++++++++++++++++++- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index f6eed3b6b..233666983 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -61,6 +61,8 @@ class FakeSecret: kms_key_id=None, version_id=None, version_stages=None, + last_changed_date=None, + created_date=None, ): self.secret_id = secret_id self.name = secret_id @@ -72,14 +74,20 @@ class FakeSecret: self.kms_key_id = kms_key_id self.version_id = version_id self.version_stages = version_stages + self.last_changed_date = last_changed_date + self.created_date = created_date self.rotation_enabled = False self.rotation_lambda_arn = "" self.auto_rotate_after_days = 0 self.deleted_date = None - def update(self, description=None, tags=None, kms_key_id=None): + def update( + self, description=None, tags=None, kms_key_id=None, last_changed_date=None + ): self.description = description self.tags = tags or [] + if last_changed_date is not None: + self.last_changed_date = last_changed_date if kms_key_id is not None: self.kms_key_id = kms_key_id @@ -134,12 +142,13 @@ class FakeSecret: "RotationLambdaARN": self.rotation_lambda_arn, "RotationRules": {"AutomaticallyAfterDays": self.auto_rotate_after_days}, "LastRotatedDate": None, - "LastChangedDate": None, + "LastChangedDate": self.last_changed_date, "LastAccessedDate": None, "DeletedDate": self.deleted_date, "Tags": self.tags, "VersionIdsToStages": version_id_to_stages, "SecretVersionsToStages": version_id_to_stages, + "CreatedDate": self.created_date, } def _form_version_ids_to_stages(self): @@ -350,10 +359,11 @@ class SecretsManagerBackend(BaseBackend): if secret_binary is not None: secret_version["secret_binary"] = secret_binary + update_time = int(time.time()) if secret_id in self.secrets: secret = self.secrets[secret_id] - secret.update(description, tags, kms_key_id) + secret.update(description, tags, kms_key_id, last_changed_date=update_time) if "AWSPENDING" in version_stages: secret.versions[version_id] = secret_version @@ -368,6 +378,8 @@ class SecretsManagerBackend(BaseBackend): description=description, tags=tags, kms_key_id=kms_key_id, + last_changed_date=update_time, + created_date=update_time, ) secret.set_versions({version_id: secret_version}) secret.set_default_version_id(version_id) diff --git a/tests/test_secretsmanager/test_list_secrets.py b/tests/test_secretsmanager/test_list_secrets.py index 7031d64d2..e86eb4d54 100644 --- a/tests/test_secretsmanager/test_list_secrets.py +++ b/tests/test_secretsmanager/test_list_secrets.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +from datetime import datetime + import boto3 +from dateutil.tz import tzlocal from moto import mock_secretsmanager from botocore.exceptions import ClientError @@ -41,6 +44,10 @@ def test_list_secrets(): assert secrets["SecretList"][1]["Name"] == "test-secret-2" assert secrets["SecretList"][1]["Tags"] == [{"Key": "a", "Value": "1"}] assert secrets["SecretList"][1]["SecretVersionsToStages"] is not None + assert secrets["SecretList"][0]["CreatedDate"] <= datetime.now(tz=tzlocal()) + assert secrets["SecretList"][1]["CreatedDate"] <= datetime.now(tz=tzlocal()) + assert secrets["SecretList"][0]["LastChangedDate"] <= datetime.now(tz=tzlocal()) + assert secrets["SecretList"][1]["LastChangedDate"] <= datetime.now(tz=tzlocal()) @mock_secretsmanager diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index fa0f99154..a0f7718c7 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -1,11 +1,15 @@ +import os + import boto3 +from dateutil.tz import tzlocal from moto import mock_secretsmanager, mock_lambda, settings from moto.core import ACCOUNT_ID from botocore.exceptions import ClientError, ParamValidationError import string import pytz -from datetime import datetime +from freezegun import freeze_time +from datetime import timedelta, datetime import sure # noqa # pylint: disable=unused-import from uuid import uuid4 import pytest @@ -430,6 +434,14 @@ def test_describe_secret(): assert secret_description["ARN"] != "" # Test arn not empty assert secret_description_2["Name"] == ("test-secret-2") assert secret_description_2["ARN"] != "" # Test arn not empty + assert secret_description["CreatedDate"] <= datetime.now(tz=tzlocal()) + assert secret_description["CreatedDate"] > datetime.fromtimestamp(1, pytz.utc) + assert secret_description_2["CreatedDate"] <= datetime.now(tz=tzlocal()) + assert secret_description_2["CreatedDate"] > datetime.fromtimestamp(1, pytz.utc) + assert secret_description["LastChangedDate"] <= datetime.now(tz=tzlocal()) + assert secret_description["LastChangedDate"] > datetime.fromtimestamp(1, pytz.utc) + assert secret_description_2["LastChangedDate"] <= datetime.now(tz=tzlocal()) + assert secret_description_2["LastChangedDate"] > datetime.fromtimestamp(1, pytz.utc) @mock_secretsmanager @@ -967,6 +979,35 @@ def test_update_secret(pass_arn): assert created_secret["VersionId"] != updated_secret["VersionId"] +@mock_secretsmanager +@pytest.mark.parametrize("pass_arn", [True, False]) +def test_update_secret_updates_last_changed_dates(pass_arn): + conn = boto3.client("secretsmanager", region_name="us-west-2") + + # create a secret + created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret") + secret_id = created_secret["ARN"] if pass_arn else "test-secret" + + # save details for secret before modification + secret_details_1 = conn.describe_secret(SecretId=secret_id) + # check if only LastChangedDate changed, CreatedDate should stay the same + with freeze_time(timedelta(minutes=1)): + conn.update_secret(SecretId="test-secret", Description="new-desc") + secret_details_2 = conn.describe_secret(SecretId=secret_id) + assert secret_details_1["CreatedDate"] == secret_details_2["CreatedDate"] + if os.environ.get("TEST_SERVER_MODE", "false").lower() == "false": + assert ( + secret_details_1["LastChangedDate"] + < secret_details_2["LastChangedDate"] + ) + else: + # Can't manipulate time in server mode, so use weaker constraints here + assert ( + secret_details_1["LastChangedDate"] + <= secret_details_2["LastChangedDate"] + ) + + @mock_secretsmanager def test_update_secret_with_tags_and_description(): conn = boto3.client("secretsmanager", region_name="us-west-2")