Repository: getmoto/moto
Pull request #7219 (2024-01-15 13:32:48)

IAM: Only load AWS Managed Policies on request
Loading these policies (and resetting them after every test) is quite time-consuming, and only a minority of Moto users will actually use these policies.

This PR only loads the IAM Managed Policies on request, either by configuring the decorator or by setting an environment variable:
```
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})`
```
```
MOTO_IAM_LOAD_MANAGED_POLICIES=true
```

The overall runtime of our internal tests drop from ~45 minutes to ~30 minutes with this change.


Patch:
diff --git a/moto/core/config.py b/moto/core/config.py
index d4e4ddb67..957025d33 100644
--- a/moto/core/config.py
+++ b/moto/core/config.py
@@ -16,9 +16,18 @@ class _core_config(TypedDict, total=False):
     passthrough: _passthrough_config
 
 
+class _iam_config(TypedDict, total=False):
+    load_aws_managed_policies: bool
+
+
 DefaultConfig = TypedDict(
     "DefaultConfig",
-    {"batch": _docker_config, "core": _core_config, "lambda": _docker_config},
+    {
+        "batch": _docker_config,
+        "core": _core_config,
+        "lambda": _docker_config,
+        "iam": _iam_config,
+    },
     total=False,
 )
 
@@ -26,6 +35,7 @@ default_user_config: DefaultConfig = {
     "batch": {"use_docker": True},
     "lambda": {"use_docker": True},
     "core": {"mock_credentials": True, "passthrough": {"urls": [], "services": []}},
+    "iam": {"load_aws_managed_policies": False},
 }
 
 
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 7d2c05430..062cdcd0e 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -1,5 +1,4 @@
 import base64
-import copy
 import json
 import os
 import re
@@ -31,6 +30,7 @@ from moto.iam.policy_validation import (
     IAMTrustPolicyDocumentValidator,
 )
 from moto.moto_api._internal import mock_random as random
+from moto.settings import load_iam_aws_managed_policies
 from moto.utilities.utils import md5_hash
 
 from ..utilities.tagging_service import TaggingService
@@ -1795,12 +1795,7 @@ def filter_items_with_path_prefix(
 
 
 class IAMBackend(BaseBackend):
-    def __init__(
-        self,
-        region_name: str,
-        account_id: str,
-        aws_policies: Optional[List[ManagedPolicy]] = None,
-    ):
+    def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name=region_name, account_id=account_id)
         self.instance_profiles: Dict[str, InstanceProfile] = {}
         self.roles: Dict[str, Role] = {}
@@ -1808,7 +1803,7 @@ class IAMBackend(BaseBackend):
         self.groups: Dict[str, Group] = {}
         self.users: Dict[str, User] = {}
         self.credential_report: Optional[bool] = None
-        self.aws_managed_policies = aws_policies or self._init_aws_policies()
+        self.aws_managed_policies = self._init_aws_policies()
         self.managed_policies = self._init_managed_policies()
         self.account_aliases: List[str] = []
         self.saml_providers: Dict[str, SAMLProvider] = {}
@@ -1825,6 +1820,8 @@ class IAMBackend(BaseBackend):
         self.initialize_service_roles()
 
     def _init_aws_policies(self) -> List[ManagedPolicy]:
+        if not load_iam_aws_managed_policies():
+            return []
         # AWS defines some of its own managed policies
         # we periodically import them via `make aws_managed_policies`
         aws_managed_policies_data_parsed = json.loads(aws_managed_policies_data)
@@ -1834,15 +1831,7 @@ class IAMBackend(BaseBackend):
         ]
 
     def _init_managed_policies(self) -> Dict[str, ManagedPolicy]:
-        return dict((p.arn, copy.deepcopy(p)) for p in self.aws_managed_policies)
-
-    def reset(self) -> None:
-        region_name = self.region_name
-        account_id = self.account_id
-        # Do not reset these policies, as they take a long time to load
-        aws_policies = self.aws_managed_policies
-        self.__dict__ = {}
-        IAMBackend.__init__(self, region_name, account_id, aws_policies)
+        return dict((p.arn, p) for p in self.aws_managed_policies)
 
     def initialize_service_roles(self) -> None:
         pass
diff --git a/moto/settings.py b/moto/settings.py
index ed85efcfe..d9ead05dc 100644
--- a/moto/settings.py
+++ b/moto/settings.py
@@ -4,6 +4,8 @@ import pathlib
 from functools import lru_cache
 from typing import List, Optional
 
+from moto.core.config import default_user_config
+
 
 def test_proxy_mode() -> bool:
     return os.environ.get("TEST_PROXY_MODE", "0").lower() == "true"
@@ -177,3 +179,11 @@ def get_cognito_idp_user_pool_id_strategy() -> Optional[str]:
 
 def enable_iso_regions() -> bool:
     return os.environ.get("MOTO_ENABLE_ISO_REGIONS", "false").lower() == "true"
+
+
+def load_iam_aws_managed_policies() -> bool:
+    return (
+        default_user_config.get("iam", {}).get("load_aws_managed_policies", False)
+        is True
+        or os.environ.get("MOTO_IAM_LOAD_MANAGED_POLICIES", "").lower() == "true"
+    )


Test patch:
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
index e2eccced1..558441dc8 100644
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -706,8 +706,10 @@ def test_get_policy():
     assert policy["Policy"]["Arn"] == f"arn:aws:iam::{ACCOUNT_ID}:policy/TestGetPolicy"
 
 
-@mock_aws
+@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
 def test_get_aws_managed_policy():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Policies not loaded in ServerMode")
     conn = boto3.client("iam", region_name="us-east-1")
     managed_policy_arn = "arn:aws:iam::aws:policy/IAMUserChangePassword"
     managed_policy_create_date = datetime.strptime(
@@ -742,8 +744,10 @@ def test_get_policy_version():
     assert retrieved.get("PolicyVersion")["IsDefaultVersion"] is False
 
 
-@mock_aws
+@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
 def test_get_aws_managed_policy_version():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Policies not loaded in ServerMode")
     conn = boto3.client("iam", region_name="us-east-1")
     managed_policy_arn = (
         "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
@@ -763,8 +767,10 @@ def test_get_aws_managed_policy_version():
     assert isinstance(retrieved["PolicyVersion"]["Document"], dict)
 
 
-@mock_aws
+@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
 def test_get_aws_managed_policy_v6_version():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Policies not loaded in ServerMode")
     conn = boto3.client("iam", region_name="us-east-1")
     managed_policy_arn = "arn:aws:iam::aws:policy/job-function/SystemAdministrator"
     with pytest.raises(ClientError):
@@ -1953,8 +1959,10 @@ def test_get_access_key_last_used_when_used():
     assert resp["AccessKeyLastUsed"]["Region"] == "us-east-1"
 
 
-@mock_aws
+@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
 def test_managed_policy():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Policies not loaded in ServerMode")
     conn = boto3.client("iam", region_name="us-west-1")
 
     conn.create_policy(
@@ -2364,8 +2372,10 @@ def test_delete_ssh_public_key():
     assert len(resp["SSHPublicKeys"]) == 0
 
 
-@mock_aws
+@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
 def test_get_account_authorization_details():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Policies not loaded in ServerMode")
     test_policy = json.dumps(
         {
             "Version": "2012-10-17",
diff --git a/tests/test_iam/test_iam_groups.py b/tests/test_iam/test_iam_groups.py
index eedc4cccf..5510b9d7c 100644
--- a/tests/test_iam/test_iam_groups.py
+++ b/tests/test_iam/test_iam_groups.py
@@ -1,5 +1,6 @@
 import json
 from datetime import datetime
+from unittest import SkipTest
 
 import boto3
 import pytest
@@ -213,8 +214,10 @@ def test_put_group_policy():
     )
 
 
-@mock_aws
+@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
 def test_attach_group_policies():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Policies not loaded in ServerMode")
     conn = boto3.client("iam", region_name="us-east-1")
     conn.create_group(GroupName="my-group")
     assert (
diff --git a/tests/test_iam/test_iam_resets.py b/tests/test_iam/test_iam_resets.py
index 91508f778..1280a09b7 100644
--- a/tests/test_iam/test_iam_resets.py
+++ b/tests/test_iam/test_iam_resets.py
@@ -1,13 +1,17 @@
 import json
+import os
+from unittest import SkipTest, mock
 
 import boto3
 
-from moto import mock_aws
+from moto import mock_aws, settings
 
 
 # Test IAM User Inline Policy
 def test_policies_are_not_kept_after_mock_ends():
-    with mock_aws():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Policies not loaded in ServerMode")
+    with mock_aws(config={"iam": {"load_aws_managed_policies": True}}):
         iam_client = boto3.client("iam", "us-east-1")
         role_name = "test"
         assume_role_policy_document = {
@@ -34,6 +38,17 @@ def test_policies_are_not_kept_after_mock_ends():
         assert iam_policies[0]["Arn"] == "arn:aws:iam::aws:policy/ReadOnlyAccess"
         assert iam_client.list_roles()["Roles"][0]["RoleName"] == "test"
 
-    with mock_aws():
+    with mock_aws(config={"iam": {"load_aws_managed_policies": True}}):
         resp = iam_client.list_policies(Scope="AWS", OnlyAttached=True)
         assert len(resp["Policies"]) == 0
+
+
+def test_policies_are_loaded_when_using_env_variable():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("EnvVar not loaded in ServerMode")
+    with mock.patch.dict(os.environ, {"MOTO_IAM_LOAD_MANAGED_POLICIES": "true"}):
+        with mock_aws():
+            iam_client = boto3.client("iam", "us-east-1")
+
+            iam_policies = iam_client.list_policies(Scope="AWS")["Policies"]
+            assert len(iam_policies) > 10
diff --git a/tests/test_moto_api/seeder/test_seeder.py b/tests/test_moto_api/seeder/test_seeder.py
index 3638cc451..970c30524 100644
--- a/tests/test_moto_api/seeder/test_seeder.py
+++ b/tests/test_moto_api/seeder/test_seeder.py
@@ -41,7 +41,7 @@ class TestDifferentAccountsDoesNotBreakSeeding:
         instances = self.ec2_client.run_instances(MaxCount=1, MinCount=1)["Instances"]
 
         instance_ids = [instance["InstanceId"] for instance in instances]
-        assert instance_ids == ["i-954ed79c032a70bf6"]
+        assert instance_ids == ["i-73bd4755d05ad7853"]
 
     def test_1(self) -> None:
         # Create some data in a different account (111111111111)