Repository: ansible-collections/community.aws

Issue:
Add support for lambda KMSKeyArn
### Summary

Please add support to set KMSKeyArn when creating and updating lambda functions.

https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-KMSKeyArn
https://docs.aws.amazon.com/lambda/latest/dg/API_UpdateFunctionCode.html#SSS-UpdateFunctionCode-response-KMSKeyArn

### Issue Type

Feature Idea

### Component Name

lambda

### Additional Information

_No response_

### Code of Conduct

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


Discussion:

Resolving patch:
diff --git a/plugins/modules/execute_lambda.py b/plugins/modules/execute_lambda.py
--- a/plugins/modules/execute_lambda.py
+++ b/plugins/modules/execute_lambda.py
@@ -260,8 +260,10 @@ def main():
 
 def wait_for_lambda(client, module, name):
     try:
-        waiter = client.get_waiter('function_active')
-        waiter.wait(FunctionName=name)
+        client_active_waiter = client.get_waiter('function_active')
+        client_updated_waiter = client.get_waiter('function_updated')
+        client_active_waiter.wait(FunctionName=name)
+        client_updated_waiter.wait(FunctionName=name)
     except botocore.exceptions.WaiterError as e:
         module.fail_json_aws(e, msg='Timeout while waiting on lambda to be Active')
     except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
diff --git a/plugins/modules/lambda.py b/plugins/modules/lambda.py
--- a/plugins/modules/lambda.py
+++ b/plugins/modules/lambda.py
@@ -107,6 +107,11 @@
     description:
       - Tag dict to apply to the function.
     type: dict
+  kms_key_arn:
+    description:
+      - The KMS key ARN used to encrypt the function's environment variables.
+    type: str
+    version_added: 3.3.0
 author:
     - 'Steyn Huizinga (@steynovich)'
 extends_documentation_fragment:
@@ -350,6 +355,7 @@ def main():
         vpc_security_group_ids=dict(type='list', elements='str'),
         environment_variables=dict(type='dict'),
         dead_letter_arn=dict(),
+        kms_key_arn=dict(type='str', no_log=False),
         tracing_mode=dict(choices=['Active', 'PassThrough']),
         tags=dict(type='dict'),
     )
@@ -387,6 +393,7 @@ def main():
     dead_letter_arn = module.params.get('dead_letter_arn')
     tracing_mode = module.params.get('tracing_mode')
     tags = module.params.get('tags')
+    kms_key_arn = module.params.get('kms_key_arn')
 
     check_mode = module.check_mode
     changed = False
@@ -442,6 +449,8 @@ def main():
                     func_kwargs.update({'DeadLetterConfig': {'TargetArn': dead_letter_arn}})
         if tracing_mode and (current_config.get('TracingConfig', {}).get('Mode', 'PassThrough') != tracing_mode):
             func_kwargs.update({'TracingConfig': {'Mode': tracing_mode}})
+        if kms_key_arn:
+            func_kwargs.update({'KMSKeyArn': kms_key_arn})
 
         # If VPC configuration is desired
         if vpc_subnet_ids:
@@ -573,17 +582,23 @@ def main():
         if tracing_mode:
             func_kwargs.update({'TracingConfig': {'Mode': tracing_mode}})
 
+        if kms_key_arn:
+            func_kwargs.update({'KMSKeyArn': kms_key_arn})
+
         # If VPC configuration is given
         if vpc_subnet_ids:
             func_kwargs.update({'VpcConfig': {'SubnetIds': vpc_subnet_ids,
                                               'SecurityGroupIds': vpc_security_group_ids}})
 
+        # Function would have been created if not check mode
+        if check_mode:
+            module.exit_json(changed=True)
+
         # Finally try to create function
         current_version = None
         try:
-            if not check_mode:
-                response = client.create_function(aws_retry=True, **func_kwargs)
-                current_version = response['Version']
+            response = client.create_function(aws_retry=True, **func_kwargs)
+            current_version = response['Version']
             changed = True
         except (BotoCoreError, ClientError) as e:
             module.fail_json_aws(e, msg="Trying to create function")


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
@@ -93,6 +93,19 @@
       - '"parameters are required together" in result.msg'
 
   # Prepare minimal Lambda
+  - name: test state=present - upload the lambda (check mode)
+    lambda:
+      name: '{{ lambda_function_name }}'
+      runtime: python3.6
+      handler: mini_lambda.handler
+      role: '{{ lambda_role_name }}'
+      zip_file: '{{ zip_res.dest }}'
+    register: result
+    check_mode: yes
+  - name: assert lambda upload succeeded
+    assert:
+      that:
+      - result.changed
 
   - name: test state=present - upload the lambda
     lambda:
