Repository: getmoto/moto

Issue:
resourceGroupsTaggingAPI.get_resources() does not follow rules for TagFilters
According to AWS rules on use of `resourcegroupstaggingapi.get-resources()`: 

> If you specify more than one filter in a single request, the response returns only those resources that satisfy **all** specified filters.

However, when running a search with 2 tag filters via with moto against 2 resources with a single common tag value and a single unique tag value, instead of returning the 1 resource that contains **all** filtering tags, it appears to find resources that find **any** of the tags.

e.g.

```python
import pytest
import boto3
import os
from moto import mock_resourcegroupstaggingapi, mock_ec2
from moto.core import moto_api_backend

@pytest.fixture(scope="function")
def aws_credentials():
    """Mocked AWS Credentials for moto."""
    moto_api_backend.reset()
    os.environ["AWS_ACCESS_KEY_ID"] = "testing"
    os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
    os.environ["AWS_SECURITY_TOKEN"] = "testing"
    boto3.setup_default_session()


@pytest.fixture(scope="function")
def resourcegroupstaggingapi(aws_credentials):
    with mock_resourcegroupstaggingapi():
        yield boto3.client("resourcegroupstaggingapi")


@pytest.fixture(scope="function")
def ec2(aws_credentials):
    with mock_ec2():
        yield boto3.client("ec2")


@pytest.fixture(scope="function")
def create_ec2_resource(ec2):
    ec2.run_instances(
        ImageId="ami-123",
        MinCount=1,
        MaxCount=1,
        InstanceType="t2.micro",
        TagSpecifications=[
            {
                "ResourceType": "instance",
                "Tags": [
                    {"Key": "MY_TAG1", "Value": "MY_UNIQUE_VALUE"},
                    {"Key": "MY_TAG2", "Value": "MY_SHARED_VALUE"},
                ],
            },
            {
                "ResourceType": "instance",
                "Tags": [{"Key": "MY_TAG3", "Value": "MY_VALUE3"}],
            },
        ],
    )


@pytest.fixture(scope="function")
def create_alt_ec2_resource(ec2):
    ec2.run_instances(
        ImageId="ami-456",
        MinCount=1,
        MaxCount=1,
        InstanceType="t2.micro",
        TagSpecifications=[
            {
                "ResourceType": "instance",
                "Tags": [
                    {"Key": "MY_TAG1", "Value": "MY_ALT_UNIQUE_VALUE"},
                    {"Key": "MY_TAG2", "Value": "MY_SHARED_VALUE"},
                ],
            },
            {
                "ResourceType": "instance",
                "Tags": [{"Key": "MY_ALT_TAG3", "Value": "MY_VALUE3"}],
            },
        ],
    )


def test_find_tags__results_1_multiple_tags(
    create_ec2_resource, create_alt_ec2_resource, resourcegroupstaggingapi
):
    results = resourcegroupstaggingapi.get_resources(
        TagFilters=[
            {"Key": "MY_TAG1", "Values": ["MY_UNIQUE_VALUE"]},
            {"Key": "MY_TAG2", "Values": ["MY_SHARED_VALUE"]},
        ]
    )

    assert len(results) == 1
```

expected result:
```python
'ResourceTagMappingList': [
    {'ResourceARN': 'arn:aws:ec2:eu-west-2::instance/i-89d91485b4613f125', 'Tags': [{'Key': 'MY_TAG1', 'Value': 'MY_UNIQUE_VALUE'}, {'Key': 'MY_TAG2', 'Value': 'MY_SHARED_VALUE'}, {'Key': 'MY_TAG3', 'Value': 'MY_VALUE3'}]}
]
```

actual result:
```python
 'ResourceTagMappingList': [
    {'ResourceARN': 'arn:aws:ec2:eu-west-2::instance/i-89d91485b4613f125', 'Tags': [{'Key': 'MY_TAG1', 'Value': 'MY_UNIQUE_VALUE'}, {'Key': 'MY_TAG2', 'Value': 'MY_SHARED_VALUE'}, {'Key': 'MY_TAG3', 'Value': 'MY_VALUE3'}]},
    {'ResourceARN': 'arn:aws:ec2:eu-west-2::instance/i-1d884b9ba71297807', 'Tags': [{'Key': 'MY_TAG1', 'Value': 'MY_ALT_UNIQUE_VALUEM'}, {'Key': 'MY_TAG2', 'Value': 'MY_SHARED_VALUE'}, {'Key': 'MY_ALT_TAG3', 'Value': 'MY_VALUE3'}]}
]
```

using:
moto @ 1.3.15.dev491
boto3 @ 1.12.21
pytest @ 5.4.1
python @ 3.7.6


