Repository: getmoto/moto
Pull request #7760 (2024-06-10 22:31:50)

Bump ruby/setup-ruby from 1.179.0 to 1.179.1
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.179.0 to 1.179.1.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/ruby/setup-ruby/commit/78c01b705fd9d5ad960d432d3a0cfa341d50e410"><code>78c01b7</code></a> Improve error message for CRuby &lt; 2.6 on macos-arm64</li>
<li>See full diff in <a href="https://github.com/ruby/setup-ruby/compare/d5fb7a202fc07872cb44f00ba8e6197b70cb0c55...78c01b705fd9d5ad960d432d3a0cfa341d50e410">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ruby/setup-ruby&package-manager=github_actions&previous-version=1.179.0&new-version=1.179.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py
index 66a350a81..1e4537a06 100644
--- a/moto/cognitoidp/models.py
+++ b/moto/cognitoidp/models.py
@@ -1212,6 +1212,7 @@ class CognitoIdpBackend(BaseBackend):
         group = self.get_group(user_pool_id, group_name)
         return list(filter(lambda user: user in group.users, user_pool.users.values()))
 
+    @paginate(pagination_model=PAGINATION_MODEL)
     def admin_list_groups_for_user(
         self, user_pool_id: str, username: str
     ) -> List[CognitoIdpGroup]:
diff --git a/moto/cognitoidp/responses.py b/moto/cognitoidp/responses.py
index faa12a03a..19b8ecb71 100644
--- a/moto/cognitoidp/responses.py
+++ b/moto/cognitoidp/responses.py
@@ -307,8 +307,15 @@ class CognitoIdpResponse(BaseResponse):
     def admin_list_groups_for_user(self) -> str:
         username = self._get_param("Username")
         user_pool_id = self._get_param("UserPoolId")
-        groups = self.backend.admin_list_groups_for_user(user_pool_id, username)
-        return json.dumps({"Groups": [group.to_json() for group in groups]})
+        limit = self._get_param("Limit")
+        token = self._get_param("NextToken")
+        groups, token = self.backend.admin_list_groups_for_user(
+            user_pool_id, username, limit=limit, next_token=token
+        )
+        response = {"Groups": [group.to_json() for group in groups]}
+        if token:
+            response["NextToken"] = token
+        return json.dumps(response)
 
     def admin_remove_user_from_group(self) -> str:
         user_pool_id = self._get_param("UserPoolId")
diff --git a/moto/cognitoidp/utils.py b/moto/cognitoidp/utils.py
index 5a758ffcf..80777aef5 100644
--- a/moto/cognitoidp/utils.py
+++ b/moto/cognitoidp/utils.py
@@ -44,6 +44,12 @@ PAGINATION_MODEL = {
         "limit_default": 60,
         "unique_attribute": "group_name",
     },
+    "admin_list_groups_for_user": {
+        "input_token": "next_token",
+        "limit_key": "limit",
+        "limit_default": 60,
+        "unique_attribute": "group_name",
+    },
     "list_users_in_group": {
         "input_token": "next_token",
         "limit_key": "limit",
diff --git a/moto/ec2/models/amis.py b/moto/ec2/models/amis.py
index 4e558b6bd..c58be632a 100644
--- a/moto/ec2/models/amis.py
+++ b/moto/ec2/models/amis.py
@@ -276,10 +276,19 @@ class AmiBackend:
             if exec_users:
                 tmp_images = []
                 for ami in images:
-                    for user_id in exec_users:
-                        for lp in ami.launch_permissions:
-                            if lp.get("UserId") == user_id:
-                                tmp_images.append(ami)
+                    if ami.is_public:
+                        tmp_images.append(ami)
+                    else:
+                        # support filtering by ExecutableUsers=['self']
+                        if "self" in exec_users:
+                            exec_users = list(set(exec_users))
+                            exec_users.remove("self")
+                            exec_users.append(self.account_id)  # type: ignore[attr-defined]
+
+                        for user_id in exec_users:
+                            for lp in ami.launch_permissions:
+                                if lp.get("UserId") == user_id:
+                                    tmp_images.append(ami)
                 images = tmp_images
 
             # Limit by owner ids


Test patch:
diff --git a/.github/workflows/tests_sdk_ruby.yml b/.github/workflows/tests_sdk_ruby.yml
index 1ec511e42..7dea2c405 100644
--- a/.github/workflows/tests_sdk_ruby.yml
+++ b/.github/workflows/tests_sdk_ruby.yml
@@ -13,7 +13,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Set up Ruby ${{ matrix.ruby-version }}
-        uses: ruby/setup-ruby@d5fb7a202fc07872cb44f00ba8e6197b70cb0c55
+        uses: ruby/setup-ruby@78c01b705fd9d5ad960d432d3a0cfa341d50e410
         with:
           ruby-version: ${{ matrix.ruby-version }}
       - name: Set up Python 3.8
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
index 567727b8b..1158b304d 100644
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -1949,6 +1949,42 @@ def test_admin_list_groups_for_user_ignores_deleted_group():
     assert result["Groups"][0]["GroupName"] == group_name2
 
 
+@mock_aws
+def test_admin_list_groups_for_users_returns_pagination_tokens():
+    conn = boto3.client("cognito-idp", "us-west-2")
+    user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+    # Given a user
+    username = str(uuid.uuid4())
+    conn.admin_create_user(UserPoolId=user_pool_id, Username=username)
+    # And given 10 groups
+    group_count = 10
+    for idx in range(group_count):
+        group_name = f"group_{idx}"
+        conn.create_group(UserPoolId=user_pool_id, GroupName=group_name)
+        conn.admin_add_user_to_group(
+            UserPoolId=user_pool_id, Username=username, GroupName=group_name
+        )
+
+    # When listing groups for the user pagination tokens are returned if limit is reached.
+    max_results = 5
+    result = conn.admin_list_groups_for_user(
+        UserPoolId=user_pool_id, Username=username, Limit=max_results
+    )
+    assert len(result["Groups"]) == max_results
+    assert "NextToken" in result
+
+    next_token = result["NextToken"]
+    result_2 = conn.admin_list_groups_for_user(
+        UserPoolId=user_pool_id,
+        Username=username,
+        Limit=max_results,
+        NextToken=next_token,
+    )
+    assert len(result_2["Groups"]) == max_results
+    assert "NextToken" not in result_2
+
+
 @mock_aws
 def test_admin_remove_user_from_group():
     conn = boto3.client("cognito-idp", "us-west-2")
diff --git a/tests/test_ec2/test_amis.py b/tests/test_ec2/test_amis.py
index 885ab4c2e..23a01c0de 100644
--- a/tests/test_ec2/test_amis.py
+++ b/tests/test_ec2/test_amis.py
@@ -770,8 +770,7 @@ def test_ami_describe_executable_users():
     assert len(attributes["LaunchPermissions"]) == 1
     assert attributes["LaunchPermissions"][0]["UserId"] == USER1
     images = conn.describe_images(ExecutableUsers=[USER1])["Images"]
-    assert len(images) == 1
-    assert images[0]["ImageId"] == image_id
+    assert image_id in [image["ImageId"] for image in images]
 
 
 @mock_aws
@@ -795,7 +794,6 @@ def test_ami_describe_executable_users_negative():
         "UserIds": [USER1],
     }
 
