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

1#!/usr/bin/python 

2# -*- coding: utf-8 -*- 

3 

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/. 

21 

22from __future__ import (absolute_import, division, print_function) 

23 

24__metaclass__ = type 

25 

26ANSIBLE_METADATA = {'metadata_version': '1.1', 

27 'status': ['preview'], 

28 'supported_by': 'community'} 

29 

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''' 

250 

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"] 

270 

271 instance_ids: ["i-abcd12346", "i-abcd12345"] 

272 force: True 

273 

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 }}' 

292 

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 }}' 

314 

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' 

322 

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 }}' 

331 

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''' 

340 

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''' 

611 

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 

620 

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 

629 

630 

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 

641 

642 

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'] 

675 

676 merged_tags = {} 

677 if tags: 

678 merged_tags = tags 

679 

680 if literal_eval(count_tag): 

681 merged_tags.update(literal_eval(count_tag)) 

682 

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') 

692 

693 client_token = "Ansible-Apsarastack-{0}-{1}".format(hash(str(module.params)), str(time.time())) 

694 

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) 

709 

710 except Exception as e: 

711 module.fail_json(msg='Unable to create instance, error: {0}'.format(e)) 

712 

713 return instances 

714 

715 

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 

723 

724 description = module.params['description'] 

725 if not description: 

726 description = instance.description 

727 

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 

732 

733 if not host_name: 

734 host_name = instance.host_name 

735 

736 # password can be modified only when restart instance 

737 password = "" 

738 if state == "restarted": 

739 password = module.params['password'] 

740 

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() 

746 

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)) 

751 

752 

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 

772 

773 

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) 

818 

819 if HAS_FOOTMARK is False: 

820 module.fail_json(msg=missing_required_lib('footmark'), exception=FOOTMARK_IMP_ERR) 

821 

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 

838 

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) 

851 

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) 

866 

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)) 

870 

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 

875 

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)) 

901 

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 

918 

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) 

930 

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) 

937 

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'] 

950 

951 if ecs.modify_instance_charge_type(**params): 

952 changed = True 

953 wait_for_instance_modify_charge(ecs, charge_type_ids, instance_charge_type) 

954 

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)) 

999 

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)) 

1011 

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)) 

1020 

1021 

1022if __name__ == '__main__': 

1023 main()