Discussion:
Hi @davidjdixon, could you try this again with the latest release, 1.3.15.dev520?
A PR just got merged this morning that solves that exact same problem for EC2 (https://github.com/spulec/moto/pull/2792)
@bblommers really appreciate the fast reply. unfortunately no dice, the results with dev520 are identical to dev491.

Resolving patch:
diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py
--- a/moto/resourcegroupstaggingapi/models.py
+++ b/moto/resourcegroupstaggingapi/models.py
@@ -113,32 +113,35 @@ def _get_resources_generator(self, tag_filters=None, resource_type_filters=None)
         # https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
 
         # TODO move these to their respective backends
-        filters = [lambda t, v: True]
+        filters = []
         for tag_filter_dict in tag_filters:
             values = tag_filter_dict.get("Values", [])
             if len(values) == 0:
                 # Check key matches
-                filters.append(lambda t, v: t == tag_filter_dict["Key"])
+                filters.append(lambda t, v, key=tag_filter_dict["Key"]: t == key)
             elif len(values) == 1:
                 # Check its exactly the same as key, value
                 filters.append(
-                    lambda t, v: t == tag_filter_dict["Key"] and v == values[0]
+                    lambda t, v, key=tag_filter_dict["Key"], value=values[0]: t == key
+                    and v == value
                 )
             else:
                 # Check key matches and value is one of the provided values
-                filters.append(lambda t, v: t == tag_filter_dict["Key"] and v in values)
+                filters.append(
+                    lambda t, v, key=tag_filter_dict["Key"], vl=values: t == key
+                    and v in vl
+                )
 
         def tag_filter(tag_list):
             result = []
             if tag_filters:
-                for tag in tag_list:
+                for f in filters:
                     temp_result = []
-                    for f in filters:
+                    for tag in tag_list:
                         f_result = f(tag["Key"], tag["Value"])
                         temp_result.append(f_result)
-                    result.append(all(temp_result))
-
-                return any(result)
+                    result.append(any(temp_result))
+                return all(result)
             else:
                 return True
 


Test patch:
diff --git a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py
--- a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py
+++ b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py
@@ -293,3 +293,62 @@ def test_get_resources_s3():
         response_keys.remove(resource["Tags"][0]["Key"])
 
     response_keys.should.have.length_of(0)
+
+
+@mock_ec2
+@mock_resourcegroupstaggingapi
+def test_multiple_tag_filters():
+    client = boto3.client("ec2", region_name="eu-central-1")
+
+    resp = client.run_instances(
+        ImageId="ami-123",
+        MinCount=1,
+        MaxCount=1,
+        InstanceType="t2.micro",
+        TagSpecifications=[
+            {
+                "ResourceType": "instance",
+                "Tags": [
+                    {"Key": "MY_TAG1", "Value": "MY_UNIQUE_VALUE"},
+                    {"Key": "MY_TAG2", "Value": "MY_SHARED_VALUE"},
+                ],
+            },
+            {
+                "ResourceType": "instance",
+                "Tags": [{"Key": "MY_TAG3", "Value": "MY_VALUE3"}],
+            },
+        ],
+    )
+    instance_1_id = resp["Instances"][0]["InstanceId"]
+
+    resp = client.run_instances(
+        ImageId="ami-456",
+        MinCount=1,
+        MaxCount=1,
+        InstanceType="t2.micro",
+        TagSpecifications=[
+            {
+                "ResourceType": "instance",
+                "Tags": [
+                    {"Key": "MY_TAG1", "Value": "MY_ALT_UNIQUE_VALUE"},
+                    {"Key": "MY_TAG2", "Value": "MY_SHARED_VALUE"},
+                ],
+            },
+            {
+                "ResourceType": "instance",
+                "Tags": [{"Key": "MY_ALT_TAG3", "Value": "MY_VALUE3"}],
+            },
+        ],
+    )
+    instance_2_id = resp["Instances"][0]["InstanceId"]
+
+    rtapi = boto3.client("resourcegroupstaggingapi", region_name="eu-central-1")
+    results = rtapi.get_resources(
+        TagFilters=[
+            {"Key": "MY_TAG1", "Values": ["MY_UNIQUE_VALUE"]},
+            {"Key": "MY_TAG2", "Values": ["MY_SHARED_VALUE"]},
+        ]
+    ).get("ResourceTagMappingList", [])
+    results.should.have.length_of(1)
+    instance_1_id.should.be.within(results[0]["ResourceARN"])
+    instance_2_id.shouldnt.be.within(results[0]["ResourceARN"])