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:
@manuelschneider Thank you for raising this issue. Would you be willing to re-open your Pull Request https://github.com/ansible-collections/community.aws/pull/772 against this repository?
Hi,

it wasn't my Pull Request. I just re-created the patch (diff linked above) - but no tests. If you want, I could create a pull request for that.
> it wasn't my Pull Request. I just re-created the patch (diff linked above) - but no tests. If you want, I could create a pull request for that.

PRs are always appreciated.  We're also doing a much better job keeping up with PRs than we were when that PR was originally filed.  If you can get the PR filed in the next couple of weeks we should be able to get it reviewed in time for amazon.aws 6.0.0 and Ansible 8.
Hi,

I've created the PR for you: #1438

~Manuel

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