@@ -105,7 +118,7 @@
   - name: assert lambda upload succeeded
     assert:
       that:
-      - result is not failed
+      - result.changed
       - result.configuration.tracing_config.mode == "PassThrough"
 
   # Test basic operation of Uploaded lambda
@@ -122,6 +135,25 @@
       - result.result.output.message == "hello Mr Ansible Tests"
 
   # Test updating Lambda
+  - name: test lambda config updates (check mode)
+    lambda:
+      name: '{{lambda_function_name}}'
+      runtime: nodejs14.x
+      tracing_mode: Active
+      handler: mini_lambda.handler
+      role: '{{ lambda_role_name }}'
+      tags:
+        CamelCase: 'ACamelCaseValue'
+        snake_case: 'a_snake_case_value'
+        'Spaced key': 'A value with spaces'
+    register: update_result
+    check_mode: yes
+  - name: assert that update succeeded
+    assert:
+      that:
+      - update_result is not failed
+      - update_result.changed == True
+
   - name: test lambda config updates
     lambda:
       name: '{{lambda_function_name}}'
@@ -142,6 +174,25 @@
       - update_result.configuration.runtime == 'nodejs14.x'
       - update_result.configuration.tracing_config.mode == 'Active'
 
+  - name: test no changes are made with the same parameters repeated (check mode)
+    lambda:
+      name: '{{lambda_function_name}}'
+      runtime: nodejs14.x
+      tracing_mode: Active
+      handler: mini_lambda.handler
+      role: '{{ lambda_role_name }}'
+      tags:
+        CamelCase: 'ACamelCaseValue'
+        snake_case: 'a_snake_case_value'
+        'Spaced key': 'A value with spaces'
+    register: update_result
+    check_mode: yes
+  - name: assert that update succeeded
+    assert:
+      that:
+      - update_result is not failed
+      - update_result.changed == False
+
   - name: test no changes are made with the same parameters repeated
     lambda:
       name: '{{lambda_function_name}}'
@@ -184,6 +235,7 @@
       name: '{{ lambda_function_name }}'
       query: all
     register: lambda_infos_all
+    check_mode: yes
   - name: lambda_info | Assert successfull retrieval of all information
     assert:
       that:
@@ -278,6 +330,23 @@
       - result is not failed
       - result.changed == False
 
+  - name: test putting an environment variable changes lambda (check mode)
+    lambda:
+      name: '{{lambda_function_name}}'
+      runtime: python3.6
+      handler: mini_lambda.handler
+      role: '{{ lambda_role_name }}'
+      zip_file: '{{zip_res.dest}}'
+      environment_variables:
+        EXTRA_MESSAGE: I think you are great!!
+    register: result
+    check_mode: yes
+  - name: assert lambda upload succeeded
+    assert:
+      that:
+      - result is not failed
+      - result.changed == True
+
   - name: test putting an environment variable changes lambda
     lambda:
       name: '{{lambda_function_name}}'
@@ -293,6 +362,8 @@
       that:
       - result is not failed
       - result.changed == True
+      - result.configuration.environment.variables.extra_message == "I think you are great!!"
+
   - name: test lambda works
     execute_lambda:
       name: '{{lambda_function_name}}'
@@ -306,6 +377,19 @@
       - result.result.output.message == "hello Mr Ansible Tests. I think you are great!!"
 
   # Deletion behavious
+  - name: test state=absent (expect changed=True) (check mode)
+    lambda:
+      name: '{{lambda_function_name}}'
+      state: absent
+    register: result
+    check_mode: yes
+
+  - name: assert state=absent
+    assert:
+      that:
+      - result is not failed
+      - result is changed
+
   - name: test state=absent (expect changed=True)
     lambda:
       name: '{{lambda_function_name}}'
@@ -318,6 +402,19 @@
       - result is not failed
       - result is changed
 
+  - name: test state=absent (expect changed=False) when already deleted (check mode)
+    lambda:
+      name: '{{lambda_function_name}}'
+      state: absent
+    register: result
+    check_mode: yes
+
+  - name: assert state=absent
+    assert:
+      that:
+      - result is not failed
+      - result is not changed
+
   - name: test state=absent (expect changed=False) when already deleted
     lambda:
       name: '{{lambda_function_name}}'