Repository: getmoto/moto

Issue:
Some /aws/service/ ssm parameters not available
Eg, `/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs` is not available as a ssm parameter, nor can it be added (understandable) like a regular parameters due to `AccessDeniedException`.

See eg https://aws.amazon.com/blogs/compute/query-for-the-latest-amazon-linux-ami-ids-using-aws-systems-manager-parameter-store/

Having these parameters set, or being able to add those kind of values (server mode), would be appreciated.


Discussion:
Moto does bootstrap `/aws/service/global-infrastructure/`, but it would indeed be nice if we could extend that to include everything under `/aws/service`.

Marking it as an enhancement.

Resolving patch:
diff --git a/moto/ec2/models/amis.py b/moto/ec2/models/amis.py
--- a/moto/ec2/models/amis.py
+++ b/moto/ec2/models/amis.py
@@ -154,6 +154,17 @@ def _load_amis(self):
             # owner_alias is required for terraform owner filters
             ami["owner_alias"] = "amazon"
             self.amis[ami_id] = Ami(self, **ami)
+        try:
+            latest_amis = load_resource(
+                __name__, f"../resources/latest_amis/{self.region_name}.json"
+            )
+            for ami in latest_amis:
+                ami_id = ami["ami_id"]
+                ami["owner_alias"] = "amazon"
+                self.amis[ami_id] = Ami(self, **ami)
+        except FileNotFoundError:
+            # Will error on unknown (new) regions - just return an empty list here
+            pass
 
     def create_image(
         self,
diff --git a/moto/ssm/models.py b/moto/ssm/models.py
--- a/moto/ssm/models.py
+++ b/moto/ssm/models.py
@@ -59,6 +59,28 @@ def _check_loading_status(self, key):
             self._load_global_parameters()
 
     def _load_global_parameters(self):
+        try:
+            latest_amis_linux = load_resource(
+                __name__, f"resources/ami-amazon-linux-latest/{self.region_name}.json"
+            )
+        except FileNotFoundError:
+            latest_amis_linux = []
+        for param in latest_amis_linux:
+            name = param["Name"]
+            super().__getitem__(name).append(
+                Parameter(
+                    account_id=self.account_id,
+                    name=name,
+                    value=param["Value"],
+                    parameter_type=param["Type"],
+                    description=None,
+                    allowed_pattern=None,
+                    keyid=None,
+                    last_modified_date=param["LastModifiedDate"],
+                    version=param["Version"],
+                    data_type=param["DataType"],
+                )
+            )
         regions = load_resource(__name__, "resources/regions.json")
         services = load_resource(__name__, "resources/services.json")
         params = []
diff --git a/scripts/ssm_get_default_amis.py b/scripts/ssm_get_default_amis.py
new file mode 100644
--- /dev/null
+++ b/scripts/ssm_get_default_amis.py
@@ -0,0 +1,72 @@
+import time
+
+import boto3
+import json
+import time
+import os
+import subprocess
+from datetime import timezone
+from moto.core.utils import unix_time
+from moto.ec2.utils import gen_moto_amis
+
+
+def retrieve_by_path(client, path):
+    x = client.get_parameters_by_path(Path=path, Recursive=True)
+    parameters = x["Parameters"]
+    next_token = x["NextToken"]
+    while next_token:
+        x = client.get_parameters_by_path(Path=path, Recursive=True, NextToken=next_token)
+        parameters.extend(x["Parameters"])
+        next_token = x.get("NextToken")
+
+    return parameters
+
+
+def main():
+    """
+    Retrieve the latest AMI-details from SSM
+     - Download from AWS
+     - Store this in the dedicated moto/ssm/resources-folder
+    """
+
+    regions = boto3.Session().get_available_regions("ssm")
+
+    for region in regions:
+        client = boto3.client('ssm', region_name=region)
+        ec2 = boto3.client("ec2", region_name=region)
+
+        default_param_path = "/aws/service/ami-amazon-linux-latest"
+
+        # Retrieve default AMI values
+        try:
+            params = retrieve_by_path(client, default_param_path)
+            for param in params:
+                param["LastModifiedDate"] = unix_time(param["LastModifiedDate"].astimezone(timezone.utc).replace(tzinfo=None))
+
+            root_dir = (
+                subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
+                    .decode()
+                    .strip()
+            )
+            dest = os.path.join(root_dir, f"moto/ssm/resources/ami-amazon-linux-latest/{region}.json")
+            print("Writing data to {0}".format(dest))
+            with open(dest, "w") as open_file:
+                json.dump(params, open_file, sort_keys=True, indent=2)
+
+            # Retrieve details about AMIs from EC2
+            image_ids = [p["Value"] for p in params]
+            images = ec2.describe_images(ImageIds=image_ids)["Images"]
+            image_as_dicts = gen_moto_amis(images)
+            dest = os.path.join(root_dir, f"moto/ec2/resources/latest_amis/{region}.json")
+            print("Writing data to {0}".format(dest))
+            with open(dest, "w") as open_file:
+                json.dump(image_as_dicts, open_file, sort_keys=True, indent=2)
+
+            time.sleep(0.5)
+        except Exception as e:
+            print(e)
+            # We might encounter an error if we do not have access to a region - just ignore and try the next region
+
+
+if __name__ == "__main__":
+    main()


Test patch:
diff --git a/tests/test_ec2/test_amis.py b/tests/test_ec2/test_amis.py
--- a/tests/test_ec2/test_amis.py
+++ b/tests/test_ec2/test_amis.py
@@ -841,7 +841,7 @@ def test_filter_description():
         Owners=["amazon"],
         Filters=[{"Name": "description", "Values": ["Amazon Linux AMI*"]}],
     )["Images"]
