Coverage for /Users/jingyu.wy/git/ansible-module-apsarastack/src/ansible_collections/alibaba/apsarastack/plugins/modules/ali_instance.py: 23%
303 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 18:06 +0800
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 18:06 +0800
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
4# Copyright (c) 2017-present Alibaba Group Holding Limited. He Guimin <heguimin36@163.com>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6#
7# This file is part of Ansible
8#
9# Ansible is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# Ansible is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with Ansible. If not, see http://www.gnu.org/licenses/.
22from __future__ import (absolute_import, division, print_function)
24__metaclass__ = type
26ANSIBLE_METADATA = {'metadata_version': '1.1',
27 'status': ['preview'],
28 'supported_by': 'community'}
30DOCUMENTATION = '''
31---
32module: ali_instance
33short_description: Create, Start, Stop, Restart or Terminate an Instance in ECS. Add or Remove Instance to/from a Security Group.
34description:
35 - Create, start, stop, restart, modify or terminate ecs instances.
36 - Add or remove ecs instances to/from security group.
37options:
38 state:
39 description:
40 - The state of the instance after operating.
41 default: 'present'
42 choices: ['present', 'running', 'stopped', 'restarted', 'absent']
43 type: str
44 availability_zone:
45 description:
46 - Aliyun availability zone ID in which to launch the instance.
47 If it is not specified, it will be allocated by system automatically.
48 aliases: ['apsarastack_zone', 'zone_id']
49 type: str
50 image_id:
51 description:
52 - Image ID used to launch instances. Required when C(state=present) and creating new ECS instances.
53 aliases: ['image']
54 type: str
55 instance_type:
56 description:
57 - Instance type used to launch instances. Required when C(state=present) and creating new ECS instances.
58 aliases: ['type']
59 type: str
60 security_groups:
61 description:
62 - A list of security group IDs.
63 aliases: ['group_ids']
64 type: list
65 elements: str
66 vswitch_id:
67 description:
68 - The subnet ID in which to launch the instances (VPC).
69 aliases: ['subnet_id']
70 type: str
71 instance_name:
72 description:
73 - The name of ECS instance, which is a string of 2 to 128 Chinese or English characters. It must begin with an
74 uppercase/lowercase letter or a Chinese character and can contain numerals, ".", "_" or "-".
75 It cannot begin with http:// or https://.
76 aliases: ['name']
77 type: str
78 description:
79 description:
80 - The description of ECS instance, which is a string of 2 to 256 characters. It cannot begin with http:// or https://.
81 type: str
82 internet_charge_type:
83 description:
84 - Internet charge type of ECS instance.
85 default: 'PayByBandwidth'
86 choices: ['PayByBandwidth', 'PayByTraffic']
87 type: str
88 max_bandwidth_in:
89 description:
90 - Maximum incoming bandwidth from the public network, measured in Mbps (Megabits per second).
91 default: 200
92 type: int
93 max_bandwidth_out:
94 description:
95 - Maximum outgoing bandwidth to the public network, measured in Mbps (Megabits per second).
96 Required when C(allocate_public_ip=True). Ignored when C(allocate_public_ip=False).
97 default: 0
98 type: int
99 host_name:
100 description:
101 - Instance host name. Ordered hostname is not supported.
102 type: str
103 unique_suffix:
104 description:
105 - Specifies whether to add sequential suffixes to the host_name.
106 The sequential suffix ranges from 001 to 999.
107 default: False
108 type: bool
109 password:
110 description:
111 - The password to login instance. After rebooting instances, modified password will take effect.
112 type: str
113 system_disk_category:
114 description:
115 - Category of the system disk.
116 default: 'cloud_efficiency'
117 choices: ['cloud_efficiency', 'cloud_ssd', 'cloud_essd']
118 type: str
119 system_disk_size:
120 description:
121 - Size of the system disk, in GB. The valid values are 40~500.
122 default: 40
123 type: int
124 system_disk_name:
125 description:
126 - Name of the system disk.
127 type: str
128 system_disk_description:
129 description:
130 - Description of the system disk.
131 type: str
132 count:
133 description:
134 - The number of the new instance. An integer value which indicates how many instances that match I(count_tag)
135 should be running. Instances are either created or terminated based on this value.
136 default: 1
137 type: int
138 count_tag:
139 description:
140 - I(count) determines how many instances based on a specific tag criteria should be present.
141 This can be expressed in multiple ways and is shown in the EXAMPLES section.
142 The specified count_tag must already exist or be passed in as the I(tags) option.
143 If it is not specified, it will be replaced by I(instance_name).
144 type: str
145 allocate_public_ip:
146 description:
147 - Whether allocate a public ip for the new instance.
148 default: False
149 aliases: [ 'assign_public_ip' ]
150 type: bool
151 instance_charge_type:
152 description:
153 - The charge type of the instance.
154 choices: ['PrePaid', 'PostPaid']
155 default: 'PostPaid'
156 type: str
157 period:
158 description:
159 - The charge duration of the instance, in month. Required when C(instance_charge_type=PrePaid).
160 - The valid value are [1-9, 12, 24, 36].
161 default: 1
162 type: int
163 auto_renew:
164 description:
165 - Whether automate renew the charge of the instance.
166 type: bool
167 default: False
168 auto_renew_period:
169 description:
170 - The duration of the automatic renew the charge of the instance. Required when C(auto_renew=True).
171 choices: [1, 2, 3, 6, 12]
172 type: int
173 instance_ids:
174 description:
175 - A list of instance ids. It is required when need to operate existing instances.
176 If it is specified, I(count) will lose efficacy.
177 type: list
178 elements: str
179 force:
180 description:
181 - Whether the current operation needs to be execute forcibly.
182 default: False
183 type: bool
184 tags:
185 description:
186 - A hash/dictionaries of instance tags, to add to the new instance or for starting/stopping instance by tag. C({"key":"value"})
187 aliases: ["instance_tags"]
188 type: dict
189 purge_tags:
190 description:
191 - Delete any tags not specified in the task that are on the instance.
192 If True, it means you have to specify all the desired tags on each task affecting an instance.
193 default: False
194 type: bool
195 key_name:
196 description:
197 - The name of key pair which is used to access ECS instance in SSH.
198 required: false
199 type: str
200 aliases: ['keypair']
201 user_data:
202 description:
203 - User-defined data to customize the startup behaviors of an ECS instance and to pass data into an ECS instance.
204 It only will take effect when launching the new ECS instances.
205 required: false
206 type: str
207 ram_role_name:
208 description:
209 - The name of the instance RAM role.
210 type: str
211 spot_price_limit:
212 description:
213 - The maximum hourly price for the preemptible instance. This parameter supports a maximum of three decimal
214 places and takes effect when the SpotStrategy parameter is set to SpotWithPriceLimit.
215 type: float
216 spot_strategy:
217 description:
218 - The bidding mode of the pay-as-you-go instance. This parameter is valid when InstanceChargeType is set to PostPaid.
219 choices: ['NoSpot', 'SpotWithPriceLimit', 'SpotAsPriceGo']
220 default: 'NoSpot'
221 type: str
222 period_unit:
223 description:
224 - The duration unit that you will buy the resource. It is valid when C(instance_charge_type=PrePaid)
225 choices: ['Month', 'Week']
226 default: 'Month'
227 type: str
228 dry_run:
229 description:
230 - Specifies whether to send a dry-run request.
231 - If I(dry_run=True), Only a dry-run request is sent and no instance is created. The system checks whether the
232 required parameters are set, and validates the request format, service permissions, and available ECS instances.
233 If the validation fails, the corresponding error code is returned. If the validation succeeds, the DryRunOperation error code is returned.
234 - If I(dry_run=False), A request is sent. If the validation succeeds, the instance is created.
235 default: False
236 type: bool
237 include_data_disks:
238 description:
239 - Whether to change instance disks charge type when changing instance charge type.
240 default: True
241 type: bool
242author:
243 - "He Guimin (@xiaozhu36)"
244requirements:
245 - "python >= 3.6"
246 - "footmark >= 1.19.0"
247extends_documentation_fragment:
248 - apsarastack
249'''
251EXAMPLES = '''
252# basic provisioning example vpc network
253- name: basic provisioning example
254 hosts: localhost
255 vars:
256 apsarastack_access_key: <your-apsarastack-access-key-id>
257 apsarastack_secret_key: <your-apsarastack-access-secret-key>
258 apsarastack_region: cn-beijing
259 image: ubuntu1404_64_40G_cloudinit_20160727.raw
260 instance_type: ecs.n4.small
261 vswitch_id: vsw-abcd1234
262 assign_public_ip: True
263 max_bandwidth_out: 10
264 host_name: myhost
265 password: mypassword
266 system_disk_category: cloud_efficiency
267 system_disk_size: 100
268 internet_charge_type: PayByBandwidth
269 security_groups: ["sg-f2rwnfh23r"]
271 instance_ids: ["i-abcd12346", "i-abcd12345"]
272 force: True
274 tasks:
275 - name: launch ECS instance in VPC network
276 ali_instance:
277 apsarastack_access_key: '{{ apsarastack_access_key }}'
278 apsarastack_secret_key: '{{ apsarastack_secret_key }}'
279 apsarastack_region: '{{ apsarastack_region }}'
280 image: '{{ image }}'
281 system_disk_category: '{{ system_disk_category }}'
282 system_disk_size: '{{ system_disk_size }}'
283 instance_type: '{{ instance_type }}'
284 vswitch_id: '{{ vswitch_id }}'
285 assign_public_ip: '{{ assign_public_ip }}'
286 internet_charge_type: '{{ internet_charge_type }}'
287 max_bandwidth_out: '{{ max_bandwidth_out }}'
288 tags:
289 Name: created_one
290 host_name: '{{ host_name }}'
291 password: '{{ password }}'
293 - name: with count and count_tag to create a number of instances
294 ali_instance:
295 apsarastack_access_key: '{{ apsarastack_access_key }}'
296 apsarastack_secret_key: '{{ apsarastack_secret_key }}'
297 apsarastack_region: '{{ apsarastack_region }}'
298 image: '{{ image }}'
299 system_disk_category: '{{ system_disk_category }}'
300 system_disk_size: '{{ system_disk_size }}'
301 instance_type: '{{ instance_type }}'
302 assign_public_ip: '{{ assign_public_ip }}'
303 security_groups: '{{ security_groups }}'
304 internet_charge_type: '{{ internet_charge_type }}'
305 max_bandwidth_out: '{{ max_bandwidth_out }}'
306 tags:
307 Name: created_one
308 Version: 0.1
309 count: 2
310 count_tag:
311 Name: created_one
312 host_name: '{{ host_name }}'
313 password: '{{ password }}'
315 - name: start instance
316 ali_instance:
317 apsarastack_access_key: '{{ apsarastack_access_key }}'
318 apsarastack_secret_key: '{{ apsarastack_secret_key }}'
319 apsarastack_region: '{{ apsarastack_region }}'
320 instance_ids: '{{ instance_ids }}'
321 state: 'running'
323 - name: reboot instance forcibly
324 ecs:
325 apsarastack_access_key: '{{ apsarastack_access_key }}'
326 apsarastack_secret_key: '{{ apsarastack_secret_key }}'
327 apsarastack_region: '{{ apsarastack_region }}'
328 instance_ids: '{{ instance_ids }}'
329 state: 'restarted'
330 force: '{{ force }}'
332 - name: Add instances to an security group
333 ecs:
334 apsarastack_access_key: '{{ apsarastack_access_key }}'
335 apsarastack_secret_key: '{{ apsarastack_secret_key }}'
336 apsarastack_region: '{{ apsarastack_region }}'
337 instance_ids: '{{ instance_ids }}'
338 security_groups: '{{ security_groups }}'
339'''
341RETURN = '''
342instances:
343 description: List of ECS instances
344 returned: always
345 type: complex
346 contains:
347 availability_zone:
348 description: The availability zone of the instance is in.
349 returned: always
350 type: str
351 sample: cn-beijing-a
352 block_device_mappings:
353 description: Any block device mapping entries for the instance.
354 returned: always
355 type: complex
356 contains:
357 device_name:
358 description: The device name exposed to the instance (for example, /dev/xvda).
359 returned: always
360 type: str
361 sample: /dev/xvda
362 attach_time:
363 description: The time stamp when the attachment initiated.
364 returned: always
365 type: str
366 sample: "2018-06-25T04:08:26Z"
367 delete_on_termination:
368 description: Indicates whether the volume is deleted on instance termination.
369 returned: always
370 type: bool
371 sample: true
372 status:
373 description: The attachment state.
374 returned: always
375 type: str
376 sample: in_use
377 volume_id:
378 description: The ID of the cloud disk.
379 returned: always
380 type: str
381 sample: d-2zei53pjsi117y6gf9t6
382 cpu:
383 description: The CPU core count of the instance.
384 returned: always
385 type: int
386 sample: 4
387 creation_time:
388 description: The time the instance was created.
389 returned: always
390 type: str
391 sample: "2018-06-25T04:08Z"
392 description:
393 description: The instance description.
394 returned: always
395 type: str
396 sample: "my ansible instance"
397 eip:
398 description: The attribution of EIP associated with the instance.
399 returned: always
400 type: complex
401 contains:
402 allocation_id:
403 description: The ID of the EIP.
404 returned: always
405 type: str
406 sample: eip-12345
407 internet_charge_type:
408 description: The internet charge type of the EIP.
409 returned: always
410 type: str
411 sample: "paybybandwidth"
412 ip_address:
413 description: EIP address.
414 returned: always
415 type: str
416 sample: 42.10.2.2
417 expired_time:
418 description: The time the instance will expire.
419 returned: always
420 type: str
421 sample: "2099-12-31T15:59Z"
422 gpu:
423 description: The attribution of instance GPU.
424 returned: always
425 type: complex
426 contains:
427 amount:
428 description: The count of the GPU.
429 returned: always
430 type: int
431 sample: 0
432 spec:
433 description: The specification of the GPU.
434 returned: always
435 type: str
436 sample: ""
437 host_name:
438 description: The host name of the instance.
439 returned: always
440 type: str
441 sample: iZ2zewaoZ
442 id:
443 description: Alias of instance_id.
444 returned: always
445 type: str
446 sample: i-abc12345
447 instance_id:
448 description: ECS instance resource ID.
449 returned: always
450 type: str
451 sample: i-abc12345
452 image_id:
453 description: The ID of the image used to launch the instance.
454 returned: always
455 type: str
456 sample: m-0011223344
457 inner_ip_address:
458 description: The inner IPv4 address of the classic instance.
459 returned: always
460 type: str
461 sample: 10.0.0.2
462 instance_charge_type:
463 description: The instance charge type.
464 returned: always
465 type: str
466 sample: PostPaid
467 instance_name:
468 description: The name of the instance.
469 returned: always
470 type: str
471 sample: my-ecs
472 instance_type:
473 description: The instance type of the running instance.
474 returned: always
475 type: str
476 sample: ecs.sn1ne.xlarge
477 instance_type_family:
478 description: The instance type family of the instance belongs.
479 returned: always
480 type: str
481 sample: ecs.sn1ne
482 internet_charge_type:
483 description: The billing method of the network bandwidth.
484 returned: always
485 type: str
486 sample: PayByBandwidth
487 internet_max_bandwidth_in:
488 description: Maximum incoming bandwidth from the internet network.
489 returned: always
490 type: int
491 sample: 200
492 internet_max_bandwidth_out:
493 description: Maximum incoming bandwidth from the internet network.
494 returned: always
495 type: int
496 sample: 20
497 io_optimized:
498 description: Indicates whether the instance is optimized for EBS I/O.
499 returned: always
500 type: bool
501 sample: false
502 memory:
503 description: Memory size of the instance.
504 returned: always
505 type: int
506 sample: 8192
507 network_interfaces:
508 description: One or more network interfaces for the instance.
509 returned: always
510 type: complex
511 contains:
512 mac_address:
513 description: The MAC address.
514 returned: always
515 type: str
516 sample: "00:11:22:33:44:55"
517 network_interface_id:
518 description: The ID of the network interface.
519 returned: always
520 type: str
521 sample: eni-01234567
522 primary_ip_address:
523 description: The primary IPv4 address of the network interface within the vswitch.
524 returned: always
525 type: str
526 sample: 10.0.0.1
527 osname:
528 description: The operation system name of the instance owned.
529 returned: always
530 type: str
531 sample: CentOS
532 ostype:
533 description: The operation system type of the instance owned.
534 returned: always
535 type: str
536 sample: linux
537 private_ip_address:
538 description: The IPv4 address of the network interface within the subnet.
539 returned: always
540 type: str
541 sample: 10.0.0.1
542 public_ip_address:
543 description: The public IPv4 address assigned to the instance or eip address
544 returned: always
545 type: str
546 sample: 43.0.0.1
547 resource_group_id:
548 description: The id of the resource group to which the instance belongs.
549 returned: always
550 type: str
551 sample: my-ecs-group
552 security_groups:
553 description: One or more security groups for the instance.
554 returned: always
555 type: list
556 elements: dict
557 contains:
558 group_id:
559 description: The ID of the security group.
560 returned: always
561 type: str
562 sample: sg-0123456
563 group_name:
564 description: The name of the security group.
565 returned: always
566 type: str
567 sample: my-security-group
568 status:
569 description: The current status of the instance.
570 returned: always
571 type: str
572 sample: running
573 tags:
574 description: Any tags assigned to the instance.
575 returned: always
576 type: dict
577 sample:
578 user_data:
579 description: User-defined data.
580 returned: always
581 type: dict
582 sample:
583 vswitch_id:
584 description: The ID of the vswitch in which the instance is running.
585 returned: always
586 type: str
587 sample: vsw-dew00abcdef
588 vpc_id:
589 description: The ID of the VPC the instance is in.
590 returned: always
591 type: str
592 sample: vpc-0011223344
593 spot_price_limit:
594 description:
595 - The maximum hourly price for the preemptible instance.
596 returned: always
597 type: float
598 sample: 0.97
599 spot_strategy:
600 description:
601 - The bidding mode of the pay-as-you-go instance.
602 returned: always
603 type: str
604 sample: NoSpot
605ids:
606 description: List of ECS instance IDs
607 returned: always
608 type: list
609 sample: [i-12345er, i-3245fs]
610'''
612import re
613from ast import literal_eval
614import time
615import traceback
616from ansible.module_utils.basic import env_fallback
617from ansible.module_utils.basic import AnsibleModule, missing_required_lib
618from ansible_collections.alibaba.apsarastack.plugins.module_utils.apsarastack_common import common_argument_spec
619from ansible_collections.alibaba.apsarastack.plugins.module_utils.apsarastack_connections import ecs_connect
621HAS_FOOTMARK = False
622FOOTMARK_IMP_ERR = None
623try:
624 from footmark.exception import ECSResponseError
625 HAS_FOOTMARK = True
626except ImportError:
627 FOOTMARK_IMP_ERR = traceback.format_exc()
628 HAS_FOOTMARK = False
631def get_instances_info(connection, ids):
632 result = []
633 instances = connection.describe_instances(instance_ids=ids)
634 if len(instances) > 0:
635 for inst in instances:
636 volumes = connection.describe_disks(instance_id=inst.id)
637 setattr(inst, 'block_device_mappings', volumes)
638 setattr(inst, 'user_data', inst.describe_user_data())
639 result.append(inst.read())
640 return result
643def run_instance(module, ecs, exact_count):
644 if exact_count <= 0:
645 return None
646 zone_id = module.params['availability_zone']
647 image_id = module.params['image_id']
648 instance_type = module.params['instance_type']
649 security_groups = module.params['security_groups']
650 vswitch_id = module.params['vswitch_id']
651 instance_name = module.params['instance_name']
652 description = module.params['description']
653 internet_charge_type = module.params['internet_charge_type']
654 max_bandwidth_out = module.params['max_bandwidth_out']
655 max_bandwidth_in = module.params['max_bandwidth_in']
656 host_name = module.params['host_name']
657 password = module.params['password']
658 system_disk_category = module.params['system_disk_category']
659 system_disk_size = module.params['system_disk_size']
660 system_disk_name = module.params['system_disk_name']
661 system_disk_description = module.params['system_disk_description']
662 allocate_public_ip = module.params['allocate_public_ip']
663 period = module.params['period']
664 auto_renew = module.params['auto_renew']
665 instance_charge_type = module.params['instance_charge_type']
666 auto_renew_period = module.params['auto_renew_period']
667 user_data = module.params['user_data']
668 key_name = module.params['key_name']
669 ram_role_name = module.params['ram_role_name']
670 spot_price_limit = module.params['spot_price_limit']
671 spot_strategy = module.params['spot_strategy']
672 unique_suffix = module.params['unique_suffix']
673 count_tag = module.params['count_tag']
674 tags = module.params['tags']
676 merged_tags = {}
677 if tags:
678 merged_tags = tags
680 if literal_eval(count_tag):
681 merged_tags.update(literal_eval(count_tag))
683 # check whether the required parameter passed or not
684 if not image_id:
685 module.fail_json(msg='image_id is required for new instance')
686 if not instance_type:
687 module.fail_json(msg='instance_type is required for new instance')
688 if not isinstance(security_groups, list):
689 module.fail_json(msg='The parameter security_groups should be a list, aborting')
690 if len(security_groups) <= 0:
691 module.fail_json(msg='Expected the parameter security_groups is non-empty when create new ECS instances, aborting')
693 client_token = "Ansible-Apsarastack-{0}-{1}".format(hash(str(module.params)), str(time.time()))
695 try:
696 # call to create_instance method from footmark
697 instances = ecs.run_instances(image_id=image_id, instance_type=instance_type, security_group_id=security_groups[0],
698 zone_id=zone_id, instance_name=instance_name, description=description,
699 internet_charge_type=internet_charge_type, internet_max_bandwidth_out=max_bandwidth_out,
700 internet_max_bandwidth_in=max_bandwidth_in, host_name=host_name, password=password,
701 io_optimized='optimized', system_disk_category=system_disk_category,
702 system_disk_size=system_disk_size, system_disk_disk_name=system_disk_name,
703 system_disk_description=system_disk_description, vswitch_id=vswitch_id,
704 amount=exact_count, instance_charge_type=instance_charge_type, period=period, period_unit="Month",
705 auto_renew=auto_renew, auto_renew_period=auto_renew_period, key_pair_name=key_name,
706 user_data=user_data, client_token=client_token, ram_role_name=ram_role_name,
707 spot_price_limit=spot_price_limit, spot_strategy=spot_strategy, unique_suffix=unique_suffix,
708 tags=merged_tags)
710 except Exception as e:
711 module.fail_json(msg='Unable to create instance, error: {0}'.format(e))
713 return instances
716def modify_instance(module, instance):
717 # According to state to modify instance's some special attribute
718 state = module.params["state"]
719 name = module.params['instance_name']
720 unique_suffix = module.params['unique_suffix']
721 if not name:
722 name = instance.name
724 description = module.params['description']
725 if not description:
726 description = instance.description
728 host_name = module.params['host_name']
729 if unique_suffix and host_name:
730 suffix = instance.host_name[-3:]
731 host_name = host_name + suffix
733 if not host_name:
734 host_name = instance.host_name
736 # password can be modified only when restart instance
737 password = ""
738 if state == "restarted":
739 password = module.params['password']
741 # userdata can be modified only when instance is stopped
742 setattr(instance, "user_data", instance.describe_user_data())
743 user_data = instance.user_data
744 if state == "stopped":
745 user_data = module.params['user_data'].encode()
747 try:
748 return instance.modify(name=name, description=description, host_name=host_name, password=password, user_data=user_data)
749 except Exception as e:
750 module.fail_json(msg="Modify instance {0} attribute got an error: {1}".format(instance.id, e))
753def wait_for_instance_modify_charge(ecs, instance_ids, charge_type, delay=10, timeout=300):
754 """
755 To verify instance charge type has become expected after modify instance charge type
756 """
757 try:
758 while True:
759 instances = ecs.describe_instances(instance_ids=instance_ids)
760 flag = True
761 for inst in instances:
762 if inst and inst.instance_charge_type != charge_type:
763 flag = False
764 if flag:
765 return
766 timeout -= delay
767 time.sleep(delay)
768 if timeout <= 0:
769 raise Exception("Timeout Error: Waiting for instance to {0}. ".format(charge_type))
770 except Exception as e:
771 raise e
774def main():
775 argument_spec = common_argument_spec()
776 argument_spec.update(dict(
777 security_groups=dict(type='list', elements='str', aliases=['group_ids']),
778 availability_zone=dict(type='str', aliases=['zone_id', 'apsarastack_zone'],
779 fallback=(env_fallback, ['APSARASTACK_ZONE', 'APSARASTACK_ZONE_ID'])),
780 instance_type=dict(type='str', aliases=['type']),
781 image_id=dict(type='str', aliases=['image']),
782 count=dict(type='int', default=1),
783 count_tag=dict(type='str'),
784 vswitch_id=dict(type='str', aliases=['subnet_id']),
785 instance_name=dict(type='str', aliases=['name']),
786 host_name=dict(type='str'),
787 password=dict(type='str', no_log=True),
788 internet_charge_type=dict(type='str', default='PayByBandwidth', choices=['PayByBandwidth', 'PayByTraffic']),
789 max_bandwidth_in=dict(type='int', default=200),
790 max_bandwidth_out=dict(type='int', default=0),
791 system_disk_category=dict(type='str', default='cloud_efficiency', choices=['cloud_efficiency', 'cloud_ssd', 'cloud_essd']),
792 system_disk_size=dict(type='int', default=40),
793 system_disk_name=dict(type='str'),
794 system_disk_description=dict(type='str'),
795 force=dict(type='bool', default=False),
796 tags=dict(type='dict', aliases=['instance_tags']),
797 purge_tags=dict(type='bool', default=False),
798 state=dict(default='present', choices=['present', 'running', 'stopped', 'restarted', 'absent']),
799 description=dict(type='str'),
800 allocate_public_ip=dict(type='bool', aliases=['assign_public_ip'], default=False),
801 instance_charge_type=dict(type='str', default='PostPaid', choices=['PrePaid', 'PostPaid']),
802 period=dict(type='int', default=1),
803 auto_renew=dict(type='bool', default=False),
804 instance_ids=dict(type='list', elements='str'),
805 auto_renew_period=dict(type='int', choices=[1, 2, 3, 6, 12]),
806 key_name=dict(type='str', aliases=['keypair']),
807 user_data=dict(type='str'),
808 ram_role_name=dict(type='str'),
809 spot_price_limit=dict(type='float'),
810 spot_strategy=dict(type='str', default='NoSpot', choices=['NoSpot', 'SpotWithPriceLimit', 'SpotAsPriceGo']),
811 unique_suffix=dict(type='bool', default=False),
812 period_unit=dict(type='str', default='Month', choices=['Month', 'Week']),
813 dry_run=dict(type='bool', default=False),
814 include_data_disks=dict(type='bool', default=True)
815 )
816 )
817 module = AnsibleModule(argument_spec=argument_spec)
819 if HAS_FOOTMARK is False:
820 module.fail_json(msg=missing_required_lib('footmark'), exception=FOOTMARK_IMP_ERR)
822 ecs = ecs_connect(module)
823 host_name = module.params['host_name']
824 state = module.params['state']
825 instance_ids = module.params['instance_ids']
826 count_tag = module.params['count_tag']
827 count = module.params['count']
828 instance_name = module.params['instance_name']
829 force = module.params['force']
830 zone_id = module.params['availability_zone']
831 key_name = module.params['key_name']
832 tags = module.params['tags']
833 max_bandwidth_out = module.params['max_bandwidth_out']
834 instance_charge_type = module.params['instance_charge_type']
835 if instance_charge_type == "PrePaid":
836 module.params['spot_strategy'] = ''
837 changed = False
839 instances = []
840 if instance_ids:
841 if not isinstance(instance_ids, list):
842 module.fail_json(msg='The parameter instance_ids should be a list, aborting')
843 instances = ecs.describe_instances(zone_id=zone_id, instance_ids=instance_ids)
844 if not instances:
845 module.fail_json(msg="There are no instances in our record based on instance_ids {0}. "
846 "Please check it and try again.".format(instance_ids))
847 elif count_tag:
848 instances = ecs.describe_instances(zone_id=zone_id, tags=literal_eval(count_tag))
849 elif instance_name:
850 instances = ecs.describe_instances(zone_id=zone_id, instance_name=instance_name)
852 ids = []
853 if state == 'absent':
854 if len(instances) < 1:
855 module.fail_json(msg='Please specify ECS instances that you want to operate by using '
856 'parameters instance_ids, tags or instance_name, aborting')
857 try:
858 targets = []
859 for inst in instances:
860 if inst.status != 'stopped' and not force:
861 module.fail_json(msg="Instance is running, and please stop it or set 'force' as True.")
862 targets.append(inst.id)
863 if ecs.delete_instances(instance_ids=targets, force=force):
864 changed = True
865 ids.extend(targets)
867 module.exit_json(changed=changed, ids=ids, instances=[])
868 except Exception as e:
869 module.fail_json(msg='Delete instance got an error: {0}'.format(e))
871 if module.params['allocate_public_ip'] and max_bandwidth_out <= 0:
872 module.fail_json(msg="'max_bandwidth_out' should be greater than 0 when 'allocate_public_ip' is True.")
873 if not module.params['allocate_public_ip']:
874 module.params['max_bandwidth_out'] = 0
876 if state == 'present':
877 if not instance_ids:
878 if len(instances) > count:
879 for i in range(0, len(instances) - count):
880 inst = instances[len(instances) - 1]
881 if inst.status != 'stopped' and not force:
882 module.fail_json(msg="That to delete instance {0} is failed results from it is running, "
883 "and please stop it or set 'force' as True.".format(inst.id))
884 try:
885 if inst.terminate(force=force):
886 changed = True
887 except Exception as e:
888 module.fail_json(msg="Delete instance {0} got an error: {1}".format(inst.id, e))
889 instances.pop(len(instances) - 1)
890 else:
891 try:
892 if host_name and re.search(r"-\[\d+,\d+\]-", host_name):
893 module.fail_json(msg='Ordered hostname is not supported, If you want to add an ordered '
894 'suffix to the hostname, you can set unique_suffix to True')
895 new_instances = run_instance(module, ecs, count - len(instances))
896 if new_instances:
897 changed = True
898 instances.extend(new_instances)
899 except Exception as e:
900 module.fail_json(msg="Create new instances got an error: {0}".format(e))
902 # Security Group join/leave begin
903 security_groups = module.params['security_groups']
904 if security_groups:
905 if not isinstance(security_groups, list):
906 module.fail_json(msg='The parameter security_groups should be a list, aborting')
907 for inst in instances:
908 existing = inst.security_group_ids['security_group_id']
909 remove = list(set(existing).difference(set(security_groups)))
910 add = list(set(security_groups).difference(set(existing)))
911 for sg in remove:
912 if inst.leave_security_group(sg):
913 changed = True
914 for sg in add:
915 if inst.join_security_group(sg):
916 changed = True
917 # Security Group join/leave ends here
919 # Attach/Detach key pair
920 keypair_ids = []
921 for inst in instances:
922 if key_name is not None and key_name != inst.key_name:
923 if key_name == "":
924 if inst.detach_key_pair():
925 changed = True
926 else:
927 keypair_ids.append(inst.id)
928 if keypair_ids:
929 changed = ecs.attach_key_pair(instance_ids=keypair_ids, key_pair_name=key_name)
931 # Modify instance attribute
932 for inst in instances:
933 if modify_instance(module, inst):
934 changed = True
935 if inst.id not in ids:
936 ids.append(inst.id)
938 # Modify instance charge type
939 charge_type_ids = []
940 for inst in instances:
941 if inst.instance_charge_type != instance_charge_type:
942 charge_type_ids.append(inst.id)
943 if charge_type_ids:
944 params = {"instance_ids": charge_type_ids, "instance_charge_type": instance_charge_type,
945 "include_data_disks": module.params['include_data_disks'], "dry_run": module.params['dry_run'],
946 "auto_pay": True}
947 if instance_charge_type == 'PrePaid':
948 params['period'] = module.params['period']
949 params['period_unit'] = module.params['period_unit']
951 if ecs.modify_instance_charge_type(**params):
952 changed = True
953 wait_for_instance_modify_charge(ecs, charge_type_ids, instance_charge_type)
955 else:
956 if len(instances) < 1:
957 module.fail_json(msg='Please specify ECS instances that you want to operate by using '
958 'parameters instance_ids, tags or instance_name, aborting')
959 if state == 'running':
960 try:
961 targets = []
962 for inst in instances:
963 if modify_instance(module, inst):
964 changed = True
965 if inst.status != "running":
966 targets.append(inst.id)
967 ids.append(inst.id)
968 if targets and ecs.start_instances(instance_ids=targets):
969 changed = True
970 ids.extend(targets)
971 except Exception as e:
972 module.fail_json(msg='Start instances got an error: {0}'.format(e))
973 elif state == 'stopped':
974 try:
975 targets = []
976 for inst in instances:
977 if inst.status != "stopped":
978 targets.append(inst.id)
979 if targets and ecs.stop_instances(instance_ids=targets, force_stop=force):
980 changed = True
981 ids.extend(targets)
982 for inst in instances:
983 if modify_instance(module, inst):
984 changed = True
985 except Exception as e:
986 module.fail_json(msg='Stop instances got an error: {0}'.format(e))
987 elif state == 'restarted':
988 try:
989 targets = []
990 for inst in instances:
991 if modify_instance(module, inst):
992 changed = True
993 targets.append(inst.id)
994 if ecs.reboot_instances(instance_ids=targets, force_stop=module.params['force']):
995 changed = True
996 ids.extend(targets)
997 except Exception as e:
998 module.fail_json(msg='Reboot instances got an error: {0}'.format(e))
1000 tags = module.params['tags']
1001 if module.params['purge_tags']:
1002 for inst in instances:
1003 if not tags:
1004 tags = inst.tags
1005 try:
1006 if inst.remove_tags(tags):
1007 changed = True
1008 except Exception as e:
1009 module.fail_json(msg="{0}".format(e))
1010 module.exit_json(changed=changed, instances=get_instances_info(ecs, ids))
1012 if tags:
1013 for inst in instances:
1014 try:
1015 if inst.add_tags(tags):
1016 changed = True
1017 except Exception as e:
1018 module.fail_json(msg="{0}".format(e))
1019 module.exit_json(changed=changed, instances=get_instances_info(ecs, ids))
1022if __name__ == '__main__':
1023 main()