-    # Add users and get no images
     # Add users and get no images
     conn.modify_image_attribute(**ADD_USER_ARGS)
 
@@ -805,7 +803,7 @@ def test_ami_describe_executable_users_negative():
     assert len(attributes["LaunchPermissions"]) == 1
     assert attributes["LaunchPermissions"][0]["UserId"] == USER1
     images = conn.describe_images(ExecutableUsers=[USER2])["Images"]
-    assert len(images) == 0
+    assert image_id not in [image["ImageId"] for image in images]
 
 
 @mock_aws
@@ -841,8 +839,75 @@ def test_ami_describe_executable_users_and_filter():
     images = conn.describe_images(
         ExecutableUsers=[USER1], Filters=[{"Name": "state", "Values": ["available"]}]
     )["Images"]
-    assert len(images) == 1
-    assert images[0]["ImageId"] == image_id
+    assert image_id in [image["ImageId"] for image in images]
+
+
+@mock_aws
+def test_ami_describe_images_executable_user_public():
+    conn = boto3.client("ec2", region_name="us-east-1")
+    ec2 = boto3.resource("ec2", "us-east-1")
+    ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
+    response = conn.describe_instances(
+        Filters=[{"Name": "instance-state-name", "Values": ["running"]}]
+    )
+    instance_id = response["Reservations"][0]["Instances"][0]["InstanceId"]
+    public_image_id = conn.create_image(InstanceId=instance_id, Name="PublicImage")[
+        "ImageId"
+    ]
+    private_image_id = conn.create_image(InstanceId=instance_id, Name="PrivateImage")[
+        "ImageId"
+    ]
+
+    USER1 = "".join([f"{random.randint(0, 9)}" for _ in range(0, 12)])
+    USER2 = "".join([f"{random.randint(0, 9)}" for _ in range(0, 12)])
+
+    SET_IMAGE_PUBLIC = {
+        "ImageId": public_image_id,
+        "Attribute": "launchPermission",
+        "OperationType": "add",
+        "UserGroups": ["all"],
+    }
+    conn.modify_image_attribute(**SET_IMAGE_PUBLIC)
+
+    SET_IMAGE_PRIVATE = {
+        "ImageId": private_image_id,
+        "Attribute": "launchPermission",
+        "OperationType": "add",
+        "UserIds": [USER2],
+    }
+    conn.modify_image_attribute(**SET_IMAGE_PRIVATE)
+
+    images = conn.describe_images(
+        ExecutableUsers=[USER1],
+    )["Images"]
+
+    image_ids = [image["ImageId"] for image in images]
+    assert public_image_id in image_ids
+    assert private_image_id not in image_ids
+
+
+@mock_aws
+def test_ami_describe_images_executable_users_self():
+    conn = boto3.client("ec2", region_name="us-east-1")
+    ec2 = boto3.resource("ec2", "us-east-1")
+    ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
+    response = conn.describe_instances(
+        Filters=[{"Name": "instance-state-name", "Values": ["running"]}]
+    )
+    instance_id = response["Reservations"][0]["Instances"][0]["InstanceId"]
+    image_id = conn.create_image(InstanceId=instance_id, Name="PublicImage")["ImageId"]
+
+    SET_IMAGE_TO_SELF = {
+        "ImageId": image_id,
+        "Attribute": "launchPermission",
+        "OperationType": "add",
+        "UserIds": [ACCOUNT_ID],
+    }
+    conn.modify_image_attribute(**SET_IMAGE_TO_SELF)
+
+    images = conn.describe_images(ExecutableUsers=["self"])["Images"]
+
+    assert image_id in [image["ImageId"] for image in images]
 
 
 @mock_aws