my ansible modules which are stock in pull request
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1772 lines
75 KiB

  1. #!/usr/bin/python
  2. # This file is part of Ansible
  3. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
  4. from __future__ import absolute_import, division, print_function
  5. __metaclass__ = type
  6. ANSIBLE_METADATA = {'metadata_version': '1.1',
  7. 'status': ['stableinterface'],
  8. 'supported_by': 'community'}
  9. DOCUMENTATION = """
  10. ---
  11. module: ec2_asg
  12. short_description: Create or delete AWS AutoScaling Groups (ASGs)
  13. description:
  14. - Can create or delete AWS AutoScaling Groups.
  15. - Can be used with the M(ec2_lc) module to manage Launch Configurations.
  16. version_added: "1.6"
  17. author: "Gareth Rushgrove (@garethr)"
  18. requirements: [ "boto3", "botocore" ]
  19. options:
  20. state:
  21. description:
  22. - Register or deregister the instance.
  23. choices: ['present', 'absent']
  24. default: present
  25. type: str
  26. name:
  27. description:
  28. - Unique name for group to be created or deleted.
  29. required: true
  30. type: str
  31. load_balancers:
  32. description:
  33. - List of ELB names to use for the group. Use for classic load balancers.
  34. type: list
  35. elements: str
  36. target_group_arns:
  37. description:
  38. - List of target group ARNs to use for the group. Use for application load balancers.
  39. version_added: "2.4"
  40. type: list
  41. elements: str
  42. availability_zones:
  43. description:
  44. - List of availability zone names in which to create the group.
  45. - Defaults to all the availability zones in the region if I(vpc_zone_identifier) is not set.
  46. type: list
  47. elements: str
  48. launch_config_name:
  49. description:
  50. - Name of the Launch configuration to use for the group. See the M(ec2_lc) module for managing these.
  51. - If unspecified then the current group value will be used. One of I(launch_config_name) or I(launch_template) must be provided.
  52. type: str
  53. launch_template:
  54. description:
  55. - Dictionary describing the Launch Template to use
  56. suboptions:
  57. version:
  58. description:
  59. - The version number of the launch template to use.
  60. - Defaults to latest version if not provided.
  61. type: str
  62. launch_template_name:
  63. description:
  64. - The name of the launch template. Only one of I(launch_template_name) or I(launch_template_id) is required.
  65. type: str
  66. launch_template_id:
  67. description:
  68. - The id of the launch template. Only one of I(launch_template_name) or I(launch_template_id) is required.
  69. type: str
  70. type: dict
  71. version_added: "2.8"
  72. min_size:
  73. description:
  74. - Minimum number of instances in group, if unspecified then the current group value will be used.
  75. type: int
  76. max_size:
  77. description:
  78. - Maximum number of instances in group, if unspecified then the current group value will be used.
  79. type: int
  80. mixed_instances_policy:
  81. description:
  82. - Using mixed intances policy while ASG present
  83. required: false
  84. version_added: "2.8"
  85. suboptions:
  86. instance_types:
  87. description:
  88. - A list of instance_types.
  89. type: list
  90. type: dict
  91. placement_group:
  92. description:
  93. - Physical location of your cluster placement group created in Amazon EC2.
  94. version_added: "2.3"
  95. type: str
  96. desired_capacity:
  97. description:
  98. - Desired number of instances in group, if unspecified then the current group value will be used.
  99. type: int
  100. replace_all_instances:
  101. description:
  102. - In a rolling fashion, replace all instances that used the old launch configuration with one from the new launch configuration.
  103. It increases the ASG size by I(replace_batch_size), waits for the new instances to be up and running.
  104. After that, it terminates a batch of old instances, waits for the replacements, and repeats, until all old instances are replaced.
  105. Once that's done the ASG size is reduced back to the expected size.
  106. version_added: "1.8"
  107. default: false
  108. type: bool
  109. replace_batch_size:
  110. description:
  111. - Number of instances you'd like to replace at a time. Used with I(replace_all_instances).
  112. required: false
  113. version_added: "1.8"
  114. default: 1
  115. type: int
  116. replace_instances:
  117. description:
  118. - List of I(instance_ids) belonging to the named AutoScalingGroup that you would like to terminate and be replaced with instances
  119. matching the current launch configuration.
  120. version_added: "1.8"
  121. type: list
  122. elements: str
  123. lc_check:
  124. description:
  125. - Check to make sure instances that are being replaced with I(replace_instances) do not already have the current I(launch_config).
  126. version_added: "1.8"
  127. default: true
  128. type: bool
  129. lt_check:
  130. description:
  131. - Check to make sure instances that are being replaced with I(replace_instances) do not already have the current
  132. I(launch_template or I(launch_template) I(version).
  133. version_added: "2.8"
  134. default: true
  135. type: bool
  136. vpc_zone_identifier:
  137. description:
  138. - List of VPC subnets to use
  139. type: list
  140. elements: str
  141. tags:
  142. description:
  143. - A list of tags to add to the Auto Scale Group.
  144. - Optional key is I(propagate_at_launch), which defaults to true.
  145. - When I(propagate_at_launch) is true the tags will be propagated to the Instances created.
  146. version_added: "1.7"
  147. type: list
  148. elements: dict
  149. health_check_period:
  150. description:
  151. - Length of time in seconds after a new EC2 instance comes into service that Auto Scaling starts checking its health.
  152. required: false
  153. default: 300
  154. version_added: "1.7"
  155. type: int
  156. health_check_type:
  157. description:
  158. - The service you want the health status from, Amazon EC2 or Elastic Load Balancer.
  159. required: false
  160. default: EC2
  161. version_added: "1.7"
  162. choices: ['EC2', 'ELB']
  163. type: str
  164. default_cooldown:
  165. description:
  166. - The number of seconds after a scaling activity completes before another can begin.
  167. default: 300
  168. version_added: "2.0"
  169. type: int
  170. wait_timeout:
  171. description:
  172. - How long to wait for instances to become viable when replaced. If you experience the error "Waited too long for ELB instances to be healthy",
  173. try increasing this value.
  174. default: 300
  175. type: int
  176. version_added: "1.8"
  177. wait_for_instances:
  178. description:
  179. - Wait for the ASG instances to be in a ready state before exiting. If instances are behind an ELB, it will wait until the ELB determines all
  180. instances have a lifecycle_state of "InService" and a health_status of "Healthy".
  181. version_added: "1.9"
  182. default: true
  183. type: bool
  184. termination_policies:
  185. description:
  186. - An ordered list of criteria used for selecting instances to be removed from the Auto Scaling group when reducing capacity.
  187. - Using I(termination_policies=Default) when modifying an existing AutoScalingGroup will result in the existing policy being retained
  188. instead of changed to C(Default).
  189. - 'Valid values include: C(Default), C(OldestInstance), C(NewestInstance), C(OldestLaunchConfiguration), C(ClosestToNextInstanceHour)'
  190. - 'Full documentation of valid values can be found in the AWS documentation:'
  191. - 'U(https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-instance-termination.html#custom-termination-policy)'
  192. default: Default
  193. version_added: "2.0"
  194. type: list
  195. elements: str
  196. notification_topic:
  197. description:
  198. - A SNS topic ARN to send auto scaling notifications to.
  199. version_added: "2.2"
  200. type: str
  201. notification_types:
  202. description:
  203. - A list of auto scaling events to trigger notifications on.
  204. default:
  205. - 'autoscaling:EC2_INSTANCE_LAUNCH'
  206. - 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR'
  207. - 'autoscaling:EC2_INSTANCE_TERMINATE'
  208. - 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR'
  209. required: false
  210. version_added: "2.2"
  211. type: list
  212. elements: str
  213. suspend_processes:
  214. description:
  215. - A list of scaling processes to suspend.
  216. - 'Valid values include:'
  217. - C(Launch), C(Terminate), C(HealthCheck), C(ReplaceUnhealthy), C(AZRebalance), C(AlarmNotification), C(ScheduledActions), C(AddToLoadBalancer)
  218. - 'Full documentation of valid values can be found in the AWS documentation:'
  219. - 'U(https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-suspend-resume-processes.html)'
  220. default: []
  221. version_added: "2.3"
  222. type: list
  223. elements: str
  224. metrics_collection:
  225. description:
  226. - Enable ASG metrics collection.
  227. type: bool
  228. default: false
  229. version_added: "2.6"
  230. metrics_granularity:
  231. description:
  232. - When I(metrics_collection=true) this will determine the granularity of metrics collected by CloudWatch.
  233. default: "1Minute"
  234. version_added: "2.6"
  235. type: str
  236. metrics_list:
  237. description:
  238. - List of autoscaling metrics to collect when I(metrics_collection=true).
  239. default:
  240. - 'GroupMinSize'
  241. - 'GroupMaxSize'
  242. - 'GroupDesiredCapacity'
  243. - 'GroupInServiceInstances'
  244. - 'GroupPendingInstances'
  245. - 'GroupStandbyInstances'
  246. - 'GroupTerminatingInstances'
  247. - 'GroupTotalInstances'
  248. version_added: "2.6"
  249. type: list
  250. elements: str
  251. extends_documentation_fragment:
  252. - aws
  253. - ec2
  254. """
  255. EXAMPLES = '''
  256. # Basic configuration with Launch Configuration
  257. - ec2_asg:
  258. name: special
  259. load_balancers: [ 'lb1', 'lb2' ]
  260. availability_zones: [ 'eu-west-1a', 'eu-west-1b' ]
  261. launch_config_name: 'lc-1'
  262. min_size: 1
  263. max_size: 10
  264. desired_capacity: 5
  265. vpc_zone_identifier: [ 'subnet-abcd1234', 'subnet-1a2b3c4d' ]
  266. tags:
  267. - environment: production
  268. propagate_at_launch: no
  269. # Rolling ASG Updates
  270. # Below is an example of how to assign a new launch config to an ASG and terminate old instances.
  271. #
  272. # All instances in "myasg" that do not have the launch configuration named "my_new_lc" will be terminated in
  273. # a rolling fashion with instances using the current launch configuration, "my_new_lc".
  274. #
  275. # This could also be considered a rolling deploy of a pre-baked AMI.
  276. #
  277. # If this is a newly created group, the instances will not be replaced since all instances
  278. # will have the current launch configuration.
  279. - name: create launch config
  280. ec2_lc:
  281. name: my_new_lc
  282. image_id: ami-lkajsf
  283. key_name: mykey
  284. region: us-east-1
  285. security_groups: sg-23423
  286. instance_type: m1.small
  287. assign_public_ip: yes
  288. - ec2_asg:
  289. name: myasg
  290. launch_config_name: my_new_lc
  291. health_check_period: 60
  292. health_check_type: ELB
  293. replace_all_instances: yes
  294. min_size: 5
  295. max_size: 5
  296. desired_capacity: 5
  297. region: us-east-1
  298. # To only replace a couple of instances instead of all of them, supply a list
  299. # to "replace_instances":
  300. - ec2_asg:
  301. name: myasg
  302. launch_config_name: my_new_lc
  303. health_check_period: 60
  304. health_check_type: ELB
  305. replace_instances:
  306. - i-b345231
  307. - i-24c2931
  308. min_size: 5
  309. max_size: 5
  310. desired_capacity: 5
  311. region: us-east-1
  312. # Basic Configuration with Launch Template
  313. - ec2_asg:
  314. name: special
  315. load_balancers: [ 'lb1', 'lb2' ]
  316. availability_zones: [ 'eu-west-1a', 'eu-west-1b' ]
  317. launch_template:
  318. version: '1'
  319. launch_template_name: 'lt-example'
  320. launch_template_id: 'lt-123456'
  321. min_size: 1
  322. max_size: 10
  323. desired_capacity: 5
  324. vpc_zone_identifier: [ 'subnet-abcd1234', 'subnet-1a2b3c4d' ]
  325. tags:
  326. - environment: production
  327. propagate_at_launch: no
  328. '''
  329. RETURN = '''
  330. ---
  331. auto_scaling_group_name:
  332. description: The unique name of the auto scaling group
  333. returned: success
  334. type: str
  335. sample: "myasg"
  336. auto_scaling_group_arn:
  337. description: The unique ARN of the autoscaling group
  338. returned: success
  339. type: str
  340. sample: "arn:aws:autoscaling:us-east-1:123456789012:autoScalingGroup:6a09ad6d-eeee-1234-b987-ee123ced01ad:autoScalingGroupName/myasg"
  341. availability_zones:
  342. description: The availability zones for the auto scaling group
  343. returned: success
  344. type: list
  345. sample: [
  346. "us-east-1d"
  347. ]
  348. created_time:
  349. description: Timestamp of create time of the auto scaling group
  350. returned: success
  351. type: str
  352. sample: "2017-11-08T14:41:48.272000+00:00"
  353. default_cooldown:
  354. description: The default cooldown time in seconds.
  355. returned: success
  356. type: int
  357. sample: 300
  358. desired_capacity:
  359. description: The number of EC2 instances that should be running in this group.
  360. returned: success
  361. type: int
  362. sample: 3
  363. healthcheck_period:
  364. description: Length of time in seconds after a new EC2 instance comes into service that Auto Scaling starts checking its health.
  365. returned: success
  366. type: int
  367. sample: 30
  368. healthcheck_type:
  369. description: The service you want the health status from, one of "EC2" or "ELB".
  370. returned: success
  371. type: str
  372. sample: "ELB"
  373. healthy_instances:
  374. description: Number of instances in a healthy state
  375. returned: success
  376. type: int
  377. sample: 5
  378. in_service_instances:
  379. description: Number of instances in service
  380. returned: success
  381. type: int
  382. sample: 3
  383. instance_facts:
  384. description: Dictionary of EC2 instances and their status as it relates to the ASG.
  385. returned: success
  386. type: dict
  387. sample: {
  388. "i-0123456789012": {
  389. "health_status": "Healthy",
  390. "launch_config_name": "public-webapp-production-1",
  391. "lifecycle_state": "InService"
  392. }
  393. }
  394. instances:
  395. description: list of instance IDs in the ASG
  396. returned: success
  397. type: list
  398. sample: [
  399. "i-0123456789012"
  400. ]
  401. launch_config_name:
  402. description: >
  403. Name of launch configuration associated with the ASG. Same as launch_configuration_name,
  404. provided for compatibility with ec2_asg module.
  405. returned: success
  406. type: str
  407. sample: "public-webapp-production-1"
  408. load_balancers:
  409. description: List of load balancers names attached to the ASG.
  410. returned: success
  411. type: list
  412. sample: ["elb-webapp-prod"]
  413. max_size:
  414. description: Maximum size of group
  415. returned: success
  416. type: int
  417. sample: 3
  418. min_size:
  419. description: Minimum size of group
  420. returned: success
  421. type: int
  422. sample: 1
  423. mixed_instance_policy:
  424. description: Returns the list of used mixed instance policy if set.
  425. returned: success
  426. type: list
  427. sample: ["t3.micro", "t3a.micro"]
  428. pending_instances:
  429. description: Number of instances in pending state
  430. returned: success
  431. type: int
  432. sample: 1
  433. tags:
  434. description: List of tags for the ASG, and whether or not each tag propagates to instances at launch.
  435. returned: success
  436. type: list
  437. sample: [
  438. {
  439. "key": "Name",
  440. "value": "public-webapp-production-1",
  441. "resource_id": "public-webapp-production-1",
  442. "resource_type": "auto-scaling-group",
  443. "propagate_at_launch": "true"
  444. },
  445. {
  446. "key": "env",
  447. "value": "production",
  448. "resource_id": "public-webapp-production-1",
  449. "resource_type": "auto-scaling-group",
  450. "propagate_at_launch": "true"
  451. }
  452. ]
  453. target_group_arns:
  454. description: List of ARNs of the target groups that the ASG populates
  455. returned: success
  456. type: list
  457. sample: [
  458. "arn:aws:elasticloadbalancing:ap-southeast-2:123456789012:targetgroup/target-group-host-hello/1a2b3c4d5e6f1a2b",
  459. "arn:aws:elasticloadbalancing:ap-southeast-2:123456789012:targetgroup/target-group-path-world/abcd1234abcd1234"
  460. ]
  461. target_group_names:
  462. description: List of names of the target groups that the ASG populates
  463. returned: success
  464. type: list
  465. sample: [
  466. "target-group-host-hello",
  467. "target-group-path-world"
  468. ]
  469. termination_policies:
  470. description: A list of termination policies for the group.
  471. returned: success
  472. type: str
  473. sample: ["Default"]
  474. unhealthy_instances:
  475. description: Number of instances in an unhealthy state
  476. returned: success
  477. type: int
  478. sample: 0
  479. viable_instances:
  480. description: Number of instances in a viable state
  481. returned: success
  482. type: int
  483. sample: 1
  484. vpc_zone_identifier:
  485. description: VPC zone ID / subnet id for the auto scaling group
  486. returned: success
  487. type: str
  488. sample: "subnet-a31ef45f"
  489. metrics_collection:
  490. description: List of enabled AutosSalingGroup metrics
  491. returned: success
  492. type: list
  493. sample: [
  494. {
  495. "Granularity": "1Minute",
  496. "Metric": "GroupInServiceInstances"
  497. }
  498. ]
  499. '''
  500. import time
  501. import traceback
  502. from ansible.module_utils._text import to_native
  503. from ansible.module_utils.basic import AnsibleModule
  504. from ansible.module_utils.ec2 import boto3_conn, ec2_argument_spec, HAS_BOTO3, camel_dict_to_snake_dict, get_aws_connection_info, AWSRetry
  505. try:
  506. import botocore
  507. except ImportError:
  508. pass # will be detected by imported HAS_BOTO3
  509. ASG_ATTRIBUTES = ('AvailabilityZones', 'DefaultCooldown', 'DesiredCapacity',
  510. 'HealthCheckGracePeriod', 'HealthCheckType', 'LaunchConfigurationName',
  511. 'LoadBalancerNames', 'MaxSize', 'MinSize', 'AutoScalingGroupName', 'PlacementGroup',
  512. 'TerminationPolicies', 'VPCZoneIdentifier')
  513. INSTANCE_ATTRIBUTES = ('instance_id', 'health_status', 'lifecycle_state', 'launch_config_name')
  514. backoff_params = dict(tries=10, delay=3, backoff=1.5)
  515. @AWSRetry.backoff(**backoff_params)
  516. def describe_autoscaling_groups(connection, group_name):
  517. pg = connection.get_paginator('describe_auto_scaling_groups')
  518. return pg.paginate(AutoScalingGroupNames=[group_name]).build_full_result().get('AutoScalingGroups', [])
  519. @AWSRetry.backoff(**backoff_params)
  520. def deregister_lb_instances(connection, lb_name, instance_id):
  521. connection.deregister_instances_from_load_balancer(LoadBalancerName=lb_name, Instances=[dict(InstanceId=instance_id)])
  522. @AWSRetry.backoff(**backoff_params)
  523. def describe_instance_health(connection, lb_name, instances):
  524. params = dict(LoadBalancerName=lb_name)
  525. if instances:
  526. params.update(Instances=instances)
  527. return connection.describe_instance_health(**params)
  528. @AWSRetry.backoff(**backoff_params)
  529. def describe_target_health(connection, target_group_arn, instances):
  530. return connection.describe_target_health(TargetGroupArn=target_group_arn, Targets=instances)
  531. @AWSRetry.backoff(**backoff_params)
  532. def suspend_asg_processes(connection, asg_name, processes):
  533. connection.suspend_processes(AutoScalingGroupName=asg_name, ScalingProcesses=processes)
  534. @AWSRetry.backoff(**backoff_params)
  535. def resume_asg_processes(connection, asg_name, processes):
  536. connection.resume_processes(AutoScalingGroupName=asg_name, ScalingProcesses=processes)
  537. @AWSRetry.backoff(**backoff_params)
  538. def describe_launch_configurations(connection, launch_config_name):
  539. pg = connection.get_paginator('describe_launch_configurations')
  540. return pg.paginate(LaunchConfigurationNames=[launch_config_name]).build_full_result()
  541. @AWSRetry.backoff(**backoff_params)
  542. def describe_launch_templates(connection, launch_template):
  543. if launch_template['launch_template_id'] is not None:
  544. try:
  545. lt = connection.describe_launch_templates(LaunchTemplateIds=[launch_template['launch_template_id']])
  546. return lt
  547. except (botocore.exceptions.ClientError) as e:
  548. module.fail_json(msg="No launch template found matching: %s" % launch_template)
  549. else:
  550. try:
  551. lt = connection.describe_launch_templates(LaunchTemplateNames=[launch_template['launch_template_name']])
  552. return lt
  553. except (botocore.exceptions.ClientError) as e:
  554. module.fail_json(msg="No launch template found matching: %s" % launch_template)
  555. @AWSRetry.backoff(**backoff_params)
  556. def create_asg(connection, **params):
  557. connection.create_auto_scaling_group(**params)
  558. @AWSRetry.backoff(**backoff_params)
  559. def put_notification_config(connection, asg_name, topic_arn, notification_types):
  560. connection.put_notification_configuration(
  561. AutoScalingGroupName=asg_name,
  562. TopicARN=topic_arn,
  563. NotificationTypes=notification_types
  564. )
  565. @AWSRetry.backoff(**backoff_params)
  566. def del_notification_config(connection, asg_name, topic_arn):
  567. connection.delete_notification_configuration(
  568. AutoScalingGroupName=asg_name,
  569. TopicARN=topic_arn
  570. )
  571. @AWSRetry.backoff(**backoff_params)
  572. def attach_load_balancers(connection, asg_name, load_balancers):
  573. connection.attach_load_balancers(AutoScalingGroupName=asg_name, LoadBalancerNames=load_balancers)
  574. @AWSRetry.backoff(**backoff_params)
  575. def detach_load_balancers(connection, asg_name, load_balancers):
  576. connection.detach_load_balancers(AutoScalingGroupName=asg_name, LoadBalancerNames=load_balancers)
  577. @AWSRetry.backoff(**backoff_params)
  578. def attach_lb_target_groups(connection, asg_name, target_group_arns):
  579. connection.attach_load_balancer_target_groups(AutoScalingGroupName=asg_name, TargetGroupARNs=target_group_arns)
  580. @AWSRetry.backoff(**backoff_params)
  581. def detach_lb_target_groups(connection, asg_name, target_group_arns):
  582. connection.detach_load_balancer_target_groups(AutoScalingGroupName=asg_name, TargetGroupARNs=target_group_arns)
  583. @AWSRetry.backoff(**backoff_params)
  584. def update_asg(connection, **params):
  585. connection.update_auto_scaling_group(**params)
  586. @AWSRetry.backoff(catch_extra_error_codes=['ScalingActivityInProgress'], **backoff_params)
  587. def delete_asg(connection, asg_name, force_delete):
  588. connection.delete_auto_scaling_group(AutoScalingGroupName=asg_name, ForceDelete=force_delete)
  589. @AWSRetry.backoff(**backoff_params)
  590. def terminate_asg_instance(connection, instance_id, decrement_capacity):
  591. connection.terminate_instance_in_auto_scaling_group(InstanceId=instance_id,
  592. ShouldDecrementDesiredCapacity=decrement_capacity)
  593. def enforce_required_arguments_for_create():
  594. ''' As many arguments are not required for autoscale group deletion
  595. they cannot be mandatory arguments for the module, so we enforce
  596. them here '''
  597. missing_args = []
  598. if module.params.get('launch_config_name') is None and module.params.get('launch_template') is None:
  599. module.fail_json(msg="Missing either launch_config_name or launch_template for autoscaling group create")
  600. for arg in ('min_size', 'max_size'):
  601. if module.params[arg] is None:
  602. missing_args.append(arg)
  603. if missing_args:
  604. module.fail_json(msg="Missing required arguments for autoscaling group create: %s" % ",".join(missing_args))
  605. def get_properties(autoscaling_group):
  606. properties = dict()
  607. properties['healthy_instances'] = 0
  608. properties['in_service_instances'] = 0
  609. properties['unhealthy_instances'] = 0
  610. properties['pending_instances'] = 0
  611. properties['viable_instances'] = 0
  612. properties['terminating_instances'] = 0
  613. instance_facts = dict()
  614. autoscaling_group_instances = autoscaling_group.get('Instances')
  615. if autoscaling_group_instances:
  616. properties['instances'] = [i['InstanceId'] for i in autoscaling_group_instances]
  617. for i in autoscaling_group_instances:
  618. if i.get('LaunchConfigurationName'):
  619. instance_facts[i['InstanceId']] = {'health_status': i['HealthStatus'],
  620. 'lifecycle_state': i['LifecycleState'],
  621. 'launch_config_name': i['LaunchConfigurationName']}
  622. elif i.get('LaunchTemplate'):
  623. instance_facts[i['InstanceId']] = {'health_status': i['HealthStatus'],
  624. 'lifecycle_state': i['LifecycleState'],
  625. 'launch_template': i['LaunchTemplate']}
  626. else:
  627. instance_facts[i['InstanceId']] = {'health_status': i['HealthStatus'],
  628. 'lifecycle_state': i['LifecycleState']}
  629. if i['HealthStatus'] == 'Healthy' and i['LifecycleState'] == 'InService':
  630. properties['viable_instances'] += 1
  631. if i['HealthStatus'] == 'Healthy':
  632. properties['healthy_instances'] += 1
  633. else:
  634. properties['unhealthy_instances'] += 1
  635. if i['LifecycleState'] == 'InService':
  636. properties['in_service_instances'] += 1
  637. if i['LifecycleState'] == 'Terminating':
  638. properties['terminating_instances'] += 1
  639. if i['LifecycleState'] == 'Pending':
  640. properties['pending_instances'] += 1
  641. else:
  642. properties['instances'] = []
  643. properties['auto_scaling_group_name'] = autoscaling_group.get('AutoScalingGroupName')
  644. properties['auto_scaling_group_arn'] = autoscaling_group.get('AutoScalingGroupARN')
  645. properties['availability_zones'] = autoscaling_group.get('AvailabilityZones')
  646. properties['created_time'] = autoscaling_group.get('CreatedTime')
  647. properties['instance_facts'] = instance_facts
  648. properties['load_balancers'] = autoscaling_group.get('LoadBalancerNames')
  649. if autoscaling_group.get('LaunchConfigurationName'):
  650. properties['launch_config_name'] = autoscaling_group.get('LaunchConfigurationName')
  651. else:
  652. properties['launch_template'] = autoscaling_group.get('LaunchTemplate')
  653. properties['tags'] = autoscaling_group.get('Tags')
  654. properties['min_size'] = autoscaling_group.get('MinSize')
  655. properties['max_size'] = autoscaling_group.get('MaxSize')
  656. properties['desired_capacity'] = autoscaling_group.get('DesiredCapacity')
  657. properties['default_cooldown'] = autoscaling_group.get('DefaultCooldown')
  658. properties['healthcheck_grace_period'] = autoscaling_group.get('HealthCheckGracePeriod')
  659. properties['healthcheck_type'] = autoscaling_group.get('HealthCheckType')
  660. properties['default_cooldown'] = autoscaling_group.get('DefaultCooldown')
  661. properties['termination_policies'] = autoscaling_group.get('TerminationPolicies')
  662. properties['target_group_arns'] = autoscaling_group.get('TargetGroupARNs')
  663. properties['vpc_zone_identifier'] = autoscaling_group.get('VPCZoneIdentifier')
  664. raw_mixed_instance_object = autoscaling_group.get('MixedInstancesPolicy')
  665. if raw_mixed_instance_object:
  666. raw_mixed_instance_object.get('LaunchTemplate').get('Overrides')
  667. properties['mixed_instances_policy'] = [x['InstanceType'] for x in raw_mixed_instance_object.get('LaunchTemplate').get('Overrides')]
  668. metrics = autoscaling_group.get('EnabledMetrics')
  669. if metrics:
  670. metrics.sort(key=lambda x: x["Metric"])
  671. properties['metrics_collection'] = metrics
  672. if properties['target_group_arns']:
  673. region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
  674. elbv2_connection = boto3_conn(module,
  675. conn_type='client',
  676. resource='elbv2',
  677. region=region,
  678. endpoint=ec2_url,
  679. **aws_connect_params)
  680. tg_paginator = elbv2_connection.get_paginator('describe_target_groups')
  681. tg_result = tg_paginator.paginate(TargetGroupArns=properties['target_group_arns']).build_full_result()
  682. target_groups = tg_result['TargetGroups']
  683. else:
  684. target_groups = []
  685. properties['target_group_names'] = [tg['TargetGroupName'] for tg in target_groups]
  686. return properties
  687. def get_launch_object(connection, ec2_connection):
  688. launch_object = dict()
  689. launch_config_name = module.params.get('launch_config_name')
  690. launch_template = module.params.get('launch_template')
  691. mixed_instances_policy = module.params.get('mixed_instances_policy')
  692. if launch_config_name is None and launch_template is None:
  693. return launch_object
  694. elif launch_config_name:
  695. try:
  696. launch_configs = describe_launch_configurations(connection, launch_config_name)
  697. except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
  698. module.fail_json(msg="Failed to describe launch configurations",
  699. exception=traceback.format_exc())
  700. if len(launch_configs['LaunchConfigurations']) == 0:
  701. module.fail_json(msg="No launch config found with name %s" % launch_config_name)
  702. launch_object = {"LaunchConfigurationName": launch_configs['LaunchConfigurations'][0]['LaunchConfigurationName']}
  703. return launch_object
  704. elif launch_template:
  705. lt = describe_launch_templates(ec2_connection, launch_template)['LaunchTemplates'][0]
  706. if launch_template['version'] is not None:
  707. launch_object = {"LaunchTemplate": {"LaunchTemplateId": lt['LaunchTemplateId'], "Version": launch_template['version']}}
  708. else:
  709. launch_object = {"LaunchTemplate": {"LaunchTemplateId": lt['LaunchTemplateId'], "Version": str(lt['LatestVersionNumber'])}}
  710. if mixed_instances_policy:
  711. instance_types = mixed_instances_policy.get('instance_types', [])
  712. policy = {
  713. 'LaunchTemplate': {
  714. 'LaunchTemplateSpecification': launch_object["LaunchTemplate"]
  715. }
  716. }
  717. if instance_types:
  718. policy['LaunchTemplate']['Overrides'] = []
  719. for instance_type in instance_types:
  720. instance_type_dict = {'InstanceType': instance_type}
  721. policy['LaunchTemplate']['Overrides'].append(instance_type_dict)
  722. launch_object['MixedInstancesPolicy'] = policy
  723. return launch_object
  724. def elb_dreg(asg_connection, group_name, instance_id):
  725. region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
  726. as_group = describe_autoscaling_groups(asg_connection, group_name)[0]
  727. wait_timeout = module.params.get('wait_timeout')
  728. count = 1
  729. if as_group['LoadBalancerNames'] and as_group['HealthCheckType'] == 'ELB':
  730. elb_connection = boto3_conn(module,
  731. conn_type='client',
  732. resource='elb',
  733. region=region,
  734. endpoint=ec2_url,
  735. **aws_connect_params)
  736. else:
  737. return
  738. for lb in as_group['LoadBalancerNames']:
  739. deregister_lb_instances(elb_connection, lb, instance_id)
  740. module.debug("De-registering %s from ELB %s" % (instance_id, lb))
  741. wait_timeout = time.time() + wait_timeout
  742. while wait_timeout > time.time() and count > 0:
  743. count = 0
  744. for lb in as_group['LoadBalancerNames']:
  745. lb_instances = describe_instance_health(elb_connection, lb, [])
  746. for i in lb_instances['InstanceStates']:
  747. if i['InstanceId'] == instance_id and i['State'] == "InService":
  748. count += 1
  749. module.debug("%s: %s, %s" % (i['InstanceId'], i['State'], i['Description']))
  750. time.sleep(10)
  751. if wait_timeout <= time.time():
  752. # waiting took too long
  753. module.fail_json(msg="Waited too long for instance to deregister. {0}".format(time.asctime()))
  754. def elb_healthy(asg_connection, elb_connection, group_name):
  755. healthy_instances = set()
  756. as_group = describe_autoscaling_groups(asg_connection, group_name)[0]
  757. props = get_properties(as_group)
  758. # get healthy, inservice instances from ASG
  759. instances = []
  760. for instance, settings in props['instance_facts'].items():
  761. if settings['lifecycle_state'] == 'InService' and settings['health_status'] == 'Healthy':
  762. instances.append(dict(InstanceId=instance))
  763. module.debug("ASG considers the following instances InService and Healthy: %s" % instances)
  764. module.debug("ELB instance status:")
  765. lb_instances = list()
  766. for lb in as_group.get('LoadBalancerNames'):
  767. # we catch a race condition that sometimes happens if the instance exists in the ASG
  768. # but has not yet show up in the ELB
  769. try:
  770. lb_instances = describe_instance_health(elb_connection, lb, instances)
  771. except botocore.exceptions.ClientError as e:
  772. if e.response['Error']['Code'] == 'InvalidInstance':
  773. return None
  774. module.fail_json(msg="Failed to get load balancer.",
  775. exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
  776. except botocore.exceptions.BotoCoreError as e:
  777. module.fail_json(msg="Failed to get load balancer.",
  778. exception=traceback.format_exc())
  779. for i in lb_instances.get('InstanceStates'):
  780. if i['State'] == "InService":
  781. healthy_instances.add(i['InstanceId'])
  782. module.debug("ELB Health State %s: %s" % (i['InstanceId'], i['State']))
  783. return len(healthy_instances)
  784. def tg_healthy(asg_connection, elbv2_connection, group_name):
  785. healthy_instances = set()
  786. as_group = describe_autoscaling_groups(asg_connection, group_name)[0]
  787. props = get_properties(as_group)
  788. # get healthy, inservice instances from ASG
  789. instances = []
  790. for instance, settings in props['instance_facts'].items():
  791. if settings['lifecycle_state'] == 'InService' and settings['health_status'] == 'Healthy':
  792. instances.append(dict(Id=instance))
  793. module.debug("ASG considers the following instances InService and Healthy: %s" % instances)
  794. module.debug("Target Group instance status:")
  795. tg_instances = list()
  796. for tg in as_group.get('TargetGroupARNs'):
  797. # we catch a race condition that sometimes happens if the instance exists in the ASG
  798. # but has not yet show up in the ELB
  799. try:
  800. tg_instances = describe_target_health(elbv2_connection, tg, instances)
  801. except botocore.exceptions.ClientError as e:
  802. if e.response['Error']['Code'] == 'InvalidInstance':
  803. return None
  804. module.fail_json(msg="Failed to get target group.",
  805. exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
  806. except botocore.exceptions.BotoCoreError as e:
  807. module.fail_json(msg="Failed to get target group.",
  808. exception=traceback.format_exc())
  809. for i in tg_instances.get('TargetHealthDescriptions'):
  810. if i['TargetHealth']['State'] == "healthy":
  811. healthy_instances.add(i['Target']['Id'])
  812. module.debug("Target Group Health State %s: %s" % (i['Target']['Id'], i['TargetHealth']['State']))
  813. return len(healthy_instances)
  814. def wait_for_elb(asg_connection, group_name):
  815. region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
  816. wait_timeout = module.params.get('wait_timeout')
  817. # if the health_check_type is ELB, we want to query the ELBs directly for instance
  818. # status as to avoid health_check_grace period that is awarded to ASG instances
  819. as_group = describe_autoscaling_groups(asg_connection, group_name)[0]
  820. if as_group.get('LoadBalancerNames') and as_group.get('HealthCheckType') == 'ELB':
  821. module.debug("Waiting for ELB to consider instances healthy.")
  822. elb_connection = boto3_conn(module,
  823. conn_type='client',
  824. resource='elb',
  825. region=region,
  826. endpoint=ec2_url,
  827. **aws_connect_params)
  828. wait_timeout = time.time() + wait_timeout
  829. healthy_instances = elb_healthy(asg_connection, elb_connection, group_name)
  830. while healthy_instances < as_group.get('MinSize') and wait_timeout > time.time():
  831. healthy_instances = elb_healthy(asg_connection, elb_connection, group_name)
  832. module.debug("ELB thinks %s instances are healthy." % healthy_instances)
  833. time.sleep(10)
  834. if wait_timeout <= time.time():
  835. # waiting took too long
  836. module.fail_json(msg="Waited too long for ELB instances to be healthy. %s" % time.asctime())
  837. module.debug("Waiting complete. ELB thinks %s instances are healthy." % healthy_instances)
  838. def wait_for_target_group(asg_connection, group_name):
  839. region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
  840. wait_timeout = module.params.get('wait_timeout')
  841. # if the health_check_type is ELB, we want to query the ELBs directly for instance
  842. # status as to avoid health_check_grace period that is awarded to ASG instances
  843. as_group = describe_autoscaling_groups(asg_connection, group_name)[0]
  844. if as_group.get('TargetGroupARNs') and as_group.get('HealthCheckType') == 'ELB':
  845. module.debug("Waiting for Target Group to consider instances healthy.")
  846. elbv2_connection = boto3_conn(module,
  847. conn_type='client',
  848. resource='elbv2',
  849. region=region,
  850. endpoint=ec2_url,
  851. **aws_connect_params)
  852. wait_timeout = time.time() + wait_timeout
  853. healthy_instances = tg_healthy(asg_connection, elbv2_connection, group_name)
  854. while healthy_instances < as_group.get('MinSize') and wait_timeout > time.time():
  855. healthy_instances = tg_healthy(asg_connection, elbv2_connection, group_name)
  856. module.debug("Target Group thinks %s instances are healthy." % healthy_instances)
  857. time.sleep(10)
  858. if wait_timeout <= time.time():
  859. # waiting took too long
  860. module.fail_json(msg="Waited too long for ELB instances to be healthy. %s" % time.asctime())
  861. module.debug("Waiting complete. Target Group thinks %s instances are healthy." % healthy_instances)
  862. def suspend_processes(ec2_connection, as_group):
  863. suspend_processes = set(module.params.get('suspend_processes'))
  864. try:
  865. suspended_processes = set([p['ProcessName'] for p in as_group['SuspendedProcesses']])
  866. except AttributeError:
  867. # New ASG being created, no suspended_processes defined yet
  868. suspended_processes = set()
  869. if suspend_processes == suspended_processes:
  870. return False
  871. resume_processes = list(suspended_processes - suspend_processes)
  872. if resume_processes:
  873. resume_asg_processes(ec2_connection, module.params.get('name'), resume_processes)
  874. if suspend_processes:
  875. suspend_asg_processes(ec2_connection, module.params.get('name'), list(suspend_processes))
  876. return True
  877. def create_autoscaling_group(connection):
  878. group_name = module.params.get('name')
  879. load_balancers = module.params['load_balancers']
  880. target_group_arns = module.params['target_group_arns']
  881. availability_zones = module.params['availability_zones']
  882. launch_config_name = module.params.get('launch_config_name')
  883. launch_template = module.params.get('launch_template')
  884. mixed_instances_policy = module.params.get('mixed_instances_policy')
  885. min_size = module.params['min_size']
  886. max_size = module.params['max_size']
  887. placement_group = module.params.get('placement_group')
  888. desired_capacity = module.params.get('desired_capacity')
  889. vpc_zone_identifier = module.params.get('vpc_zone_identifier')
  890. set_tags = module.params.get('tags')
  891. health_check_period = module.params.get('health_check_period')
  892. health_check_type = module.params.get('health_check_type')
  893. default_cooldown = module.params.get('default_cooldown')
  894. wait_for_instances = module.params.get('wait_for_instances')
  895. wait_timeout = module.params.get('wait_timeout')
  896. termination_policies = module.params.get('termination_policies')
  897. notification_topic = module.params.get('notification_topic')
  898. notification_types = module.params.get('notification_types')
  899. metrics_collection = module.params.get('metrics_collection')
  900. metrics_granularity = module.params.get('metrics_granularity')
  901. metrics_list = module.params.get('metrics_list')
  902. try:
  903. as_groups = describe_autoscaling_groups(connection, group_name)
  904. except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
  905. module.fail_json(msg="Failed to describe auto scaling groups.",
  906. exception=traceback.format_exc())
  907. region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
  908. ec2_connection = boto3_conn(module,
  909. conn_type='client',
  910. resource='ec2',
  911. region=region,
  912. endpoint=ec2_url,
  913. **aws_connect_params)
  914. if vpc_zone_identifier:
  915. vpc_zone_identifier = ','.join(vpc_zone_identifier)
  916. asg_tags = []
  917. for tag in set_tags:
  918. for k, v in tag.items():
  919. if k != 'propagate_at_launch':
  920. asg_tags.append(dict(Key=k,
  921. Value=to_native(v),
  922. PropagateAtLaunch=bool(tag.get('propagate_at_launch', True)),
  923. ResourceType='auto-scaling-group',
  924. ResourceId=group_name))
  925. if not as_groups:
  926. if not vpc_zone_identifier and not availability_zones:
  927. availability_zones = module.params['availability_zones'] = [zone['ZoneName'] for
  928. zone in ec2_connection.describe_availability_zones()['AvailabilityZones']]
  929. enforce_required_arguments_for_create()
  930. if desired_capacity is None:
  931. desired_capacity = min_size
  932. ag = dict(
  933. AutoScalingGroupName=group_name,
  934. MinSize=min_size,
  935. MaxSize=max_size,
  936. DesiredCapacity=desired_capacity,
  937. Tags=asg_tags,
  938. HealthCheckGracePeriod=health_check_period,
  939. HealthCheckType=health_check_type,
  940. DefaultCooldown=default_cooldown,
  941. TerminationPolicies=termination_policies)
  942. if vpc_zone_identifier:
  943. ag['VPCZoneIdentifier'] = vpc_zone_identifier
  944. if availability_zones:
  945. ag['AvailabilityZones'] = availability_zones
  946. if placement_group:
  947. ag['PlacementGroup'] = placement_group
  948. if load_balancers:
  949. ag['LoadBalancerNames'] = load_balancers
  950. if target_group_arns:
  951. ag['TargetGroupARNs'] = target_group_arns
  952. launch_object = get_launch_object(connection, ec2_connection)
  953. if 'LaunchConfigurationName' in launch_object:
  954. ag['LaunchConfigurationName'] = launch_object['LaunchConfigurationName']
  955. elif 'LaunchTemplate' in launch_object:
  956. if 'MixedInstancesPolicy' in launch_object:
  957. ag['MixedInstancesPolicy'] = launch_object['MixedInstancesPolicy']
  958. else:
  959. ag['LaunchTemplate'] = launch_object['LaunchTemplate']
  960. else:
  961. module.fail_json(msg="Missing LaunchConfigurationName or LaunchTemplate",
  962. exception=traceback.format_exc())
  963. try:
  964. create_asg(connection, **ag)
  965. if metrics_collection:
  966. connection.enable_metrics_collection(AutoScalingGroupName=group_name, Granularity=metrics_granularity, Metrics=metrics_list)
  967. all_ag = describe_autoscaling_groups(connection, group_name)
  968. if len(all_ag) == 0:
  969. module.fail_json(msg="No auto scaling group found with the name %s" % group_name)
  970. as_group = all_ag[0]
  971. suspend_processes(connection, as_group)
  972. if wait_for_instances:
  973. wait_for_new_inst(connection, group_name, wait_timeout, desired_capacity, 'viable_instances')
  974. if load_balancers:
  975. wait_for_elb(connection, group_name)
  976. # Wait for target group health if target group(s)defined
  977. if target_group_arns:
  978. wait_for_target_group(connection, group_name)
  979. if notification_topic:
  980. put_notification_config(connection, group_name, notification_topic, notification_types)
  981. as_group = describe_autoscaling_groups(connection, group_name)[0]
  982. asg_properties = get_properties(as_group)
  983. changed = True
  984. return changed, asg_properties
  985. except botocore.exceptions.ClientError as e:
  986. module.fail_json(msg="Failed to create Autoscaling Group.",
  987. exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
  988. except botocore.exceptions.BotoCoreError as e:
  989. module.fail_json(msg="Failed to create Autoscaling Group.",
  990. exception=traceback.format_exc())
  991. else:
  992. as_group = as_groups[0]
  993. initial_asg_properties = get_properties(as_group)
  994. changed = False
  995. if suspend_processes(connection, as_group):
  996. changed = True
  997. # process tag changes
  998. if len(set_tags) > 0:
  999. have_tags = as_group.get('Tags')
  1000. want_tags = asg_tags
  1001. if have_tags:
  1002. have_tags.sort(key=lambda x: x["Key"])
  1003. if want_tags:
  1004. want_tags.sort(key=lambda x: x["Key"])
  1005. dead_tags = []
  1006. have_tag_keyvals = [x['Key'] for x in have_tags]
  1007. want_tag_keyvals = [x['Key'] for x in want_tags]
  1008. for dead_tag in set(have_tag_keyvals).difference(want_tag_keyvals):
  1009. changed = True
  1010. dead_tags.append(dict(ResourceId=as_group['AutoScalingGroupName'],
  1011. ResourceType='auto-scaling-group', Key=dead_tag))
  1012. have_tags = [have_tag for have_tag in have_tags if have_tag['Key'] != dead_tag]
  1013. if dead_tags:
  1014. connection.delete_tags(Tags=dead_tags)
  1015. zipped = zip(have_tags, want_tags)
  1016. if len(have_tags) != len(want_tags) or not all(x == y for x, y in zipped):
  1017. changed = True
  1018. connection.create_or_update_tags(Tags=asg_tags)
  1019. # Handle load balancer attachments/detachments
  1020. # Attach load balancers if they are specified but none currently exist
  1021. if load_balancers and not as_group['LoadBalancerNames']:
  1022. changed = True
  1023. try:
  1024. attach_load_balancers(connection, group_name, load_balancers)
  1025. except botocore.exceptions.ClientError as e:
  1026. module.fail_json(msg="Failed to update Autoscaling Group.",
  1027. exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
  1028. except botocore.exceptions.BotoCoreError as e:
  1029. module.fail_json(msg="Failed to update Autoscaling Group.",
  1030. exception=traceback.format_exc())
  1031. # Update load balancers if they are specified and one or more already exists
  1032. elif as_group['LoadBalancerNames']:
  1033. change_load_balancers = load_balancers is not None
  1034. # Get differences
  1035. if not load_balancers:
  1036. load_balancers = list()
  1037. wanted_elbs = set(load_balancers)
  1038. has_elbs = set(as_group['LoadBalancerNames'])
  1039. # check if all requested are already existing
  1040. if has_elbs - wanted_elbs and change_load_balancers:
  1041. # if wanted contains less than existing, then we need to delete some
  1042. elbs_to_detach = has_elbs.difference(wanted_elbs)
  1043. if elbs_to_detach:
  1044. changed = True
  1045. try:
  1046. detach_load_balancers(connection, group_name, list(elbs_to_detach))
  1047. except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
  1048. module.fail_json(msg="Failed to detach load balancers %s: %s." % (elbs_to_detach, to_native(e)),
  1049. exception=traceback.format_exc())
  1050. if wanted_elbs - has_elbs:
  1051. # if has contains less than wanted, then we need to add some
  1052. elbs_to_attach = wanted_elbs.difference(has_elbs)
  1053. if elbs_to_attach:
  1054. changed = True
  1055. try:
  1056. attach_load_balancers(connection, group_name, list(elbs_to_attach))
  1057. except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
  1058. module.fail_json(msg="Failed to attach load balancers %s: %s." % (elbs_to_attach, to_native(e)),
  1059. exception=traceback.format_exc())
  1060. # Handle target group attachments/detachments
  1061. # Attach target groups if they are specified but none currently exist
  1062. if target_group_arns and not as_group['TargetGroupARNs']:
  1063. changed = True
  1064. try:
  1065. attach_lb_target_groups(connection, group_name, target_group_arns)
  1066. except botocore.exceptions.ClientError as e:
  1067. module.fail_json(msg="Failed to update Autoscaling Group.",
  1068. exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
  1069. except botocore.exceptions.BotoCoreError as e:
  1070. module.fail_json(msg="Failed to update Autoscaling Group.",
  1071. exception=traceback.format_exc())
  1072. # Update target groups if they are specified and one or more already exists
  1073. elif target_group_arns is not None and as_group['TargetGroupARNs']:
  1074. # Get differences
  1075. wanted_tgs = set(target_group_arns)
  1076. has_tgs = set(as_group['TargetGroupARNs'])
  1077. # check if all requested are already existing
  1078. if has_tgs.issuperset(wanted_tgs):
  1079. # if wanted contains less than existing, then we need to delete some
  1080. tgs_to_detach = has_tgs.difference(wanted_tgs)
  1081. if tgs_to_detach:
  1082. changed = True
  1083. try:
  1084. detach_lb_target_groups(connection, group_name, list(tgs_to_detach))
  1085. except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
  1086. module.fail_json(msg="Failed to detach load balancer target groups %s: %s" % (tgs_to_detach, to_native(e)),
  1087. exception=traceback.format_exc())
  1088. if wanted_tgs.issuperset(has_tgs):
  1089. # if has contains less than wanted, then we need to add some
  1090. tgs_to_attach = wanted_tgs.difference(has_tgs)
  1091. if tgs_to_attach:
  1092. changed = True
  1093. try:
  1094. attach_lb_target_groups(connection, group_name, list(tgs_to_attach))
  1095. except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
  1096. module.fail_json(msg="Failed to attach load balancer target groups %s: %s" % (tgs_to_attach, to_native(e)),
  1097. exception=traceback.format_exc())
  1098. # check for attributes that aren't required for updating an existing ASG
  1099. # check if min_size/max_size/desired capacity have been specified and if not use ASG values
  1100. if min_size is None:
  1101. min_size = as_group['MinSize']
  1102. if max_size is None:
  1103. max_size = as_group['MaxSize']
  1104. if desired_capacity is None:
  1105. desired_capacity = as_group['DesiredCapacity']
  1106. ag = dict(
  1107. AutoScalingGroupName=group_name,
  1108. MinSize=min_size,
  1109. MaxSize=max_size,
  1110. DesiredCapacity=desired_capacity,
  1111. HealthCheckGracePeriod=health_check_period,
  1112. HealthCheckType=health_check_type,
  1113. DefaultCooldown=default_cooldown,
  1114. TerminationPolicies=termination_policies)
  1115. # Get the launch object (config or template) if one is provided in args or use the existing one attached to ASG if not.
  1116. launch_object = get_launch_object(connection, ec2_connection)
  1117. if 'LaunchConfigurationName' in launch_object:
  1118. ag['LaunchConfigurationName'] = launch_object['LaunchConfigurationName']
  1119. elif 'LaunchTemplate' in launch_object:
  1120. if 'MixedInstancesPolicy' in launch_object:
  1121. ag['MixedInstancesPolicy'] = launch_object['MixedInstancesPolicy']
  1122. else:
  1123. ag['LaunchTemplate'] = launch_object['LaunchTemplate']
  1124. else:
  1125. try:
  1126. ag['LaunchConfigurationName'] = as_group['LaunchConfigurationName']
  1127. except Exception:
  1128. launch_template = as_group['LaunchTemplate']
  1129. # Prefer LaunchTemplateId over Name as it's more specific. Only one can be used for update_asg.
  1130. ag['LaunchTemplate'] = {"LaunchTemplateId": launch_template['LaunchTemplateId'], "Version": launch_template['Version']}
  1131. if availability_zones:
  1132. ag['AvailabilityZones'] = availability_zones
  1133. if vpc_zone_identifier:
  1134. ag['VPCZoneIdentifier'] = vpc_zone_identifier
  1135. try:
  1136. update_asg(connection, **ag)
  1137. if metrics_collection:
  1138. connection.enable_metrics_collection(AutoScalingGroupName=group_name, Granularity=metrics_granularity, Metrics=metrics_list)
  1139. else:
  1140. connection.disable_metrics_collection(AutoScalingGroupName=group_name, Metrics=metrics_list)
  1141. except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
  1142. module.fail_json(msg="Failed to update autoscaling group: %s" % to_native(e),
  1143. exception=traceback.format_exc())
  1144. if notification_topic:
  1145. try:
  1146. put_notification_config(connection, group_name, notification_topic, notification_types)
  1147. except botocore.exceptions.ClientError as e:
  1148. module.fail_json(msg="Failed to update Autoscaling Group notifications.",
  1149. exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
  1150. except botocore.exceptions.BotoCoreError as e:
  1151. module.fail_json(msg="Failed to update Autoscaling Group notifications.",
  1152. exception=traceback.format_exc())
  1153. if wait_for_instances:
  1154. wait_for_new_inst(connection, group_name, wait_timeout, desired_capacity, 'viable_instances')
  1155. # Wait for ELB health if ELB(s)defined
  1156. if load_balancers:
  1157. module.debug('\tWAITING FOR ELB HEALTH')
  1158. wait_for_elb(connection, group_name)
  1159. # Wait for target group health if target group(s)defined
  1160. if target_group_arns:
  1161. module.debug('\tWAITING FOR TG HEALTH')
  1162. wait_for_target_group(connection, group_name)
  1163. try:
  1164. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1165. asg_properties = get_properties(as_group)
  1166. if asg_properties != initial_asg_properties:
  1167. changed = True
  1168. except botocore.exceptions.ClientError as e:
  1169. module.fail_json(msg="Failed to read existing Autoscaling Groups.",
  1170. exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
  1171. except botocore.exceptions.BotoCoreError as e:
  1172. module.fail_json(msg="Failed to read existing Autoscaling Groups.",
  1173. exception=traceback.format_exc())
  1174. return changed, asg_properties
  1175. def delete_autoscaling_group(connection):
  1176. group_name = module.params.get('name')
  1177. notification_topic = module.params.get('notification_topic')
  1178. wait_for_instances = module.params.get('wait_for_instances')
  1179. wait_timeout = module.params.get('wait_timeout')
  1180. if notification_topic:
  1181. del_notification_config(connection, group_name, notification_topic)
  1182. groups = describe_autoscaling_groups(connection, group_name)
  1183. if groups:
  1184. wait_timeout = time.time() + wait_timeout
  1185. if not wait_for_instances:
  1186. delete_asg(connection, group_name, force_delete=True)
  1187. else:
  1188. updated_params = dict(AutoScalingGroupName=group_name, MinSize=0, MaxSize=0, DesiredCapacity=0)
  1189. update_asg(connection, **updated_params)
  1190. instances = True
  1191. while instances and wait_for_instances and wait_timeout >= time.time():
  1192. tmp_groups = describe_autoscaling_groups(connection, group_name)
  1193. if tmp_groups:
  1194. tmp_group = tmp_groups[0]
  1195. if not tmp_group.get('Instances'):
  1196. instances = False
  1197. time.sleep(10)
  1198. if wait_timeout <= time.time():
  1199. # waiting took too long
  1200. module.fail_json(msg="Waited too long for old instances to terminate. %s" % time.asctime())
  1201. delete_asg(connection, group_name, force_delete=False)
  1202. while describe_autoscaling_groups(connection, group_name) and wait_timeout >= time.time():
  1203. time.sleep(5)
  1204. if wait_timeout <= time.time():
  1205. # waiting took too long
  1206. module.fail_json(msg="Waited too long for ASG to delete. %s" % time.asctime())
  1207. return True
  1208. return False
  1209. def get_chunks(l, n):
  1210. for i in range(0, len(l), n):
  1211. yield l[i:i + n]
  1212. def update_size(connection, group, max_size, min_size, dc):
  1213. module.debug("setting ASG sizes")
  1214. module.debug("minimum size: %s, desired_capacity: %s, max size: %s" % (min_size, dc, max_size))
  1215. updated_group = dict()
  1216. updated_group['AutoScalingGroupName'] = group['AutoScalingGroupName']
  1217. updated_group['MinSize'] = min_size
  1218. updated_group['MaxSize'] = max_size
  1219. updated_group['DesiredCapacity'] = dc
  1220. update_asg(connection, **updated_group)
  1221. def replace(connection):
  1222. batch_size = module.params.get('replace_batch_size')
  1223. wait_timeout = module.params.get('wait_timeout')
  1224. group_name = module.params.get('name')
  1225. max_size = module.params.get('max_size')
  1226. min_size = module.params.get('min_size')
  1227. desired_capacity = module.params.get('desired_capacity')
  1228. launch_config_name = module.params.get('launch_config_name')
  1229. # Required to maintain the default value being set to 'true'
  1230. if launch_config_name:
  1231. lc_check = module.params.get('lc_check')
  1232. else:
  1233. lc_check = False
  1234. # Mirror above behaviour for Launch Templates
  1235. launch_template = module.params.get('launch_template')
  1236. if launch_template:
  1237. lt_check = module.params.get('lt_check')
  1238. else:
  1239. lt_check = False
  1240. replace_instances = module.params.get('replace_instances')
  1241. replace_all_instances = module.params.get('replace_all_instances')
  1242. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1243. if desired_capacity is None:
  1244. desired_capacity = as_group['DesiredCapacity']
  1245. wait_for_new_inst(connection, group_name, wait_timeout, as_group['MinSize'], 'viable_instances')
  1246. props = get_properties(as_group)
  1247. instances = props['instances']
  1248. if replace_all_instances:
  1249. # If replacing all instances, then set replace_instances to current set
  1250. # This allows replace_instances and replace_all_instances to behave same
  1251. replace_instances = instances
  1252. if replace_instances:
  1253. instances = replace_instances
  1254. # check to see if instances are replaceable if checking launch configs
  1255. if launch_config_name:
  1256. new_instances, old_instances = get_instances_by_launch_config(props, lc_check, instances)
  1257. elif launch_template:
  1258. new_instances, old_instances = get_instances_by_launch_template(props, lt_check, instances)
  1259. num_new_inst_needed = desired_capacity - len(new_instances)
  1260. if lc_check or lt_check:
  1261. if num_new_inst_needed == 0 and old_instances:
  1262. module.debug("No new instances needed, but old instances are present. Removing old instances")
  1263. terminate_batch(connection, old_instances, instances, True)
  1264. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1265. props = get_properties(as_group)
  1266. changed = True
  1267. return(changed, props)
  1268. # we don't want to spin up extra instances if not necessary
  1269. if num_new_inst_needed < batch_size:
  1270. module.debug("Overriding batch size to %s" % num_new_inst_needed)
  1271. batch_size = num_new_inst_needed
  1272. if not old_instances:
  1273. changed = False
  1274. return(changed, props)
  1275. # check if min_size/max_size/desired capacity have been specified and if not use ASG values
  1276. if min_size is None:
  1277. min_size = as_group['MinSize']
  1278. if max_size is None:
  1279. max_size = as_group['MaxSize']
  1280. # set temporary settings and wait for them to be reached
  1281. # This should get overwritten if the number of instances left is less than the batch size.
  1282. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1283. update_size(connection, as_group, max_size + batch_size, min_size + batch_size, desired_capacity + batch_size)
  1284. wait_for_new_inst(connection, group_name, wait_timeout, as_group['MinSize'] + batch_size, 'viable_instances')
  1285. wait_for_elb(connection, group_name)
  1286. wait_for_target_group(connection, group_name)
  1287. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1288. props = get_properties(as_group)
  1289. instances = props['instances']
  1290. if replace_instances:
  1291. instances = replace_instances
  1292. module.debug("beginning main loop")
  1293. for i in get_chunks(instances, batch_size):
  1294. # break out of this loop if we have enough new instances
  1295. break_early, desired_size, term_instances = terminate_batch(connection, i, instances, False)
  1296. wait_for_term_inst(connection, term_instances)
  1297. wait_for_new_inst(connection, group_name, wait_timeout, desired_size, 'viable_instances')
  1298. wait_for_elb(connection, group_name)
  1299. wait_for_target_group(connection, group_name)
  1300. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1301. if break_early:
  1302. module.debug("breaking loop")
  1303. break
  1304. update_size(connection, as_group, max_size, min_size, desired_capacity)
  1305. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1306. asg_properties = get_properties(as_group)
  1307. module.debug("Rolling update complete.")
  1308. changed = True
  1309. return(changed, asg_properties)
  1310. def get_instances_by_launch_config(props, lc_check, initial_instances):
  1311. new_instances = []
  1312. old_instances = []
  1313. # old instances are those that have the old launch config
  1314. if lc_check:
  1315. for i in props['instances']:
  1316. # Check if migrating from launch_template to launch_config first
  1317. if 'launch_template' in props['instance_facts'][i]:
  1318. old_instances.append(i)
  1319. elif props['instance_facts'][i]['launch_config_name'] == props['launch_config_name']:
  1320. new_instances.append(i)
  1321. else:
  1322. old_instances.append(i)
  1323. else:
  1324. module.debug("Comparing initial instances with current: %s" % initial_instances)
  1325. for i in props['instances']:
  1326. if i not in initial_instances:
  1327. new_instances.append(i)
  1328. else:
  1329. old_instances.append(i)
  1330. module.debug("New instances: %s, %s" % (len(new_instances), new_instances))
  1331. module.debug("Old instances: %s, %s" % (len(old_instances), old_instances))
  1332. return new_instances, old_instances
  1333. def get_instances_by_launch_template(props, lt_check, initial_instances):
  1334. new_instances = []
  1335. old_instances = []
  1336. # old instances are those that have the old launch template or version of the same launch template
  1337. if lt_check:
  1338. for i in props['instances']:
  1339. # Check if migrating from launch_config_name to launch_template_name first
  1340. if 'launch_config_name' in props['instance_facts'][i]:
  1341. old_instances.append(i)
  1342. elif props['instance_facts'][i]['launch_template'] == props['launch_template']:
  1343. new_instances.append(i)
  1344. else:
  1345. old_instances.append(i)
  1346. else:
  1347. module.debug("Comparing initial instances with current: %s" % initial_instances)
  1348. for i in props['instances']:
  1349. if i not in initial_instances:
  1350. new_instances.append(i)
  1351. else:
  1352. old_instances.append(i)
  1353. module.debug("New instances: %s, %s" % (len(new_instances), new_instances))
  1354. module.debug("Old instances: %s, %s" % (len(old_instances), old_instances))
  1355. return new_instances, old_instances
  1356. def list_purgeable_instances(props, lc_check, lt_check, replace_instances, initial_instances):
  1357. instances_to_terminate = []
  1358. instances = (inst_id for inst_id in replace_instances if inst_id in props['instances'])
  1359. # check to make sure instances given are actually in the given ASG
  1360. # and they have a non-current launch config
  1361. if module.params.get('launch_config_name'):
  1362. if lc_check:
  1363. for i in instances:
  1364. if 'launch_template' in props['instance_facts'][i]:
  1365. instances_to_terminate.append(i)
  1366. elif props['instance_facts'][i]['launch_config_name'] != props['launch_config_name']:
  1367. instances_to_terminate.append(i)
  1368. else:
  1369. for i in instances:
  1370. if i in initial_instances:
  1371. instances_to_terminate.append(i)
  1372. elif module.params.get('launch_template'):
  1373. if lt_check:
  1374. for i in instances:
  1375. if 'launch_config_name' in props['instance_facts'][i]:
  1376. instances_to_terminate.append(i)
  1377. elif props['instance_facts'][i]['launch_template'] != props['launch_template']:
  1378. instances_to_terminate.append(i)
  1379. else:
  1380. for i in instances:
  1381. if i in initial_instances:
  1382. instances_to_terminate.append(i)
  1383. return instances_to_terminate
  1384. def terminate_batch(connection, replace_instances, initial_instances, leftovers=False):
  1385. batch_size = module.params.get('replace_batch_size')
  1386. min_size = module.params.get('min_size')
  1387. desired_capacity = module.params.get('desired_capacity')
  1388. group_name = module.params.get('name')
  1389. lc_check = module.params.get('lc_check')
  1390. lt_check = module.params.get('lt_check')
  1391. decrement_capacity = False
  1392. break_loop = False
  1393. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1394. if desired_capacity is None:
  1395. desired_capacity = as_group['DesiredCapacity']
  1396. props = get_properties(as_group)
  1397. desired_size = as_group['MinSize']
  1398. if module.params.get('launch_config_name'):
  1399. new_instances, old_instances = get_instances_by_launch_config(props, lc_check, initial_instances)
  1400. else:
  1401. new_instances, old_instances = get_instances_by_launch_template(props, lt_check, initial_instances)
  1402. num_new_inst_needed = desired_capacity - len(new_instances)
  1403. # check to make sure instances given are actually in the given ASG
  1404. # and they have a non-current launch config
  1405. instances_to_terminate = list_purgeable_instances(props, lc_check, lt_check, replace_instances, initial_instances)
  1406. module.debug("new instances needed: %s" % num_new_inst_needed)
  1407. module.debug("new instances: %s" % new_instances)
  1408. module.debug("old instances: %s" % old_instances)
  1409. module.debug("batch instances: %s" % ",".join(instances_to_terminate))
  1410. if num_new_inst_needed == 0:
  1411. decrement_capacity = True
  1412. if as_group['MinSize'] != min_size:
  1413. if min_size is None:
  1414. min_size = as_group['MinSize']
  1415. updated_params = dict(AutoScalingGroupName=as_group['AutoScalingGroupName'], MinSize=min_size)
  1416. update_asg(connection, **updated_params)
  1417. module.debug("Updating minimum size back to original of %s" % min_size)
  1418. # if are some leftover old instances, but we are already at capacity with new ones
  1419. # we don't want to decrement capacity
  1420. if leftovers:
  1421. decrement_capacity = False
  1422. break_loop = True
  1423. instances_to_terminate = old_instances
  1424. desired_size = min_size
  1425. module.debug("No new instances needed")
  1426. if num_new_inst_needed < batch_size and num_new_inst_needed != 0:
  1427. instances_to_terminate = instances_to_terminate[:num_new_inst_needed]
  1428. decrement_capacity = False
  1429. break_loop = False
  1430. module.debug("%s new instances needed" % num_new_inst_needed)
  1431. module.debug("decrementing capacity: %s" % decrement_capacity)
  1432. for instance_id in instances_to_terminate:
  1433. elb_dreg(connection, group_name, instance_id)
  1434. module.debug("terminating instance: %s" % instance_id)
  1435. terminate_asg_instance(connection, instance_id, decrement_capacity)
  1436. # we wait to make sure the machines we marked as Unhealthy are
  1437. # no longer in the list
  1438. return break_loop, desired_size, instances_to_terminate
  1439. def wait_for_term_inst(connection, term_instances):
  1440. wait_timeout = module.params.get('wait_timeout')
  1441. group_name = module.params.get('name')
  1442. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1443. count = 1
  1444. wait_timeout = time.time() + wait_timeout
  1445. while wait_timeout > time.time() and count > 0:
  1446. module.debug("waiting for instances to terminate")
  1447. count = 0
  1448. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1449. props = get_properties(as_group)
  1450. instance_facts = props['instance_facts']
  1451. instances = (i for i in instance_facts if i in term_instances)
  1452. for i in instances:
  1453. lifecycle = instance_facts[i]['lifecycle_state']
  1454. health = instance_facts[i]['health_status']
  1455. module.debug("Instance %s has state of %s,%s" % (i, lifecycle, health))
  1456. if lifecycle.startswith('Terminating') or health == 'Unhealthy':
  1457. count += 1
  1458. time.sleep(10)
  1459. if wait_timeout <= time.time():
  1460. # waiting took too long
  1461. module.fail_json(msg="Waited too long for old instances to terminate. %s" % time.asctime())
  1462. def wait_for_new_inst(connection, group_name, wait_timeout, desired_size, prop):
  1463. # make sure we have the latest stats after that last loop.
  1464. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1465. props = get_properties(as_group)
  1466. module.debug("Waiting for %s = %s, currently %s" % (prop, desired_size, props[prop]))
  1467. # now we make sure that we have enough instances in a viable state
  1468. wait_timeout = time.time() + wait_timeout
  1469. while wait_timeout > time.time() and desired_size > props[prop]:
  1470. module.debug("Waiting for %s = %s, currently %s" % (prop, desired_size, props[prop]))
  1471. time.sleep(10)
  1472. as_group = describe_autoscaling_groups(connection, group_name)[0]
  1473. props = get_properties(as_group)
  1474. if wait_timeout <= time.time():
  1475. # waiting took too long
  1476. module.fail_json(msg="Waited too long for new instances to become viable. %s" % time.asctime())
  1477. module.debug("Reached %s: %s" % (prop, desired_size))
  1478. return props
  1479. def asg_exists(connection):
  1480. group_name = module.params.get('name')
  1481. as_group = describe_autoscaling_groups(connection, group_name)
  1482. return bool(len(as_group))
  1483. def main():
  1484. argument_spec = ec2_argument_spec()
  1485. argument_spec.update(
  1486. dict(
  1487. name=dict(required=True, type='str'),
  1488. load_balancers=dict(type='list'),
  1489. target_group_arns=dict(type='list'),
  1490. availability_zones=dict(type='list'),
  1491. launch_config_name=dict(type='str'),
  1492. launch_template=dict(type='dict',
  1493. default=None,
  1494. options=dict(
  1495. version=dict(type='str'),
  1496. launch_template_name=dict(type='str'),
  1497. launch_template_id=dict(type='str'),
  1498. ),
  1499. ),
  1500. mixed_instances_policy=dict(type='dict',
  1501. default=None,
  1502. options=dict(
  1503. instance_types=dict(type='list'),
  1504. )),
  1505. min_size=dict(type='int'),
  1506. max_size=dict(type='int'),
  1507. placement_group=dict(type='str'),
  1508. desired_capacity=dict(type='int'),
  1509. vpc_zone_identifier=dict(type='list'),
  1510. replace_batch_size=dict(type='int', default=1),
  1511. replace_all_instances=dict(type='bool', default=False),
  1512. replace_instances=dict(type='list', default=[]),
  1513. lc_check=dict(type='bool', default=True),
  1514. lt_check=dict(type='bool', default=True),
  1515. wait_timeout=dict(type='int', default=300),
  1516. state=dict(default='present', choices=['present', 'absent']),
  1517. tags=dict(type='list', default=[]),
  1518. health_check_period=dict(type='int', default=300),
  1519. health_check_type=dict(default='EC2', choices=['EC2', 'ELB']),
  1520. default_cooldown=dict(type='int', default=300),
  1521. wait_for_instances=dict(type='bool', default=True),
  1522. termination_policies=dict(type='list', default='Default'),
  1523. notification_topic=dict(type='str', default=None),
  1524. notification_types=dict(type='list', default=[
  1525. 'autoscaling:EC2_INSTANCE_LAUNCH',
  1526. 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR',
  1527. 'autoscaling:EC2_INSTANCE_TERMINATE',
  1528. 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR'
  1529. ]),
  1530. suspend_processes=dict(type='list', default=[]),
  1531. metrics_collection=dict(type='bool', default=False),
  1532. metrics_granularity=dict(type='str', default='1Minute'),
  1533. metrics_list=dict(type='list', default=[
  1534. 'GroupMinSize',
  1535. 'GroupMaxSize',
  1536. 'GroupDesiredCapacity',
  1537. 'GroupInServiceInstances',
  1538. 'GroupPendingInstances',
  1539. 'GroupStandbyInstances',
  1540. 'GroupTerminatingInstances',
  1541. 'GroupTotalInstances'
  1542. ])
  1543. ),
  1544. )
  1545. global module
  1546. module = AnsibleModule(
  1547. argument_spec=argument_spec,
  1548. mutually_exclusive=[
  1549. ['replace_all_instances', 'replace_instances'],
  1550. ['launch_config_name', 'launch_template']]
  1551. )
  1552. if not HAS_BOTO3:
  1553. module.fail_json(msg='boto3 required for this module')
  1554. state = module.params.get('state')
  1555. replace_instances = module.params.get('replace_instances')
  1556. replace_all_instances = module.params.get('replace_all_instances')
  1557. region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
  1558. connection = boto3_conn(module,
  1559. conn_type='client',
  1560. resource='autoscaling',
  1561. region=region,
  1562. endpoint=ec2_url,
  1563. **aws_connect_params)
  1564. changed = create_changed = replace_changed = False
  1565. exists = asg_exists(connection)
  1566. if state == 'present':
  1567. create_changed, asg_properties = create_autoscaling_group(connection)
  1568. elif state == 'absent':
  1569. changed = delete_autoscaling_group(connection)
  1570. module.exit_json(changed=changed)
  1571. # Only replace instances if asg existed at start of call
  1572. if exists and (replace_all_instances or replace_instances) and (module.params.get('launch_config_name') or module.params.get('launch_template')):
  1573. replace_changed, asg_properties = replace(connection)
  1574. if create_changed or replace_changed:
  1575. changed = True
  1576. module.exit_json(changed=changed, **asg_properties)
  1577. if __name__ == '__main__':
  1578. main()