-    resp.should.have.length_of(4)
+    resp.should.have.length_of(9)
 
     # Search for full description
     resp = client.describe_images(
diff --git a/tests/test_ssm/test_ssm_default_amis.py b/tests/test_ssm/test_ssm_default_amis.py
new file mode 100644
--- /dev/null
+++ b/tests/test_ssm/test_ssm_default_amis.py
@@ -0,0 +1,32 @@
+import boto3
+import sure  # noqa # pylint: disable=unused-import
+
+from moto import mock_ssm
+
+
+test_ami = "/aws/service/ami-amazon-linux-latest/al2022-ami-kernel-default-x86_64"
+
+
+@mock_ssm
+def test_ssm_get_latest_ami_by_path():
+    client = boto3.client("ssm", region_name="us-west-1")
+    path = "/aws/service/ami-amazon-linux-latest"
+    params = client.get_parameters_by_path(Path=path)["Parameters"]
+    params.should.have.length_of(10)
+
+    ami = [p for p in params if p["Name"] == test_ami][0]
+    ami["Type"].should.equal("String")
+    ami["Version"].should.equal(11)
+    ami["Value"].should.equal("ami-0b4659d78cb302fd7")
+    ami.should.have.key("LastModifiedDate")
+
+
+@mock_ssm
+def test_ssm_latest_amis_are_different_in_regions():
+    client = boto3.client("ssm", region_name="us-west-1")
+    ami_uswest = client.get_parameter(Name=test_ami)["Parameter"]["Value"]
+
+    client = boto3.client("ssm", region_name="eu-north-1")
+    ami_eunorth = client.get_parameter(Name=test_ami)["Parameter"]["Value"]
+
+    ami_uswest.shouldnt.equal(ami_eunorth)
diff --git a/tests/test_ssm/test_ssm_ec2_integration.py b/tests/test_ssm/test_ssm_ec2_integration.py
new file mode 100644
--- /dev/null
+++ b/tests/test_ssm/test_ssm_ec2_integration.py
@@ -0,0 +1,17 @@
+import boto3
+import sure  # noqa # pylint: disable=unused-import
+
+from moto import mock_ec2, mock_ssm
+
+
+test_ami = "/aws/service/ami-amazon-linux-latest/al2022-ami-kernel-default-x86_64"
+
+
+@mock_ec2
+@mock_ssm
+def test_ssm_get_latest_ami_by_path():
+    ssm = boto3.client("ssm", region_name="us-east-1")
+    ami = ssm.get_parameter(Name=test_ami)["Parameter"]["Value"]
+
+    ec2 = boto3.client("ec2", region_name="us-east-1")
+    ec2.describe_images(ImageIds=[ami])["Images"].should.have.length_of(1)
diff --git a/tests/test_ssm/test_ssm_parameterstore.py b/tests/test_ssm/test_ssm_parameterstore.py
--- a/tests/test_ssm/test_ssm_parameterstore.py
+++ b/tests/test_ssm/test_ssm_parameterstore.py
@@ -83,3 +83,12 @@ def test_get_key_beginning_with_aws():
             "/aws/service/global-infrastructure/regions/us-west-1/partition",
         }
     )
+
+
+def test_ssm_parameter_from_unknown_region():
+    store = ParameterDict("accnt", "region")
+    list(
+        store.get_keys_beginning_with(
+            "/aws/service/ami-amazon-linux-latest", recursive=False
+        )
+    ).should.equal([])