Repository: getmoto/moto (python)

Issue:
Feature: ability to pass tags via json configured in `MOTO_AMIS_PATH`
For my use case, I have code heavily depend on AWS tags (specifically on AMIs)

I would be very helpful if I can describe the labels for each AMI I wan't to be loaded is a declarative why

for now I need to list all of the AMIs, and have code by their ids to tag tag them via boto3 calls

```json
  {
    "ami_id": "ami-000a6b029162fa600",
    "name": "ScyllaDB Enterprise 2022.2.0-rc7",
    "description": "ScyllaDB Enterprise 2022.2.0-rc7",
    "owner_id": "797456418907",
    "public": true,
    "virtualization_type": "hvm",
    "architecture": "x86_64",
    "state": "available",
    "platform": null,
    "image_type": "machine",
    "hypervisor": "xen",
    "root_device_name": "/dev/sda1",
    "root_device_type": "ebs",
    "sriov": "simple",
    "tags":  [
        {"Key": "scylla_version", "Value": "2022.2.0-rc7"},
        {"Key": "user_data_format_version", "Value": "3"},
    ]
  }
```

would be nice if `gen_moto_amis` would be able to extract tags as well...

Pull request description:
Support tagging in custom AMI definitions
Resolves #8517 

Patch:
diff --git a/moto/ec2/models/amis.py b/moto/ec2/models/amis.py
index c58be632a..185fba794 100644
--- a/moto/ec2/models/amis.py
+++ b/moto/ec2/models/amis.py
@@ -56,6 +56,7 @@ class Ami(TaggedEC2Resource):
         snapshot_description: Optional[str] = None,
         product_codes: Set[str] = set(),
         boot_mode: str = "uefi",
+        tags: Optional[Dict[str, Any]] = None,
     ):
         self.ec2_backend = ec2_backend
         self.id = ami_id
@@ -78,6 +79,9 @@ class Ami(TaggedEC2Resource):
         self.product_codes = product_codes
         self.boot_mode = boot_mode
 
+        if tags is not None:
+            self.add_tags(tags)
+
         if instance:
             self.instance = instance
             self.instance_id = instance.id
diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py
index c50516152..4c1590a6f 100644
--- a/moto/ec2/utils.py
+++ b/moto/ec2/utils.py
@@ -912,6 +912,7 @@ def gen_moto_amis(
                 "root_device_name": image["RootDeviceName"],
                 "root_device_type": image["RootDeviceType"],
                 "sriov": image.get("SriovNetSupport", "simple"),
+                "tags": {tag["Key"]: tag["Value"] for tag in image.get("Tags", [])},
             }
             result.append(tmp)
         except Exception as err:


Test patch:
diff --git a/tests/test_ec2/test_utils.py b/tests/test_ec2/test_utils.py
index 5752e65f9..5829d5f24 100644
--- a/tests/test_ec2/test_utils.py
+++ b/tests/test_ec2/test_utils.py
@@ -54,6 +54,10 @@ def test_gen_moto_amis():
         "Name": "Windows_Server-2016-English-Nano-Base-2017.10.13",
         "VirtualizationType": "hvm",
         "Hypervisor": "xen",
+        "Tags": [
+            {"Key": "foo", "Value": "bar"},
+            {"Key": "bar", "Value": "baz"},
+        ],
     }
 
     images = []
@@ -67,6 +71,8 @@ def test_gen_moto_amis():
     assert len(images) == 2
     amis = utils.gen_moto_amis(images, drop_images_missing_keys=True)
     assert len(amis) == 1
+    expected_tags = {"foo": "bar", "bar": "baz"}
+    assert amis[0]["tags"] == expected_tags
 
     # with drop=False, it should raise KeyError because of the missing key
     with raises(KeyError, match="'Public'"):
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
index a8424c6a7..4c8152e94 100644
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -958,6 +958,30 @@ def test_upload_file_with_checksum_algorithm():
     assert random_bytes == actual_content
 
 
+@mock_aws
+def test_download_file(tmp_path):
+    random_bytes = (
+        b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00"
+        b"\x00\xff\n\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\n"
+    )
+    upload_file = tmp_path / "upload.txt"
+    with open(upload_file, mode="wb") as fp:
+        fp.write(random_bytes)
+    client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
+    bucket_name = "test-bucket"
+    object_keys = ["key-for-put-object", "key-for-upload-file"]
+    client.create_bucket(Bucket=bucket_name)
+    # Test both object upload methods.
+    client.put_object(Bucket=bucket_name, Key=object_keys[0], Body=random_bytes)
+    client.upload_file(upload_file, bucket_name, object_keys[1])
+    for object_key in object_keys:
+        download_file = tmp_path / f"download-{object_key}.txt"
+        client.download_file(bucket_name, object_key, download_file)
+        with open(download_file, mode="rb") as fp:
+            downloaded_content = fp.read()
+        assert downloaded_content == random_bytes
+
+
 @mock_aws
 def test_put_large_with_checksum_algorithm():
     s3_client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
diff --git a/tests/test_special_cases/test_custom_amis.py b/tests/test_special_cases/test_custom_amis.py
index 82f19c175..a2800f5d0 100644
--- a/tests/test_special_cases/test_custom_amis.py
+++ b/tests/test_special_cases/test_custom_amis.py
@@ -41,6 +41,10 @@ class TestEC2CustomAMIs(TestCase):
                 "root_device_type": "ebs",
                 "sriov": "simple",
                 "creation_date": "2021-11-10T08:13:01.000Z",
+                "tags": {
+                    "tag1": "value1",
+                    "tag2": "value2",
+                },
             }
         ]
         with test_ami_path.open("w") as fp:
@@ -72,3 +76,9 @@ class TestEC2CustomAMIs(TestCase):
         # Now reload our images with only the custom AMIs loaded
         images = ec2_client.describe_images()["Images"]
         assert len(images) == 1
+        image = images[0]
+        expected_tags = [
+            {"Key": "tag1", "Value": "value1"},
+            {"Key": "tag2", "Value": "value2"},
+        ]
+        assert image["Tags"] == expected_tags