328 lines
10 KiB
Python
328 lines
10 KiB
Python
#!/usr/bin/python
|
|
# Copyright: Ansible Project
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: lambda_alias
|
|
version_added: 1.0.0
|
|
short_description: Creates, updates or deletes AWS Lambda function aliases
|
|
description:
|
|
- This module allows the management of AWS Lambda functions aliases via the Ansible
|
|
framework. It is idempotent and supports "Check" mode. Use module M(community.aws.lambda) to manage the lambda function
|
|
itself and M(community.aws.lambda_event) to manage event source mappings.
|
|
|
|
|
|
author: Pierre Jodouin (@pjodouin), Ryan Scott Brown (@ryansb)
|
|
options:
|
|
function_name:
|
|
description:
|
|
- The name of the function alias.
|
|
required: true
|
|
type: str
|
|
state:
|
|
description:
|
|
- Describes the desired state.
|
|
default: "present"
|
|
choices: ["present", "absent"]
|
|
type: str
|
|
name:
|
|
description:
|
|
- Name of the function alias.
|
|
required: true
|
|
aliases: ['alias_name']
|
|
type: str
|
|
description:
|
|
description:
|
|
- A short, user-defined function alias description.
|
|
type: str
|
|
function_version:
|
|
description:
|
|
- Version associated with the Lambda function alias.
|
|
A value of 0 (or omitted parameter) sets the alias to the $LATEST version.
|
|
aliases: ['version']
|
|
type: int
|
|
extends_documentation_fragment:
|
|
- amazon.aws.aws
|
|
- amazon.aws.ec2
|
|
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
---
|
|
# Simple example to create a lambda function and publish a version
|
|
- hosts: localhost
|
|
gather_facts: no
|
|
vars:
|
|
state: present
|
|
project_folder: /path/to/deployment/package
|
|
deployment_package: lambda.zip
|
|
account: 123456789012
|
|
production_version: 5
|
|
tasks:
|
|
- name: AWS Lambda Function
|
|
lambda:
|
|
state: "{{ state | default('present') }}"
|
|
name: myLambdaFunction
|
|
publish: True
|
|
description: lambda function description
|
|
code_s3_bucket: package-bucket
|
|
code_s3_key: "lambda/{{ deployment_package }}"
|
|
local_path: "{{ project_folder }}/{{ deployment_package }}"
|
|
runtime: python2.7
|
|
timeout: 5
|
|
handler: lambda.handler
|
|
memory_size: 128
|
|
role: "arn:aws:iam::{{ account }}:role/API2LambdaExecRole"
|
|
|
|
- name: Get information
|
|
lambda_info:
|
|
name: myLambdaFunction
|
|
register: lambda_info
|
|
- name: show results
|
|
ansible.builtin.debug:
|
|
msg: "{{ lambda_info['lambda_facts'] }}"
|
|
|
|
# The following will set the Dev alias to the latest version ($LATEST) since version is omitted (or = 0)
|
|
- name: "alias 'Dev' for function {{ lambda_info.lambda_facts.FunctionName }} "
|
|
community.aws.lambda_alias:
|
|
state: "{{ state | default('present') }}"
|
|
function_name: "{{ lambda_info.lambda_facts.FunctionName }}"
|
|
name: Dev
|
|
description: Development is $LATEST version
|
|
|
|
# The QA alias will only be created when a new version is published (i.e. not = '$LATEST')
|
|
- name: "alias 'QA' for function {{ lambda_info.lambda_facts.FunctionName }} "
|
|
community.aws.lambda_alias:
|
|
state: "{{ state | default('present') }}"
|
|
function_name: "{{ lambda_info.lambda_facts.FunctionName }}"
|
|
name: QA
|
|
version: "{{ lambda_info.lambda_facts.Version }}"
|
|
description: "QA is version {{ lambda_info.lambda_facts.Version }}"
|
|
when: lambda_info.lambda_facts.Version != "$LATEST"
|
|
|
|
# The Prod alias will have a fixed version based on a variable
|
|
- name: "alias 'Prod' for function {{ lambda_info.lambda_facts.FunctionName }} "
|
|
community.aws.lambda_alias:
|
|
state: "{{ state | default('present') }}"
|
|
function_name: "{{ lambda_info.lambda_facts.FunctionName }}"
|
|
name: Prod
|
|
version: "{{ production_version }}"
|
|
description: "Production is version {{ production_version }}"
|
|
'''
|
|
|
|
RETURN = '''
|
|
---
|
|
alias_arn:
|
|
description: Full ARN of the function, including the alias
|
|
returned: success
|
|
type: str
|
|
sample: arn:aws:lambda:us-west-2:123456789012:function:myFunction:dev
|
|
description:
|
|
description: A short description of the alias
|
|
returned: success
|
|
type: str
|
|
sample: The development stage for my hot new app
|
|
function_version:
|
|
description: The qualifier that the alias refers to
|
|
returned: success
|
|
type: str
|
|
sample: $LATEST
|
|
name:
|
|
description: The name of the alias assigned
|
|
returned: success
|
|
type: str
|
|
sample: dev
|
|
revision_id:
|
|
description: A unique identifier that changes when you update the alias.
|
|
returned: success
|
|
type: str
|
|
sample: 12345678-1234-1234-1234-123456789abc
|
|
'''
|
|
|
|
import re
|
|
|
|
try:
|
|
import botocore
|
|
except ImportError:
|
|
pass # Handled by AnsibleAWSModule
|
|
|
|
from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
|
|
from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict
|
|
|
|
from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
|
|
from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
|
|
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
|
|
|
|
|
|
def set_api_params(module, module_params):
|
|
"""
|
|
Sets non-None module parameters to those expected by the boto3 API.
|
|
|
|
:param module:
|
|
:param module_params:
|
|
:return:
|
|
"""
|
|
|
|
api_params = dict()
|
|
|
|
for param in module_params:
|
|
module_param = module.params.get(param, None)
|
|
if module_param:
|
|
api_params[param] = module_param
|
|
|
|
return snake_dict_to_camel_dict(api_params, capitalize_first=True)
|
|
|
|
|
|
def validate_params(module):
|
|
"""
|
|
Performs basic parameter validation.
|
|
|
|
:param module: AnsibleAWSModule reference
|
|
:return:
|
|
"""
|
|
|
|
function_name = module.params['function_name']
|
|
|
|
# validate function name
|
|
if not re.search(r'^[\w\-:]+$', function_name):
|
|
module.fail_json(
|
|
msg='Function name {0} is invalid. Names must contain only alphanumeric characters and hyphens.'.format(function_name)
|
|
)
|
|
if len(function_name) > 64:
|
|
module.fail_json(msg='Function name "{0}" exceeds 64 character limit'.format(function_name))
|
|
|
|
# if parameter 'function_version' is zero, set it to $LATEST, else convert it to a string
|
|
if module.params['function_version'] == 0:
|
|
module.params['function_version'] = '$LATEST'
|
|
else:
|
|
module.params['function_version'] = str(module.params['function_version'])
|
|
|
|
return
|
|
|
|
|
|
def get_lambda_alias(module, client):
|
|
"""
|
|
Returns the lambda function alias if it exists.
|
|
|
|
:param module: AnsibleAWSModule
|
|
:param client: (wrapped) boto3 lambda client
|
|
:return:
|
|
"""
|
|
|
|
# set API parameters
|
|
api_params = set_api_params(module, ('function_name', 'name'))
|
|
|
|
# check if alias exists and get facts
|
|
try:
|
|
results = client.get_alias(aws_retry=True, **api_params)
|
|
except is_boto3_error_code('ResourceNotFoundException'):
|
|
results = None
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except
|
|
module.fail_json_aws(e, msg='Error retrieving function alias')
|
|
|
|
return results
|
|
|
|
|
|
def lambda_alias(module, client):
|
|
"""
|
|
Adds, updates or deletes lambda function aliases.
|
|
|
|
:param module: AnsibleAWSModule
|
|
:param client: (wrapped) boto3 lambda client
|
|
:return dict:
|
|
"""
|
|
results = dict()
|
|
changed = False
|
|
current_state = 'absent'
|
|
state = module.params['state']
|
|
|
|
facts = get_lambda_alias(module, client)
|
|
if facts:
|
|
current_state = 'present'
|
|
|
|
if state == 'present':
|
|
if current_state == 'present':
|
|
snake_facts = camel_dict_to_snake_dict(facts)
|
|
|
|
# check if alias has changed -- only version and description can change
|
|
alias_params = ('function_version', 'description')
|
|
for param in alias_params:
|
|
if module.params.get(param) is None:
|
|
continue
|
|
if module.params.get(param) != snake_facts.get(param):
|
|
changed = True
|
|
break
|
|
|
|
if changed:
|
|
api_params = set_api_params(module, ('function_name', 'name'))
|
|
api_params.update(set_api_params(module, alias_params))
|
|
|
|
if not module.check_mode:
|
|
try:
|
|
results = client.update_alias(aws_retry=True, **api_params)
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
|
module.fail_json_aws(e, msg='Error updating function alias')
|
|
|
|
else:
|
|
# create new function alias
|
|
api_params = set_api_params(module, ('function_name', 'name', 'function_version', 'description'))
|
|
|
|
try:
|
|
if not module.check_mode:
|
|
results = client.create_alias(aws_retry=True, **api_params)
|
|
changed = True
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
|
module.fail_json_aws(e, msg='Error creating function alias')
|
|
|
|
else: # state = 'absent'
|
|
if current_state == 'present':
|
|
# delete the function
|
|
api_params = set_api_params(module, ('function_name', 'name'))
|
|
|
|
try:
|
|
if not module.check_mode:
|
|
results = client.delete_alias(aws_retry=True, **api_params)
|
|
changed = True
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
|
module.fail_json_aws(e, msg='Error deleting function alias')
|
|
|
|
return dict(changed=changed, **dict(results or facts or {}))
|
|
|
|
|
|
def main():
|
|
"""
|
|
Main entry point.
|
|
|
|
:return dict: ansible facts
|
|
"""
|
|
argument_spec = dict(
|
|
state=dict(required=False, default='present', choices=['present', 'absent']),
|
|
function_name=dict(required=True),
|
|
name=dict(required=True, aliases=['alias_name']),
|
|
function_version=dict(type='int', required=False, default=0, aliases=['version']),
|
|
description=dict(required=False, default=None),
|
|
)
|
|
|
|
module = AnsibleAWSModule(
|
|
argument_spec=argument_spec,
|
|
supports_check_mode=True,
|
|
mutually_exclusive=[],
|
|
required_together=[],
|
|
)
|
|
|
|
client = module.client('lambda', retry_decorator=AWSRetry.jittered_backoff())
|
|
|
|
validate_params(module)
|
|
results = lambda_alias(module, client)
|
|
|
|
module.exit_json(**camel_dict_to_snake_dict(results))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|