Repository: ansible-collections/amazon.aws

Issue:
Support image_uri
### Summary

Please support lambdas from custom containers in AWS ECR.

There have been a pull request here: https://github.com/ansible-collections/community.aws/pull/772

I have created a patch, but no tests yet - thus no pull request. But in case somebody else has time to finish it, I wanted to share my work.

https://github.com/ansible-collections/amazon.aws/compare/main...manuelschneider:amazon.aws:main

Thank you very much for the modules!

### Issue Type

Feature Idea

### Component Name

lambda

### Additional Information

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
-   name: "Create and/or configure the lambda {{ lambda.key }}"
    amazon.aws.lambda:
        profile: "{{ awsProfile }}"
        region: "{{ aws_region }}"
        state: present
        name: "{{ lambda.key }}"
        role: arn:aws:iam::123456:role/myrole"
        image_uri: 123456.dkr.ecr.eu-central-1.amazonaws.com/mycontainer:stage
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:

Resolving patch:
diff --git a/plugins/modules/lambda.py b/plugins/modules/lambda.py
--- a/plugins/modules/lambda.py
+++ b/plugins/modules/lambda.py
@@ -142,6 +142,13 @@
     type: list
     elements: dict
     version_added: 5.5.0
+  image_uri:
+    description:
+      - The Amazon ECR URI of the image to use.
+      - Required (alternative to runtime zip_file and s3_bucket) when creating a function.
+      - Required when I(state=present).
+    type: str
+    version_added: 7.3.0
 author:
   - 'Steyn Huizinga (@steynovich)'
 extends_documentation_fragment:
@@ -533,12 +540,21 @@ def _s3_args(s3_bucket, s3_key, s3_object_version):
     return code
 
 
+def _image_args(image_uri):
+    if not image_uri:
+        return {}
+
+    code = {"ImageUri": image_uri}
+    return code
+
+
 def _code_args(module, current_config):
     s3_bucket = module.params.get("s3_bucket")
     s3_key = module.params.get("s3_key")
     s3_object_version = module.params.get("s3_object_version")
     zip_file = module.params.get("zip_file")
     architectures = module.params.get("architecture")
+    image_uri = module.params.get("image_uri")
 
     code_kwargs = {}
 
@@ -552,6 +568,7 @@ def _code_args(module, current_config):
         module.fail_json(msg=str(e), exception=traceback.format_exc())
 
     code_kwargs.update(_s3_args(s3_bucket, s3_key, s3_object_version))
+    code_kwargs.update(_image_args(image_uri))
 
     if not code_kwargs:
         return {}
@@ -566,6 +583,7 @@ def main():
     argument_spec = dict(
         name=dict(required=True),
         state=dict(default="present", choices=["present", "absent"]),
+        image_uri=dict(),
         runtime=dict(),
         role=dict(),
         handler=dict(),
@@ -603,17 +621,27 @@ def main():
         ["zip_file", "s3_key"],
         ["zip_file", "s3_bucket"],
         ["zip_file", "s3_object_version"],
+        ["image_uri", "zip_file"],
+        ["image_uri", "runtime"],
+        ["image_uri", "handler"],
+        ["image_uri", "s3_key"],
+        ["image_uri", "s3_bucket"],
+        ["image_uri", "s3_object_version"],
     ]
 
+    required_by = {"runtime": ["handler"]}
+
     required_together = [
         ["s3_key", "s3_bucket"],
         ["vpc_subnet_ids", "vpc_security_group_ids"],
+        ["runtime", "handler"],
     ]
 
     required_if = [
-        ["state", "present", ["runtime", "handler", "role"]],
-        ["architecture", "x86_64", ["zip_file", "s3_bucket"], True],
-        ["architecture", "arm64", ["zip_file", "s3_bucket"], True],
+        ["state", "present", ["role"]],
+        ["state", "present", ["runtime", "image_uri"], True],
+        ["architecture", "x86_64", ["zip_file", "s3_bucket", "image_uri"], True],
+        ["architecture", "arm64", ["zip_file", "s3_bucket", "image_uri"], True],
     ]
 
     module = AnsibleAWSModule(
@@ -641,6 +669,7 @@ def main():
     purge_tags = module.params.get("purge_tags")
     kms_key_arn = module.params.get("kms_key_arn")
     architectures = module.params.get("architecture")
+    image_uri = module.params.get("image_uri")
     layers = []
 
     check_mode = module.check_mode
@@ -692,6 +721,8 @@ def main():
             func_kwargs.update({"Timeout": timeout})
         if memory_size and current_config["MemorySize"] != memory_size:
             func_kwargs.update({"MemorySize": memory_size})
+        if image_uri is not None and current_config["PackageType"] != "Image":
+            func_kwargs.update({"PackageType": "Image"})
         if runtime and current_config["Runtime"] != runtime:
             func_kwargs.update({"Runtime": runtime})
         if (environment_variables is not None) and (
@@ -779,12 +810,11 @@ def main():
         # We're done
         module.exit_json(changed=changed, code_kwargs=code_kwargs, func_kwargs=func_kwargs, **response)
 
-    # Function doesn't exists, create new Lambda function
+    # Function doesn't exist, create new Lambda function
     elif state == "present":
         func_kwargs = {
             "FunctionName": name,
             "Publish": True,
-            "Runtime": runtime,
             "Role": role_arn,
             "Timeout": timeout,
             "MemorySize": memory_size,
@@ -800,6 +830,12 @@ def main():
         if description is not None:
             func_kwargs.update({"Description": description})
 
+        if image_uri is not None:
+            func_kwargs.update({"PackageType": "Image"})
+
+        if runtime is not None:
+            func_kwargs.update({"Runtime": runtime})
+
         if handler is not None:
             func_kwargs.update({"Handler": handler})
 


Test patch:
diff --git a/tests/integration/targets/lambda/tasks/main.yml b/tests/integration/targets/lambda/tasks/main.yml
--- a/tests/integration/targets/lambda/tasks/main.yml
+++ b/tests/integration/targets/lambda/tasks/main.yml
@@ -62,10 +62,9 @@
           - result.failed
           - 'result.msg.startswith("missing required arguments: name")'
 
-    - name: test with no role or handler
+    - name: test with no role
       amazon.aws.lambda:
         name: ansible-testing-fake-should-not-be-created
-        runtime: "{{ lambda_python_runtime }}"
       register: result
       ignore_errors: true
     - name: assert failure when called with no parameters
@@ -73,9 +72,22 @@
         that:
           - result.failed
           - 'result.msg.startswith("state is present but all of the following are missing: ")'
-          - '"handler" in result.msg'
           - '"role" in result.msg'
 
+    - name: test with no handler/image_uri
+      amazon.aws.lambda:
+        name: ansible-testing-fake-should-not-be-created
+        role: "{{ lambda_role_name }}"
+      register: result
+      ignore_errors: true
+    - name: assert failure when called with no parameters
+      ansible.builtin.assert:
+        that:
+          - result.failed
+          - 'result.msg.startswith("state is present but any of the following are missing: ")'
+          - '"runtime" in result.msg'
+          - '"image_uri" in result.msg'
+
     - name: test execute lambda with no function arn or name
       amazon.aws.lambda_execute:
       register: result