Repository: getmoto/moto
Pull request #8359 (2024-12-02 19:03:50)

MemoryDB: adding tagging functionality to snapshots and subnets
Added Tagging functionality to Memorydb Snapshots and subnets.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8359?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
Attention: Patch coverage is `76.92308%` with `6 lines` in your changes missing coverage. Please review.
> Project coverage is 94.58%. Comparing base [(`8d67306`)](https://app.codecov.io/gh/getmoto/moto/commit/8d673068b3821d33407c2a2eecb735802d944d16?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`75a9c89`)](https://app.codecov.io/gh/getmoto/moto/commit/75a9c8997f5a387150e759b17350efacebfe0430?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 24 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/getmoto/moto/pull/8359?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Patch % | Lines |
|---|---|---|
| [moto/memorydb/models.py](https://app.codecov.io/gh/getmoto/moto/pull/8359?src=pr&el=tree&filepath=moto%2Fmemorydb%2Fmodels.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9tZW1vcnlkYi9tb2RlbHMucHk=) | 76.92% | [6 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/8359?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |

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


```diff
@@           Coverage Diff            @@
##           master    #8359    +/-   ##
========================================
  Coverage   94.58%   94.58%            
========================================
  Files        1159     1159            
  Lines      101059   101162   +103     
========================================
+ Hits        95583    95682    +99     
- Misses       5476     5480     +4     
```

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


Patch:
diff --git a/moto/memorydb/models.py b/moto/memorydb/models.py
index 37396b589..e6fd366c1 100644
--- a/moto/memorydb/models.py
+++ b/moto/memorydb/models.py
@@ -369,7 +369,10 @@ class MemoryDBBackend(BaseBackend):
         return default_subnet_ids
 
     def _list_arns(self) -> List[str]:
-        return [cluster.arn for cluster in self.clusters.values()]
+        cluster_arns = [cluster.arn for cluster in self.clusters.values()]
+        snapshot_arns = [snapshot.arn for snapshot in self.snapshots.values()]
+        subnet_group_arns = [subnet.arn for subnet in self.subnet_groups.values()]
+        return cluster_arns + snapshot_arns + subnet_group_arns
 
     def create_cluster(
         self,
@@ -455,6 +458,8 @@ class MemoryDBBackend(BaseBackend):
             tags,
         )
         self.subnet_groups[subnet_group_name] = subnet_group
+        if tags:
+            self.tag_resource(subnet_group.arn, tags)
         return subnet_group
 
     def create_snapshot(
@@ -483,6 +488,8 @@ class MemoryDBBackend(BaseBackend):
             source=source,
         )
         self.snapshots[snapshot_name] = snapshot
+        if tags:
+            self.tag_resource(snapshot.arn, tags)
         return snapshot
 
     def describe_clusters(
@@ -561,16 +568,27 @@ class MemoryDBBackend(BaseBackend):
 
     def list_tags(self, resource_arn: str) -> List[Dict[str, str]]:
         if resource_arn not in self._list_arns():
-            cluster_name = resource_arn.split("/")[-1]
-            raise ClusterNotFoundFault(f"{cluster_name} is not present")
+            # Get the resource name from the resource_arn
+            resource_name = resource_arn.split("/")[-1]
+            if "subnetgroup" in resource_arn:
+                raise SubnetGroupNotFoundFault(f"{resource_name} is not present")
+            elif "snapshot" in resource_arn:
+                raise SnapshotNotFoundFault(f"{resource_name} is not present")
+            else:
+                raise ClusterNotFoundFault(f"{resource_name} is not present")
         return self.tagger.list_tags_for_resource(arn=resource_arn)["Tags"]
 
     def tag_resource(
         self, resource_arn: str, tags: List[Dict[str, str]]
     ) -> List[Dict[str, str]]:
         if resource_arn not in self._list_arns():
-            cluster_name = resource_arn.split("/")[-1]
-            raise ClusterNotFoundFault(f"{cluster_name} is not present")
+            resource_name = resource_arn.split("/")[-1]
+            if "subnetgroup" in resource_arn:
+                raise SubnetGroupNotFoundFault(f"{resource_name} is not present")
+            elif "snapshot" in resource_arn:
+                raise SnapshotNotFoundFault(f"{resource_name} is not present")
+            else:
+                raise ClusterNotFoundFault(f"{resource_name} is not present")
         self.tagger.tag_resource(resource_arn, tags)
         return self.tagger.list_tags_for_resource(arn=resource_arn)["Tags"]
 
@@ -578,8 +596,13 @@ class MemoryDBBackend(BaseBackend):
         self, resource_arn: str, tag_keys: List[str]
     ) -> List[Dict[str, str]]:
         if resource_arn not in self._list_arns():
-            cluster_name = resource_arn.split("/")[-1]
-            raise ClusterNotFoundFault(f"{cluster_name} is not present")
+            resource_name = resource_arn.split("/")[-1]
+            if "subnetgroup" in resource_arn:
+                raise SubnetGroupNotFoundFault(f"{resource_name} is not present")
+            elif "snapshot" in resource_arn:
+                raise SnapshotNotFoundFault(f"{resource_name} is not present")
+            else:
+                raise ClusterNotFoundFault(f"{resource_name} is not present")
         list_tags = self.list_tags(resource_arn=resource_arn)
         list_keys = [i["Key"] for i in list_tags]
         invalid_keys = [key for key in tag_keys if key not in list_keys]


Test patch:
diff --git a/tests/test_memorydb/test_memorydb.py b/tests/test_memorydb/test_memorydb.py
index 8b4ff0c5c..6f20771cf 100644
--- a/tests/test_memorydb/test_memorydb.py
+++ b/tests/test_memorydb/test_memorydb.py
@@ -21,6 +21,9 @@ def create_subnet_group(client, region_name):
         SubnetGroupName="my_subnet_group",
         Description="This is my subnet group",
         SubnetIds=[subnet1.id, subnet2.id],
+        Tags=[
+            {"Key": "foo", "Value": "bar"},
+        ],
     )
     return subnet_group
 
@@ -395,6 +398,32 @@ def test_list_tags():
     assert "bar" in resp["TagList"][0]["Value"]
 
 
+@mock_aws
+def test_list_tags_snapshot():
+    client = boto3.client("memorydb", region_name="us-east-2")
+    client.create_cluster(
+        ClusterName="test-memory-db", NodeType="db.t4g.small", ACLName="open-access"
+    )
+    snapshot = client.create_snapshot(
+        ClusterName="test-memory-db",
+        SnapshotName="my-snapshot",
+        Tags=[
+            {"Key": "foo1", "Value": "bar1"},
+        ],
+    )
+    resp = client.list_tags(ResourceArn=snapshot["Snapshot"]["ARN"])
+    assert len(resp["TagList"]) == 1
+
+
+@mock_aws
+def test_list_tags_subnetgroups():
+    client = boto3.client("memorydb", region_name="us-east-2")
+    subnet_group = create_subnet_group(client, "us-east-2")
+    sg = subnet_group["SubnetGroup"]
+    resp = client.list_tags(ResourceArn=sg["ARN"])
+    assert len(resp["TagList"]) == 1
+
+
 @mock_aws
 def test_list_tags_invalid_cluster_fails():
     client = boto3.client("memorydb", region_name="us-east-2")
@@ -406,6 +435,28 @@ def test_list_tags_invalid_cluster_fails():
     assert err["Code"] == "ClusterNotFoundFault"
 
 
+@mock_aws
+def test_list_tags_invalid_snapshot_fails():
+    client = boto3.client("memorydb", region_name="us-east-2")
+    with pytest.raises(ClientError) as ex:
+        client.list_tags(
+            ResourceArn=f"arn:aws:memorydb:us-east-1:{ACCOUNT_ID}:snapshot/foobar",
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "SnapshotNotFoundFault"
+
+
+@mock_aws
+def test_list_tags_invalid_subnetgroup_fails():
+    client = boto3.client("memorydb", region_name="us-east-2")
+    with pytest.raises(ClientError) as ex:
+        client.list_tags(
+            ResourceArn=f"arn:aws:memorydb:us-east-1:{ACCOUNT_ID}:subnetgroup/foobar",
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "SubnetGroupNotFoundFault"
+
+
 @mock_aws
 def test_tag_resource():
     client = boto3.client("memorydb", region_name="us-east-2")
@@ -441,6 +492,30 @@ def test_tag_resource_invalid_cluster_fails():
     assert err["Code"] == "ClusterNotFoundFault"
 
 
+@mock_aws
+def test_tag_resource_invalid_snapshot_fails():
+    client = boto3.client("memorydb", region_name="us-east-2")
+    with pytest.raises(ClientError) as ex:
+        client.tag_resource(
+            ResourceArn=f"arn:aws:memorydb:us-east-1:{ACCOUNT_ID}:snapshot/foobar",
+            Tags=[{"Key": "key2", "Value": "value2"}],
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "SnapshotNotFoundFault"
+
+
+@mock_aws
+def test_tag_resource_invalid_subnetgroup_fails():
+    client = boto3.client("memorydb", region_name="us-east-2")
+    with pytest.raises(ClientError) as ex:
+        client.tag_resource(
+            ResourceArn=f"arn:aws:memorydb:us-east-1:{ACCOUNT_ID}:subnetgroup/foobar",
+            Tags=[{"Key": "key2", "Value": "value2"}],
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "SubnetGroupNotFoundFault"
+
+
 @mock_aws
 def test_untag_resource():
     client = boto3.client("memorydb", region_name="us-east-2")
@@ -474,6 +549,30 @@ def test_untag_resource_invalid_cluster_fails():
     assert err["Code"] == "ClusterNotFoundFault"
 
 
+@mock_aws
+def test_untag_resource_invalid_snapshot_fails():
+    client = boto3.client("memorydb", region_name="us-east-2")
+    with pytest.raises(ClientError) as ex:
+        client.untag_resource(
+            ResourceArn=f"arn:aws:memorydb:us-east-1:{ACCOUNT_ID}:snapshot/foobar",
+            TagKeys=["key1"],
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "SnapshotNotFoundFault"
+
+
+@mock_aws
+def test_untag_resource_invalid_subnetgroup_fails():
+    client = boto3.client("memorydb", region_name="us-east-2")
+    with pytest.raises(ClientError) as ex:
+        client.untag_resource(
+            ResourceArn=f"arn:aws:memorydb:us-east-1:{ACCOUNT_ID}:subnetgroup/foobar",
+            TagKeys=["key1"],
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "SubnetGroupNotFoundFault"
+
+
 @mock_aws
 def test_untag_resource_invalid_keys_fails():
     client = boto3.client("memorydb", region_name="us-east-2")