1#!/usr/bin/python 2# This file is part of Ansible 3# 4# Ansible is free software: you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation, either version 3 of the License, or 7# (at your option) any later version. 8# 9# Ansible is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 16 17ANSIBLE_METADATA = {'metadata_version': '1.1', 18 'status': ['preview'], 19 'supported_by': 'community'} 20 21 22DOCUMENTATION = ''' 23--- 24module: lambda_info 25short_description: Gathers AWS Lambda function details 26description: 27 - Gathers various details related to Lambda functions, including aliases, versions and event source mappings. 28 Use module M(lambda) to manage the lambda function itself, M(lambda_alias) to manage function aliases and 29 M(lambda_event) to manage lambda event source mappings. 30 31version_added: "2.9" 32 33options: 34 query: 35 description: 36 - Specifies the resource type for which to gather information. Leave blank to retrieve all information. 37 required: true 38 choices: [ "aliases", "all", "config", "mappings", "policy", "versions" ] 39 default: "all" 40 function_name: 41 description: 42 - The name of the lambda function for which information is requested. 43 aliases: [ "function", "name"] 44 event_source_arn: 45 description: 46 - For query type 'mappings', this is the Amazon Resource Name (ARN) of the Amazon Kinesis or DynamoDB stream. 47author: Pierre Jodouin (@pjodouin) 48requirements: 49 - boto3 50extends_documentation_fragment: 51 - aws 52 - ec2 53''' 54 55EXAMPLES = ''' 56--- 57# Simple example of listing all info for a function 58- name: List all for a specific function 59 lambda_info: 60 query: all 61 function_name: myFunction 62 register: my_function_details 63# List all versions of a function 64- name: List function versions 65 lambda_info: 66 query: versions 67 function_name: myFunction 68 register: my_function_versions 69# List all lambda function versions 70- name: List all function 71 lambda_info: 72 query: all 73 max_items: 20 74 register: output 75- name: show Lambda information 76 debug: 77 msg: "{{ output['function'] }}" 78''' 79 80RETURN = ''' 81--- 82function: 83 description: lambda function list 84 returned: success 85 type: dict 86function.TheName: 87 description: lambda function information, including event, mapping, and version information 88 returned: success 89 type: dict 90''' 91 92from ansible.module_utils.aws.core import AnsibleAWSModule 93from ansible.module_utils.ec2 import camel_dict_to_snake_dict, get_aws_connection_info, boto3_conn 94import json 95import datetime 96import sys 97import re 98 99 100try: 101 from botocore.exceptions import ClientError 102except ImportError: 103 pass # protected by AnsibleAWSModule 104 105 106def fix_return(node): 107 """ 108 fixup returned dictionary 109 110 :param node: 111 :return: 112 """ 113 114 if isinstance(node, datetime.datetime): 115 node_value = str(node) 116 117 elif isinstance(node, list): 118 node_value = [fix_return(item) for item in node] 119 120 elif isinstance(node, dict): 121 node_value = dict([(item, fix_return(node[item])) for item in node.keys()]) 122 123 else: 124 node_value = node 125 126 return node_value 127 128 129def alias_details(client, module): 130 """ 131 Returns list of aliases for a specified function. 132 133 :param client: AWS API client reference (boto3) 134 :param module: Ansible module reference 135 :return dict: 136 """ 137 138 lambda_info = dict() 139 140 function_name = module.params.get('function_name') 141 if function_name: 142 params = dict() 143 if module.params.get('max_items'): 144 params['MaxItems'] = module.params.get('max_items') 145 146 if module.params.get('next_marker'): 147 params['Marker'] = module.params.get('next_marker') 148 try: 149 lambda_info.update(aliases=client.list_aliases(FunctionName=function_name, **params)['Aliases']) 150 except ClientError as e: 151 if e.response['Error']['Code'] == 'ResourceNotFoundException': 152 lambda_info.update(aliases=[]) 153 else: 154 module.fail_json_aws(e, msg="Trying to get aliases") 155 else: 156 module.fail_json(msg='Parameter function_name required for query=aliases.') 157 158 return {function_name: camel_dict_to_snake_dict(lambda_info)} 159 160 161def all_details(client, module): 162 """ 163 Returns all lambda related facts. 164 165 :param client: AWS API client reference (boto3) 166 :param module: Ansible module reference 167 :return dict: 168 """ 169 170 if module.params.get('max_items') or module.params.get('next_marker'): 171 module.fail_json(msg='Cannot specify max_items nor next_marker for query=all.') 172 173 lambda_info = dict() 174 175 function_name = module.params.get('function_name') 176 if function_name: 177 lambda_info[function_name] = {} 178 lambda_info[function_name].update(config_details(client, module)[function_name]) 179 lambda_info[function_name].update(alias_details(client, module)[function_name]) 180 lambda_info[function_name].update(policy_details(client, module)[function_name]) 181 lambda_info[function_name].update(version_details(client, module)[function_name]) 182 lambda_info[function_name].update(mapping_details(client, module)[function_name]) 183 else: 184 lambda_info.update(config_details(client, module)) 185 186 return lambda_info 187 188 189def config_details(client, module): 190 """ 191 Returns configuration details for one or all lambda functions. 192 193 :param client: AWS API client reference (boto3) 194 :param module: Ansible module reference 195 :return dict: 196 """ 197 198 lambda_info = dict() 199 200 function_name = module.params.get('function_name') 201 if function_name: 202 try: 203 lambda_info.update(client.get_function_configuration(FunctionName=function_name)) 204 except ClientError as e: 205 if e.response['Error']['Code'] == 'ResourceNotFoundException': 206 lambda_info.update(function={}) 207 else: 208 module.fail_json_aws(e, msg="Trying to get {0} configuration".format(function_name)) 209 else: 210 params = dict() 211 if module.params.get('max_items'): 212 params['MaxItems'] = module.params.get('max_items') 213 214 if module.params.get('next_marker'): 215 params['Marker'] = module.params.get('next_marker') 216 217 try: 218 lambda_info.update(function_list=client.list_functions(**params)['Functions']) 219 except ClientError as e: 220 if e.response['Error']['Code'] == 'ResourceNotFoundException': 221 lambda_info.update(function_list=[]) 222 else: 223 module.fail_json_aws(e, msg="Trying to get function list") 224 225 functions = dict() 226 for func in lambda_info.pop('function_list', []): 227 functions[func['FunctionName']] = camel_dict_to_snake_dict(func) 228 return functions 229 230 return {function_name: camel_dict_to_snake_dict(lambda_info)} 231 232 233def mapping_details(client, module): 234 """ 235 Returns all lambda event source mappings. 236 237 :param client: AWS API client reference (boto3) 238 :param module: Ansible module reference 239 :return dict: 240 """ 241 242 lambda_info = dict() 243 params = dict() 244 function_name = module.params.get('function_name') 245 246 if function_name: 247 params['FunctionName'] = module.params.get('function_name') 248 249 if module.params.get('event_source_arn'): 250 params['EventSourceArn'] = module.params.get('event_source_arn') 251 252 if module.params.get('max_items'): 253 params['MaxItems'] = module.params.get('max_items') 254 255 if module.params.get('next_marker'): 256 params['Marker'] = module.params.get('next_marker') 257 258 try: 259 lambda_info.update(mappings=client.list_event_source_mappings(**params)['EventSourceMappings']) 260 except ClientError as e: 261 if e.response['Error']['Code'] == 'ResourceNotFoundException': 262 lambda_info.update(mappings=[]) 263 else: 264 module.fail_json_aws(e, msg="Trying to get source event mappings") 265 266 if function_name: 267 return {function_name: camel_dict_to_snake_dict(lambda_info)} 268 269 return camel_dict_to_snake_dict(lambda_info) 270 271 272def policy_details(client, module): 273 """ 274 Returns policy attached to a lambda function. 275 276 :param client: AWS API client reference (boto3) 277 :param module: Ansible module reference 278 :return dict: 279 """ 280 281 if module.params.get('max_items') or module.params.get('next_marker'): 282 module.fail_json(msg='Cannot specify max_items nor next_marker for query=policy.') 283 284 lambda_info = dict() 285 286 function_name = module.params.get('function_name') 287 if function_name: 288 try: 289 # get_policy returns a JSON string so must convert to dict before reassigning to its key 290 lambda_info.update(policy=json.loads(client.get_policy(FunctionName=function_name)['Policy'])) 291 except ClientError as e: 292 if e.response['Error']['Code'] == 'ResourceNotFoundException': 293 lambda_info.update(policy={}) 294 else: 295 module.fail_json_aws(e, msg="Trying to get {0} policy".format(function_name)) 296 else: 297 module.fail_json(msg='Parameter function_name required for query=policy.') 298 299 return {function_name: camel_dict_to_snake_dict(lambda_info)} 300 301 302def version_details(client, module): 303 """ 304 Returns all lambda function versions. 305 306 :param client: AWS API client reference (boto3) 307 :param module: Ansible module reference 308 :return dict: 309 """ 310 311 lambda_info = dict() 312 313 function_name = module.params.get('function_name') 314 if function_name: 315 params = dict() 316 if module.params.get('max_items'): 317 params['MaxItems'] = module.params.get('max_items') 318 319 if module.params.get('next_marker'): 320 params['Marker'] = module.params.get('next_marker') 321 322 try: 323 lambda_info.update(versions=client.list_versions_by_function(FunctionName=function_name, **params)['Versions']) 324 except ClientError as e: 325 if e.response['Error']['Code'] == 'ResourceNotFoundException': 326 lambda_info.update(versions=[]) 327 else: 328 module.fail_json_aws(e, msg="Trying to get {0} versions".format(function_name)) 329 else: 330 module.fail_json(msg='Parameter function_name required for query=versions.') 331 332 return {function_name: camel_dict_to_snake_dict(lambda_info)} 333 334 335def main(): 336 """ 337 Main entry point. 338 339 :return dict: ansible facts 340 """ 341 argument_spec = dict( 342 function_name=dict(required=False, default=None, aliases=['function', 'name']), 343 query=dict(required=False, choices=['aliases', 'all', 'config', 'mappings', 'policy', 'versions'], default='all'), 344 event_source_arn=dict(required=False, default=None) 345 ) 346 347 module = AnsibleAWSModule( 348 argument_spec=argument_spec, 349 supports_check_mode=True, 350 mutually_exclusive=[], 351 required_together=[] 352 ) 353 354 # validate function_name if present 355 function_name = module.params['function_name'] 356 if function_name: 357 if not re.search(r"^[\w\-:]+$", function_name): 358 module.fail_json( 359 msg='Function name {0} is invalid. Names must contain only alphanumeric characters and hyphens.'.format(function_name) 360 ) 361 if len(function_name) > 64: 362 module.fail_json(msg='Function name "{0}" exceeds 64 character limit'.format(function_name)) 363 364 try: 365 region, endpoint, aws_connect_kwargs = get_aws_connection_info(module, boto3=True) 366 aws_connect_kwargs.update(dict(region=region, 367 endpoint=endpoint, 368 conn_type='client', 369 resource='lambda' 370 )) 371 client = boto3_conn(module, **aws_connect_kwargs) 372 except ClientError as e: 373 module.fail_json_aws(e, "trying to set up boto connection") 374 375 this_module = sys.modules[__name__] 376 377 invocations = dict( 378 aliases='alias_details', 379 all='all_details', 380 config='config_details', 381 mappings='mapping_details', 382 policy='policy_details', 383 versions='version_details', 384 ) 385 386 this_module_function = getattr(this_module, invocations[module.params['query']]) 387 all_facts = fix_return(this_module_function(client, module)) 388 389 results = dict(function=all_facts, changed=False) 390 391 if module.check_mode: 392 results['msg'] = 'Check mode set but ignored for fact gathering only.' 393 394 module.exit_json(**results) 395 396 397if __name__ == '__main__': 398 main() 399