1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/plugins/modules/spotinst_aws_elastigroup.py
patchback[bot] ba559d24cd
[PR #6744/13e9e4b1 backport][stable-7] Add more semantic markup ()
Add more semantic markup ()

Add more semantic markup.

(cherry picked from commit 13e9e4b196)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-06-20 11:08:32 +02:00

1595 lines
50 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
DOCUMENTATION = '''
---
module: spotinst_aws_elastigroup
short_description: Create, update or delete Spotinst AWS Elastigroups
author: Spotinst (@talzur)
description:
- Can create, update, or delete Spotinst AWS Elastigroups
Launch configuration is part of the elastigroup configuration,
so no additional modules are necessary for handling the launch configuration.
You will have to have a credentials file in this location - <home>/.spotinst/credentials
The credentials file must contain a row that looks like this
token = <YOUR TOKEN>
Full documentation available at https://help.spotinst.com/hc/en-us/articles/115003530285-Ansible-
requirements:
- python >= 2.7
- spotinst_sdk >= 1.0.38
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: none
diff_mode:
support: none
options:
credentials_path:
description:
- Optional parameter that allows to set a non-default credentials path.
default: ~/.spotinst/credentials
type: path
account_id:
description:
- Optional parameter that allows to set an account-id inside the module configuration.
By default this is retrieved from the credentials path.
type: str
token:
description:
- A Personal API Access Token issued by Spotinst.
- >-
When not specified, the module will try to obtain it, in that order, from: environment variable E(SPOTINST_TOKEN), or from the credentials path.
type: str
availability_vs_cost:
description:
- The strategy orientation.
- "The choices available are: V(availabilityOriented), V(costOriented), V(balanced)."
required: true
type: str
availability_zones:
description:
- A list of hash/dictionaries of Availability Zones that are configured in the elastigroup;
'[{"key":"value", "key":"value"}]';
keys allowed are
name (String),
subnet_id (String),
placement_group_name (String),
required: true
type: list
elements: dict
block_device_mappings:
description:
- A list of hash/dictionaries of Block Device Mappings for elastigroup instances;
You can specify virtual devices and EBS volumes.;
'[{"key":"value", "key":"value"}]';
keys allowed are
device_name (List of Strings),
virtual_name (String),
no_device (String),
ebs (Object, expects the following keys-
delete_on_termination(Boolean),
encrypted(Boolean),
iops (Integer),
snapshot_id(Integer),
volume_type(String),
volume_size(Integer))
type: list
elements: dict
chef:
description:
- The Chef integration configuration.;
Expects the following keys - chef_server (String),
organization (String),
user (String),
pem_key (String),
chef_version (String)
type: dict
draining_timeout:
description:
- Time for instance to be drained from incoming requests and deregistered from ELB before termination.
type: int
ebs_optimized:
description:
- Enable EBS optimization for supported instances which are not enabled by default.;
Note - additional charges will be applied.
type: bool
ebs_volume_pool:
description:
- A list of hash/dictionaries of EBS devices to reattach to the elastigroup when available;
'[{"key":"value", "key":"value"}]';
keys allowed are -
volume_ids (List of Strings),
device_name (String)
type: list
elements: dict
ecs:
description:
- The ECS integration configuration.;
Expects the following key -
cluster_name (String)
type: dict
elastic_ips:
description:
- List of ElasticIps Allocation Ids (example V(eipalloc-9d4e16f8)) to associate to the group instances
type: list
elements: str
fallback_to_od:
description:
- In case of no spots available, Elastigroup will launch an On-demand instance instead
type: bool
health_check_grace_period:
description:
- The amount of time, in seconds, after the instance has launched to start and check its health.
- If not specified, it defaults to V(300).
type: int
health_check_unhealthy_duration_before_replacement:
description:
- Minimal mount of time instance should be unhealthy for us to consider it unhealthy.
type: int
health_check_type:
description:
- The service to use for the health check.
- "The choices available are: V(ELB), V(HCS), V(TARGET_GROUP), V(MLB), V(EC2)."
type: str
iam_role_name:
description:
- The instance profile iamRole name
- Only use iam_role_arn, or iam_role_name
type: str
iam_role_arn:
description:
- The instance profile iamRole arn
- Only use iam_role_arn, or iam_role_name
type: str
id:
description:
- The group id if it already exists and you want to update, or delete it.
This will not work unless the uniqueness_by field is set to id.
When this is set, and the uniqueness_by field is set, the group will either be updated or deleted, but not created.
type: str
image_id:
description:
- The image Id used to launch the instance.;
In case of conflict between Instance type and image type, an error will be returned
required: true
type: str
key_pair:
description:
- Specify a Key Pair to attach to the instances
type: str
kubernetes:
description:
- The Kubernetes integration configuration.
Expects the following keys -
api_server (String),
token (String)
type: dict
lifetime_period:
description:
- Lifetime period
type: int
load_balancers:
description:
- List of classic ELB names
type: list
elements: str
max_size:
description:
- The upper limit number of instances that you can scale up to
required: true
type: int
mesosphere:
description:
- The Mesosphere integration configuration.
Expects the following key -
api_server (String)
type: dict
min_size:
description:
- The lower limit number of instances that you can scale down to
required: true
type: int
monitoring:
description:
- Describes whether instance Enhanced Monitoring is enabled
type: str
name:
description:
- Unique name for elastigroup to be created, updated or deleted
required: true
type: str
network_interfaces:
description:
- A list of hash/dictionaries of network interfaces to add to the elastigroup;
'[{"key":"value", "key":"value"}]';
keys allowed are -
description (String),
device_index (Integer),
secondary_private_ip_address_count (Integer),
associate_public_ip_address (Boolean),
delete_on_termination (Boolean),
groups (List of Strings),
network_interface_id (String),
private_ip_address (String),
subnet_id (String),
associate_ipv6_address (Boolean),
private_ip_addresses (List of Objects, Keys are privateIpAddress (String, required) and primary (Boolean))
type: list
elements: dict
on_demand_count:
description:
- Required if risk is not set
- Number of on demand instances to launch. All other instances will be spot instances.;
Either set this parameter or the risk parameter
type: int
on_demand_instance_type:
description:
- On-demand instance type that will be provisioned
type: str
opsworks:
description:
- The elastigroup OpsWorks integration configration.;
Expects the following key -
layer_id (String)
type: dict
persistence:
description:
- The Stateful elastigroup configration.;
Accepts the following keys -
should_persist_root_device (Boolean),
should_persist_block_devices (Boolean),
should_persist_private_ip (Boolean)
type: dict
product:
description:
- Operation system type.
- "Available choices are: V(Linux/UNIX), V(SUSE Linux), V(Windows), V(Linux/UNIX (Amazon VPC)), V(SUSE Linux (Amazon VPC))."
required: true
type: str
rancher:
description:
- The Rancher integration configuration.;
Expects the following keys -
version (String),
access_key (String),
secret_key (String),
master_host (String)
type: dict
right_scale:
description:
- The Rightscale integration configuration.;
Expects the following keys -
account_id (String),
refresh_token (String)
type: dict
risk:
description:
- Required if on demand is not set. The percentage of Spot instances to launch (0 - 100).
type: int
roll_config:
description:
- Roll configuration.;
If you would like the group to roll after updating, please use this feature.
Accepts the following keys -
batch_size_percentage(Integer, Required),
grace_period - (Integer, Required),
health_check_type(String, Optional)
type: dict
scheduled_tasks:
description:
- A list of hash/dictionaries of scheduled tasks to configure in the elastigroup;
'[{"key":"value", "key":"value"}]';
keys allowed are -
adjustment (Integer),
scale_target_capacity (Integer),
scale_min_capacity (Integer),
scale_max_capacity (Integer),
adjustment_percentage (Integer),
batch_size_percentage (Integer),
cron_expression (String),
frequency (String),
grace_period (Integer),
task_type (String, required),
is_enabled (Boolean)
type: list
elements: dict
security_group_ids:
description:
- One or more security group IDs. ;
In case of update it will override the existing Security Group with the new given array
required: true
type: list
elements: str
shutdown_script:
description:
- The Base64-encoded shutdown script that executes prior to instance termination.
Encode before setting.
type: str
signals:
description:
- A list of hash/dictionaries of signals to configure in the elastigroup;
keys allowed are -
name (String, required),
timeout (Integer)
type: list
elements: dict
spin_up_time:
description:
- Spin up time, in seconds, for the instance
type: int
spot_instance_types:
description:
- Spot instance type that will be provisioned.
required: true
type: list
elements: str
state:
choices:
- present
- absent
description:
- Create or delete the elastigroup
default: present
type: str
tags:
description:
- A list of tags to configure in the elastigroup. Please specify list of keys and values (key colon value);
type: list
elements: dict
target:
description:
- The number of instances to launch
required: true
type: int
target_group_arns:
description:
- List of target group arns instances should be registered to
type: list
elements: str
tenancy:
description:
- Dedicated vs shared tenancy.
- "The available choices are: V(default), V(dedicated)."
type: str
terminate_at_end_of_billing_hour:
description:
- Terminate at the end of billing hour
type: bool
unit:
description:
- The capacity unit to launch instances by.
- "The available choices are: V(instance), V(weight)."
type: str
up_scaling_policies:
description:
- A list of hash/dictionaries of scaling policies to configure in the elastigroup;
'[{"key":"value", "key":"value"}]';
keys allowed are -
policy_name (String, required),
namespace (String, required),
metric_name (String, required),
dimensions (List of Objects, Keys allowed are name (String, required) and value (String)),
statistic (String, required)
evaluation_periods (String, required),
period (String, required),
threshold (String, required),
cooldown (String, required),
unit (String, required),
operator (String, required),
action_type (String, required),
adjustment (String),
min_target_capacity (String),
target (String),
maximum (String),
minimum (String)
type: list
elements: dict
down_scaling_policies:
description:
- A list of hash/dictionaries of scaling policies to configure in the elastigroup;
'[{"key":"value", "key":"value"}]';
keys allowed are -
policy_name (String, required),
namespace (String, required),
metric_name (String, required),
dimensions ((List of Objects), Keys allowed are name (String, required) and value (String)),
statistic (String, required),
evaluation_periods (String, required),
period (String, required),
threshold (String, required),
cooldown (String, required),
unit (String, required),
operator (String, required),
action_type (String, required),
adjustment (String),
max_target_capacity (String),
target (String),
maximum (String),
minimum (String)
type: list
elements: dict
target_tracking_policies:
description:
- A list of hash/dictionaries of target tracking policies to configure in the elastigroup;
'[{"key":"value", "key":"value"}]';
keys allowed are -
policy_name (String, required),
namespace (String, required),
source (String, required),
metric_name (String, required),
statistic (String, required),
unit (String, required),
cooldown (String, required),
target (String, required)
type: list
elements: dict
uniqueness_by:
choices:
- id
- name
description:
- If your group names are not unique, you may use this feature to update or delete a specific group.
Whenever this property is set, you must set a group_id in order to update or delete a group, otherwise a group will be created.
default: name
type: str
user_data:
description:
- Base64-encoded MIME user data. Encode before setting the value.
type: str
utilize_reserved_instances:
description:
- In case of any available Reserved Instances,
Elastigroup will utilize your reservations before purchasing Spot instances.
type: bool
wait_for_instances:
description:
- Whether or not the elastigroup creation / update actions should wait for the instances to spin
type: bool
default: false
wait_timeout:
description:
- How long the module should wait for instances before failing the action.;
Only works if wait_for_instances is True.
type: int
do_not_update:
description:
- TODO document.
type: list
elements: str
default: []
multai_token:
description:
- Token used for Multai configuration.
type: str
multai_load_balancers:
description:
- Configuration parameters for Multai load balancers.
type: list
elements: dict
elastic_beanstalk:
description:
- Placeholder parameter for future implementation of Elastic Beanstalk configurations.
type: dict
'''
EXAMPLES = '''
# Basic configuration YAML example
- hosts: localhost
tasks:
- name: Create elastigroup
community.general.spotinst_aws_elastigroup:
state: present
risk: 100
availability_vs_cost: balanced
availability_zones:
- name: us-west-2a
subnet_id: subnet-2b68a15c
image_id: ami-f173cc91
key_pair: spotinst-oregon
max_size: 15
min_size: 0
target: 0
unit: instance
monitoring: true
name: ansible-group
on_demand_instance_type: c3.large
product: Linux/UNIX
load_balancers:
- test-lb-1
security_group_ids:
- sg-8f4b8fe9
spot_instance_types:
- c3.large
do_not_update:
- image_id
- target
register: result
- ansible.builtin.debug: var=result
# In this example, we create an elastigroup and wait 600 seconds to retrieve the instances, and use their private ips
- hosts: localhost
tasks:
- name: Create elastigroup
community.general.spotinst_aws_elastigroup:
state: present
account_id: act-1a9dd2b
risk: 100
availability_vs_cost: balanced
availability_zones:
- name: us-west-2a
subnet_id: subnet-2b68a15c
tags:
- Environment: someEnvValue
- OtherTagKey: otherValue
image_id: ami-f173cc91
key_pair: spotinst-oregon
max_size: 5
min_size: 0
target: 0
unit: instance
monitoring: true
name: ansible-group-tal
on_demand_instance_type: c3.large
product: Linux/UNIX
security_group_ids:
- sg-8f4b8fe9
block_device_mappings:
- device_name: '/dev/sda1'
ebs:
volume_size: 100
volume_type: gp2
spot_instance_types:
- c3.large
do_not_update:
- image_id
wait_for_instances: true
wait_timeout: 600
register: result
- name: Store private ips to file
ansible.builtin.shell: echo {{ item.private_ip }}\\n >> list-of-private-ips
with_items: "{{ result.instances }}"
- ansible.builtin.debug: var=result
# In this example, we create an elastigroup with multiple block device mappings, tags, and also an account id
# In organizations with more than one account, it is required to specify an account_id
- hosts: localhost
tasks:
- name: Create elastigroup
community.general.spotinst_aws_elastigroup:
state: present
account_id: act-1a9dd2b
risk: 100
availability_vs_cost: balanced
availability_zones:
- name: us-west-2a
subnet_id: subnet-2b68a15c
tags:
- Environment: someEnvValue
- OtherTagKey: otherValue
image_id: ami-f173cc91
key_pair: spotinst-oregon
max_size: 5
min_size: 0
target: 0
unit: instance
monitoring: true
name: ansible-group-tal
on_demand_instance_type: c3.large
product: Linux/UNIX
security_group_ids:
- sg-8f4b8fe9
block_device_mappings:
- device_name: '/dev/xvda'
ebs:
volume_size: 60
volume_type: gp2
- device_name: '/dev/xvdb'
ebs:
volume_size: 120
volume_type: gp2
spot_instance_types:
- c3.large
do_not_update:
- image_id
wait_for_instances: true
wait_timeout: 600
register: result
- name: Store private ips to file
ansible.builtin.shell: echo {{ item.private_ip }}\\n >> list-of-private-ips
with_items: "{{ result.instances }}"
- ansible.builtin.debug: var=result
# In this example we have set up block device mapping with ephemeral devices
- hosts: localhost
tasks:
- name: Create elastigroup
community.general.spotinst_aws_elastigroup:
state: present
risk: 100
availability_vs_cost: balanced
availability_zones:
- name: us-west-2a
subnet_id: subnet-2b68a15c
image_id: ami-f173cc91
key_pair: spotinst-oregon
max_size: 15
min_size: 0
target: 0
unit: instance
block_device_mappings:
- device_name: '/dev/xvda'
virtual_name: ephemeral0
- device_name: '/dev/xvdb/'
virtual_name: ephemeral1
monitoring: true
name: ansible-group
on_demand_instance_type: c3.large
product: Linux/UNIX
load_balancers:
- test-lb-1
security_group_ids:
- sg-8f4b8fe9
spot_instance_types:
- c3.large
do_not_update:
- image_id
- target
register: result
- ansible.builtin.debug: var=result
# In this example we create a basic group configuration with a network interface defined.
# Each network interface must have a device index
- hosts: localhost
tasks:
- name: Create elastigroup
community.general.spotinst_aws_elastigroup:
state: present
risk: 100
availability_vs_cost: balanced
network_interfaces:
- associate_public_ip_address: true
device_index: 0
availability_zones:
- name: us-west-2a
subnet_id: subnet-2b68a15c
image_id: ami-f173cc91
key_pair: spotinst-oregon
max_size: 15
min_size: 0
target: 0
unit: instance
monitoring: true
name: ansible-group
on_demand_instance_type: c3.large
product: Linux/UNIX
load_balancers:
- test-lb-1
security_group_ids:
- sg-8f4b8fe9
spot_instance_types:
- c3.large
do_not_update:
- image_id
- target
register: result
- ansible.builtin.debug: var=result
# In this example we create a basic group configuration with a target tracking scaling policy defined
- hosts: localhost
tasks:
- name: Create elastigroup
community.general.spotinst_aws_elastigroup:
account_id: act-92d45673
state: present
risk: 100
availability_vs_cost: balanced
availability_zones:
- name: us-west-2a
subnet_id: subnet-79da021e
image_id: ami-f173cc91
fallback_to_od: true
tags:
- Creator: ValueOfCreatorTag
- Environment: ValueOfEnvironmentTag
key_pair: spotinst-labs-oregon
max_size: 10
min_size: 0
target: 2
unit: instance
monitoring: true
name: ansible-group-1
on_demand_instance_type: c3.large
product: Linux/UNIX
security_group_ids:
- sg-46cdc13d
spot_instance_types:
- c3.large
target_tracking_policies:
- policy_name: target-tracking-1
namespace: AWS/EC2
metric_name: CPUUtilization
statistic: average
unit: percent
target: 50
cooldown: 120
do_not_update:
- image_id
register: result
- ansible.builtin.debug: var=result
'''
RETURN = '''
---
instances:
description: List of active elastigroup instances and their details.
returned: success
type: dict
sample: [
{
"spotInstanceRequestId": "sir-regs25zp",
"instanceId": "i-09640ad8678234c",
"instanceType": "m4.large",
"product": "Linux/UNIX",
"availabilityZone": "us-west-2b",
"privateIp": "180.0.2.244",
"createdAt": "2017-07-17T12:46:18.000Z",
"status": "fulfilled"
}
]
group_id:
description: Created / Updated group's ID.
returned: success
type: str
sample: "sig-12345"
'''
HAS_SPOTINST_SDK = False
__metaclass__ = type
import os
import time
from ansible.module_utils.basic import AnsibleModule
try:
import spotinst_sdk as spotinst
from spotinst_sdk import SpotinstClientException
HAS_SPOTINST_SDK = True
except ImportError:
pass
eni_fields = ('description',
'device_index',
'secondary_private_ip_address_count',
'associate_public_ip_address',
'delete_on_termination',
'groups',
'network_interface_id',
'private_ip_address',
'subnet_id',
'associate_ipv6_address')
private_ip_fields = ('private_ip_address',
'primary')
capacity_fields = (dict(ansible_field_name='min_size',
spotinst_field_name='minimum'),
dict(ansible_field_name='max_size',
spotinst_field_name='maximum'),
'target',
'unit')
lspec_fields = ('user_data',
'key_pair',
'tenancy',
'shutdown_script',
'monitoring',
'ebs_optimized',
'image_id',
'health_check_type',
'health_check_grace_period',
'health_check_unhealthy_duration_before_replacement',
'security_group_ids')
iam_fields = (dict(ansible_field_name='iam_role_name',
spotinst_field_name='name'),
dict(ansible_field_name='iam_role_arn',
spotinst_field_name='arn'))
scheduled_task_fields = ('adjustment',
'adjustment_percentage',
'batch_size_percentage',
'cron_expression',
'frequency',
'grace_period',
'task_type',
'is_enabled',
'scale_target_capacity',
'scale_min_capacity',
'scale_max_capacity')
scaling_policy_fields = ('policy_name',
'namespace',
'metric_name',
'dimensions',
'statistic',
'evaluation_periods',
'period',
'threshold',
'cooldown',
'unit',
'operator')
tracking_policy_fields = ('policy_name',
'namespace',
'source',
'metric_name',
'statistic',
'unit',
'cooldown',
'target',
'threshold')
action_fields = (dict(ansible_field_name='action_type',
spotinst_field_name='type'),
'adjustment',
'min_target_capacity',
'max_target_capacity',
'target',
'minimum',
'maximum')
signal_fields = ('name',
'timeout')
multai_lb_fields = ('balancer_id',
'project_id',
'target_set_id',
'az_awareness',
'auto_weight')
persistence_fields = ('should_persist_root_device',
'should_persist_block_devices',
'should_persist_private_ip')
strategy_fields = ('risk',
'utilize_reserved_instances',
'fallback_to_od',
'on_demand_count',
'availability_vs_cost',
'draining_timeout',
'spin_up_time',
'lifetime_period')
ebs_fields = ('delete_on_termination',
'encrypted',
'iops',
'snapshot_id',
'volume_type',
'volume_size')
bdm_fields = ('device_name',
'virtual_name',
'no_device')
kubernetes_fields = ('api_server',
'token')
right_scale_fields = ('account_id',
'refresh_token')
rancher_fields = ('access_key',
'secret_key',
'master_host',
'version')
chef_fields = ('chef_server',
'organization',
'user',
'pem_key',
'chef_version')
az_fields = ('name',
'subnet_id',
'placement_group_name')
opsworks_fields = ('layer_id',)
scaling_strategy_fields = ('terminate_at_end_of_billing_hour',)
mesosphere_fields = ('api_server',)
ecs_fields = ('cluster_name',)
multai_fields = ('multai_token',)
def handle_elastigroup(client, module):
has_changed = False
group_id = None
message = 'None'
name = module.params.get('name')
state = module.params.get('state')
uniqueness_by = module.params.get('uniqueness_by')
external_group_id = module.params.get('id')
if uniqueness_by == 'id':
if external_group_id is None:
should_create = True
else:
should_create = False
group_id = external_group_id
else:
groups = client.get_elastigroups()
should_create, group_id = find_group_with_same_name(groups, name)
if should_create is True:
if state == 'present':
eg = expand_elastigroup(module, is_update=False)
module.debug(str(" [INFO] " + message + "\n"))
group = client.create_elastigroup(group=eg)
group_id = group['id']
message = 'Created group Successfully.'
has_changed = True
elif state == 'absent':
message = 'Cannot delete non-existent group.'
has_changed = False
else:
eg = expand_elastigroup(module, is_update=True)
if state == 'present':
group = client.update_elastigroup(group_update=eg, group_id=group_id)
message = 'Updated group successfully.'
try:
roll_config = module.params.get('roll_config')
if roll_config:
eg_roll = spotinst.aws_elastigroup.Roll(
batch_size_percentage=roll_config.get('batch_size_percentage'),
grace_period=roll_config.get('grace_period'),
health_check_type=roll_config.get('health_check_type')
)
roll_response = client.roll_group(group_roll=eg_roll, group_id=group_id)
message = 'Updated and started rolling the group successfully.'
except SpotinstClientException as exc:
message = 'Updated group successfully, but failed to perform roll. Error:' + str(exc)
has_changed = True
elif state == 'absent':
try:
client.delete_elastigroup(group_id=group_id)
except SpotinstClientException as exc:
if "GROUP_DOESNT_EXIST" in exc.message:
pass
else:
module.fail_json(msg="Error while attempting to delete group : " + exc.message)
message = 'Deleted group successfully.'
has_changed = True
return group_id, message, has_changed
def retrieve_group_instances(client, module, group_id):
wait_timeout = module.params.get('wait_timeout')
wait_for_instances = module.params.get('wait_for_instances')
health_check_type = module.params.get('health_check_type')
if wait_timeout is None:
wait_timeout = 300
wait_timeout = time.time() + wait_timeout
target = module.params.get('target')
state = module.params.get('state')
instances = list()
if state == 'present' and group_id is not None and wait_for_instances is True:
is_amount_fulfilled = False
while is_amount_fulfilled is False and wait_timeout > time.time():
instances = list()
amount_of_fulfilled_instances = 0
if health_check_type is not None:
healthy_instances = client.get_instance_healthiness(group_id=group_id)
for healthy_instance in healthy_instances:
if healthy_instance.get('healthStatus') == 'HEALTHY':
amount_of_fulfilled_instances += 1
instances.append(healthy_instance)
else:
active_instances = client.get_elastigroup_active_instances(group_id=group_id)
for active_instance in active_instances:
if active_instance.get('private_ip') is not None:
amount_of_fulfilled_instances += 1
instances.append(active_instance)
if amount_of_fulfilled_instances >= target:
is_amount_fulfilled = True
time.sleep(10)
return instances
def find_group_with_same_name(groups, name):
for group in groups:
if group['name'] == name:
return False, group.get('id')
return True, None
def expand_elastigroup(module, is_update):
do_not_update = module.params['do_not_update']
name = module.params.get('name')
eg = spotinst.aws_elastigroup.Elastigroup()
description = module.params.get('description')
if name is not None:
eg.name = name
if description is not None:
eg.description = description
# Capacity
expand_capacity(eg, module, is_update, do_not_update)
# Strategy
expand_strategy(eg, module)
# Scaling
expand_scaling(eg, module)
# Third party integrations
expand_integrations(eg, module)
# Compute
expand_compute(eg, module, is_update, do_not_update)
# Multai
expand_multai(eg, module)
# Scheduling
expand_scheduled_tasks(eg, module)
return eg
def expand_compute(eg, module, is_update, do_not_update):
elastic_ips = module.params['elastic_ips']
on_demand_instance_type = module.params.get('on_demand_instance_type')
spot_instance_types = module.params['spot_instance_types']
ebs_volume_pool = module.params['ebs_volume_pool']
availability_zones_list = module.params['availability_zones']
product = module.params.get('product')
eg_compute = spotinst.aws_elastigroup.Compute()
if product is not None:
# Only put product on group creation
if is_update is not True:
eg_compute.product = product
if elastic_ips is not None:
eg_compute.elastic_ips = elastic_ips
if on_demand_instance_type or spot_instance_types is not None:
eg_instance_types = spotinst.aws_elastigroup.InstanceTypes()
if on_demand_instance_type is not None:
eg_instance_types.spot = spot_instance_types
if spot_instance_types is not None:
eg_instance_types.ondemand = on_demand_instance_type
if eg_instance_types.spot is not None or eg_instance_types.ondemand is not None:
eg_compute.instance_types = eg_instance_types
expand_ebs_volume_pool(eg_compute, ebs_volume_pool)
eg_compute.availability_zones = expand_list(availability_zones_list, az_fields, 'AvailabilityZone')
expand_launch_spec(eg_compute, module, is_update, do_not_update)
eg.compute = eg_compute
def expand_ebs_volume_pool(eg_compute, ebs_volumes_list):
if ebs_volumes_list is not None:
eg_volumes = []
for volume in ebs_volumes_list:
eg_volume = spotinst.aws_elastigroup.EbsVolume()
if volume.get('device_name') is not None:
eg_volume.device_name = volume.get('device_name')
if volume.get('volume_ids') is not None:
eg_volume.volume_ids = volume.get('volume_ids')
if eg_volume.device_name is not None:
eg_volumes.append(eg_volume)
if len(eg_volumes) > 0:
eg_compute.ebs_volume_pool = eg_volumes
def expand_launch_spec(eg_compute, module, is_update, do_not_update):
eg_launch_spec = expand_fields(lspec_fields, module.params, 'LaunchSpecification')
if module.params['iam_role_arn'] is not None or module.params['iam_role_name'] is not None:
eg_launch_spec.iam_role = expand_fields(iam_fields, module.params, 'IamRole')
tags = module.params['tags']
load_balancers = module.params['load_balancers']
target_group_arns = module.params['target_group_arns']
block_device_mappings = module.params['block_device_mappings']
network_interfaces = module.params['network_interfaces']
if is_update is True:
if 'image_id' in do_not_update:
delattr(eg_launch_spec, 'image_id')
expand_tags(eg_launch_spec, tags)
expand_load_balancers(eg_launch_spec, load_balancers, target_group_arns)
expand_block_device_mappings(eg_launch_spec, block_device_mappings)
expand_network_interfaces(eg_launch_spec, network_interfaces)
eg_compute.launch_specification = eg_launch_spec
def expand_integrations(eg, module):
rancher = module.params.get('rancher')
mesosphere = module.params.get('mesosphere')
ecs = module.params.get('ecs')
kubernetes = module.params.get('kubernetes')
right_scale = module.params.get('right_scale')
opsworks = module.params.get('opsworks')
chef = module.params.get('chef')
integration_exists = False
eg_integrations = spotinst.aws_elastigroup.ThirdPartyIntegrations()
if mesosphere is not None:
eg_integrations.mesosphere = expand_fields(mesosphere_fields, mesosphere, 'Mesosphere')
integration_exists = True
if ecs is not None:
eg_integrations.ecs = expand_fields(ecs_fields, ecs, 'EcsConfiguration')
integration_exists = True
if kubernetes is not None:
eg_integrations.kubernetes = expand_fields(kubernetes_fields, kubernetes, 'KubernetesConfiguration')
integration_exists = True
if right_scale is not None:
eg_integrations.right_scale = expand_fields(right_scale_fields, right_scale, 'RightScaleConfiguration')
integration_exists = True
if opsworks is not None:
eg_integrations.opsworks = expand_fields(opsworks_fields, opsworks, 'OpsWorksConfiguration')
integration_exists = True
if rancher is not None:
eg_integrations.rancher = expand_fields(rancher_fields, rancher, 'Rancher')
integration_exists = True
if chef is not None:
eg_integrations.chef = expand_fields(chef_fields, chef, 'ChefConfiguration')
integration_exists = True
if integration_exists:
eg.third_parties_integration = eg_integrations
def expand_capacity(eg, module, is_update, do_not_update):
eg_capacity = expand_fields(capacity_fields, module.params, 'Capacity')
if is_update is True:
delattr(eg_capacity, 'unit')
if 'target' in do_not_update:
delattr(eg_capacity, 'target')
eg.capacity = eg_capacity
def expand_strategy(eg, module):
persistence = module.params.get('persistence')
signals = module.params.get('signals')
eg_strategy = expand_fields(strategy_fields, module.params, 'Strategy')
terminate_at_end_of_billing_hour = module.params.get('terminate_at_end_of_billing_hour')
if terminate_at_end_of_billing_hour is not None:
eg_strategy.eg_scaling_strategy = expand_fields(scaling_strategy_fields,
module.params, 'ScalingStrategy')
if persistence is not None:
eg_strategy.persistence = expand_fields(persistence_fields, persistence, 'Persistence')
if signals is not None:
eg_signals = expand_list(signals, signal_fields, 'Signal')
if len(eg_signals) > 0:
eg_strategy.signals = eg_signals
eg.strategy = eg_strategy
def expand_multai(eg, module):
multai_load_balancers = module.params.get('multai_load_balancers')
eg_multai = expand_fields(multai_fields, module.params, 'Multai')
if multai_load_balancers is not None:
eg_multai_load_balancers = expand_list(multai_load_balancers, multai_lb_fields, 'MultaiLoadBalancer')
if len(eg_multai_load_balancers) > 0:
eg_multai.balancers = eg_multai_load_balancers
eg.multai = eg_multai
def expand_scheduled_tasks(eg, module):
scheduled_tasks = module.params.get('scheduled_tasks')
if scheduled_tasks is not None:
eg_scheduling = spotinst.aws_elastigroup.Scheduling()
eg_tasks = expand_list(scheduled_tasks, scheduled_task_fields, 'ScheduledTask')
if len(eg_tasks) > 0:
eg_scheduling.tasks = eg_tasks
eg.scheduling = eg_scheduling
def expand_load_balancers(eg_launchspec, load_balancers, target_group_arns):
if load_balancers is not None or target_group_arns is not None:
eg_load_balancers_config = spotinst.aws_elastigroup.LoadBalancersConfig()
eg_total_lbs = []
if load_balancers is not None:
for elb_name in load_balancers:
eg_elb = spotinst.aws_elastigroup.LoadBalancer()
if elb_name is not None:
eg_elb.name = elb_name
eg_elb.type = 'CLASSIC'
eg_total_lbs.append(eg_elb)
if target_group_arns is not None:
for target_arn in target_group_arns:
eg_elb = spotinst.aws_elastigroup.LoadBalancer()
if target_arn is not None:
eg_elb.arn = target_arn
eg_elb.type = 'TARGET_GROUP'
eg_total_lbs.append(eg_elb)
if len(eg_total_lbs) > 0:
eg_load_balancers_config.load_balancers = eg_total_lbs
eg_launchspec.load_balancers_config = eg_load_balancers_config
def expand_tags(eg_launchspec, tags):
if tags is not None:
eg_tags = []
for tag in tags:
eg_tag = spotinst.aws_elastigroup.Tag()
if tag:
eg_tag.tag_key, eg_tag.tag_value = list(tag.items())[0]
eg_tags.append(eg_tag)
if len(eg_tags) > 0:
eg_launchspec.tags = eg_tags
def expand_block_device_mappings(eg_launchspec, bdms):
if bdms is not None:
eg_bdms = []
for bdm in bdms:
eg_bdm = expand_fields(bdm_fields, bdm, 'BlockDeviceMapping')
if bdm.get('ebs') is not None:
eg_bdm.ebs = expand_fields(ebs_fields, bdm.get('ebs'), 'EBS')
eg_bdms.append(eg_bdm)
if len(eg_bdms) > 0:
eg_launchspec.block_device_mappings = eg_bdms
def expand_network_interfaces(eg_launchspec, enis):
if enis is not None:
eg_enis = []
for eni in enis:
eg_eni = expand_fields(eni_fields, eni, 'NetworkInterface')
eg_pias = expand_list(eni.get('private_ip_addresses'), private_ip_fields, 'PrivateIpAddress')
if eg_pias is not None:
eg_eni.private_ip_addresses = eg_pias
eg_enis.append(eg_eni)
if len(eg_enis) > 0:
eg_launchspec.network_interfaces = eg_enis
def expand_scaling(eg, module):
up_scaling_policies = module.params['up_scaling_policies']
down_scaling_policies = module.params['down_scaling_policies']
target_tracking_policies = module.params['target_tracking_policies']
eg_scaling = spotinst.aws_elastigroup.Scaling()
if up_scaling_policies is not None:
eg_up_scaling_policies = expand_scaling_policies(up_scaling_policies)
if len(eg_up_scaling_policies) > 0:
eg_scaling.up = eg_up_scaling_policies
if down_scaling_policies is not None:
eg_down_scaling_policies = expand_scaling_policies(down_scaling_policies)
if len(eg_down_scaling_policies) > 0:
eg_scaling.down = eg_down_scaling_policies
if target_tracking_policies is not None:
eg_target_tracking_policies = expand_target_tracking_policies(target_tracking_policies)
if len(eg_target_tracking_policies) > 0:
eg_scaling.target = eg_target_tracking_policies
if eg_scaling.down is not None or eg_scaling.up is not None or eg_scaling.target is not None:
eg.scaling = eg_scaling
def expand_list(items, fields, class_name):
if items is not None:
new_objects_list = []
for item in items:
new_obj = expand_fields(fields, item, class_name)
new_objects_list.append(new_obj)
return new_objects_list
def expand_fields(fields, item, class_name):
class_ = getattr(spotinst.aws_elastigroup, class_name)
new_obj = class_()
# Handle primitive fields
if item is not None:
for field in fields:
if isinstance(field, dict):
ansible_field_name = field['ansible_field_name']
spotinst_field_name = field['spotinst_field_name']
else:
ansible_field_name = field
spotinst_field_name = field
if item.get(ansible_field_name) is not None:
setattr(new_obj, spotinst_field_name, item.get(ansible_field_name))
return new_obj
def expand_scaling_policies(scaling_policies):
eg_scaling_policies = []
for policy in scaling_policies:
eg_policy = expand_fields(scaling_policy_fields, policy, 'ScalingPolicy')
eg_policy.action = expand_fields(action_fields, policy, 'ScalingPolicyAction')
eg_scaling_policies.append(eg_policy)
return eg_scaling_policies
def expand_target_tracking_policies(tracking_policies):
eg_tracking_policies = []
for policy in tracking_policies:
eg_policy = expand_fields(tracking_policy_fields, policy, 'TargetTrackingPolicy')
eg_tracking_policies.append(eg_policy)
return eg_tracking_policies
def main():
fields = dict(
account_id=dict(type='str'),
availability_vs_cost=dict(type='str', required=True),
availability_zones=dict(type='list', elements='dict', required=True),
block_device_mappings=dict(type='list', elements='dict'),
chef=dict(type='dict'),
credentials_path=dict(type='path', default="~/.spotinst/credentials"),
do_not_update=dict(default=[], type='list', elements='str'),
down_scaling_policies=dict(type='list', elements='dict'),
draining_timeout=dict(type='int'),
ebs_optimized=dict(type='bool'),
ebs_volume_pool=dict(type='list', elements='dict'),
ecs=dict(type='dict'),
elastic_beanstalk=dict(type='dict'),
elastic_ips=dict(type='list', elements='str'),
fallback_to_od=dict(type='bool'),
id=dict(type='str'),
health_check_grace_period=dict(type='int'),
health_check_type=dict(type='str'),
health_check_unhealthy_duration_before_replacement=dict(type='int'),
iam_role_arn=dict(type='str'),
iam_role_name=dict(type='str'),
image_id=dict(type='str', required=True),
key_pair=dict(type='str', no_log=False),
kubernetes=dict(type='dict'),
lifetime_period=dict(type='int'),
load_balancers=dict(type='list', elements='str'),
max_size=dict(type='int', required=True),
mesosphere=dict(type='dict'),
min_size=dict(type='int', required=True),
monitoring=dict(type='str'),
multai_load_balancers=dict(type='list', elements='dict'),
multai_token=dict(type='str', no_log=True),
name=dict(type='str', required=True),
network_interfaces=dict(type='list', elements='dict'),
on_demand_count=dict(type='int'),
on_demand_instance_type=dict(type='str'),
opsworks=dict(type='dict'),
persistence=dict(type='dict'),
product=dict(type='str', required=True),
rancher=dict(type='dict'),
right_scale=dict(type='dict'),
risk=dict(type='int'),
roll_config=dict(type='dict'),
scheduled_tasks=dict(type='list', elements='dict'),
security_group_ids=dict(type='list', elements='str', required=True),
shutdown_script=dict(type='str'),
signals=dict(type='list', elements='dict'),
spin_up_time=dict(type='int'),
spot_instance_types=dict(type='list', elements='str', required=True),
state=dict(default='present', choices=['present', 'absent']),
tags=dict(type='list', elements='dict'),
target=dict(type='int', required=True),
target_group_arns=dict(type='list', elements='str'),
tenancy=dict(type='str'),
terminate_at_end_of_billing_hour=dict(type='bool'),
token=dict(type='str', no_log=True),
unit=dict(type='str'),
user_data=dict(type='str'),
utilize_reserved_instances=dict(type='bool'),
uniqueness_by=dict(default='name', choices=['name', 'id']),
up_scaling_policies=dict(type='list', elements='dict'),
target_tracking_policies=dict(type='list', elements='dict'),
wait_for_instances=dict(type='bool', default=False),
wait_timeout=dict(type='int')
)
module = AnsibleModule(argument_spec=fields)
if not HAS_SPOTINST_SDK:
module.fail_json(msg="the Spotinst SDK library is required. (pip install spotinst_sdk)")
# Retrieve creds file variables
creds_file_loaded_vars = dict()
credentials_path = module.params.get('credentials_path')
try:
with open(credentials_path, "r") as creds:
for line in creds:
eq_index = line.find('=')
var_name = line[:eq_index].strip()
string_value = line[eq_index + 1:].strip()
creds_file_loaded_vars[var_name] = string_value
except IOError:
pass
# End of creds file retrieval
token = module.params.get('token')
if not token:
token = os.environ.get('SPOTINST_TOKEN')
if not token:
token = creds_file_loaded_vars.get("token")
account = module.params.get('account_id')
if not account:
account = os.environ.get('SPOTINST_ACCOUNT_ID') or os.environ.get('ACCOUNT')
if not account:
account = creds_file_loaded_vars.get("account")
client = spotinst.SpotinstClient(auth_token=token, print_output=False)
if account is not None:
client = spotinst.SpotinstClient(auth_token=token, print_output=False, account_id=account)
group_id, message, has_changed = handle_elastigroup(client=client, module=module)
instances = retrieve_group_instances(client=client, module=module, group_id=group_id)
module.exit_json(changed=has_changed, group_id=group_id, message=message, instances=instances)
if __name__ == '__main__':
main()