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