1#!/usr/bin/python 2# Copyright (c) 2018 Ansible Project 3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4from __future__ import absolute_import, division, print_function 5__metaclass__ = type 6 7ANSIBLE_METADATA = { 8 'metadata_version': '1.1', 9 'status': ['preview'], 10 'supported_by': 'community' 11} 12 13DOCUMENTATION = ''' 14--- 15module: ec2_launch_template 16version_added: "2.8" 17short_description: Manage EC2 launch templates 18description: 19 - Create, modify, and delete EC2 Launch Templates, which can be used to 20 create individual instances or with Autoscaling Groups. 21 - The I(ec2_instance) and I(ec2_asg) modules can, instead of specifying all 22 parameters on those tasks, be passed a Launch Template which contains 23 settings like instance size, disk type, subnet, and more. 24requirements: 25 - botocore 26 - boto3 >= 1.6.0 27extends_documentation_fragment: 28 - aws 29 - ec2 30author: 31 - Ryan Scott Brown (@ryansb) 32options: 33 template_id: 34 description: 35 - The ID for the launch template, can be used for all cases except creating a new Launch Template. 36 aliases: [id] 37 template_name: 38 description: 39 - The template name. This must be unique in the region-account combination you are using. 40 aliases: [name] 41 default_version: 42 description: 43 - Which version should be the default when users spin up new instances based on this template? By default, the latest version will be made the default. 44 default: latest 45 state: 46 description: 47 - Whether the launch template should exist or not. 48 - Deleting specific versions of a launch template is not supported at this time. 49 choices: [present, absent] 50 default: present 51 block_device_mappings: 52 description: 53 - The block device mapping. Supplying both a snapshot ID and an encryption 54 value as arguments for block-device mapping results in an error. This is 55 because only blank volumes can be encrypted on start, and these are not 56 created from a snapshot. If a snapshot is the basis for the volume, it 57 contains data by definition and its encryption status cannot be changed 58 using this action. 59 suboptions: 60 device_name: 61 description: The device name (for example, /dev/sdh or xvdh). 62 no_device: 63 description: Suppresses the specified device included in the block device mapping of the AMI. 64 virtual_name: 65 description: > 66 The virtual device name (ephemeralN). Instance store volumes are 67 numbered starting from 0. An instance type with 2 available instance 68 store volumes can specify mappings for ephemeral0 and ephemeral1. The 69 number of available instance store volumes depends on the instance 70 type. After you connect to the instance, you must mount the volume. 71 ebs: 72 description: Parameters used to automatically set up EBS volumes when the instance is launched. 73 suboptions: 74 delete_on_termination: 75 description: Indicates whether the EBS volume is deleted on instance termination. 76 type: bool 77 encrypted: 78 description: > 79 Indicates whether the EBS volume is encrypted. Encrypted volumes 80 can only be attached to instances that support Amazon EBS 81 encryption. If you are creating a volume from a snapshot, you 82 can't specify an encryption value. 83 iops: 84 description: 85 - The number of I/O operations per second (IOPS) that the volume 86 supports. For io1, this represents the number of IOPS that are 87 provisioned for the volume. For gp2, this represents the baseline 88 performance of the volume and the rate at which the volume 89 accumulates I/O credits for bursting. For more information about 90 General Purpose SSD baseline performance, I/O credits, and 91 bursting, see Amazon EBS Volume Types in the Amazon Elastic 92 Compute Cloud User Guide. 93 - > 94 Condition: This parameter is required for requests to create io1 95 volumes; it is not used in requests to create gp2, st1, sc1, or 96 standard volumes. 97 kms_key_id: 98 description: The ARN of the AWS Key Management Service (AWS KMS) CMK used for encryption. 99 snapshot_id: 100 description: The ID of the snapshot to create the volume from 101 volume_size: 102 description: 103 - The size of the volume, in GiB. 104 - "Default: If you're creating the volume from a snapshot and don't specify a volume size, the default is the snapshot size." 105 volume_type: 106 description: The volume type 107 cpu_options: 108 description: 109 - Choose CPU settings for the EC2 instances that will be created with this template. 110 - For more information, see U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html) 111 suboptions: 112 core_count: 113 description: The number of CPU cores for the instance. 114 threads_per_core: 115 description: > 116 The number of threads per CPU core. To disable Intel Hyper-Threading 117 Technology for the instance, specify a value of 1. Otherwise, specify 118 the default value of 2. 119 credit_specification: 120 description: The credit option for CPU usage of the instance. Valid for T2 or T3 instances only. 121 suboptions: 122 cpu_credits: 123 description: > 124 The credit option for CPU usage of a T2 or T3 instance. Valid values 125 are I(standard) and I(unlimited). 126 choices: [standard, unlimited] 127 disable_api_termination: 128 description: > 129 This helps protect instances from accidental termination. If set to true, 130 you can't terminate the instance using the Amazon EC2 console, CLI, or 131 API. To change this attribute to false after launch, use 132 I(ModifyInstanceAttribute). 133 type: bool 134 ebs_optimized: 135 description: > 136 Indicates whether the instance is optimized for Amazon EBS I/O. This 137 optimization provides dedicated throughput to Amazon EBS and an optimized 138 configuration stack to provide optimal Amazon EBS I/O performance. This 139 optimization isn't available with all instance types. Additional usage 140 charges apply when using an EBS-optimized instance. 141 type: bool 142 elastic_gpu_specifications: 143 description: Settings for Elastic GPU attachments. See U(https://aws.amazon.com/ec2/elastic-gpus/) for details. 144 suboptions: 145 type: 146 description: The type of Elastic GPU to attach 147 iam_instance_profile: 148 description: > 149 The name or ARN of an IAM instance profile. Requires permissions to 150 describe existing instance roles to confirm ARN is properly formed. 151 image_id: 152 description: > 153 The AMI ID to use for new instances launched with this template. This 154 value is region-dependent since AMIs are not global resources. 155 instance_initiated_shutdown_behavior: 156 description: > 157 Indicates whether an instance stops or terminates when you initiate 158 shutdown from the instance using the operating system shutdown command. 159 choices: [stop, terminate] 160 instance_market_options: 161 description: Options for alternative instance markets, currently only the spot market is supported. 162 suboptions: 163 market_type: 164 description: The market type. This should always be 'spot'. 165 spot_options: 166 description: Spot-market specific settings 167 suboptions: 168 block_duration_minutes: 169 description: > 170 The required duration for the Spot Instances (also known as Spot 171 blocks), in minutes. This value must be a multiple of 60 (60, 172 120, 180, 240, 300, or 360). 173 instance_interruption_behavior: 174 description: The behavior when a Spot Instance is interrupted. The default is I(terminate) 175 choices: [hibernate, stop, terminate] 176 max_price: 177 description: The highest hourly price you're willing to pay for this Spot Instance. 178 spot_instance_type: 179 description: The request type to send. 180 choices: [one-time, persistent] 181 type: dict 182 instance_type: 183 description: > 184 The instance type, such as I(c5.2xlarge). For a full list of instance types, see 185 http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html 186 kernel_id: 187 description: > 188 The ID of the kernel. We recommend that you use PV-GRUB instead of 189 kernels and RAM disks. For more information, see 190 U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedkernels.html) 191 key_name: 192 description: 193 - The name of the key pair. You can create a key pair using 194 I(CreateKeyPair) or I(ImportKeyPair). 195 - If you do not specify a key pair, you can't connect to the instance 196 unless you choose an AMI that is configured to allow users another way to 197 log in. 198 monitoring: 199 description: Settings for instance monitoring 200 suboptions: 201 enabled: 202 type: bool 203 description: Whether to turn on detailed monitoring for new instances. This will incur extra charges. 204 network_interfaces: 205 description: One or more network interfaces. 206 suboptions: 207 associate_public_ip_address: 208 description: Associates a public IPv4 address with eth0 for a new network interface. 209 type: bool 210 delete_on_termination: 211 description: Indicates whether the network interface is deleted when the instance is terminated. 212 type: bool 213 description: 214 description: A description for the network interface. 215 device_index: 216 description: The device index for the network interface attachment. 217 groups: 218 description: List of security group IDs to include on this instance 219 ipv6_address_count: 220 description: > 221 The number of IPv6 addresses to assign to a network interface. Amazon 222 EC2 automatically selects the IPv6 addresses from the subnet range. 223 You can't use this option if specifying the I(ipv6_addresses) option. 224 ipv6_addresses: 225 description: > 226 A list of one or more specific IPv6 addresses from the IPv6 CIDR 227 block range of your subnet. You can't use this option if you're 228 specifying the I(ipv6_address_count) option. 229 network_interface_id: 230 description: The eni ID of a network interface to attach. 231 private_ip_address: 232 description: The primary private IPv4 address of the network interface. 233 private_ip_addresses: 234 description: One or more private IPv4 addresses. 235 suboptions: 236 primary: 237 description: > 238 Indicates whether the private IPv4 address is the primary private 239 IPv4 address. Only one IPv4 address can be designated as primary. 240 private_ip_address: 241 description: The primary private IPv4 address of the network interface. 242 subnet_id: 243 description: The ID of the subnet for the network interface. 244 secondary_private_ip_address_count: 245 description: The number of secondary private IPv4 addresses to assign to a network interface. 246 placement: 247 description: The placement group settings for the instance. 248 suboptions: 249 affinity: 250 description: The affinity setting for an instance on a Dedicated Host. 251 availability_zone: 252 description: The Availability Zone for the instance. 253 group_name: 254 description: The name of the placement group for the instance. 255 host_id: 256 description: The ID of the Dedicated Host for the instance. 257 tenancy: 258 description: > 259 The tenancy of the instance (if the instance is running in a VPC). An 260 instance with a tenancy of dedicated runs on single-tenant hardware. 261 ram_disk_id: 262 description: > 263 The ID of the RAM disk to launch the instance with. We recommend that you 264 use PV-GRUB instead of kernels and RAM disks. For more information, see 265 U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedkernels.html) 266 security_group_ids: 267 description: A list of security group IDs (VPC or EC2-Classic) that the new instances will be added to. 268 type: list 269 security_groups: 270 description: A list of security group names (VPC or EC2-Classic) that the new instances will be added to. 271 type: list 272 tags: 273 type: dict 274 description: 275 - A set of key-value pairs to be applied to resources when this Launch Template is used. 276 - "Tag key constraints: Tag keys are case-sensitive and accept a maximum of 127 Unicode characters. May not begin with I(aws:)" 277 - "Tag value constraints: Tag values are case-sensitive and accept a maximum of 255 Unicode characters." 278 user_data: 279 description: > 280 The Base64-encoded user data to make available to the instance. For more information, see the Linux 281 U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) and Windows 282 U(http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-instance-metadata.html#instancedata-add-user-data) 283 documentation on user-data. 284''' 285 286EXAMPLES = ''' 287- name: Create an ec2 launch template 288 ec2_launch_template: 289 name: "my_template" 290 image_id: "ami-04b762b4289fba92b" 291 key_name: my_ssh_key 292 instance_type: t2.micro 293 iam_instance_profile: myTestProfile 294 disable_api_termination: true 295 296- name: > 297 Create a new version of an existing ec2 launch template with a different instance type, 298 while leaving an older version as the default version 299 ec2_launch_template: 300 name: "my_template" 301 default_version: 1 302 instance_type: c5.4xlarge 303 304- name: Delete an ec2 launch template 305 ec2_launch_template: 306 name: "my_template" 307 state: absent 308 309# This module does not yet allow deletion of specific versions of launch templates 310''' 311 312RETURN = ''' 313latest_version: 314 description: Latest available version of the launch template 315 returned: when state=present 316 type: int 317default_version: 318 description: The version that will be used if only the template name is specified. Often this is the same as the latest version, but not always. 319 returned: when state=present 320 type: int 321''' 322import re 323from uuid import uuid4 324 325from ansible.module_utils._text import to_text 326from ansible.module_utils.aws.core import AnsibleAWSModule, is_boto3_error_code, get_boto3_client_method_parameters 327from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict, snake_dict_to_camel_dict 328from ansible.module_utils.ec2 import ansible_dict_to_boto3_tag_list, AWSRetry, boto3_tag_list_to_ansible_dict, ansible_dict_to_boto3_tag_list 329 330try: 331 from botocore.exceptions import ClientError, BotoCoreError, WaiterError 332except ImportError: 333 pass # caught by AnsibleAWSModule 334 335 336def determine_iam_role(module, name_or_arn): 337 if re.match(r'^arn:aws:iam::\d+:instance-profile/[\w+=/,.@-]+$', name_or_arn): 338 return name_or_arn 339 iam = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) 340 try: 341 role = iam.get_instance_profile(InstanceProfileName=name_or_arn, aws_retry=True) 342 return {'arn': role['InstanceProfile']['Arn']} 343 except is_boto3_error_code('NoSuchEntity') as e: 344 module.fail_json_aws(e, msg="Could not find instance_role {0}".format(name_or_arn)) 345 except (BotoCoreError, ClientError) as e: # pylint: disable=duplicate-except 346 module.fail_json_aws(e, msg="An error occurred while searching for instance_role {0}. Please try supplying the full ARN.".format(name_or_arn)) 347 348 349def existing_templates(module): 350 ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) 351 matches = None 352 try: 353 if module.params.get('template_id'): 354 matches = ec2.describe_launch_templates(LaunchTemplateIds=[module.params.get('template_id')]) 355 elif module.params.get('template_name'): 356 matches = ec2.describe_launch_templates(LaunchTemplateNames=[module.params.get('template_name')]) 357 except is_boto3_error_code('InvalidLaunchTemplateName.NotFoundException') as e: 358 # no named template was found, return nothing/empty versions 359 return None, [] 360 except is_boto3_error_code('InvalidLaunchTemplateId.Malformed') as e: # pylint: disable=duplicate-except 361 module.fail_json_aws(e, msg='Launch template with ID {0} is not a valid ID. It should start with `lt-....`'.format( 362 module.params.get('launch_template_id'))) 363 except is_boto3_error_code('InvalidLaunchTemplateId.NotFoundException') as e: # pylint: disable=duplicate-except 364 module.fail_json_aws( 365 e, msg='Launch template with ID {0} could not be found, please supply a name ' 366 'instead so that a new template can be created'.format(module.params.get('launch_template_id'))) 367 except (ClientError, BotoCoreError, WaiterError) as e: # pylint: disable=duplicate-except 368 module.fail_json_aws(e, msg='Could not check existing launch templates. This may be an IAM permission problem.') 369 else: 370 template = matches['LaunchTemplates'][0] 371 template_id, template_version, template_default = template['LaunchTemplateId'], template['LatestVersionNumber'], template['DefaultVersionNumber'] 372 try: 373 return template, ec2.describe_launch_template_versions(LaunchTemplateId=template_id)['LaunchTemplateVersions'] 374 except (ClientError, BotoCoreError, WaiterError) as e: 375 module.fail_json_aws(e, msg='Could not find launch template versions for {0} (ID: {1}).'.format(template['LaunchTemplateName'], template_id)) 376 377 378def params_to_launch_data(module, template_params): 379 if template_params.get('tags'): 380 template_params['tag_specifications'] = [ 381 { 382 'resource_type': r_type, 383 'tags': [ 384 {'Key': k, 'Value': v} for k, v 385 in template_params['tags'].items() 386 ] 387 } 388 for r_type in ('instance', 'volume') 389 ] 390 del template_params['tags'] 391 if module.params.get('iam_instance_profile'): 392 template_params['iam_instance_profile'] = determine_iam_role(module, module.params['iam_instance_profile']) 393 params = snake_dict_to_camel_dict( 394 dict((k, v) for k, v in template_params.items() if v is not None), 395 capitalize_first=True, 396 ) 397 return params 398 399 400def delete_template(module): 401 ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) 402 template, template_versions = existing_templates(module) 403 deleted_versions = [] 404 if template or template_versions: 405 non_default_versions = [to_text(t['VersionNumber']) for t in template_versions if not t['DefaultVersion']] 406 if non_default_versions: 407 try: 408 v_resp = ec2.delete_launch_template_versions( 409 LaunchTemplateId=template['LaunchTemplateId'], 410 Versions=non_default_versions, 411 ) 412 if v_resp['UnsuccessfullyDeletedLaunchTemplateVersions']: 413 module.warn('Failed to delete template versions {0} on launch template {1}'.format( 414 v_resp['UnsuccessfullyDeletedLaunchTemplateVersions'], 415 template['LaunchTemplateId'], 416 )) 417 deleted_versions = [camel_dict_to_snake_dict(v) for v in v_resp['SuccessfullyDeletedLaunchTemplateVersions']] 418 except (ClientError, BotoCoreError) as e: 419 module.fail_json_aws(e, msg="Could not delete existing versions of the launch template {0}".format(template['LaunchTemplateId'])) 420 try: 421 resp = ec2.delete_launch_template( 422 LaunchTemplateId=template['LaunchTemplateId'], 423 ) 424 except (ClientError, BotoCoreError) as e: 425 module.fail_json_aws(e, msg="Could not delete launch template {0}".format(template['LaunchTemplateId'])) 426 return { 427 'deleted_versions': deleted_versions, 428 'deleted_template': camel_dict_to_snake_dict(resp['LaunchTemplate']), 429 'changed': True, 430 } 431 else: 432 return {'changed': False} 433 434 435def create_or_update(module, template_options): 436 ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) 437 template, template_versions = existing_templates(module) 438 out = {} 439 lt_data = params_to_launch_data(module, dict((k, v) for k, v in module.params.items() if k in template_options)) 440 if not (template or template_versions): 441 # create a full new one 442 try: 443 resp = ec2.create_launch_template( 444 LaunchTemplateName=module.params['template_name'], 445 LaunchTemplateData=lt_data, 446 ClientToken=uuid4().hex, 447 aws_retry=True, 448 ) 449 except (ClientError, BotoCoreError) as e: 450 module.fail_json_aws(e, msg="Couldn't create launch template") 451 template, template_versions = existing_templates(module) 452 out['changed'] = True 453 elif template and template_versions: 454 most_recent = sorted(template_versions, key=lambda x: x['VersionNumber'])[-1] 455 if lt_data == most_recent['LaunchTemplateData']: 456 out['changed'] = False 457 return out 458 try: 459 resp = ec2.create_launch_template_version( 460 LaunchTemplateId=template['LaunchTemplateId'], 461 LaunchTemplateData=lt_data, 462 ClientToken=uuid4().hex, 463 aws_retry=True, 464 ) 465 if module.params.get('default_version') in (None, ''): 466 # no need to do anything, leave the existing version as default 467 pass 468 elif module.params.get('default_version') == 'latest': 469 set_default = ec2.modify_launch_template( 470 LaunchTemplateId=template['LaunchTemplateId'], 471 DefaultVersion=to_text(resp['LaunchTemplateVersion']['VersionNumber']), 472 ClientToken=uuid4().hex, 473 aws_retry=True, 474 ) 475 else: 476 try: 477 int(module.params.get('default_version')) 478 except ValueError: 479 module.fail_json(msg='default_version param was not a valid integer, got "{0}"'.format(module.params.get('default_version'))) 480 set_default = ec2.modify_launch_template( 481 LaunchTemplateId=template['LaunchTemplateId'], 482 DefaultVersion=to_text(int(module.params.get('default_version'))), 483 ClientToken=uuid4().hex, 484 aws_retry=True, 485 ) 486 except (ClientError, BotoCoreError) as e: 487 module.fail_json_aws(e, msg="Couldn't create subsequent launch template version") 488 template, template_versions = existing_templates(module) 489 out['changed'] = True 490 return out 491 492 493def format_module_output(module): 494 output = {} 495 template, template_versions = existing_templates(module) 496 template = camel_dict_to_snake_dict(template) 497 template_versions = [camel_dict_to_snake_dict(v) for v in template_versions] 498 for v in template_versions: 499 for ts in (v['launch_template_data'].get('tag_specifications') or []): 500 ts['tags'] = boto3_tag_list_to_ansible_dict(ts.pop('tags')) 501 output.update(dict(template=template, versions=template_versions)) 502 output['default_template'] = [ 503 v for v in template_versions 504 if v.get('default_version') 505 ][0] 506 output['latest_template'] = [ 507 v for v in template_versions 508 if ( 509 v.get('version_number') and 510 int(v['version_number']) == int(template['latest_version_number']) 511 ) 512 ][0] 513 return output 514 515 516def main(): 517 template_options = dict( 518 block_device_mappings=dict( 519 type='list', 520 options=dict( 521 device_name=dict(), 522 ebs=dict( 523 type='dict', 524 options=dict( 525 delete_on_termination=dict(type='bool'), 526 encrypted=dict(type='bool'), 527 iops=dict(type='int'), 528 kms_key_id=dict(), 529 snapshot_id=dict(), 530 volume_size=dict(type='int'), 531 volume_type=dict(), 532 ), 533 ), 534 no_device=dict(), 535 virtual_name=dict(), 536 ), 537 ), 538 cpu_options=dict( 539 type='dict', 540 options=dict( 541 core_count=dict(type='int'), 542 threads_per_core=dict(type='int'), 543 ), 544 ), 545 credit_specification=dict( 546 dict(type='dict'), 547 options=dict( 548 cpu_credits=dict(), 549 ), 550 ), 551 disable_api_termination=dict(type='bool'), 552 ebs_optimized=dict(type='bool'), 553 elastic_gpu_specifications=dict( 554 options=dict(type=dict()), 555 type='list', 556 ), 557 iam_instance_profile=dict(), 558 image_id=dict(), 559 instance_initiated_shutdown_behavior=dict(choices=['stop', 'terminate']), 560 instance_market_options=dict( 561 type='dict', 562 options=dict( 563 market_type=dict(), 564 spot_options=dict( 565 type='dict', 566 options=dict( 567 block_duration_minutes=dict(type='int'), 568 instance_interruption_behavior=dict(choices=['hibernate', 'stop', 'terminate']), 569 max_price=dict(), 570 spot_instance_type=dict(choices=['one-time', 'persistent']), 571 ), 572 ), 573 ), 574 ), 575 instance_type=dict(), 576 kernel_id=dict(), 577 key_name=dict(), 578 monitoring=dict( 579 type='dict', 580 options=dict( 581 enabled=dict(type='bool') 582 ), 583 ), 584 network_interfaces=dict( 585 type='list', 586 options=dict( 587 associate_public_ip_address=dict(type='bool'), 588 delete_on_termination=dict(type='bool'), 589 description=dict(), 590 device_index=dict(type='int'), 591 groups=dict(type='list'), 592 ipv6_address_count=dict(type='int'), 593 ipv6_addresses=dict(type='list'), 594 network_interface_id=dict(), 595 private_ip_address=dict(), 596 subnet_id=dict(), 597 ), 598 ), 599 placement=dict( 600 options=dict( 601 affinity=dict(), 602 availability_zone=dict(), 603 group_name=dict(), 604 host_id=dict(), 605 tenancy=dict(), 606 ), 607 type='dict', 608 ), 609 ram_disk_id=dict(), 610 security_group_ids=dict(type='list'), 611 security_groups=dict(type='list'), 612 tags=dict(type='dict'), 613 user_data=dict(), 614 ) 615 616 arg_spec = dict( 617 state=dict(choices=['present', 'absent'], default='present'), 618 template_name=dict(aliases=['name']), 619 template_id=dict(aliases=['id']), 620 default_version=dict(default='latest'), 621 ) 622 623 arg_spec.update(template_options) 624 625 module = AnsibleAWSModule( 626 argument_spec=arg_spec, 627 required_one_of=[ 628 ('template_name', 'template_id') 629 ], 630 supports_check_mode=True 631 ) 632 633 if not module.boto3_at_least('1.6.0'): 634 module.fail_json(msg="ec2_launch_template requires boto3 >= 1.6.0") 635 636 for interface in (module.params.get('network_interfaces') or []): 637 if interface.get('ipv6_addresses'): 638 interface['ipv6_addresses'] = [{'ipv6_address': x} for x in interface['ipv6_addresses']] 639 640 if module.params.get('state') == 'present': 641 out = create_or_update(module, template_options) 642 out.update(format_module_output(module)) 643 elif module.params.get('state') == 'absent': 644 out = delete_template(module) 645 else: 646 module.fail_json(msg='Unsupported value "{0}" for `state` parameter'.format(module.params.get('state'))) 647 648 module.exit_json(**out) 649 650 651if __name__ == '__main__': 652 main() 653