New module: AWS EC2 Launch Template (#46972)
* Add launch template integration tests
This commit is contained in:
parent
5a7f2b6b08
commit
a51eca364f
13 changed files with 1133 additions and 0 deletions
|
@ -140,6 +140,8 @@ groupings:
|
||||||
- aws
|
- aws
|
||||||
ec2_key:
|
ec2_key:
|
||||||
- aws
|
- aws
|
||||||
|
ec2_launch_template:
|
||||||
|
- aws
|
||||||
ec2_lc:
|
ec2_lc:
|
||||||
- aws
|
- aws
|
||||||
ec2_lc_facts:
|
ec2_lc_facts:
|
||||||
|
|
649
lib/ansible/modules/cloud/amazon/ec2_launch_template.py
Normal file
649
lib/ansible/modules/cloud/amazon/ec2_launch_template.py
Normal file
|
@ -0,0 +1,649 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright (c) 2018 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {
|
||||||
|
'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: ec2_launch_template
|
||||||
|
version_added: "2.8"
|
||||||
|
short_description: Manage EC2 launch templates
|
||||||
|
description:
|
||||||
|
- Create, modify, and delete EC2 Launch Templates, which can be used to
|
||||||
|
create individual instances or with Autoscaling Groups.
|
||||||
|
- The I(ec2_instance) and I(ec2_asg) modules can, instead of specifying all
|
||||||
|
parameters on those tasks, be passed a Launch Template which contains
|
||||||
|
settings like instance size, disk type, subnet, and more.
|
||||||
|
requirements:
|
||||||
|
- botocore
|
||||||
|
- boto3 >= 1.6.0
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- aws
|
||||||
|
- ec2
|
||||||
|
author:
|
||||||
|
- Ryan Scott Brown (@ryansb)
|
||||||
|
options:
|
||||||
|
template_id:
|
||||||
|
description:
|
||||||
|
- The ID for the launch template, can be used for all cases except creating a new Launch Template.
|
||||||
|
aliases: [id]
|
||||||
|
template_name:
|
||||||
|
description:
|
||||||
|
- The template name. This must be unique in the region-account combination you are using.
|
||||||
|
aliases: [name]
|
||||||
|
default_version:
|
||||||
|
description:
|
||||||
|
- Which version should be the default when users spin up new instances based on this template? By default, the latest version will be made the default.
|
||||||
|
default: latest
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Whether the launch template should exist or not. To delete only a
|
||||||
|
specific version of a launch template, combine I(state=absent) with
|
||||||
|
the I(version) option. By default, I(state=absent) will remove all
|
||||||
|
versions of the template.
|
||||||
|
choices: [present, absent]
|
||||||
|
default: present
|
||||||
|
block_device_mappings:
|
||||||
|
description:
|
||||||
|
- The block device mapping. Supplying both a snapshot ID and an encryption
|
||||||
|
value as arguments for block-device mapping results in an error. This is
|
||||||
|
because only blank volumes can be encrypted on start, and these are not
|
||||||
|
created from a snapshot. If a snapshot is the basis for the volume, it
|
||||||
|
contains data by definition and its encryption status cannot be changed
|
||||||
|
using this action.
|
||||||
|
suboptions:
|
||||||
|
device_name:
|
||||||
|
description: The device name (for example, /dev/sdh or xvdh).
|
||||||
|
no_device:
|
||||||
|
description: Suppresses the specified device included in the block device mapping of the AMI.
|
||||||
|
virtual_name:
|
||||||
|
description: >
|
||||||
|
The virtual device name (ephemeralN). Instance store volumes are
|
||||||
|
numbered starting from 0. An instance type with 2 available instance
|
||||||
|
store volumes can specify mappings for ephemeral0 and ephemeral1. The
|
||||||
|
number of available instance store volumes depends on the instance
|
||||||
|
type. After you connect to the instance, you must mount the volume.
|
||||||
|
ebs:
|
||||||
|
description: Parameters used to automatically set up EBS volumes when the instance is launched.
|
||||||
|
suboptions:
|
||||||
|
delete_on_termintation:
|
||||||
|
description: Indicates whether the EBS volume is deleted on instance termination.
|
||||||
|
type: bool
|
||||||
|
encrypted:
|
||||||
|
description: >
|
||||||
|
Indicates whether the EBS volume is encrypted. Encrypted volumes
|
||||||
|
can only be attached to instances that support Amazon EBS
|
||||||
|
encryption. If you are creating a volume from a snapshot, you
|
||||||
|
can't specify an encryption value.
|
||||||
|
iops:
|
||||||
|
description:
|
||||||
|
- The number of I/O operations per second (IOPS) that the volume
|
||||||
|
supports. For io1, this represents the number of IOPS that are
|
||||||
|
provisioned for the volume. For gp2, this represents the baseline
|
||||||
|
performance of the volume and the rate at which the volume
|
||||||
|
accumulates I/O credits for bursting. For more information about
|
||||||
|
General Purpose SSD baseline performance, I/O credits, and
|
||||||
|
bursting, see Amazon EBS Volume Types in the Amazon Elastic
|
||||||
|
Compute Cloud User Guide.
|
||||||
|
- >
|
||||||
|
Condition: This parameter is required for requests to create io1
|
||||||
|
volumes; it is not used in requests to create gp2, st1, sc1, or
|
||||||
|
standard volumes.
|
||||||
|
kms_key_id:
|
||||||
|
description: The ARN of the AWS Key Management Service (AWS KMS) CMK used for encryption.
|
||||||
|
snapshot_id:
|
||||||
|
description: The ID of the snapshot to create the volume from
|
||||||
|
volume_size:
|
||||||
|
description:
|
||||||
|
- The size of the volume, in GiB.
|
||||||
|
- "Default: If you're creating the volume from a snapshot and don't specify a volume size, the default is the snapshot size."
|
||||||
|
volume_type:
|
||||||
|
description: The volume type
|
||||||
|
cpu_options:
|
||||||
|
description:
|
||||||
|
- Choose CPU settings for the EC2 instances that will be created with this template.
|
||||||
|
- For more information, see U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html)
|
||||||
|
suboptions:
|
||||||
|
core_count:
|
||||||
|
description: The number of CPU cores for the instance.
|
||||||
|
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.
|
||||||
|
credit_specification:
|
||||||
|
description: The credit option for CPU usage of the instance. Valid for T2 or T3 instances only.
|
||||||
|
suboptions:
|
||||||
|
cpu_credits:
|
||||||
|
description: >
|
||||||
|
The credit option for CPU usage of a T2 or T3 instance. Valid values
|
||||||
|
are I(standard) and I(unlimited).
|
||||||
|
choices: [standard, unlimited]
|
||||||
|
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).
|
||||||
|
type: bool
|
||||||
|
ebs_optimized:
|
||||||
|
description: >
|
||||||
|
Indicates whether the instance is optimized for Amazon EBS I/O. This
|
||||||
|
optimization provides dedicated throughput to Amazon EBS and an optimized
|
||||||
|
configuration stack to provide optimal Amazon EBS I/O performance. This
|
||||||
|
optimization isn't available with all instance types. Additional usage
|
||||||
|
charges apply when using an EBS-optimized instance.
|
||||||
|
type: bool
|
||||||
|
elastic_gpu_specifications:
|
||||||
|
description: Settings for Elastic GPU attachments. See U(https://aws.amazon.com/ec2/elastic-gpus/) for details.
|
||||||
|
suboptions:
|
||||||
|
type:
|
||||||
|
description: The type of Elastic GPU to attach
|
||||||
|
iam_instance_profile:
|
||||||
|
description: >
|
||||||
|
The name or ARN of an IAM instance profile. Requires permissions to
|
||||||
|
describe existing instance roles to confirm ARN is properly formed.
|
||||||
|
image_id:
|
||||||
|
description: >
|
||||||
|
The AMI ID to use for new instances launched with this template. This
|
||||||
|
value is region-dependent since AMIs are not global resources.
|
||||||
|
instance_initiated_shutdown_behavior:
|
||||||
|
description: >
|
||||||
|
Indicates whether an instance stops or terminates when you initiate
|
||||||
|
shutdown from the instance using the operating system shutdown command.
|
||||||
|
choices: [stop, terminate]
|
||||||
|
instance_market_options:
|
||||||
|
description: Options for alternative instance markets, currently only the spot market is supported.
|
||||||
|
suboptions:
|
||||||
|
market_type:
|
||||||
|
description: The market type. This should always be 'spot'.
|
||||||
|
spot_options:
|
||||||
|
description: Spot-market specific settings
|
||||||
|
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).
|
||||||
|
instance_interruption_behavior:
|
||||||
|
description: The behavior when a Spot Instance is interrupted. The default is I(terminate)
|
||||||
|
choices: [hibernate, stop, terminate]
|
||||||
|
max_price:
|
||||||
|
description: The highest hourly price you're willing to pay for this Spot Instance.
|
||||||
|
spot_instance_type:
|
||||||
|
description: The request type to send.
|
||||||
|
choices: [one-time, persistent]
|
||||||
|
type: dict
|
||||||
|
instance_type:
|
||||||
|
description: >
|
||||||
|
The instance type, such as I(c5.2xlarge). For a full list of instance types, see
|
||||||
|
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html
|
||||||
|
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)
|
||||||
|
key_name:
|
||||||
|
description:
|
||||||
|
- The name of the key pair. You can create a key pair using
|
||||||
|
I(CreateKeyPair) or I(ImportKeyPair).
|
||||||
|
- 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.
|
||||||
|
monitoring:
|
||||||
|
description: Settings for instance monitoring
|
||||||
|
suboptions:
|
||||||
|
enabled:
|
||||||
|
type: bool
|
||||||
|
description: Whether to turn on detailed monitoring for new instances. This will incur extra charges.
|
||||||
|
network_interfaces:
|
||||||
|
description: One or more network interfaces.
|
||||||
|
suboptions:
|
||||||
|
associate_public_ip_address:
|
||||||
|
description: Associates a public IPv4 address with eth0 for a new network interface.
|
||||||
|
type: bool
|
||||||
|
delete_on_termination:
|
||||||
|
description: Indicates whether the network interface is deleted when the instance is terminated.
|
||||||
|
type: bool
|
||||||
|
description:
|
||||||
|
description: A description for the network interface.
|
||||||
|
device_index:
|
||||||
|
description: The device index for the network interface attachment.
|
||||||
|
groups:
|
||||||
|
description: List of security group IDs to include on this instance
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
network_interface_id:
|
||||||
|
description: The eni ID of a network interface to attach.
|
||||||
|
private_ip_address:
|
||||||
|
description: The primary private IPv4 address of the network interface.
|
||||||
|
private_ip_addresses:
|
||||||
|
description: One or more private IPv4 addresses.
|
||||||
|
suboptions:
|
||||||
|
primary:
|
||||||
|
description: >
|
||||||
|
Indicates whether the private IPv4 address is the primary private
|
||||||
|
IPv4 address. Only one IPv4 address can be designated as primary.
|
||||||
|
private_ip_address:
|
||||||
|
description: The primary private IPv4 address of the network interface.
|
||||||
|
subnet_id:
|
||||||
|
description: The ID of the subnet for the network interface.
|
||||||
|
secondary_private_ip_address_count:
|
||||||
|
description: The number of secondary private IPv4 addresses to assign to a network interface.
|
||||||
|
placement:
|
||||||
|
description: The placement group settings for the instance.
|
||||||
|
suboptions:
|
||||||
|
affinity:
|
||||||
|
description: The affinity setting for an instance on a Dedicated Host.
|
||||||
|
availability_zone:
|
||||||
|
description: The Availability Zone for the instance.
|
||||||
|
group_name:
|
||||||
|
description: The name of the placement group for the instance.
|
||||||
|
host_id:
|
||||||
|
description: The ID of the Dedicated Host for the instance.
|
||||||
|
tenancy:
|
||||||
|
description: >
|
||||||
|
The tenancy of the instance (if the instance is running in a VPC). An
|
||||||
|
instance with a tenancy of dedicated runs on single-tenant hardware.
|
||||||
|
ram_disk_id:
|
||||||
|
description: >
|
||||||
|
The ID of the RAM disk to launch the instance with. 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)
|
||||||
|
security_group_ids:
|
||||||
|
description: A list of security group IDs (VPC or EC2-Classic) that the new instances will be added to.
|
||||||
|
type: list
|
||||||
|
security_groups:
|
||||||
|
description: A list of security group names (VPC or EC2-Classic) that the new instances will be added to.
|
||||||
|
type: list
|
||||||
|
tags:
|
||||||
|
type: dict
|
||||||
|
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."
|
||||||
|
user_data:
|
||||||
|
description: >
|
||||||
|
The Base64-encoded user data to make available to the instance. For more information, see the Linux
|
||||||
|
U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) and Windows
|
||||||
|
U(http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-instance-metadata.html#instancedata-add-user-data)
|
||||||
|
documentation on user-data.
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Make instance with an instance_role
|
||||||
|
ec2_launch_template:
|
||||||
|
name: "test-with-instance-role"
|
||||||
|
image_id: "ami-foobarbaz"
|
||||||
|
key_name: my_ssh_key
|
||||||
|
instance_type: t2.micro
|
||||||
|
iam_instance_profile: myTestProfile
|
||||||
|
disable_api_termination: true
|
||||||
|
|
||||||
|
- name: Make one with a different instance type, but leave the older version as default
|
||||||
|
ec2_launch_template:
|
||||||
|
name: "test-with-instance-role"
|
||||||
|
image_id: "ami-foobarbaz"
|
||||||
|
default_version: 1
|
||||||
|
key_name: my_ssh_key
|
||||||
|
instance_type: c5.4xlarge
|
||||||
|
iam_instance_profile: myTestProfile
|
||||||
|
disable_api_termination: true
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
latest_version:
|
||||||
|
description: Latest available version of the launch template
|
||||||
|
returned: when state=present
|
||||||
|
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
|
||||||
|
type: int
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.module_utils.aws.core import AnsibleAWSModule, is_boto3_error_code, get_boto3_client_method_parameters
|
||||||
|
from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict, snake_dict_to_camel_dict
|
||||||
|
from ansible.module_utils.ec2 import ansible_dict_to_boto3_tag_list, AWSRetry, boto3_tag_list_to_ansible_dict, ansible_dict_to_boto3_tag_list
|
||||||
|
|
||||||
|
try:
|
||||||
|
from botocore.exceptions import ClientError, BotoCoreError, WaiterError
|
||||||
|
except ImportError:
|
||||||
|
pass # caught by AnsibleAWSModule
|
||||||
|
|
||||||
|
|
||||||
|
def determine_iam_role(module, name_or_arn):
|
||||||
|
if re.match(r'^arn:aws:iam::\d+:instance-profile/[\w+=/,.@-]+$', name_or_arn):
|
||||||
|
return 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="Could not find instance_role {0}".format(name_or_arn))
|
||||||
|
except (BotoCoreError, ClientError) as e: # pylint: disable=duplicate-except
|
||||||
|
module.fail_json_aws(e, msg="An error occurred while searching for instance_role {0}. Please try supplying the full ARN.".format(name_or_arn))
|
||||||
|
|
||||||
|
|
||||||
|
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')])
|
||||||
|
elif module.params.get('template_name'):
|
||||||
|
matches = ec2.describe_launch_templates(LaunchTemplateNames=[module.params.get('template_name')])
|
||||||
|
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='Launch template with ID {0} is not a valid ID. It should start with `lt-....`'.format(
|
||||||
|
module.params.get('launch_template_id')))
|
||||||
|
except is_boto3_error_code('InvalidLaunchTemplateId.NotFoundException') as e: # pylint: disable=duplicate-except
|
||||||
|
module.fail_json_aws(
|
||||||
|
e, msg='Launch template with ID {0} could not be found, please supply a name '
|
||||||
|
'instead so that a new template can be created'.format(module.params.get('launch_template_id')))
|
||||||
|
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.')
|
||||||
|
else:
|
||||||
|
template = matches['LaunchTemplates'][0]
|
||||||
|
template_id, template_version, template_default = template['LaunchTemplateId'], template['LatestVersionNumber'], template['DefaultVersionNumber']
|
||||||
|
try:
|
||||||
|
return template, ec2.describe_launch_template_versions(LaunchTemplateId=template_id)['LaunchTemplateVersions']
|
||||||
|
except (ClientError, BotoCoreError, WaiterError) as e:
|
||||||
|
module.fail_json_aws(e, msg='Could not find launch template versions for {0} (ID: {1}).'.format(template['LaunchTemplateName'], template_id))
|
||||||
|
|
||||||
|
|
||||||
|
def params_to_launch_data(module, template_params):
|
||||||
|
if template_params.get('tags'):
|
||||||
|
template_params['tag_specifications'] = [
|
||||||
|
{
|
||||||
|
'resource_type': r_type,
|
||||||
|
'tags': [
|
||||||
|
{'Key': k, 'Value': v} for k, v
|
||||||
|
in template_params['tags'].items()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
for r_type in ('instance', 'network-interface', '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'])
|
||||||
|
params = snake_dict_to_camel_dict(
|
||||||
|
dict((k, v) for k, v in template_params.items() if v is not None),
|
||||||
|
capitalize_first=True,
|
||||||
|
)
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def delete_template(module):
|
||||||
|
ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff())
|
||||||
|
template, template_versions = existing_templates(module)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
if v_resp['UnsuccessfullyDeletedLaunchTemplateVersions']:
|
||||||
|
module.warn('Failed to delete template versions {0} on launch template {1}'.format(
|
||||||
|
v_resp['UnsuccessfullyDeletedLaunchTemplateVersions'],
|
||||||
|
template['LaunchTemplateId'],
|
||||||
|
))
|
||||||
|
deleted_versions = [camel_dict_to_snake_dict(v) for v in v_resp['SuccessfullyDeletedLaunchTemplateVersions']]
|
||||||
|
except (ClientError, BotoCoreError) as e:
|
||||||
|
module.fail_json_aws(e, msg="Could not delete existing versions of the launch template {0}".format(template['LaunchTemplateId']))
|
||||||
|
try:
|
||||||
|
resp = ec2.delete_launch_template(
|
||||||
|
LaunchTemplateId=template['LaunchTemplateId'],
|
||||||
|
)
|
||||||
|
except (ClientError, BotoCoreError) as e:
|
||||||
|
module.fail_json_aws(e, msg="Could not delete launch template {0}".format(template['LaunchTemplateId']))
|
||||||
|
return {
|
||||||
|
'deleted_versions': deleted_versions,
|
||||||
|
'deleted_template': camel_dict_to_snake_dict(resp['LaunchTemplate']),
|
||||||
|
'changed': True,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {'changed': False}
|
||||||
|
|
||||||
|
|
||||||
|
def create_or_update(module, template_options):
|
||||||
|
ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff())
|
||||||
|
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))
|
||||||
|
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']:
|
||||||
|
out['changed'] = False
|
||||||
|
return out
|
||||||
|
try:
|
||||||
|
resp = ec2.create_launch_template_version(
|
||||||
|
LaunchTemplateId=template['LaunchTemplateId'],
|
||||||
|
LaunchTemplateData=lt_data,
|
||||||
|
ClientToken=uuid4().hex,
|
||||||
|
aws_retry=True,
|
||||||
|
)
|
||||||
|
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='default_version param was not a valid integer, got "{0}"'.format(module.params.get('default_version')))
|
||||||
|
set_default = ec2.modify_launch_template(
|
||||||
|
LaunchTemplateId=template['LaunchTemplateId'],
|
||||||
|
DefaultVersion=to_text(int(module.params.get('default_version'))),
|
||||||
|
ClientToken=uuid4().hex,
|
||||||
|
aws_retry=True,
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def format_module_output(module):
|
||||||
|
output = {}
|
||||||
|
template, template_versions = existing_templates(module)
|
||||||
|
template = camel_dict_to_snake_dict(template)
|
||||||
|
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]
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
template_options = dict(
|
||||||
|
block_device_mappings=dict(
|
||||||
|
type='list',
|
||||||
|
options=dict(
|
||||||
|
device_name=dict(),
|
||||||
|
ebs=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
delete_on_termination=dict(type='bool'),
|
||||||
|
encrypted=dict(type='bool'),
|
||||||
|
iops=dict(type='int'),
|
||||||
|
kms_key_id=dict(),
|
||||||
|
snapshot_id=dict(),
|
||||||
|
volume_size=dict(type='int'),
|
||||||
|
volume_type=dict(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
no_device=dict(),
|
||||||
|
virtual_name=dict(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cpu_options=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
core_count=dict(type='int'),
|
||||||
|
threads_per_core=dict(type='int'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
credit_specification=dict(
|
||||||
|
dict(type='dict'),
|
||||||
|
options=dict(
|
||||||
|
cpu_credits=dict(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
disable_api_termination=dict(type='bool'),
|
||||||
|
ebs_optimized=dict(type='bool'),
|
||||||
|
elastic_gpu_specifications=dict(
|
||||||
|
options=dict(type=dict()),
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
iam_instance_profile=dict(),
|
||||||
|
image_id=dict(),
|
||||||
|
instance_initiated_shutdown_behavior=dict(choices=['stop', 'terminate']),
|
||||||
|
instance_market_options=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
market_type=dict(),
|
||||||
|
spot_options=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
block_duration_minutes=dict(type='int'),
|
||||||
|
instance_interruption_behavior=dict(choices=['hibernate', 'stop', 'terminate']),
|
||||||
|
max_price=dict(),
|
||||||
|
spot_instance_type=dict(choices=['one-time', 'persistent']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
instance_type=dict(),
|
||||||
|
kernel_id=dict(),
|
||||||
|
key_name=dict(),
|
||||||
|
monitoring=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
enabled=dict(type='bool')
|
||||||
|
),
|
||||||
|
),
|
||||||
|
network_interfaces=dict(
|
||||||
|
type='list',
|
||||||
|
options=dict(
|
||||||
|
associate_public_ip_address=dict(type='bool'),
|
||||||
|
delete_on_termination=dict(type='bool'),
|
||||||
|
description=dict(),
|
||||||
|
device_index=dict(type='int'),
|
||||||
|
groups=dict(type='list'),
|
||||||
|
ipv6_address_count=dict(type='int'),
|
||||||
|
ipv6_addresses=dict(type='list'),
|
||||||
|
network_interface_id=dict(),
|
||||||
|
private_ip_address=dict(),
|
||||||
|
subnet_id=dict(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
placement=dict(
|
||||||
|
options=dict(
|
||||||
|
affinity=dict(),
|
||||||
|
availability_zone=dict(),
|
||||||
|
group_name=dict(),
|
||||||
|
host_id=dict(),
|
||||||
|
tenancy=dict(),
|
||||||
|
),
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
ram_disk_id=dict(),
|
||||||
|
security_group_ids=dict(type='list'),
|
||||||
|
security_groups=dict(type='list'),
|
||||||
|
tags=dict(type='dict'),
|
||||||
|
user_data=dict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
arg_spec = dict(
|
||||||
|
state=dict(choices=['present', 'absent'], default='present'),
|
||||||
|
template_name=dict(aliases=['name']),
|
||||||
|
template_id=dict(aliases=['id']),
|
||||||
|
default_version=dict(default='latest'),
|
||||||
|
)
|
||||||
|
|
||||||
|
arg_spec.update(template_options)
|
||||||
|
|
||||||
|
module = AnsibleAWSModule(
|
||||||
|
argument_spec=arg_spec,
|
||||||
|
required_one_of=[
|
||||||
|
('template_name', 'template_id')
|
||||||
|
],
|
||||||
|
supports_check_mode=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if not module.boto3_at_least('1.6.0'):
|
||||||
|
module.fail_json(msg="ec2_launch_template requires boto3 >= 1.6.0")
|
||||||
|
|
||||||
|
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='Unsupported value "{0}" for `state` parameter'.format(module.params.get('state')))
|
||||||
|
|
||||||
|
module.exit_json(**out)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
2
test/integration/targets/ec2_launch_template/aliases
Normal file
2
test/integration/targets/ec2_launch_template/aliases
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
cloud/aws
|
||||||
|
unsupported
|
|
@ -0,0 +1,4 @@
|
||||||
|
- hosts: localhost
|
||||||
|
connection: local
|
||||||
|
roles:
|
||||||
|
- ec2_launch_template
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
resource_prefix: ansible-test-default-group
|
||||||
|
ec2_ami_image:
|
||||||
|
# https://wiki.centos.org/Cloud/AWS collected 2018-01-10
|
||||||
|
ap-northeast-1: ami-571e3c30
|
||||||
|
ap-northeast-2: ami-97cb19f9
|
||||||
|
ap-south-1: ami-11f0837e
|
||||||
|
ap-southeast-1: ami-30318f53
|
||||||
|
ap-southeast-2: ami-24959b47
|
||||||
|
ca-central-1: ami-daeb57be
|
||||||
|
eu-central-1: ami-7cbc6e13
|
||||||
|
eu-west-1: ami-0d063c6b
|
||||||
|
eu-west-2: ami-c22236a6
|
||||||
|
sa-east-1: ami-864f2dea
|
||||||
|
us-east-1: ami-ae7bfdb8
|
||||||
|
us-east-2: ami-9cbf9bf9
|
||||||
|
us-west-1: ami-7c280d1c
|
||||||
|
us-west-2: ami-0c2aba6c
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"Version": "2008-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"Service": "ec2.amazonaws.com"
|
||||||
|
},
|
||||||
|
"Action": "sts:AssumeRole"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
dependencies:
|
||||||
|
- prepare_tests
|
||||||
|
- setup_ec2
|
|
@ -0,0 +1,38 @@
|
||||||
|
- 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_image[aws_region] }}"
|
||||||
|
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
|
|
@ -0,0 +1,104 @@
|
||||||
|
- block:
|
||||||
|
- name: Create IAM role for test
|
||||||
|
iam_role:
|
||||||
|
name: "{{ resource_prefix }}-test-policy"
|
||||||
|
assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
|
||||||
|
state: present
|
||||||
|
create_instance_profile: yes
|
||||||
|
managed_policy:
|
||||||
|
- AmazonS3ReadOnlyAccess
|
||||||
|
register: iam_role
|
||||||
|
|
||||||
|
- name: Create second IAM role for test
|
||||||
|
iam_role:
|
||||||
|
name: "{{ resource_prefix }}-test-policy-2"
|
||||||
|
assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
|
||||||
|
state: present
|
||||||
|
create_instance_profile: yes
|
||||||
|
managed_policy:
|
||||||
|
- AmazonS3ReadOnlyAccess
|
||||||
|
register: iam_role_2
|
||||||
|
|
||||||
|
- name: Make instance with an instance_role
|
||||||
|
ec2_launch_template:
|
||||||
|
name: "{{ resource_prefix }}-test-instance-role"
|
||||||
|
image_id: "{{ ec2_ami_image[aws_region] }}"
|
||||||
|
instance_type: t2.micro
|
||||||
|
iam_instance_profile: "{{ resource_prefix }}-test-policy"
|
||||||
|
register: template_with_role
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'template_with_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role.arn.replace(":role/", ":instance-profile/")'
|
||||||
|
|
||||||
|
- name: Create template again, with no change to instance_role
|
||||||
|
ec2_launch_template:
|
||||||
|
name: "{{ resource_prefix }}-test-instance-role"
|
||||||
|
image_id: "{{ ec2_ami_image[aws_region] }}"
|
||||||
|
instance_type: t2.micro
|
||||||
|
iam_instance_profile: "{{ resource_prefix }}-test-policy"
|
||||||
|
register: template_with_role
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'template_with_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role.arn.replace(":role/", ":instance-profile/")'
|
||||||
|
- 'template_with_role is not changed'
|
||||||
|
|
||||||
|
- name: Update instance with new instance_role
|
||||||
|
ec2_launch_template:
|
||||||
|
name: "{{ resource_prefix }}-test-instance-role"
|
||||||
|
image_id: "{{ ec2_ami_image[aws_region] }}"
|
||||||
|
instance_type: t2.micro
|
||||||
|
iam_instance_profile: "{{ resource_prefix }}-test-policy-2"
|
||||||
|
register: template_with_updated_role
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'template_with_updated_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role_2.arn.replace(":role/", ":instance-profile/")'
|
||||||
|
- 'template_with_updated_role.default_template.launch_template_data.iam_instance_profile.arn == iam_role_2.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'
|
||||||
|
|
||||||
|
- name: Re-set with same new instance_role
|
||||||
|
ec2_launch_template:
|
||||||
|
name: "{{ resource_prefix }}-test-instance-role"
|
||||||
|
image_id: "{{ ec2_ami_image[aws_region] }}"
|
||||||
|
instance_type: t2.micro
|
||||||
|
iam_instance_profile: "{{ resource_prefix }}-test-policy-2"
|
||||||
|
register: template_with_updated_role
|
||||||
|
|
||||||
|
- 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.arn.replace(":role/", ":instance-profile/")'
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: delete launch template
|
||||||
|
ec2_launch_template:
|
||||||
|
name: "{{ resource_prefix }}-test-instance-role"
|
||||||
|
state: absent
|
||||||
|
register: lt_removed
|
||||||
|
until: lt_removed is not failed
|
||||||
|
ignore_errors: yes
|
||||||
|
retries: 10
|
||||||
|
- name: Delete IAM role for test
|
||||||
|
iam_role:
|
||||||
|
name: "{{ resource_prefix }}-test-policy"
|
||||||
|
assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
|
||||||
|
state: absent
|
||||||
|
create_instance_profile: yes
|
||||||
|
register: iam_removed
|
||||||
|
until: iam_removed is not failed
|
||||||
|
ignore_errors: yes
|
||||||
|
retries: 10
|
||||||
|
- name: Delete IAM role for test
|
||||||
|
iam_role:
|
||||||
|
name: "{{ resource_prefix }}-test-policy-2"
|
||||||
|
assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
|
||||||
|
state: absent
|
||||||
|
create_instance_profile: yes
|
||||||
|
register: iam_2_removed
|
||||||
|
until: iam_2_removed is not failed
|
||||||
|
ignore_errors: yes
|
||||||
|
retries: 10
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
# A Note about ec2 environment variable name preference:
|
||||||
|
# - EC2_URL -> AWS_URL
|
||||||
|
# - EC2_ACCESS_KEY -> AWS_ACCESS_KEY_ID -> AWS_ACCESS_KEY
|
||||||
|
# - EC2_SECRET_KEY -> AWS_SECRET_ACCESS_KEY -> AWX_SECRET_KEY
|
||||||
|
# - EC2_REGION -> AWS_REGION
|
||||||
|
#
|
||||||
|
|
||||||
|
# - include: ../../../../../setup_ec2/tasks/common.yml module_name: ec2_instance
|
||||||
|
|
||||||
|
- module_defaults:
|
||||||
|
group/aws:
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
block:
|
||||||
|
- include_tasks: cpu_options.yml
|
||||||
|
- include_tasks: iam_instance_role.yml
|
||||||
|
|
||||||
|
always:
|
||||||
|
- debug:
|
||||||
|
msg: teardown goes here
|
|
@ -0,0 +1,216 @@
|
||||||
|
- 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_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_image[aws_region] }}"
|
||||||
|
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
|
||||||
|
<<: *aws_connection_info
|
||||||
|
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_image[aws_region] }}"
|
||||||
|
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
|
||||||
|
<<: *aws_connection_info
|
||||||
|
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_image[aws_region] }}"
|
||||||
|
tags:
|
||||||
|
TestId: "{{ resource_prefix }}"
|
||||||
|
Another: thing
|
||||||
|
security_groups: "{{ sg.group_id }}"
|
||||||
|
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
|
||||||
|
instance_type: t2.micro
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: add_another_tag
|
||||||
|
|
||||||
|
- ec2_instance_facts:
|
||||||
|
instance_ids: "{{ add_another_tag.instance_ids }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
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_image[aws_region] }}"
|
||||||
|
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
|
||||||
|
<<: *aws_connection_info
|
||||||
|
- ec2_instance_facts:
|
||||||
|
instance_ids: "{{ add_another_tag.instance_ids }}"
|
||||||
|
<<: *aws_connection_info
|
||||||
|
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
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that: result.changed
|
||||||
|
|
||||||
|
- name: Terminate instance
|
||||||
|
ec2_instance:
|
||||||
|
instance_ids: "{{ in_test_vpc.instance_ids }}"
|
||||||
|
state: absent
|
||||||
|
<<: *aws_connection_info
|
||||||
|
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_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
|
|
@ -0,0 +1,35 @@
|
||||||
|
- hosts: localhost
|
||||||
|
connection: local
|
||||||
|
vars:
|
||||||
|
resource_prefix: 'ansible-testing'
|
||||||
|
module_defaults:
|
||||||
|
group/aws:
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
tasks:
|
||||||
|
- block:
|
||||||
|
- name: Include vars file in roles/ec2_instance/defaults/main.yml
|
||||||
|
include_vars:
|
||||||
|
file: 'roles/ec2_launch_template/defaults/main.yml'
|
||||||
|
|
||||||
|
- name: create c4.large template (failure expected)
|
||||||
|
ec2_launch_template:
|
||||||
|
state: present
|
||||||
|
name: "ansible-test-{{ resource_prefix | regex_search('([0-9]+)$') }}-tpl"
|
||||||
|
instance_type: c4.large
|
||||||
|
register: ec2_lt
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: check that graceful error message is returned when creation with cpu_options and old botocore
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- ec2_lt is failed
|
||||||
|
- 'ec2_lt.msg == "ec2_launch_template requires boto3 >= 1.6.0"'
|
||||||
|
always:
|
||||||
|
- name: delete the c4.large template just in case it was created
|
||||||
|
ec2_launch_template:
|
||||||
|
state: absent
|
||||||
|
name: "ansible-test-{{ resource_prefix | regex_search('([0-9]+)$') }}-tpl"
|
||||||
|
ignore_errors: yes
|
26
test/integration/targets/ec2_launch_template/runme.sh
Executable file
26
test/integration/targets/ec2_launch_template/runme.sh
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# We don't set -u here, due to pypa/virtualenv#150
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
MYTMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
|
||||||
|
|
||||||
|
trap 'rm -rf "${MYTMPDIR}"' EXIT
|
||||||
|
|
||||||
|
# This is needed for the ubuntu1604py3 tests
|
||||||
|
# Ubuntu patches virtualenv to make the default python2
|
||||||
|
# but for the python3 tests we need virtualenv to use python3
|
||||||
|
PYTHON=${ANSIBLE_TEST_PYTHON_INTERPRETER:-python}
|
||||||
|
|
||||||
|
# Test graceful failure for older versions of botocore
|
||||||
|
export ANSIBLE_ROLES_PATH=../
|
||||||
|
virtualenv --system-site-packages --python "${PYTHON}" "${MYTMPDIR}/boto3-less-than-1.6.0"
|
||||||
|
source "${MYTMPDIR}/boto3-less-than-1.6.0/bin/activate"
|
||||||
|
"${PYTHON}" -m pip install 'boto3<1.6.0'
|
||||||
|
ansible-playbook -i ../../inventory -e @../../integration_config.yml -e @../../cloud-config-aws.yml -v playbooks/version_fail.yml "$@"
|
||||||
|
|
||||||
|
# Run full test suite
|
||||||
|
virtualenv --system-site-packages --python "${PYTHON}" "${MYTMPDIR}/boto3-recent"
|
||||||
|
source "${MYTMPDIR}/boto3-recent/bin/activate"
|
||||||
|
$PYTHON -m pip install 'boto3>1.6.0'
|
||||||
|
ansible-playbook -i ../../inventory -e @../../integration_config.yml -e @../../cloud-config-aws.yml -v playbooks/full_test.yml "$@"
|
Loading…
Reference in a new issue