diff --git a/changelogs/fragments/20240110-ec2_launch_template-refactor.yml b/changelogs/fragments/20240110-ec2_launch_template-refactor.yml new file mode 100644 index 00000000000..d2e7293fa9a --- /dev/null +++ b/changelogs/fragments/20240110-ec2_launch_template-refactor.yml @@ -0,0 +1,9 @@ +--- +breaking_changes: + - ec2_launch_template - Tags defined using option ``tags`` are now applied to the launch template resources not the resource created using this launch template (https://github.com/ansible-collections/community.aws/issues/176). +minor_changes: + - ec2_launch_template - Refactor module to use shared code from ``amazon.aws.plugins.module_utils.ec2`` and update ``RETURN`` block (https://github.com/ansible-collections/community.aws/pull/2164). + - ec2_launch_template - Add the possibility to delete specific versions of a launch template using ``versions_to_delete`` (https://github.com/ansible-collections/community.aws/pull/2164). + - ec2_launch_template - Add suboption ``throughput`` to ``block_device_mappings`` argument (https://github.com/ansible-collections/community.aws/issues/1944). + - ec2_launch_template - Add option ``tag_specifications`` to define tags to be applied to the resources created with the launch template (https://github.com/ansible-collections/community.aws/issues/176). + - ec2_launch_template - Add support ``purge_tags`` parameter (https://github.com/ansible-collections/community.aws/issues/176). diff --git a/plugins/modules/ec2_launch_template.py b/plugins/modules/ec2_launch_template.py index 9fd32711f91..aa055535a9a 100644 --- a/plugins/modules/ec2_launch_template.py +++ b/plugins/modules/ec2_launch_template.py @@ -21,6 +21,7 @@ template_id: description: - The ID for the launch template, can be used for all cases except creating a new Launch Template. + - At least one of O(template_id) and O(template_name) must be specified. aliases: [id] type: str template_name: @@ -31,6 +32,7 @@ nothing happens. - If a launch template with the specified name already exists and the configuration has changed, a new version of the launch template is created. + - At least one of O(template_id) and O(template_name) must be specified. aliases: [name] type: str default_version: @@ -44,6 +46,15 @@ - The description of a launch template version. default: "" type: str + versions_to_delete: + description: + - The version numbers of a launch template versions to delete. + - Use O(default_version) to specify a new default version when deleting the current default version. + - By default, the latest version will be made the default. + - Ignored when O(state=present). + type: list + elements: int + version_added: 9.0.0 state: description: - Whether the launch template should exist or not. @@ -63,7 +74,7 @@ elements: dict suboptions: device_name: - description: The device name (for example, /dev/sdh or xvdh). + description: The device name (for example, V(/dev/sdh) or V(xvdh)). type: str no_device: description: Suppresses the specified device included in the block device mapping of the AMI. @@ -119,6 +130,12 @@ volume_type: description: The volume type type: str + throughput: + description: > + The throughput to provision for a gp3 volume, with a maximum of 1,000 MiB/s. + Valid Range - Minimum value of V(125). Maximum value of V(1000). + type: int + version_added: 9.0.0 cpu_options: description: - Choose CPU settings for the EC2 instances that will be created with this template. @@ -131,24 +148,21 @@ threads_per_core: description: > The number of threads per CPU core. To disable Intel Hyper-Threading - Technology for the instance, specify a value of 1. Otherwise, specify - the default value of 2. + Technology for the instance, specify a value of V(1). Otherwise, specify + the default value of V(2). type: int credit_specification: description: The credit option for CPU usage of the instance. Valid for T2 or T3 instances only. type: dict suboptions: cpu_credits: - description: > - The credit option for CPU usage of a T2 or T3 instance. Valid values - are C(standard) and C(unlimited). + description: + - The credit option for CPU usage of a T2 or T3 instance. Valid values are C(standard) and C(unlimited). type: str disable_api_termination: - description: > - This helps protect instances from accidental termination. If set to true, - you can't terminate the instance using the Amazon EC2 console, CLI, or - API. To change this attribute to false after launch, use - I(ModifyInstanceAttribute). + description: + - This helps protect instances from accidental termination. + - If set to V(true), you can't terminate the instance using the Amazon EC2 console, CLI, or API. type: bool ebs_optimized: description: > @@ -187,20 +201,19 @@ type: dict suboptions: market_type: - description: The market type. This should always be 'spot'. + description: The market type. This should always be V(spot). type: str spot_options: description: Spot-market specific settings. type: dict suboptions: block_duration_minutes: - description: > - The required duration for the Spot Instances (also known as Spot - blocks), in minutes. This value must be a multiple of 60 (60, - 120, 180, 240, 300, or 360). + description: + - The required duration for the Spot Instances (also known as Spot blocks), in minutes. + - This value must be a multiple of V(60) (V(60), V(120), V(180), V(240), V(300), or V(360)). type: int instance_interruption_behavior: - description: The behavior when a Spot Instance is interrupted. The default is C(terminate). + description: The behavior when a Spot Instance is interrupted. The default is V(terminate). choices: [hibernate, stop, terminate] type: str max_price: @@ -211,22 +224,21 @@ choices: [one-time, persistent] type: str instance_type: - description: > - The instance type, such as C(c5.2xlarge). For a full list of instance types, see - U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html). + description: + - The instance type, such as V(c5.2xlarge). For a full list of instance types, see + U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html). type: str kernel_id: - description: > - The ID of the kernel. We recommend that you use PV-GRUB instead of - kernels and RAM disks. For more information, see - U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedkernels.html) + description: + - The ID of the kernel. + - We recommend that you use PV-GRUB instead of kernels and RAM disks. For more information, see + U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedkernels.html) type: str key_name: description: - The name of the key pair. You can create a key pair using M(amazon.aws.ec2_key). - If you do not specify a key pair, you can't connect to the instance - unless you choose an AMI that is configured to allow users another way to - log in. + unless you choose an AMI that is configured to allow users another way to log in. type: str monitoring: description: Settings for instance monitoring. @@ -257,16 +269,15 @@ type: list elements: str ipv6_address_count: - description: > - The number of IPv6 addresses to assign to a network interface. Amazon - EC2 automatically selects the IPv6 addresses from the subnet range. - You can't use this option if specifying the I(ipv6_addresses) option. + description: + - The number of IPv6 addresses to assign to a network interface. + - Amazon EC2 automatically selects the IPv6 addresses from the subnet range. + - You can't use this option if specifying the O(network_interfaces.ipv6_addresses) option. type: int ipv6_addresses: - description: > - A list of one or more specific IPv6 addresses from the IPv6 CIDR - block range of your subnet. You can't use this option if you're - specifying the I(ipv6_address_count) option. + description: + - A list of one or more specific IPv6 addresses from the IPv6 CIDR block range of your subnet. + - You can't use this option if you're specifying the O(network_interfaces.ipv6_address_count) option. type: list elements: str network_interface_id: @@ -312,24 +323,41 @@ security_groups: description: > A list of security group names (Default VPC or EC2-Classic) that the new instances will be added to. - For any VPC other than Default, you must use I(security_group_ids). + For any VPC other than Default, you must use O(security_group_ids). type: list elements: str source_version: - description: > - The version number of the launch template version on which to base the new version. - The new version inherits the same launch parameters as the source version, except for parameters that you explicity specify. - Snapshots applied to the block device mapping are ignored when creating a new version unless they are explicitly included. + description: + - The version number of the launch template version on which to base the new version. + - The new version inherits the same launch parameters as the source version, except for parameters that you explicity specify. + - Snapshots applied to the O(block_device_mappings) are ignored when creating a new version unless they are explicitly included. type: str default: latest version_added: 4.1.0 - tags: - type: dict + tag_specifications: description: - - A set of key-value pairs to be applied to resources when this Launch Template is used. - - "Tag key constraints: Tag keys are case-sensitive and accept a maximum of 127 Unicode characters. May not begin with I(aws:)" - - "Tag value constraints: Tag values are case-sensitive and accept a maximum of 255 Unicode characters." - aliases: ['resource_tags'] + - The tags to apply to the resources when this Launch template is used. + type: list + elements: dict + version_added: 9.0.0 + suboptions: + resource_type: + description: + - The type of resource to tag. + - If the instance does not include the resource type that you specify, the instance launch fails. + type: str + default: instance + choices: + - instance + - volume + - network-interface + - spot-instances-request + tags: + description: + - A set of key-value pairs to be applied to the resource type. + - "Tag key constraints: Tag keys are case-sensitive and accept a maximum of 127 Unicode characters. May not begin with I(aws:)" + - "Tag value constraints: Tag values are case-sensitive and accept a maximum of 255 Unicode characters." + type: dict user_data: description: > The Base64-encoded user data to make available to the instance. For more information, see the Linux @@ -347,40 +375,43 @@ suboptions: http_endpoint: type: str - description: > - This parameter enables or disables the HTTP metadata endpoint on your instances. + description: This parameter enables or disables the HTTP metadata endpoint on your instances. choices: [enabled, disabled] default: 'enabled' http_put_response_hop_limit: type: int - description: > - The desired HTTP PUT response hop limit for instance metadata requests. - The larger the number, the further instance metadata requests can travel. + description: + - The desired HTTP PUT response hop limit for instance metadata requests. + - The larger the number, the further instance metadata requests can travel. default: 1 http_tokens: type: str - description: > - The state of token usage for your instance metadata requests. + description: The state of token usage for your instance metadata requests. choices: [optional, required] default: 'optional' http_protocol_ipv6: version_added: 3.1.0 type: str - description: > - - Wether the instance metadata endpoint is available via IPv6 (C(enabled)) or not (C(disabled)). + description: + - Whether the instance metadata endpoint is available via IPv6. choices: [enabled, disabled] default: 'disabled' instance_metadata_tags: version_added: 3.1.0 type: str description: - - Wether the instance tags are availble (C(enabled)) via metadata endpoint or not (C(disabled)). + - Whether the instance tags are availble (V(enabled)) via metadata endpoint or not (V(disabled)). choices: [enabled, disabled] default: 'disabled' +notes: + - The O(tags) option used has been in release 9.0.0 to be applied to the launch template resource instead of launch template resource. + - Use O(tag_specifications) to define tags to be applied to resources when this Launch Template is used. + - Support for O(purge_tags) was added in release 9.0.0. extends_documentation_fragment: - amazon.aws.common.modules - amazon.aws.region.modules - amazon.aws.boto3 +- amazon.aws.tags """ EXAMPLES = r""" @@ -406,122 +437,751 @@ name: "my_template" state: absent -# This module does not yet allow deletion of specific versions of launch templates +- name: Delete a specific version of an ec2 launch template + community.aws.ec2_launch_template: + name: "my_template" + versions_to_delete: + - 2 + state: absent + +- name: Delete a specific version of an ec2 launch template and change the default version + community.aws.ec2_launch_template: + name: "my_template" + versions_to_delete: + - 1 + default_version: 2 + state: absent + +- name: Create an ec2 launch template with specific tags + community.aws.ec2_launch_template: + name: "my_template" + image_id: "ami-04b762b4289fba92b" + instance_type: t2.micro + disable_api_termination: true + tags: + Some: tag + Another: tag + +- name: Create an ec2 launch template with different tag for volume and instance + community.aws.ec2_launch_template: + name: "my_template" + image_id: "ami-04b762b4289fba92b" + instance_type: t2.micro + block_device_mappings: + - device_name: /dev/sdb + ebs: + volume_size: 20 + delete_on_termination: true + volume_type: standard + tag_specifications: + - resource_type: instance + tags: + OsType: Linux + - resource_type: volume + tags: + foo: bar """ RETURN = r""" latest_version: - description: Latest available version of the launch template - returned: when state=present + description: The latest available version number of the launch template. + returned: when RV(latest_template) has a version number. type: int default_version: 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. - returned: when state=present + returned: when RV(default_template) has a version number. type: int +template: + description: Latest available version of the launch template. + returned: when O(state=present) + type: complex + contains: + launch_template_id: + description: The ID of the launch template. + type: str + returned: always + launch_template_name: + description: The name of the launch template. + type: str + returned: always + create_time: + description: The time launch template was created. + type: str + returned: always + created_by: + description: The principal that created the launch template. + type: str + returned: always + default_version_number: + description: The version number of the default version of the launch template. + type: int + returned: always + latest_version_number: + description: The version number of the latest version of the launch template. + type: int + returned: always + tags: + description: A dictionary of tags assigned to image. + returned: when AMI is created or already exists + type: dict + sample: { + "Env": "devel", + "Name": "nat-server" + } +versions: + description: All available versions of the launch template. + returned: when O(state=present) + type: list + elements: dict + contains: + launch_template_id: + description: The ID of the launch template. + type: str + returned: always + launch_template_name: + description: The name of the launch template. + type: str + returned: always + create_time: + description: The time the version was created. + type: str + returned: always + created_by: + description: The principal that created the version. + type: str + returned: always + default_version: + description: Indicates whether the version is the default version. + type: bool + returned: always + version_number: + description: The version number. + type: int + returned: always + version_description: + description: The description for the version. + type: str + returned: always + launch_template_data: + description: Information about the launch template. + returned: always + type: dict + contains: + kernel_id: + description: + - The ID of the kernel. + returned: if applicable + type: str + image_id: + description: The ID of the AMI or a Systems Manager parameter. + type: str + returned: if applicable + instance_type: + description: The instance type. + type: str + returned: if applicable + key_name: + description: The name of the key pair. + type: str + returned: if applicable + monitoring: + description: The monitoring for the instance. + type: dict + returned: if applicable + contains: + enabled: + description: Indicates whether detailed monitoring is enabled. Otherwise, basic monitoring is enabled. + type: bool + returned: always + placement: + description: The placement of the instance. + type: dict + returned: if applicable + contains: + availability_zone: + description: The Availability Zone of the instance. + type: str + returned: if applicable + affinity: + description: The affinity setting for the instance on the Dedicated Host. + type: str + returned: if applicable + group_name: + description: The name of the placement group for the instance. + type: str + returned: if applicable + host_id: + description: The ID of the Dedicated Host for the instance. + type: str + returned: if applicable + tenancy: + description: The tenancy of the instance. + type: str + returned: if applicable + host_resource_group_arn: + description: The ARN of the host resource group in which to launch the instances. + type: str + returned: if applicable + partition_number: + description: The number of the partition the instance should launch in. + type: int + returned: if applicable + group_id: + description: The Group ID of the placement group. + type: str + returned: if applicable + ebs_optimized: + description: + - Indicates whether the instance is optimized for Amazon EBS I/O. + type: bool + returned: always + iam_instance_profile: + description: + - The IAM instance profile. + type: dict + returned: if application + contains: + arn: + description: The Amazon Resource Name (ARN) of the instance profile. + type: str + returned: always + name: + description: The name of the instance profile. + type: str + returned: always + block_device_mappings: + description: The block device mappings. + type: list + elements: dict + returned: if applicable + contains: + device_name: + description: The device name. + type: str + returned: always + virtual_name: + description: The virtual device name. + type: str + returned: always + ebs: + description: Information about the block device for an EBS volume. + type: str + returned: if applicable + contains: + encrypted: + description: Indicates whether the EBS volume is encrypted. + type: bool + returned: always + delete_on_termination: + description: Indicates whether the EBS volume is deleted on instance termination. + type: bool + returned: always + iops: + description: The number of I/O operations per second (IOPS) that the volume supports. + type: int + returned: always + kms_key_id: + description: The ARN of the Key Management Service (KMS) CMK used for encryption. + type: int + returned: always + snapshot_id: + description: The ID of the snapshot. + type: str + returned: always + volume_size: + description: The size of the volume, in GiB. + type: int + returned: always + volume_type: + description: The volume type. + type: str + returned: always + throughput: + description: The throughput that the volume supports, in MiB/s. + type: int + returned: always + no_device: + description: To omit the device from the block device mapping, specify an empty string. + type: str + network_interfaces: + description: The network interfaces. + type: list + elements: dict + returned: if applicable + contains: + associate_carrier_ip_address: + description: Indicates whether to associate a Carrier IP address with eth0 for a new network interface. + type: bool + returned: always + associate_public_ip_address: + description: Indicates whether to associate a public IPv4 address with eth0 for a new network interface. + type: bool + returned: always + delete_on_termination: + description: Indicates whether the network interface is deleted when the instance is terminated. + type: bool + returned: always + description: + description: A description for the network interface. + type: str + returned: always + device_index: + description: The device index for the network interface attachment. + type: int + returned: always + groups: + description: The IDs of one or more security groups. + type: list + elements: str + returned: if applicable + interface_type: + description: The type of network interface. + type: str + returned: always + ipv6_address_count: + description: The number of IPv6 addresses for the network interface. + type: int + returned: if applicable + ipv6_addresses: + description: The IPv6 addresses for the network interface. + returned: if applicable + type: list + elements: dict + contains: + ipv6_address: + description: The IPv6 address. + type: str + returned: always + is_primary_ipv6: + description: Determines if an IPv6 address associated with a network interface is the primary IPv6 address. + type: bool + returned: always + network_interface_id: + description: The ID of the network interface. + type: str + returned: always + private_ip_address: + description: The primary private IPv4 address of the network interface. + type: str + returned: if applicable + private_ip_addresses: + description: A list of private IPv4 addresses. + type: list + elements: str + returned: if applicable + contains: + primary: + description: Indicates whether the private IPv4 address is the primary private IPv4 address. + type: bool + returned: always + private_ip_address: + description: The private IPv4 address. + type: bool + returned: always + secondary_private_ip_address_count: + description: The number of secondary private IPv4 addresses for the network interface. + type: int + returned: if applicable + subnet_id: + description: The ID of the subnet for the network interface. + type: str + returned: always + network_card_index: + description: The index of the network card. + type: int + returned: if applicable + ipv4_prefixes: + description: A list of IPv4 prefixes assigned to the network interface. + type: list + elements: dict + returned: if applicable + contains: + ipv4_prefix: + description: The IPv4 delegated prefixes assigned to the network interface. + type: str + returned: always + ipv4_prefix_count: + description: The number of IPv4 prefixes that Amazon Web Services automatically assigned to the network interface. + type: int + returned: if applicable + ipv6_prefixes: + description: A list of IPv6 prefixes assigned to the network interface. + type: list + elements: dict + returned: if applicable + contains: + ipv6_prefix: + description: The IPv6 delegated prefixes assigned to the network interface. + type: str + returned: always + ipv6_prefix_count: + description: The number of IPv6 prefixes that Amazon Web Services automatically assigned to the network interface. + type: int + returned: if applicable + primary_ipv6: + description: The primary IPv6 address of the network interface. + type: str + returned: if applicable + ena_srd_specification: + description: Contains the ENA Express settings for instances launched from your launch template. + type: dict + returned: if applicable + contains: + ena_srd_enabled: + description: Indicates whether ENA Express is enabled for the network interface. + type: bool + returned: always + ena_srd_udp_specification: + description: Configures ENA Express for UDP network traffic. + type: dict + returned: always + contains: + ena_srd_udp_enabled: + description: Indicates whether UDP traffic to and from the instance uses ENA Express. + type: bool + returned: always + connection_tracking_specification: + description: + - A security group connection tracking specification that enables you to set the timeout + for connection tracking on an Elastic network interface. + type: dict + returned: if applicable + contains: + tcp_established_timeout: + description: Timeout (in seconds) for idle TCP connections in an established state. + type: int + returned: always + udp_timeout: + description: + - Timeout (in seconds) for idle UDP flows that have seen traffic only in a single direction + or a single request-response transaction. + type: int + returned: always + udp_stream_timeout: + description: + - Timeout (in seconds) for idle UDP flows classified as streams which have seen more + than one request-response transaction. + type: int + returned: always + ram_disk_id: + description: The ID of the RAM disk, if applicable. + type: str + returned: if applicable + disable_api_termination: + description: If set to true, indicates that the instance cannot be terminated using the Amazon EC2 console, command line tool, or API. + type: bool + returned: if applicable + instance_initiated_shutdown_behavior: + description: Indicates whether an instance stops or terminates when you initiate shutdown from the instance. + type: str + returned: if applicable + user_data: + description: The user data for the instance. + type: str + returned: if applicable + tag_specifications: + description: The tags that are applied to the resources that are created during instance launch. + type: list + elements: dict + returned: if applicable + contains: + resource_type: + description: The type of resource to tag. + type: str + returned: always + tags: + description: The tags for the resource. + type: list + elements: dict + contains: + key: + description: The key of the tag. + type: str + returned: always + value: + description: The value of the tag. + type: str + returned: always + enclave_options: + description: Indicates whether the instance is enabled for Amazon Web Services Nitro Enclaves. + type: dict + returned: if applicable + contains: + enabled: + description: If this parameter is set to true, the instance is enabled for Amazon Web Services Nitro Enclaves. + type: bool + returned: always + metadata_options: + description: The metadata options for the instance. + type: dict + returned: if applicable + contains: + state: + description: The state of the metadata option changes. + type: str + returned: if applicable + http_tokens: + description: Indicates whether IMDSv2 is required. + type: str + returned: if applicable + http_put_response_hop_limit: + description: The desired HTTP PUT response hop limit for instance metadata requests. + type: int + returned: if applicable + http_endpoint: + description: Enables or disables the HTTP metadata endpoint on your instances. + type: str + returned: if applicable + http_protocol_ipv6: + description: Enables or disables the IPv6 endpoint for the instance metadata service. + type: str + returned: if applicable + instance_metadata_tags: + description: Set to enabled to allow access to instance tags from the instance metadata. + type: str + returned: if applicable + cpu_options: + description: The CPU options for the instance. + type: dict + returned: if applicable + contains: + core_count: + description: The number of CPU cores for the instance. + type: int + returned: if applicable + threads_per_core: + description: The number of threads per CPU core. + type: int + returned: if applicable + amd_sev_snp: + description: Indicates whether the instance is enabled for AMD SEV-SNP. + type: int + returned: if applicable + security_group_ids: + description: The security group IDs. + type: list + elements: str + returned: if applicable + security_groups: + description: The security group names. + type: list + elements: str + returned: if applicable + sample: { + "block_device_mappings": [ + { + "device_name": "/dev/sdb", + "ebs": { + "delete_on_termination": true, + "encrypted": true, + "volumeSize": 5 + } + } + ], + "ebs_optimized": false, + "image_id": "ami-0231217be14a6f3ba", + "instance_type": "t2.micro", + "network_interfaces": [ + { + "associate_public_ip_address": false, + "device_index": 0, + "ipv6_addresses": [ + { + "ipv6_address": "2001:0:130F:0:0:9C0:876A:130B" + } + ] + } + ] + } +latest_template: + description: The latest available version of the launch template. + returned: when O(state=present) + type: complex + contains: + launch_template_id: + description: The ID of the launch template. + type: str + returned: always + launch_template_name: + description: The name of the launch template. + type: str + returned: always + create_time: + description: The time launch template was created. + type: str + returned: always + created_by: + description: The principal that created the launch template. + type: str + returned: always + default_version_number: + description: The version number of the default version of the launch template. + type: int + returned: always + latest_version_number: + description: The version number of the latest version of the launch template. + type: int + returned: always + tags: + description: A dictionary of tags assigned to image. + returned: when AMI is created or already exists + type: dict + sample: { + "Env": "devel", + "Name": "nat-server" + } +default_template: + description: + - The launch template version that will be used if only the template name is specified. + - Often this is the same as the latest version, but not always. + returned: when O(state=present) + type: complex + contains: + launch_template_id: + description: The ID of the launch template. + type: str + returned: always + launch_template_name: + description: The name of the launch template. + type: str + returned: always + create_time: + description: The time launch template was created. + type: str + returned: always + created_by: + description: The principal that created the launch template. + type: str + returned: always + default_version_number: + description: The version number of the default version of the launch template. + type: int + returned: always + latest_version_number: + description: The version number of the latest version of the launch template. + type: int + returned: always + tags: + description: A dictionary of tags assigned to image. + returned: when AMI is created or already exists + type: dict + sample: { + "Env": "devel", + "Name": "nat-server" + } +deleted_template: + description: information about a launch template deleted. + returned: when O(state=absent) + type: complex + contains: + launch_template_id: + description: The ID of the launch template. + type: str + returned: always + launch_template_name: + description: The name of the launch template. + type: str + returned: always + create_time: + description: The time launch template was created. + type: str + returned: always + created_by: + description: The principal that created the launch template. + type: str + returned: always + default_version_number: + description: The version number of the default version of the launch template. + type: int + returned: always + latest_version_number: + description: The version number of the latest version of the launch template. + type: int + returned: always + tags: + description: A dictionary of tags assigned to image. + returned: when AMI is created or already exists + type: dict + sample: { + "Env": "devel", + "Name": "nat-server" + } +deleted_versions: + description: Information about deleted launch template versions. + returned: when O(state=absent) + type: list + elements: dict + contains: + launch_template_id: + description: The ID of the launch template. + type: str + returned: always + launch_template_name: + description: The name of the launch template. + type: str + returned: always + version_number: + description: The version number of the launch template. + type: int + returned: always """ +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple from uuid import uuid4 -try: - from botocore.exceptions import BotoCoreError - from botocore.exceptions import ClientError - from botocore.exceptions import WaiterError -except ImportError: - pass # caught by AnsibleAWSModule - from ansible.module_utils._text import to_text 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.arn import validate_aws_arn -from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code -from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry -from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list +from ansible_collections.amazon.aws.plugins.module_utils.botocore import normalize_boto3_result +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AnsibleEC2Error +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import create_launch_template +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import create_launch_template_version +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import delete_launch_template +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import delete_launch_template_versions +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_launch_template_versions +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_launch_templates +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import determine_iam_arn_from_name +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ensure_ec2_tags +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import modify_launch_template from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict +from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_specifications from ansible_collections.amazon.aws.plugins.module_utils.transformation import scrub_none_parameters from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule -def determine_iam_role(module, name_or_arn): - if validate_aws_arn(name_or_arn, service="iam", resource_type="instance-profile"): - return {"arn": name_or_arn} - iam = module.client("iam", retry_decorator=AWSRetry.jittered_backoff()) - try: - role = iam.get_instance_profile(InstanceProfileName=name_or_arn, aws_retry=True) - return {"arn": role["InstanceProfile"]["Arn"]} - except is_boto3_error_code("NoSuchEntity") as e: - module.fail_json_aws(e, msg=f"Could not find instance_role {name_or_arn}") - except (BotoCoreError, ClientError) as e: # pylint: disable=duplicate-except - module.fail_json_aws( - e, - msg=f"An error occurred while searching for instance_role {name_or_arn}. Please try supplying the full ARN.", - ) - - -def existing_templates(module): - ec2 = module.client("ec2", retry_decorator=AWSRetry.jittered_backoff()) - matches = None - try: - if module.params.get("template_id"): - matches = ec2.describe_launch_templates( - LaunchTemplateIds=[module.params.get("template_id")], aws_retry=True - ) - elif module.params.get("template_name"): - matches = ec2.describe_launch_templates( - LaunchTemplateNames=[module.params.get("template_name")], aws_retry=True - ) - except is_boto3_error_code("InvalidLaunchTemplateName.NotFoundException") as e: - # no named template was found, return nothing/empty versions - return None, [] - except is_boto3_error_code("InvalidLaunchTemplateId.Malformed") as e: # pylint: disable=duplicate-except - module.fail_json_aws( - e, - msg=( - f"Launch template with ID {module.params.get('launch_template_id')} is not a valid ID. It should start" - " with `lt-....`" - ), - ) - except is_boto3_error_code("InvalidLaunchTemplateId.NotFoundException") as e: # pylint: disable=duplicate-except - module.fail_json_aws( - e, - msg=( - f"Launch template with ID {module.params.get('launch_template_id')} could not be found, please supply a" - " name instead so that a new template can be created" - ), - ) - except (ClientError, BotoCoreError, WaiterError) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg="Could not check existing launch templates. This may be an IAM permission problem.") +def find_existing(client, module: AnsibleAWSModule) -> Tuple[Optional[Dict[str, Any]], Optional[List[Dict[str, Any]]]]: + launch_template = None + launch_template_versions = [] + params = {} + template_id = module.params.get("template_id") + template_name = module.params.get("template_name") + if template_id: + params["launch_template_ids"] = [template_id] else: - template = matches["LaunchTemplates"][0] - template_id, template_version, template_default = ( - template["LaunchTemplateId"], - template["LatestVersionNumber"], - template["DefaultVersionNumber"], + params["launch_template_names"] = [template_name] + launch_templates = describe_launch_templates(client, **params) + if launch_templates: + launch_template = launch_templates[0] + launch_template_versions = describe_launch_template_versions( + client, LaunchTemplateId=launch_template["LaunchTemplateId"] ) - try: - return ( - template, - ec2.describe_launch_template_versions(LaunchTemplateId=template_id, aws_retry=True)[ - "LaunchTemplateVersions" - ], - ) - except (ClientError, BotoCoreError, WaiterError) as e: - module.fail_json_aws( - e, - msg=f"Could not find launch template versions for {template['LaunchTemplateName']} (ID: {template_id}).", - ) + return normalize_boto3_result(launch_template), normalize_boto3_result(launch_template_versions) -def params_to_launch_data(module, template_params): - if template_params.get("tags"): - tag_list = ansible_dict_to_boto3_tag_list(template_params.get("tags")) - template_params["tag_specifications"] = [ - {"resource_type": r_type, "tags": tag_list} for r_type in ("instance", "volume") - ] - del template_params["tags"] - if module.params.get("iam_instance_profile"): - template_params["iam_instance_profile"] = determine_iam_role(module, module.params["iam_instance_profile"]) +def params_to_launch_data( + template_params: Dict[str, Any], iam_instance_profile_arn: Optional[str] = None +) -> Dict[str, Any]: + if iam_instance_profile_arn: + template_params["iam_instance_profile"] = {"arn": iam_instance_profile_arn} + for interface in template_params.get("network_interfaces") or []: + if interface.get("ipv6_addresses"): + interface["ipv6_addresses"] = [{"ipv6_address": x} for x in interface["ipv6_addresses"]] params = snake_dict_to_camel_dict( dict((k, v) for k, v in template_params.items() if v is not None), capitalize_first=True, @@ -529,170 +1189,279 @@ def params_to_launch_data(module, template_params): return params -def delete_template(module): - ec2 = module.client("ec2", retry_decorator=AWSRetry.jittered_backoff()) - template, template_versions = existing_templates(module) +def validate_string_as_int(module: AnsibleAWSModule, version: str, param_name: str) -> int: + try: + return int(version) + except ValueError: + module.fail_json(msg=f'{param_name} param was not a valid integer, got "{version}"') + + +def validate_version_deletion( + module: AnsibleAWSModule, launch_template_id: str, existing_versions: List[Dict[str, Any]] +) -> Tuple[List[str], Optional[int]]: + versions_to_delete = module.params.get("versions_to_delete") + launch_template_versions_to_delete = [] + default_version_to_set = None + if versions_to_delete: + unique_versions_to_delete = list(set(versions_to_delete)) + launch_template_versions_to_delete = [ + t["VersionNumber"] for t in existing_versions if t["VersionNumber"] in unique_versions_to_delete + ] + if len(launch_template_versions_to_delete) != len(unique_versions_to_delete): + missing = [m for m in unique_versions_to_delete if m not in launch_template_versions_to_delete] + module.fail_json( + msg=f"The following versions {missing} do not exist for launch template id '{launch_template_id}'." + ) + + remaining_versions = [ + t["VersionNumber"] + for t in existing_versions + if t["VersionNumber"] not in launch_template_versions_to_delete + ] + + # Find the default version + default_version = module.params.get("default_version") + if default_version in (None, ""): + default_version_int = [t["VersionNumber"] for t in existing_versions if t["DefaultVersion"]][0] + elif default_version == "latest": + default_version_int = max(remaining_versions, default=None) + default_version_to_set = default_version_int + else: + default_version_int = validate_string_as_int(module, default_version, "default_version") + default_version_to_set = default_version_int + + # Ensure we are not deleting the default version + if default_version_int in launch_template_versions_to_delete or not remaining_versions: + module.fail_json(msg="Cannot delete the launch template default version.") + + if default_version_to_set and default_version_to_set not in remaining_versions: + module.fail_json( + msg=f"Could not set version '{default_version_to_set}' as default, " + "the launch template version was not found for the specified launch template id '{launch_template_id}'." + ) + else: + # By default delete all non default version before the launch template deletion + launch_template_versions_to_delete = [t["VersionNumber"] for t in existing_versions if not t["DefaultVersion"]] + + return [to_text(v) for v in launch_template_versions_to_delete], default_version_to_set + + +def ensure_absent( + client, module: AnsibleAWSModule, existing: Optional[Dict[str, Any]], existing_versions: List[Dict[str, Any]] +) -> None: deleted_versions = [] - if template or template_versions: - non_default_versions = [to_text(t["VersionNumber"]) for t in template_versions if not t["DefaultVersion"]] - if non_default_versions: - try: - v_resp = ec2.delete_launch_template_versions( - LaunchTemplateId=template["LaunchTemplateId"], - Versions=non_default_versions, - aws_retry=True, + deleted_template = {} + changed = False + + if existing: + launch_template_id = existing["LaunchTemplateId"] + v_to_delete, v_default = validate_version_deletion(module, launch_template_id, existing_versions) + + # Update default version + if v_default: + changed = True + if not module.check_mode: + modify_launch_template( + client, + LaunchTemplateId=launch_template_id, + ClientToken=uuid4().hex, + DefaultVersion=to_text(v_default), ) - if v_resp["UnsuccessfullyDeletedLaunchTemplateVersions"]: + # Delete versions + if v_to_delete: + changed = True + if not module.check_mode: + response = delete_launch_template_versions( + client, launch_template_id=launch_template_id, versions=v_to_delete + ) + if response["UnsuccessfullyDeletedLaunchTemplateVersions"]: module.warn( - f"Failed to delete template versions {v_resp['UnsuccessfullyDeletedLaunchTemplateVersions']} on" - f" launch template {template['LaunchTemplateId']}" + f"Failed to delete template versions {response['UnsuccessfullyDeletedLaunchTemplateVersions']} on" + f" launch template {launch_template_id}" ) deleted_versions = [ - camel_dict_to_snake_dict(v) for v in v_resp["SuccessfullyDeletedLaunchTemplateVersions"] + camel_dict_to_snake_dict(v) for v in response["SuccessfullyDeletedLaunchTemplateVersions"] ] - except (ClientError, BotoCoreError) as e: - module.fail_json_aws( - e, - msg=f"Could not delete existing versions of the launch template {template['LaunchTemplateId']}", - ) - try: - resp = ec2.delete_launch_template( - LaunchTemplateId=template["LaunchTemplateId"], - aws_retry=True, - ) - except (ClientError, BotoCoreError) as e: - module.fail_json_aws(e, msg=f"Could not delete launch template {template['LaunchTemplateId']}") - return { - "deleted_versions": deleted_versions, - "deleted_template": camel_dict_to_snake_dict(resp["LaunchTemplate"]), - "changed": True, - } - else: - return {"changed": False} + # Delete the launch template when a list of versions was not specified + if not module.params.get("versions_to_delete"): + changed = True + if not module.check_mode: + deleted_template = delete_launch_template(client, launch_template_id=launch_template_id) + deleted_template = camel_dict_to_snake_dict(deleted_template, ignore_list=["Tags"]) + if "tags" in deleted_template: + deleted_template["tags"] = boto3_tag_list_to_ansible_dict(deleted_template.get("tags")) + + module.exit_json(changed=changed, deleted_versions=deleted_versions, deleted_template=deleted_template) + + +def add_launch_template_version( + client, + module: AnsibleAWSModule, + launch_template_id: str, + launch_template_data: Dict[str, Any], + existing_versions: List[Dict[str, Any]], + most_recent_version_number: str, +) -> int: + source_version = module.params.get("source_version") + version_description = module.params.get("version_description") + + params = { + "LaunchTemplateId": launch_template_id, + "ClientToken": uuid4().hex, + "VersionDescription": version_description, + } + + if source_version == "latest": + params.update({"SourceVersion": most_recent_version_number}) + elif source_version not in (None, ""): + # Source version passed as int + source_version_int = validate_string_as_int(module, source_version, "source_version") + # get source template version + next_source_version = next( + (v for v in existing_versions if v["VersionNumber"] == source_version_int), + None, + ) + if next_source_version is None: + module.fail_json(msg=f'source_version does not exist, got "{source_version}"') + params.update({"SourceVersion": str(next_source_version["VersionNumber"])}) + + if module.check_mode: + module.exit_json(changed=True, msg="Would have created launch template version if not in check mode.") -def create_or_update(module, template_options): - ec2 = module.client( - "ec2", retry_decorator=AWSRetry.jittered_backoff(catch_extra_error_codes=["InvalidLaunchTemplateId.NotFound"]) + # Create Launch template version + launch_template_version = create_launch_template_version( + client, launch_template_data=launch_template_data, **params ) - template, template_versions = existing_templates(module) - out = {} - lt_data = params_to_launch_data(module, dict((k, v) for k, v in module.params.items() if k in template_options)) - lt_data = scrub_none_parameters(lt_data, descend_into_lists=True) - - if not (template or template_versions): - # create a full new one - try: - resp = ec2.create_launch_template( - LaunchTemplateName=module.params["template_name"], - LaunchTemplateData=lt_data, - ClientToken=uuid4().hex, - aws_retry=True, - ) - except (ClientError, BotoCoreError) as e: - module.fail_json_aws(e, msg="Couldn't create launch template") - template, template_versions = existing_templates(module) - out["changed"] = True - elif template and template_versions: - most_recent = sorted(template_versions, key=lambda x: x["VersionNumber"])[-1] - if lt_data == most_recent["LaunchTemplateData"] and module.params["version_description"] == most_recent.get( - "VersionDescription", "" - ): - out["changed"] = False - return out - try: - if module.params.get("source_version") in (None, ""): - resp = ec2.create_launch_template_version( - LaunchTemplateId=template["LaunchTemplateId"], - LaunchTemplateData=lt_data, - ClientToken=uuid4().hex, - VersionDescription=str(module.params["version_description"]), - aws_retry=True, - ) - elif module.params.get("source_version") == "latest": - resp = ec2.create_launch_template_version( - LaunchTemplateId=template["LaunchTemplateId"], - LaunchTemplateData=lt_data, - ClientToken=uuid4().hex, - SourceVersion=str(most_recent["VersionNumber"]), - VersionDescription=str(module.params["version_description"]), - aws_retry=True, - ) - else: - try: - int(module.params.get("source_version")) - except ValueError: - module.fail_json( - msg=f"source_version param was not a valid integer, got \"{module.params.get('source_version')}\"" - ) - # get source template version - source_version = next( - (v for v in template_versions if v["VersionNumber"] == int(module.params.get("source_version"))), - None, - ) - if source_version is None: - module.fail_json( - msg=f"source_version does not exist, got \"{module.params.get('source_version')}\"" - ) - resp = ec2.create_launch_template_version( - LaunchTemplateId=template["LaunchTemplateId"], - LaunchTemplateData=lt_data, - ClientToken=uuid4().hex, - SourceVersion=str(source_version["VersionNumber"]), - VersionDescription=str(module.params["version_description"]), - aws_retry=True, - ) + return launch_template_version["VersionNumber"] - if module.params.get("default_version") in (None, ""): - # no need to do anything, leave the existing version as default - pass - elif module.params.get("default_version") == "latest": - set_default = ec2.modify_launch_template( - LaunchTemplateId=template["LaunchTemplateId"], - DefaultVersion=to_text(resp["LaunchTemplateVersion"]["VersionNumber"]), - ClientToken=uuid4().hex, - aws_retry=True, - ) - else: - try: - int(module.params.get("default_version")) - except ValueError: - module.fail_json( - msg=f"default_version param was not a valid integer, got \"{module.params.get('default_version')}\"" - ) - set_default = ec2.modify_launch_template( - LaunchTemplateId=template["LaunchTemplateId"], - DefaultVersion=to_text(int(module.params.get("default_version"))), + +def ensure_default_version( + client, + module: AnsibleAWSModule, + launch_template_id: str, + current_default_version_number: int, + most_recent_version_number: int, +) -> bool: + # Modify default version + default_version = module.params.get("default_version") + changed = False + if default_version not in (None, ""): + if default_version == "latest": + default_version = to_text(most_recent_version_number) + else: + default_version = to_text(validate_string_as_int(module, default_version, "default_version")) + if to_text(current_default_version_number) != default_version: + changed = True + if not module.check_mode: + modify_launch_template( + client, + LaunchTemplateId=launch_template_id, ClientToken=uuid4().hex, - aws_retry=True, + DefaultVersion=default_version, ) - except (ClientError, BotoCoreError) as e: - module.fail_json_aws(e, msg="Couldn't create subsequent launch template version") - template, template_versions = existing_templates(module) - out["changed"] = True - return out + return changed -def format_module_output(module): - output = {} - template, template_versions = existing_templates(module) - template = camel_dict_to_snake_dict(template) +def format_module_output(client, module: AnsibleAWSModule) -> Dict[str, Any]: + # Describe launch template + template, template_versions = find_existing(client, module) + template = camel_dict_to_snake_dict(template, ignore_list=["Tags"]) + if "tags" in template: + template["tags"] = boto3_tag_list_to_ansible_dict(template.get("tags")) template_versions = [camel_dict_to_snake_dict(v) for v in template_versions] - for v in template_versions: - for ts in v["launch_template_data"].get("tag_specifications") or []: - ts["tags"] = boto3_tag_list_to_ansible_dict(ts.pop("tags")) - output.update(dict(template=template, versions=template_versions)) - output["default_template"] = [v for v in template_versions if v.get("default_version")][0] - output["latest_template"] = [ - v - for v in template_versions - if (v.get("version_number") and int(v["version_number"]) == int(template["latest_version_number"])) - ][0] - if "version_number" in output["default_template"]: - output["default_version"] = output["default_template"]["version_number"] - if "version_number" in output["latest_template"]: - output["latest_version"] = output["latest_template"]["version_number"] - return output + result = { + "template": template, + "versions": template_versions, + "default_template": [v for v in template_versions if v.get("default_version")][0], + "latest_template": [ + v + for v in template_versions + if (v.get("version_number") and int(v["version_number"]) == int(template["latest_version_number"])) + ][0], + } + if "version_number" in result["default_template"]: + result["default_version"] = result["default_template"]["version_number"] + if "version_number" in result["latest_template"]: + result["latest_version"] = result["latest_template"]["version_number"] + return result + + +def ensure_present( + client, + module: AnsibleAWSModule, + template_options: Dict[str, Any], + existing: Optional[Dict[str, Any]], + existing_versions: List[Dict[str, Any]], +) -> None: + template_name = module.params["template_name"] + tags = module.params["tags"] + tag_specifications = module.params.get("tag_specifications") + version_description = module.params.get("version_description") + iam_instance_profile = module.params.get("iam_instance_profile") + # IAM instance profile + if iam_instance_profile: + iam_instance_profile = determine_iam_arn_from_name(module.client("iam"), iam_instance_profile) + # Convert Launch template data + launch_template_data = params_to_launch_data( + dict((k, v) for k, v in module.params.items() if k in template_options), iam_instance_profile + ) + # Tag specifications + if tag_specifications: + boto3_tag_specs = [] + for tag_spec in tag_specifications: + boto3_tag_specs.extend(boto3_tag_specifications(tag_spec["tags"], types=tag_spec["resource_type"])) + launch_template_data["TagSpecifications"] = boto3_tag_specs + launch_template_data = scrub_none_parameters(launch_template_data, descend_into_lists=True) + changed = False + + if not (existing or existing_versions): + # Create Launch template + if module.check_mode: + module.exit_json(changed=True, msg="Would have created launch template if not in check mode.") + create_launch_template( + client, + launch_template_name=template_name, + launch_template_data=launch_template_data, + tags=tags, + ClientToken=uuid4().hex, + VersionDescription=version_description, + ) + changed = True + else: + launch_template_id = existing["LaunchTemplateId"] + default_version_number = existing["DefaultVersionNumber"] + most_recent = sorted(existing_versions, key=lambda x: x["VersionNumber"])[-1] + most_recent_version_number = most_recent["VersionNumber"] + if not ( + launch_template_data == most_recent["LaunchTemplateData"] + and version_description == most_recent.get("VersionDescription", "") + ): + changed = True + most_recent_version_number = add_launch_template_version( + client, + module, + launch_template_id, + launch_template_data, + existing_versions, + str(most_recent["VersionNumber"]), + ) + + # Ensure default version + changed |= ensure_default_version( + client, module, launch_template_id, default_version_number, most_recent_version_number + ) + # Ensure tags + changed |= ensure_ec2_tags( + client, + module, + launch_template_id, + resource_type="launch-template", + tags=tags, + purge_tags=module.params["purge_tags"], + ) + + module.exit_json(changed=changed, **format_module_output(client, module)) def main(): @@ -712,6 +1481,7 @@ def main(): snapshot_id=dict(), volume_size=dict(type="int"), volume_type=dict(), + throughput=dict(type="int"), ), ), no_device=dict(), @@ -738,7 +1508,6 @@ def main(): type="list", elements="dict", ), - iam_instance_profile=dict(), image_id=dict(), instance_initiated_shutdown_behavior=dict(choices=["stop", "terminate"]), instance_market_options=dict( @@ -802,42 +1571,55 @@ def main(): ram_disk_id=dict(), security_group_ids=dict(type="list", elements="str"), security_groups=dict(type="list", elements="str"), - tags=dict(type="dict", aliases=["resource_tags"]), user_data=dict(), ) - arg_spec = dict( + argument_spec = dict( state=dict(choices=["present", "absent"], default="present"), template_name=dict(aliases=["name"]), template_id=dict(aliases=["id"]), default_version=dict(default="latest"), source_version=dict(default="latest"), version_description=dict(default=""), + iam_instance_profile=dict(), + tags=dict(type="dict", aliases=["resource_tags"]), + purge_tags=dict(type="bool", default=True), + versions_to_delete=dict(type="list", elements="int"), + tag_specifications=dict( + type="list", + elements="dict", + options=dict( + resource_type=dict( + type="str", + default="instance", + choices=["instance", "volume", "network-interface", "spot-instances-request"], + ), + tags=dict(type="dict"), + ), + ), ) - arg_spec.update(template_options) + argument_spec.update(template_options) module = AnsibleAWSModule( - argument_spec=arg_spec, + argument_spec=argument_spec, required_one_of=[ ("template_name", "template_id"), ], supports_check_mode=True, ) - for interface in module.params.get("network_interfaces") or []: - if interface.get("ipv6_addresses"): - interface["ipv6_addresses"] = [{"ipv6_address": x} for x in interface["ipv6_addresses"]] - - if module.params.get("state") == "present": - out = create_or_update(module, template_options) - out.update(format_module_output(module)) - elif module.params.get("state") == "absent": - out = delete_template(module) - else: - module.fail_json(msg=f"Unsupported value \"{module.params.get('state')}\" for `state` parameter") + state = module.params.get("state") + client = module.client("ec2") + launch_template, launch_template_versions = find_existing(client, module) - module.exit_json(**out) + try: + if state == "present": + ensure_present(client, module, template_options, launch_template, launch_template_versions) + else: + ensure_absent(client, module, launch_template, launch_template_versions) + except AnsibleEC2Error as e: + module.fail_json_aws_error(e) if __name__ == "__main__": diff --git a/tests/integration/targets/ec2_launch_template/aliases b/tests/integration/targets/ec2_launch_template/aliases index 4ef4b2067d0..42f0f3c880b 100644 --- a/tests/integration/targets/ec2_launch_template/aliases +++ b/tests/integration/targets/ec2_launch_template/aliases @@ -1 +1,3 @@ cloud/aws +time=3m +ec2_launch_template_info \ No newline at end of file diff --git a/tests/integration/targets/ec2_launch_template/tasks/cpu_options.yml b/tests/integration/targets/ec2_launch_template/tasks/cpu_options.yml deleted file mode 100644 index 92d7fac5fe1..00000000000 --- a/tests/integration/targets/ec2_launch_template/tasks/cpu_options.yml +++ /dev/null @@ -1,38 +0,0 @@ -- block: - - name: delete a non-existent template - ec2_launch_template: - name: "{{ resource_prefix }}-not-a-real-template" - state: absent - register: del_fake_lt - ignore_errors: true - - assert: - that: - - del_fake_lt is not failed - - name: create c4.large instance with cpu_options - ec2_launch_template: - name: "{{ resource_prefix }}-c4large-1-threads-per-core" - image_id: "{{ ec2_ami_id }}" - tags: - TestId: "{{ resource_prefix }}" - instance_type: c4.large - cpu_options: - core_count: 1 - threads_per_core: 1 - register: lt - - - name: instance with cpu_options created with the right options - assert: - that: - - lt is success - - lt is changed - - "lt.latest_template.launch_template_data.cpu_options.core_count == 1" - - "lt.latest_template.launch_template_data.cpu_options.threads_per_core == 1" - always: - - name: delete the template - ec2_launch_template: - name: "{{ resource_prefix }}-c4large-1-threads-per-core" - state: absent - register: del_lt - retries: 10 - until: del_lt is not failed - ignore_errors: true diff --git a/tests/integration/targets/ec2_launch_template/tasks/deletion.yml b/tests/integration/targets/ec2_launch_template/tasks/deletion.yml new file mode 100644 index 00000000000..3936888dc53 --- /dev/null +++ b/tests/integration/targets/ec2_launch_template/tasks/deletion.yml @@ -0,0 +1,404 @@ +- name: Test deletion of launch template + vars: + deletion_launch_template_name: "{{ resource_prefix }}-deletion" + deletion_launch_template_name_2: "{{ resource_prefix }}-deletion-2" + test_ec2_instance_types: + - t2.micro + - t2.small + - t2.medium + - t2.large + - t2.xlarge + block: + - name: Create multiple versions of the launch template + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + instance_type: "{{ item }}" + with_items: "{{ test_ec2_instance_types }}" + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates + + - name: Validate Launch template details + ansible.builtin.assert: + that: + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 5 + - _templates.launch_templates[0].latest_version_number == 5 + - _templates.launch_templates[0].default_version_number == 5 + + #==================================================================== + # Validate deletion errors + #==================================================================== + - name: Delete a non-existent template + community.aws.ec2_launch_template: + name: "{{ resource_prefix }}-not-a-real-template" + state: absent + register: delete_fake_template + ignore_errors: true + + - name: Ensure module did not failed while trying to delete non-existent template + assert: + that: + - delete_fake_template is not failed + + - name: Trying to delete the default version + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: "{{ range(1, 6) }}" + ignore_errors: true + register: delete_default_v + + - name: Ensure the module failed with proper message + ansible.builtin.assert: + that: + - delete_default_v is failed + - 'delete_default_v.msg == "Cannot delete the launch template default version."' + + - name: Trying to delete a version and a non-existing version as default + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: "{{ range(3, 6) }}" + default_version: 6 + ignore_errors: true + register: delete_set_non_existing_v + + - name: Ensure the module failed with proper message + ansible.builtin.assert: + that: + - delete_set_non_existing_v is failed + - error_m in delete_set_non_existing_v.msg + vars: + error_m: "Could not set version '6' as default, the launch template version was not found" + + - name: Trying to delete non-existing version (should failed) + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: 10 + ignore_errors: true + register: delete_non_existing_v + + - name: Ensure the module failed with proper message + ansible.builtin.assert: + that: + - delete_non_existing_v is failed + - error_m in delete_non_existing_v.msg + vars: + error_m: "The following versions [10] do not exist for launch template id" + + #==================================================================== + # Delete default version and set latest version as new default + #==================================================================== + - name: Delete default version and set a new default version (check mode) + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: 5 + check_mode: true + register: delete_default_v_check_mode + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates + + - name: Ensure module reported change while no change was made + ansible.builtin.assert: + that: + - delete_default_v_check_mode is changed + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 5 + - _templates.launch_templates[0].latest_version_number == 5 + - _templates.launch_templates[0].default_version_number == 5 + + - name: Delete default version and set a new default version + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: 5 + register: delete_default_v + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates + + - name: Validate deletion result + ansible.builtin.assert: + that: + - delete_default_v is changed + - delete_default_v.deleted_template == {} + - delete_default_v.deleted_versions | length == 1 + - delete_default_v.deleted_versions.0.version_number == 5 + - delete_default_v.deleted_versions.0.launch_template_name == deletion_launch_template_name + - delete_default_v.deleted_versions.0.launch_template_id == _templates.launch_templates[0].launch_template_id + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 4 + - _templates.launch_templates[0].latest_version_number == 4 + - _templates.launch_templates[0].default_version_number == 4 + + #==================================================================== + # Delete a single version and set a new default version different from the latest + #==================================================================== + - name: Delete a single version and set a new default version different from the latest (check mode) + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: 3 + default_version: 1 + check_mode: true + register: delete_single_v_check_mode + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates + + - name: Ensure module reported change while no change was made + ansible.builtin.assert: + that: + - delete_default_v_check_mode is changed + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 4 + - _templates.launch_templates[0].latest_version_number == 4 + - _templates.launch_templates[0].default_version_number == 4 + + - name: Delete a single version and set a new default version different from the latest + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: 3 + default_version: 1 + register: delete_single_v + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates + + - name: Validate deletion result + ansible.builtin.assert: + that: + - delete_single_v is changed + - delete_single_v.deleted_template == {} + - delete_single_v.deleted_versions | length == 1 + - delete_single_v.deleted_versions.0.version_number == 3 + - delete_single_v.deleted_versions.0.launch_template_name == deletion_launch_template_name + - delete_single_v.deleted_versions.0.launch_template_id == _templates.launch_templates[0].launch_template_id + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 3 + - _templates.launch_templates[0].latest_version_number == 4 + - _templates.launch_templates[0].default_version_number == 1 + + #==================================================================== + # Delete multiple versions + #==================================================================== + - name: Delete multiple versions (check mode) + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: [1, 2] + check_mode: true + register: delete_multiple_v_check_mode + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates + + - name: Ensure module reported change while no change was made + ansible.builtin.assert: + that: + - delete_multiple_v_check_mode is changed + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 3 + - _templates.launch_templates[0].latest_version_number == 4 + - _templates.launch_templates[0].default_version_number == 1 + + - name: Delete multiple versions + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + versions_to_delete: [1, 2] + register: delete_multiple_v + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates + + - name: Ensure module reported change while no change was made + ansible.builtin.assert: + that: + - delete_multiple_v is changed + - delete_multiple_v.deleted_template == {} + - delete_multiple_v.deleted_versions | length == 2 + - delete_multiple_v.deleted_versions | map(attribute='launch_template_name') | unique | list == [deletion_launch_template_name] + - delete_multiple_v.deleted_versions | map(attribute='launch_template_id') | unique | list == [_templates.launch_templates[0].launch_template_id] + - delete_multiple_v.deleted_versions | map(attribute='version_number') | sort | list == [1, 2] + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 1 + - _templates.launch_templates[0].latest_version_number == 4 + - _templates.launch_templates[0].default_version_number == 4 + + #==================================================================== + # Delete launch template + #==================================================================== + - name: Delete launch template (check mode) + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + check_mode: true + register: delete_template_check_mode + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates + + - name: Ensure module reported change while no change was made + ansible.builtin.assert: + that: + - delete_template_check_mode is changed + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 1 + - _templates.launch_templates[0].latest_version_number == 4 + - _templates.launch_templates[0].default_version_number == 4 + + - name: Delete launch template + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + register: delete_template + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name }}" + register: _templates_v + + - name: Ensure the launch template was deleted + ansible.builtin.assert: + that: + - delete_template is changed + - delete_template.deleted_template.launch_template_id == _templates.launch_templates[0].launch_template_id + - delete_template.deleted_template.latest_version_number == _templates.launch_templates[0].latest_version_number + - delete_template.deleted_template.default_version_number == _templates.launch_templates[0].default_version_number + - delete_template.deleted_versions | length == 0 + - _templates_v.launch_templates | length == 0 + + - name: Delete launch template once again (idempotency) + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name }}" + state: absent + register: delete_template_idempotency + + - name: Ensure module idempotency + ansible.builtin.assert: + that: + - delete_template_idempotency is not changed + - delete_template_idempotency.deleted_template == {} + - delete_template_idempotency.deleted_versions == [] + + #==================================================================== + # Delete launch template with multiple versions + #==================================================================== + - name: Create multiple versions of the launch template + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name_2 }}" + instance_type: "{{ item }}" + with_items: "{{ test_ec2_instance_types }}" + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name_2 }}" + register: _templates + + - name: Validate Launch template details + ansible.builtin.assert: + that: + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 5 + - _templates.launch_templates[0].latest_version_number == 5 + - _templates.launch_templates[0].default_version_number == 5 + + - name: Delete launch template with multiple versions (check mode) + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name_2 }}" + state: absent + check_mode: true + register: delete_template_check_mode + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name_2 }}" + register: _templates + + - name: Ensure module reported change while no change was made + ansible.builtin.assert: + that: + - delete_template_check_mode is changed + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 5 + + - name: Delete launch template with multiple versions + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name_2 }}" + state: absent + register: delete_template + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ deletion_launch_template_name_2 }}" + register: _templates_v + + - name: Ensure the launch template was deleted + ansible.builtin.assert: + that: + - delete_template is changed + - delete_template.deleted_template.launch_template_id == _templates.launch_templates[0].launch_template_id + - delete_template.deleted_template.latest_version_number == _templates.launch_templates[0].latest_version_number + - delete_template.deleted_template.default_version_number == _templates.launch_templates[0].default_version_number + - delete_template.deleted_versions | length == 4 + - delete_template.deleted_versions | map(attribute='launch_template_name') | unique | list == [deletion_launch_template_name_2] + - delete_template.deleted_versions | map(attribute='launch_template_id') | unique | list == [_templates.launch_templates[0].launch_template_id] + - delete_template.deleted_versions | map(attribute='version_number') | sort | list == [1, 2, 3, 4] + - _templates_v.launch_templates | length == 0 + + - name: Delete launch template with multiple versions once again (idempotency) + community.aws.ec2_launch_template: + name: "{{ deletion_launch_template_name_2 }}" + state: absent + register: delete_template_idempotency + + - name: Ensure module idempotency + ansible.builtin.assert: + that: + - delete_template_idempotency is not changed + - delete_template_idempotency.deleted_template == {} + - delete_template_idempotency.deleted_versions == [] + + always: + - name: Delete the launch template + community.aws.ec2_launch_template: + name: "{{ item }}" + state: absent + with_items: + - "{{ deletion_launch_template_name }}" + - "{{ deletion_launch_template_name_2 }}" + ignore_errors: true diff --git a/tests/integration/targets/ec2_launch_template/tasks/iam_instance_role.yml b/tests/integration/targets/ec2_launch_template/tasks/iam_instance_role.yml index ad797fabb79..08a39dff855 100644 --- a/tests/integration/targets/ec2_launch_template/tasks/iam_instance_role.yml +++ b/tests/integration/targets/ec2_launch_template/tasks/iam_instance_role.yml @@ -1,6 +1,9 @@ -- block: +- name: Test using IAM instance profile + vars: + test_launch_template_name: "{{ resource_prefix }}-test-instance-role" + block: - name: Create IAM role for test - iam_role: + amazon.aws.iam_role: name: "{{ test_role_name }}-1" assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}" state: present @@ -10,7 +13,7 @@ register: iam_role - name: Create second IAM role for test - iam_role: + amazon.aws.iam_role: name: "{{ test_role_name }}-2" assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}" state: present @@ -20,115 +23,181 @@ register: iam_role_2 - name: Make instance with an instance_role - ec2_launch_template: - name: "{{ resource_prefix }}-test-instance-role" + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" image_id: "{{ ec2_ami_id }}" instance_type: t2.micro iam_instance_profile: "{{ test_role_name }}-1" register: template_with_role - - assert: + - name: Get launch template details + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure the launch template was created with IAM instance profile + ansible.builtin.assert: that: - 'template_with_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role.iam_role.arn.replace(":role/", ":instance-profile/")' + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].versions | length == 1 + - _template_info.launch_templates[0].versions[0].launch_template_data.iam_instance_profile.arn == iam_role.iam_role.arn.replace(":role/", ":instance-profile/") + - _template_info.launch_templates[0].versions[0].launch_template_data.instance_type == "t2.micro" + - _template_info.launch_templates[0].versions[0].launch_template_data.image_id == ec2_ami_id - name: Create template again, with no change to instance_role - ec2_launch_template: - name: "{{ resource_prefix }}-test-instance-role" + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" image_id: "{{ ec2_ami_id }}" instance_type: t2.micro iam_instance_profile: "{{ test_role_name }}-1" register: template_with_role - - assert: + - name: Get launch template details + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Validate idempotency + ansible.builtin.assert: that: - 'template_with_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role.iam_role.arn.replace(":role/", ":instance-profile/")' - 'template_with_role is not changed' + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].versions | length == 1 + - _template_info.launch_templates[0].versions[0].launch_template_data.iam_instance_profile.arn == iam_role.iam_role.arn.replace(":role/", ":instance-profile/") + - _template_info.launch_templates[0].versions[0].launch_template_data.instance_type == "t2.micro" + - _template_info.launch_templates[0].versions[0].launch_template_data.image_id == ec2_ami_id - name: Update instance with new instance_role - ec2_launch_template: - name: "{{ resource_prefix }}-test-instance-role" + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" image_id: "{{ ec2_ami_id }}" instance_type: t2.micro iam_instance_profile: "{{ test_role_name }}-2" register: template_with_updated_role - - assert: + - name: Get launch template details + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure that the launch template was updated with new IAM instance profile + ansible.builtin.assert: that: - 'template_with_updated_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role_2.iam_role.arn.replace(":role/", ":instance-profile/")' - 'template_with_updated_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role_2.iam_role.arn.replace(":role/", ":instance-profile/")' - 'template_with_role.default_template.version_number < template_with_updated_role.default_template.version_number' - 'template_with_updated_role is changed' - 'template_with_updated_role is not failed' + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].versions | length == 2 + - template_version.launch_template_data.iam_instance_profile.arn == iam_role_2.iam_role.arn.replace(":role/", ":instance-profile/") + - template_version.launch_template_data.instance_type == "t2.micro" + - template_version.launch_template_data.image_id == ec2_ami_id + vars: + template_version: "{{ _template_info.launch_templates[0].versions | selectattr('version_number', 'equalto', 2) | list | first }}" - name: Re-set with same new instance_role - ec2_launch_template: - name: "{{ resource_prefix }}-test-instance-role" + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" image_id: "{{ ec2_ami_id }}" instance_type: t2.micro iam_instance_profile: "{{ test_role_name }}-2" register: template_with_updated_role - - assert: + - name: Get launch template details + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure that module did not reported change + ansible.builtin.assert: that: - 'template_with_updated_role is not changed' - 'template_with_updated_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role_2.iam_role.arn.replace(":role/", ":instance-profile/")' + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].versions | length == 2 - name: Update instance with original instance_role (pass profile ARN) - ec2_launch_template: - name: "{{ resource_prefix }}-test-instance-role" + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" image_id: "{{ ec2_ami_id }}" instance_type: t2.micro # By default an instance profile will be created with the same name as the role iam_instance_profile: '{{ iam_role.iam_role.arn.replace(":role/", ":instance-profile/") }}' register: template_with_updated_role - - assert: + - name: Get launch template details + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Validate that the launch template was updated + ansible.builtin.assert: that: - 'template_with_updated_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role.iam_role.arn.replace(":role/", ":instance-profile/")' - 'template_with_updated_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role.iam_role.arn.replace(":role/", ":instance-profile/")' - 'template_with_role.default_template.version_number < template_with_updated_role.default_template.version_number' - 'template_with_updated_role is changed' - 'template_with_updated_role is not failed' + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].versions | length == 3 + - template_version.launch_template_data.iam_instance_profile.arn == iam_role.iam_role.arn.replace(":role/", ":instance-profile/") + - template_version.launch_template_data.instance_type == "t2.micro" + - template_version.launch_template_data.image_id == ec2_ami_id + vars: + template_version: "{{ _template_info.launch_templates[0].versions | selectattr('version_number', 'equalto', 3) | list | first }}" - name: Re-set with same new instance_role (pass profile ARN) - ec2_launch_template: - name: "{{ resource_prefix }}-test-instance-role" + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" image_id: "{{ ec2_ami_id }}" instance_type: t2.micro iam_instance_profile: '{{ iam_role.iam_role.arn.replace(":role/", ":instance-profile/") }}' register: template_with_updated_role - - assert: + - name: Get launch template details + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Assert that the template was not updated + ansible.builtin.assert: that: - 'template_with_updated_role is not changed' - 'template_with_updated_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role.iam_role.arn.replace(":role/", ":instance-profile/")' + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].versions | length == 3 always: - - name: delete launch template - ec2_launch_template: - name: "{{ resource_prefix }}-test-instance-role" + - name: Delete launch template + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" state: absent register: lt_removed - until: lt_removed is not failed - ignore_errors: yes - retries: 10 + ignore_errors: true + - name: Delete IAM role for test - iam_role: + amazon.aws.iam_role: name: "{{ test_role_name }}-1" assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}" state: absent - delete_instance_profile: yes + delete_instance_profile: true register: iam_removed - until: iam_removed is not failed - ignore_errors: yes - retries: 10 + ignore_errors: true + - name: Delete IAM role for test iam_role: name: "{{ test_role_name }}-2" assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}" state: absent - delete_instance_profile: yes + delete_instance_profile: true register: iam_2_removed - until: iam_2_removed is not failed - ignore_errors: yes - retries: 10 + ignore_errors: true diff --git a/tests/integration/targets/ec2_launch_template/tasks/instance-metadata.yml b/tests/integration/targets/ec2_launch_template/tasks/instance-metadata.yml deleted file mode 100644 index 7648f00efb8..00000000000 --- a/tests/integration/targets/ec2_launch_template/tasks/instance-metadata.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -- name: instance_metadata_tags - block: - - name: metadata_options - ec2_launch_template: - name: "{{ resource_prefix }}-test-metadata" - metadata_options: - http_put_response_hop_limit: 1 - http_tokens: required - http_protocol_ipv6: enabled - instance_metadata_tags: enabled - state: present - register: metadata_options_launch_template - - name: instance with metadata_options created with the right options - assert: - that: - - metadata_options_launch_template is changed - - "metadata_options_launch_template.latest_template.launch_template_data.metadata_options.http_put_response_hop_limit == 1" - - "metadata_options_launch_template.latest_template.launch_template_data.metadata_options.http_tokens == 'required'" - - "metadata_options_launch_template.latest_template.launch_template_data.metadata_options.http_protocol_ipv6 == 'enabled'" - - "metadata_options_launch_template.latest_template.launch_template_data.metadata_options.instance_metadata_tags == 'enabled'" - always: - - name: delete the template - ec2_launch_template: - name: "{{ resource_prefix }}-test-metadata" - state: absent - register: del_lt - retries: 10 - until: del_lt is not failed - ignore_errors: true diff --git a/tests/integration/targets/ec2_launch_template/tasks/main.yml b/tests/integration/targets/ec2_launch_template/tasks/main.yml index e89dfceb557..c8ea5f055da 100644 --- a/tests/integration/targets/ec2_launch_template/tasks/main.yml +++ b/tests/integration/targets/ec2_launch_template/tasks/main.yml @@ -6,8 +6,8 @@ session_token: "{{ security_token | default(omit) }}" region: "{{ aws_region }}" block: - - include_tasks: cpu_options.yml + - include_tasks: template_data.yml + - include_tasks: tagging.yml - include_tasks: iam_instance_role.yml - include_tasks: versions.yml - - include_tasks: instance-metadata.yml - - include_tasks: network_interfaces.yml + - include_tasks: deletion.yml diff --git a/tests/integration/targets/ec2_launch_template/tasks/network_interfaces.yml b/tests/integration/targets/ec2_launch_template/tasks/network_interfaces.yml deleted file mode 100644 index a2ca0e5f6b9..00000000000 --- a/tests/integration/targets/ec2_launch_template/tasks/network_interfaces.yml +++ /dev/null @@ -1,53 +0,0 @@ -- block: - - name: network_interfaces - ec2_launch_template: - name: "{{ resource_prefix }}-test-nic" - state: present - network_interfaces: - - device_index: 0 - associate_public_ip_address: false - delete_on_termination: true - - device_index: 1 - associate_public_ip_address: true - delete_on_termination: false - ipv6_address_count: 1 - register: nic_template - - name: instance with network_interfaces created with the right settings - assert: - that: - - nic_template is changed - - nic_template.default_template.launch_template_data.network_interfaces[0].associate_public_ip_address == False - - nic_template.default_template.launch_template_data.network_interfaces[0].delete_on_termination == True - - nic_template.default_template.launch_template_data.network_interfaces[0].device_index == 0 - - nic_template.default_template.launch_template_data.network_interfaces[1].associate_public_ip_address == True - - nic_template.default_template.launch_template_data.network_interfaces[1].delete_on_termination == False - - nic_template.default_template.launch_template_data.network_interfaces[1].device_index == 1 - - nic_template.default_template.launch_template_data.network_interfaces[1].ipv6_address_count == 1 - - - name: network_interfaces - ec2_launch_template: - name: "{{ resource_prefix }}-test-nic" - state: present - network_interfaces: - - device_index: 0 - associate_public_ip_address: false - delete_on_termination: true - - device_index: 1 - associate_public_ip_address: true - delete_on_termination: false - ipv6_address_count: 1 - register: nic_template - - name: instance with network_interfaces created with the right settings - assert: - that: - - nic_template is not changed - - always: - - name: delete the template - ec2_launch_template: - name: "{{ resource_prefix }}-test-nic" - state: absent - register: del_lt - retries: 10 - until: del_lt is not failed - ignore_errors: true diff --git a/tests/integration/targets/ec2_launch_template/tasks/tagging.yml b/tests/integration/targets/ec2_launch_template/tasks/tagging.yml new file mode 100644 index 00000000000..b4d38da70c9 --- /dev/null +++ b/tests/integration/targets/ec2_launch_template/tasks/tagging.yml @@ -0,0 +1,210 @@ +--- +- name: Test tagging + vars: + test_launch_template_name: "{{ resource_prefix }}-tagging" + launch_template_instance_tags: + - key: foo + value: bar + - key: environment + value: test + launch_template_network_tags: + - key: owner + value: ansible + ansible_instance_tags: + foo: bar + environment: test + ansible_network_tags: + owner: ansible + block: + # Create launch template with tags + - name: Create Launch template with tags + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + instance_type: t2.micro + network_interfaces: + - associate_public_ip_address: false + delete_on_termination: true + device_index: 0 + tag_specifications: + - resource_type: instance + tags: "{{ ansible_instance_tags }}" + - resource_type: network-interface + tags: "{{ ansible_network_tags }}" + tags: + ResourcePrefix: "{{ resource_prefix }}" + InstanceType: "t2.micro" + register: _create_with_tags + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _templates + + - name: Ensure the launch template was created with tags + ansible.builtin.assert: + that: + - _create_with_tags is changed + - '"tags" in _create_with_tags.template' + - _create_with_tags.template.tags.InstanceType == "t2.micro" + - _create_with_tags.template.tags.ResourcePrefix == resource_prefix + - _templates.launch_templates[0].tags.InstanceType == "t2.micro" + - _templates.launch_templates[0].tags.ResourcePrefix == resource_prefix + - _templates.launch_templates[0].versions | length == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | length == 2 + - instance_tags.tags == launch_template_instance_tags + - network_interface_tags.tags == launch_template_network_tags + vars: + instance_tags: "{{ _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | selectattr('resource_type', 'equalto', 'instance') | list | first }}" + network_interface_tags: "{{ _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | selectattr('resource_type', 'equalto', 'network-interface') | list | first }}" + + # Create launch template once again with same tags (expected no change) + - name: Create launch template once again with same tags (expected no change) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + instance_type: t2.micro + network_interfaces: + - associate_public_ip_address: false + delete_on_termination: true + device_index: 0 + tag_specifications: + - resource_type: instance + tags: "{{ ansible_instance_tags }}" + - resource_type: network-interface + tags: "{{ ansible_network_tags }}" + tags: + ResourcePrefix: "{{ resource_prefix }}" + InstanceType: "t2.micro" + register: _create_with_tags_idempotency + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _templates + + - name: Ensure the launch template was created with tags + ansible.builtin.assert: + that: + - _create_with_tags_idempotency is not changed + - '"tags" in _create_with_tags_idempotency.template' + - _create_with_tags_idempotency.template.tags.InstanceType == "t2.micro" + - _create_with_tags_idempotency.template.tags.ResourcePrefix == resource_prefix + - _templates.launch_templates[0].tags.InstanceType == "t2.micro" + - _templates.launch_templates[0].tags.ResourcePrefix == resource_prefix + - _templates.launch_templates[0].versions | length == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | length == 2 + - instance_tags.tags == launch_template_instance_tags + - network_interface_tags.tags == launch_template_network_tags + vars: + instance_tags: "{{ _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | selectattr('resource_type', 'equalto', 'instance') | list | first }}" + network_interface_tags: "{{ _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | selectattr('resource_type', 'equalto', 'network-interface') | list | first }}" + + # Add new tag + - name: Add new tag with purge_tags=false + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + instance_type: t2.micro + network_interfaces: + - associate_public_ip_address: false + delete_on_termination: true + device_index: 0 + tag_specifications: + - resource_type: instance + tags: "{{ ansible_instance_tags }}" + - resource_type: network-interface + tags: "{{ ansible_network_tags }}" + tags: + Phase: integration + purge_tags: false + register: _add_tag + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _templates + + - name: Ensure the launch template was created with tags + ansible.builtin.assert: + that: + - _add_tag is changed + - '"tags" in _add_tag.template' + - _add_tag.template.tags.InstanceType == "t2.micro" + - _add_tag.template.tags.ResourcePrefix == resource_prefix + - _add_tag.template.tags.Phase == "integration" + - _templates.launch_templates[0].tags.InstanceType == "t2.micro" + - _templates.launch_templates[0].tags.ResourcePrefix == resource_prefix + - _templates.launch_templates[0].tags.Phase == "integration" + - _templates.launch_templates[0].versions | length == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | length == 2 + - instance_tags.tags == launch_template_instance_tags + - network_interface_tags.tags == launch_template_network_tags + vars: + instance_tags: "{{ _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | selectattr('resource_type', 'equalto', 'instance') | list | first }}" + network_interface_tags: "{{ _templates.launch_templates[0].versions[0].launch_template_data.tag_specifications | selectattr('resource_type', 'equalto', 'network-interface') | list | first }}" + + # Add new launch template version and update tags + - name: Add new launch template version and update tags + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + instance_type: t3.micro + tags: + Team: Ansible + purge_tags: true + source_version: "" + register: _add_tag_and_version + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _templates + + - name: Ensure the launch template was created with tags + ansible.builtin.assert: + that: + - _add_tag_and_version is changed + - '"tags" in _add_tag.template' + - '"InstanceType" not in _add_tag_and_version.template.tags' + - '"ResourcePrefix" not in _add_tag_and_version.template.tags' + - '"Phase" not in _add_tag_and_version.template.tags' + - _add_tag_and_version.template.tags.Team == "Ansible" + - '"InstanceType" not in _templates.launch_templates[0].tags' + - '"ResourcePrefix" not in _templates.launch_templates[0].tags' + - '"Phase" not in _templates.launch_templates[0].tags' + - _templates.launch_templates[0].tags.Team == "Ansible" + - _templates.launch_templates[0].versions | length == 2 + - '"tag_specifications" not in latest_version_template_data.launch_template_data' + vars: + latest_version_template_data: '{{ _templates.launch_templates[0].versions | selectattr("version_number", "equalto", 2) | list | first }}' + + # Purge tags + - name: Purge all tags from launch template + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + instance_type: t3.micro + tags: {} + purge_tags: true + register: _purge_tags + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _templates + + - name: Ensure the launch template was created with tags + ansible.builtin.assert: + that: + - _purge_tags is changed + - '"tags" not in _purge_tags.template' + - _templates.launch_templates[0].tags == {} + - _templates.launch_templates[0].versions | length == 2 + + always: + - name: Delete launch template + community.aws.ec2_launch_template: + state: absent + name: "{{ test_launch_template_name }}" + ignore_errors: true diff --git a/tests/integration/targets/ec2_launch_template/tasks/tags_and_vpc_settings.yml b/tests/integration/targets/ec2_launch_template/tasks/tags_and_vpc_settings.yml deleted file mode 100644 index 41ff9082b76..00000000000 --- a/tests/integration/targets/ec2_launch_template/tasks/tags_and_vpc_settings.yml +++ /dev/null @@ -1,208 +0,0 @@ -- block: - # ============================================================ - # set up VPC - - name: Create VPC for use in testing - ec2_vpc_net: - name: "{{ resource_prefix }}-vpc" - cidr_block: 10.99.0.0/16 - tags: - Name: Ansible ec2_instance Testing VPC - tenancy: default - register: testing_vpc - - - name: Create default subnet in zone A - ec2_vpc_subnet: - state: present - vpc_id: "{{ testing_vpc.vpc.id }}" - cidr: 10.99.0.0/24 - az: "{{ aws_region }}a" - resource_tags: - Name: "{{ resource_prefix }}-subnet-a" - register: testing_subnet_a - - - name: Create secondary subnet in zone B - ec2_vpc_subnet: - state: present - vpc_id: "{{ testing_vpc.vpc.id }}" - cidr: 10.99.1.0/24 - az: "{{ aws_region }}b" - resource_tags: - Name: "{{ resource_prefix }}-subnet-b" - register: testing_subnet_b - - - name: create a security group with the vpc - ec2_security_group: - name: "{{ resource_prefix }}-sg" - description: a security group for ansible tests - vpc_id: "{{ testing_vpc.vpc.id }}" - rules: - - proto: tcp - ports: [22, 80] - cidr_ip: 0.0.0.0/0 - register: sg - # TODO: switch these tests from instances - - assert: - that: - - 1 == 0 - # ============================================================ - # start subnet/sg testing - - name: Make instance in the testing subnet created in the test VPC - ec2_instance: - name: "{{ resource_prefix }}-test-basic-vpc-create" - image_id: "{{ ec2_ami_id }}" - user_data: | - #cloud-config - package_upgrade: true - package_update: true - tags: - TestId: "{{ resource_prefix }}" - Something: else - security_groups: "{{ sg.group_id }}" - network: - source_dest_check: false - vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}" - instance_type: t2.micro - volumes: - - device_name: /dev/sda1 - ebs: - delete_on_termination: true - register: in_test_vpc - - - name: Try to re-make the instance, hopefully this shows changed=False - ec2_instance: - name: "{{ resource_prefix }}-test-basic-vpc-create" - image_id: "{{ ec2_ami_id }}" - user_data: | - #cloud-config - package_upgrade: true - package_update: true - tags: - TestId: "{{ resource_prefix }}" - Something: else - security_groups: "{{ sg.group_id }}" - vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}" - instance_type: t2.micro - register: remake_in_test_vpc - - name: "Remaking the same instance resulted in no changes" - assert: - that: not remake_in_test_vpc.changed - - name: check that instance IDs match anyway - assert: - that: 'remake_in_test_vpc.instance_ids[0] == in_test_vpc.instance_ids[0]' - - name: check that source_dest_check was set to false - assert: - that: 'not remake_in_test_vpc.instances[0].source_dest_check' - - - name: Alter it by adding tags - ec2_instance: - name: "{{ resource_prefix }}-test-basic-vpc-create" - image_id: "{{ ec2_ami_id }}" - tags: - TestId: "{{ resource_prefix }}" - Another: thing - security_groups: "{{ sg.group_id }}" - vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}" - instance_type: t2.micro - register: add_another_tag - - - ec2_instance_info: - instance_ids: "{{ add_another_tag.instance_ids }}" - register: check_tags - - name: "Remaking the same instance resulted in no changes" - assert: - that: - - check_tags.instances[0].tags.Another == 'thing' - - check_tags.instances[0].tags.Something == 'else' - - - name: Purge a tag - ec2_instance: - name: "{{ resource_prefix }}-test-basic-vpc-create" - image_id: "{{ ec2_ami_id }}" - purge_tags: true - tags: - TestId: "{{ resource_prefix }}" - Another: thing - security_groups: "{{ sg.group_id }}" - vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}" - instance_type: t2.micro - - ec2_instance_info: - instance_ids: "{{ add_another_tag.instance_ids }}" - register: check_tags - - name: "Remaking the same instance resulted in no changes" - assert: - that: - - "'Something' not in check_tags.instances[0].tags" - - - name: Terminate instance - ec2_instance: - filters: - tag:TestId: "{{ resource_prefix }}" - state: absent - register: result - - assert: - that: result.changed - - - name: Terminate instance - ec2_instance: - instance_ids: "{{ in_test_vpc.instance_ids }}" - state: absent - register: result - - assert: - that: not result.changed - - - name: check that subnet-default public IP rule was followed - assert: - that: - - in_test_vpc.instances[0].public_dns_name == "" - - in_test_vpc.instances[0].private_ip_address.startswith("10.22.33") - - in_test_vpc.instances[0].subnet_id == testing_subnet_b.subnet.id - - name: check that tags were applied - assert: - that: - - in_test_vpc.instances[0].tags.Name.startswith(resource_prefix) - - in_test_vpc.instances[0].state.name == 'running' - - always: - - name: remove the security group - ec2_security_group: - name: "{{ resource_prefix }}-sg" - description: a security group for ansible tests - vpc_id: "{{ testing_vpc.vpc.id }}" - state: absent - register: removed - until: removed is not failed - ignore_errors: yes - retries: 10 - - - name: remove subnet A - ec2_vpc_subnet: - state: absent - vpc_id: "{{ testing_vpc.vpc.id }}" - cidr: 10.99.0.0/24 - register: removed - until: removed is not failed - ignore_errors: yes - retries: 10 - - - name: remove subnet B - ec2_vpc_subnet: - state: absent - vpc_id: "{{ testing_vpc.vpc.id }}" - cidr: 10.99.1.0/24 - register: removed - until: removed is not failed - ignore_errors: yes - retries: 10 - - - name: remove the VPC - ec2_vpc_net: - name: "{{ resource_prefix }}-vpc" - cidr_block: 10.99.0.0/16 - state: absent - tags: - Name: Ansible Testing VPC - tenancy: default - register: removed - until: removed is not failed - ignore_errors: yes - retries: 10 diff --git a/tests/integration/targets/ec2_launch_template/tasks/template_data.yml b/tests/integration/targets/ec2_launch_template/tasks/template_data.yml new file mode 100644 index 00000000000..cc9a4962f62 --- /dev/null +++ b/tests/integration/targets/ec2_launch_template/tasks/template_data.yml @@ -0,0 +1,145 @@ +- name: Test launch template data + vars: + test_launch_template_name: "{{ resource_prefix }}-template-data" + block: + # Launch template meta data + - name: Create launch template (check mode) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + image_id: "{{ ec2_ami_id }}" + instance_type: c4.large + cpu_options: + core_count: 1 + threads_per_core: 1 + network_interfaces: + - device_index: 0 + associate_public_ip_address: false + delete_on_termination: true + - device_index: 1 + associate_public_ip_address: true + delete_on_termination: false + ipv6_address_count: 1 + metadata_options: + http_put_response_hop_limit: 1 + http_tokens: required + http_protocol_ipv6: enabled + instance_metadata_tags: enabled + register: _create_check + check_mode: true + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _templates + + - name: Ensure module reported change while the template was not created + ansible.builtin.assert: + that: + - _create_check is changed + - _templates.launch_templates | length == 0 + + - name: Create launch template + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + image_id: "{{ ec2_ami_id }}" + instance_type: c4.large + cpu_options: + core_count: 1 + threads_per_core: 3 + network_interfaces: + - device_index: 0 + associate_public_ip_address: false + delete_on_termination: true + - device_index: 1 + associate_public_ip_address: true + delete_on_termination: false + ipv6_address_count: 1 + metadata_options: + http_put_response_hop_limit: 1 + http_tokens: required + http_protocol_ipv6: enabled + instance_metadata_tags: enabled + register: _create + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _templates + + - name: Ensure module reported change while the template was not created + ansible.builtin.assert: + that: + - _create is changed + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.cpu_options.core_count == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.cpu_options.threads_per_core == 3 + - _templates.launch_templates[0].versions[0].launch_template_data.metadata_options.http_put_response_hop_limit == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.metadata_options.http_tokens == 'required' + - _templates.launch_templates[0].versions[0].launch_template_data.metadata_options.http_protocol_ipv6 == 'enabled' + - _templates.launch_templates[0].versions[0].launch_template_data.metadata_options.instance_metadata_tags == 'enabled' + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[0].associate_public_ip_address == False + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[0].delete_on_termination == True + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[0].device_index == 0 + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[1].associate_public_ip_address == True + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[1].delete_on_termination == False + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[1].device_index == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[1].ipv6_address_count == 1 + + - name: Create launch template once again with same parameters (idempotency) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + image_id: "{{ ec2_ami_id }}" + instance_type: c4.large + cpu_options: + core_count: 1 + threads_per_core: 3 + network_interfaces: + - device_index: 0 + associate_public_ip_address: false + delete_on_termination: true + - device_index: 1 + associate_public_ip_address: true + delete_on_termination: false + ipv6_address_count: 1 + metadata_options: + http_put_response_hop_limit: 1 + http_tokens: required + http_protocol_ipv6: enabled + instance_metadata_tags: enabled + register: _create_idempotency + + - name: Retrieve Launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _templates + + - name: Ensure module reported change while the template was not created + ansible.builtin.assert: + that: + - _create_idempotency is not changed + - _templates.launch_templates | length == 1 + - _templates.launch_templates[0].versions | length == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.cpu_options.core_count == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.cpu_options.threads_per_core == 3 + - _templates.launch_templates[0].versions[0].launch_template_data.metadata_options.http_put_response_hop_limit == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.metadata_options.http_tokens == 'required' + - _templates.launch_templates[0].versions[0].launch_template_data.metadata_options.http_protocol_ipv6 == 'enabled' + - _templates.launch_templates[0].versions[0].launch_template_data.metadata_options.instance_metadata_tags == 'enabled' + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[0].associate_public_ip_address == False + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[0].delete_on_termination == True + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[0].device_index == 0 + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[1].associate_public_ip_address == True + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[1].delete_on_termination == False + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[1].device_index == 1 + - _templates.launch_templates[0].versions[0].launch_template_data.network_interfaces[1].ipv6_address_count == 1 + + always: + - name: delete the template + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + state: absent + ignore_errors: true diff --git a/tests/integration/targets/ec2_launch_template/tasks/versions.yml b/tests/integration/targets/ec2_launch_template/tasks/versions.yml index a9e40cd0843..666c5653836 100644 --- a/tests/integration/targets/ec2_launch_template/tasks/versions.yml +++ b/tests/integration/targets/ec2_launch_template/tasks/versions.yml @@ -1,95 +1,462 @@ -- block: - - name: create simple instance template - ec2_launch_template: - name: "{{ resource_prefix }}-simple" +- name: Test launch template versioning + vars: + test_launch_template_name: "{{ resource_prefix }}-versioning" + block: + #===================================================================== + # Create the launch template + #===================================================================== + - name: Create a launch template (check mode) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" image_id: "{{ ec2_ami_id }}" tags: TestId: "{{ resource_prefix }}" instance_type: c4.large - register: lt + register: _create_check + check_mode: true - - name: instance with cpu_options created with the right options - assert: + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure module reported changed while the template was not created + ansible.builtin.assert: + that: + - _create_check is changed + - '"default_version" not in _create_check' + - '"latest_version" not in _create_check' + - _template_info.launch_templates | length == 0 + + - name: Create a launch template + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: c4.large + register: _create + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + launch_template_ids: + - "{{ _create.template.launch_template_id }}" + register: _template_info + + - name: Ensure the launch template was created with the right version + ansible.builtin.assert: that: - - lt is success - - lt is changed - - lt.default_version == 1 - - lt.latest_version == 1 + - _create is changed + - _create.default_version == 1 + - _create.latest_version == 1 + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 1 + - _template_info.launch_templates[0].latest_version_number == 1 + - _template_info.launch_templates[0].versions | length == 1 + - _template_info.launch_templates[0].versions.0.launch_template_data.image_id == ec2_ami_id + - _template_info.launch_templates[0].versions.0.launch_template_data.instance_type == "c4.large" - - name: update simple instance template - ec2_launch_template: - name: "{{ resource_prefix }}-simple" + - name: Create the same launch template once again + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: c4.large + register: _create_idempotency + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure the module did not reported change (idempotency) + ansible.builtin.assert: + that: + - _create_idempotency is not changed + - _create.default_version == 1 + - _create.latest_version == 1 + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 1 + - _template_info.launch_templates[0].latest_version_number == 1 + - _template_info.launch_templates[0].versions | length == 1 + - _template_info.launch_templates[0].versions.0.launch_template_data.image_id == ec2_ami_id + - _template_info.launch_templates[0].versions.0.launch_template_data.instance_type == "c4.large" + + #===================================================================== + # Create a new version of the launch template (set first version as default) + #===================================================================== + - name: Create a new version of the launch template (check mode) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" default_version: 1 image_id: "{{ ec2_ami_id }}" tags: TestId: "{{ resource_prefix }}" instance_type: m5.large - register: lt + register: _update_check + check_mode: true + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info - - name: instance with cpu_options created with the right options - assert: + - name: Ensure the module reported change in check mode + ansible.builtin.assert: that: - - lt is success - - lt is changed - - lt.default_version == 1 - - lt.latest_version == 2 + - _update_check is changed + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 1 + - _template_info.launch_templates[0].latest_version_number == 1 + - _template_info.launch_templates[0].versions | length == 1 - - name: update simple instance template - ec2_launch_template: - name: "{{ resource_prefix }}-simple" + - name: Create a new version of the launch template + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + default_version: 1 + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: m5.large + register: _update + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure the launch template latest version has changed + ansible.builtin.assert: + that: + - _update is changed + - _update.default_version == 1 + - _update.latest_version == 2 + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 1 + - _template_info.launch_templates[0].latest_version_number == 2 + - _template_info.launch_templates[0].versions | length == 2 + - created_template.launch_template_data.image_id == ec2_ami_id + - created_template.launch_template_data.instance_type == "m5.large" + vars: + created_template: "{{ _template_info.launch_templates[0].versions | selectattr('version_number', 'equalto', 2) | first }}" + + - name: Create a new version of the launch template (idempotency) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + default_version: 1 + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: m5.large + register: _update_idempotency + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure the module did not reported change (idempotency) + ansible.builtin.assert: + that: + - _update_idempotency is not changed + - _update_idempotency.default_version == 1 + - _update_idempotency.latest_version == 2 + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 1 + - _template_info.launch_templates[0].latest_version_number == 2 + - _template_info.launch_templates[0].versions | length == 2 + + #===================================================================== + # Set the latest version of the launch template as default + #===================================================================== + - name: Set the latest version of the launch template as default (check mode) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + default_version: latest + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: m5.large + register: _set_version_check + check_mode: true + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure the module reported change in check mode + ansible.builtin.assert: + that: + - _set_version_check is changed + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 1 + - _template_info.launch_templates[0].latest_version_number == 2 + - _template_info.launch_templates[0].versions | length == 2 + + - name: Set the latest version of the launch template as default + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + default_version: latest + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: m5.large + register: _set_version + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure the launch template latest version has changed + ansible.builtin.assert: + that: + - _set_version is changed + - _set_version.default_version == 2 + - _set_version.latest_version == 2 + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 2 + - _template_info.launch_templates[0].latest_version_number == 2 + - _template_info.launch_templates[0].versions | length == 2 + - created_template.launch_template_data.image_id == ec2_ami_id + - created_template.launch_template_data.instance_type == "m5.large" + vars: + created_template: "{{ _template_info.launch_templates[0].versions | selectattr('version_number', 'equalto', 2) | first }}" + + - name: Set the latest version of the launch template as default (idempotency) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + default_version: latest + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: m5.large + register: _set_version_idempotency + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + filters: + launch-template-name: "{{ test_launch_template_name }}" + register: _template_info + + - name: Ensure the module did not reported change (idempotency) + ansible.builtin.assert: + that: + - _set_version_idempotency is not changed + - _set_version_idempotency.default_version == 2 + - _set_version_idempotency.latest_version == 2 + - _template_info.launch_templates[0].default_version_number == 2 + - _template_info.launch_templates[0].latest_version_number == 2 + - _template_info.launch_templates[0].versions | length == 2 + + #===================================================================== + # Create another version + #===================================================================== + - name: Create a new launch template version (check mode) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: t3.medium + register: _another_version_check + check_mode: true + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + launch_template_ids: + - "{{ _create.template.launch_template_id }}" + register: _template_info + + - name: Ensure the module reported change in check_mode + ansible.builtin.assert: + that: + - _another_version_check is changed + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 2 + - _template_info.launch_templates[0].latest_version_number == 2 + - _template_info.launch_templates[0].versions | length == 2 + + - name: Create a new launch template version + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" image_id: "{{ ec2_ami_id }}" tags: TestId: "{{ resource_prefix }}" instance_type: t3.medium - register: lt + register: _another_version + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + launch_template_ids: + - "{{ _create.template.launch_template_id }}" + register: _template_info - - name: instance with cpu_options created with the right options - assert: + - name: Ensure the launch template latest version has changed + ansible.builtin.assert: that: - - lt is success - - lt is changed - - lt.default_version == 3 - - lt.latest_version == 3 + - _another_version is changed + - _another_version.default_version == 3 + - _another_version.latest_version == 3 + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 3 + - _template_info.launch_templates[0].latest_version_number == 3 + - _template_info.launch_templates[0].versions | length == 3 + - created_template.launch_template_data.image_id == ec2_ami_id + - created_template.launch_template_data.instance_type == "t3.medium" + vars: + created_template: "{{ _template_info.launch_templates[0].versions | selectattr('version_number', 'equalto', 3) | first }}" - - name: create new template version based on an old version - ec2_launch_template: - name: "{{ resource_prefix }}-simple" + - name: Create a new launch template version (idempotency) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + instance_type: t3.medium + register: _another_version_idempotency + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + launch_template_ids: + - "{{ _create.template.launch_template_id }}" + register: _template_info + + - name: Ensure the module did not reported change (idempotency) + ansible.builtin.assert: + that: + - _another_version_idempotency is not changed + - _another_version_idempotency.default_version == 3 + - _another_version_idempotency.latest_version == 3 + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 3 + - _template_info.launch_templates[0].latest_version_number == 3 + - _template_info.launch_templates[0].versions | length == 3 + + #===================================================================== + # Create another version based on an old version + #===================================================================== + - name: Create new template version based on an old version (check mode) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + cpu_options: + core_count: 1 + threads_per_core: 1 + source_version: 1 + register: _version_based_on_old_version_check + check_mode: true + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + launch_template_ids: + - "{{ _create.template.launch_template_id }}" + register: _template_info + + - name: Ensure module reported change in check mode + ansible.builtin.assert: + that: + - _version_based_on_old_version_check is changed + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 3 + - _template_info.launch_templates[0].latest_version_number == 3 + - _template_info.launch_templates[0].versions | length == 3 + + - name: Create new template version based on an old version + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" cpu_options: core_count: 1 threads_per_core: 1 source_version: 1 - register: lt + register: _version_based_on_old_version + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + launch_template_ids: + - "{{ _create.template.launch_template_id }}" + register: _template_info - - name: instance with cpu_options created with the right options - assert: + - name: Ensure the new launch template has been created with the right options + ansible.builtin.assert: that: - - lt is success - - lt is changed - - lt.default_version == 4 - - lt.latest_version == 4 - - lt.latest_template.launch_template_data.instance_type == "c4.large" + - _version_based_on_old_version is changed + - _version_based_on_old_version.default_version == 4 + - _version_based_on_old_version.latest_version == 4 + - _version_based_on_old_version.latest_template.launch_template_data.instance_type == "c4.large" + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 4 + - _template_info.launch_templates[0].latest_version_number == 4 + - _template_info.launch_templates[0].versions | length == 4 + - created_template.launch_template_data.image_id == ec2_ami_id + - created_template.launch_template_data.cpu_options.core_count == 1 + - created_template.launch_template_data.cpu_options.threads_per_core == 1 + vars: + created_template: "{{ _template_info.launch_templates[0].versions | selectattr('version_number', 'equalto', 4) | first }}" - - name: update simple instance template - ec2_launch_template: - name: "{{ resource_prefix }}-simple" + #===================================================================== + # Create another version with updated description + #===================================================================== + - name: Create a launch template version with another description (check mode) + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" version_description: "Fix something." - register: lt + register: _version_description_check + check_mode: true + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + launch_template_ids: + - "{{ _create.template.launch_template_id }}" + register: _template_info + + - name: Ensure module reported change in check mode + ansible.builtin.assert: + that: + - _version_description_check is changed + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 4 + - _template_info.launch_templates[0].latest_version_number == 4 + - _template_info.launch_templates[0].versions | length == 4 + + - name: Create a launch template version with another description + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" + version_description: "Fix something." + register: _version_description + + - name: Read launch template information + amazon.aws.ec2_launch_template_info: + launch_template_ids: + - "{{ _create.template.launch_template_id }}" + register: _template_info - - name: instance with cpu_options created with the right options - assert: + - name: Ensure module reported change + ansible.builtin.assert: that: - - lt is success - - lt is changed - - lt.default_version == 5 - - lt.latest_version == 5 - - lt.latest_template.version_description == "Fix something." + - _version_description is changed + - _version_description.default_version == 5 + - _version_description.latest_version == 5 + - _version_description.latest_template.version_description == "Fix something." + - _template_info.launch_templates | length == 1 + - _template_info.launch_templates[0].default_version_number == 5 + - _template_info.launch_templates[0].latest_version_number == 5 + - _template_info.launch_templates[0].versions | length == 5 + - created_template.version_description == "Fix something." + vars: + created_template: "{{ _template_info.launch_templates[0].versions | selectattr('version_number', 'equalto', 5) | first }}" always: - - name: delete the template - ec2_launch_template: - name: "{{ resource_prefix }}-simple" + - name: Delete the template + community.aws.ec2_launch_template: + name: "{{ test_launch_template_name }}" state: absent - register: del_lt - retries: 10 - until: del_lt is not failed ignore_errors: true