From 85ba30a3db22681ad45bb1e972177edc5b6b5f6c Mon Sep 17 00:00:00 2001 From: He Guimin Date: Wed, 24 Oct 2018 02:02:21 +0800 Subject: [PATCH] Add new module ali_instance (#36898) --- .../dev_guide/developing_module_utilities.rst | 1 + .../rst/scenario_guides/guide_alicloud.rst | 125 +++ lib/ansible/module_utils/alicloud_ecs.py | 158 ++++ .../modules/cloud/alicloud/__init__.py | 0 .../modules/cloud/alicloud/ali_instance.py | 777 ++++++++++++++++++ .../cloud/alicloud/ali_instance_facts.py | 394 +++++++++ .../utils/module_docs_fragments/alicloud.py | 61 ++ 7 files changed, 1516 insertions(+) create mode 100644 docs/docsite/rst/scenario_guides/guide_alicloud.rst create mode 100644 lib/ansible/module_utils/alicloud_ecs.py create mode 100644 lib/ansible/modules/cloud/alicloud/__init__.py create mode 100644 lib/ansible/modules/cloud/alicloud/ali_instance.py create mode 100644 lib/ansible/modules/cloud/alicloud/ali_instance_facts.py create mode 100644 lib/ansible/utils/module_docs_fragments/alicloud.py diff --git a/docs/docsite/rst/dev_guide/developing_module_utilities.rst b/docs/docsite/rst/dev_guide/developing_module_utilities.rst index b6795875eb..96fc58e818 100644 --- a/docs/docsite/rst/dev_guide/developing_module_utilities.rst +++ b/docs/docsite/rst/dev_guide/developing_module_utilities.rst @@ -13,6 +13,7 @@ The following is a list of ``module_utils`` files and a general description. The .. include:: shared_snippets/licensing.txt +- alicloud_ecs.py - Definitions and utilities for modules working with Alibaba Cloud ECS. - api.py - Adds shared support for generic API modules. - azure_rm_common.py - Definitions and utilities for Microsoft Azure Resource Manager template deployments. - basic.py - General definitions and helper utilities for Ansible modules. diff --git a/docs/docsite/rst/scenario_guides/guide_alicloud.rst b/docs/docsite/rst/scenario_guides/guide_alicloud.rst new file mode 100644 index 0000000000..af4e45c855 --- /dev/null +++ b/docs/docsite/rst/scenario_guides/guide_alicloud.rst @@ -0,0 +1,125 @@ +Alibaba Cloud Compute Services Guide +==================================== + +.. _alicloud_intro: + +Introduction +```````````` + +Ansible contains several modules for controlling and managing Alibaba Cloud Compute Services (Alicloud). This guide +explains how to use the Alicloud Ansible modules together. + +All Alicloud modules require ``footmark`` - install it on your control machine with ``pip install footmark``. + +Cloud modules, including Alicloud modules, execute on your local machine (the control machine) with ``connection: local``, rather than on remote machines defined in your hosts. + +Normally, you'll use the following pattern for plays that provision Alicloud resources:: + + - hosts: localhost + connection: local + vars: + - ... + tasks: + - ... + +.. _alicloud_authentication: + +Authentication +`````````````` + +You can specify your Alicloud authentication credentials (access key and secret key) by passing them as +environment variables or by storing them in a vars file. + +To pass authentication credentials as environment variables:: + + export ALICLOUD_ACCESS_KEY='Alicloud123' + export ALICLOUD_SECRET_KEY='AlicloudSecret123' + +To store authentication credentials in a vars_file, encrypt them with :doc:`Ansible Vault<../user_guide/vault>` to keep them secure, then list them:: + + --- + alicloud_access_key: "--REMOVED--" + alicloud_secret_key: "--REMOVED--" + +Note that if you store your credentials in a vars_file, you need to refer to them in each Alicloud module. For example:: + + - ali_instance: + alicloud_access_key: "{{alicloud_access_key}}" + alicloud_secret_key: "{{alicloud_secret_key}}" + image_id: "..." + +.. _alicloud_provisioning: + +Provisioning +```````````` + +Alicloud modules create Alicloud ECS instances, disks, virtual private clouds, virtual switches, security groups and other resources. + +You can use the ``count`` parameter to control the number of resources you create or terminate. For example, if you want exactly 5 instances tagged ``NewECS``, +set the ``count`` of instances to 5 and the ``count_tag`` to ``NewECS``, as shown in the last task of the example playbook below. +If there are no instances with the tag ``NewECS``, the task creates 5 new instances. If there are 2 instances with that tag, the task +creates 3 more. If there are 8 instances with that tag, the task terminates 3 of those instances. + +If you do not specify a ``count_tag``, the task creates the number of instances you specify in ``count`` with the ``instance_name`` you provide. + +:: + + # alicloud_setup.yml + + - hosts: localhost + connection: local + + tasks: + + - name: Create VPC + ali_vpc: + cidr_block: '{{ cidr_block }}' + vpc_name: new_vpc + register: created_vpc + + - name: Create VSwitch + ali_vswitch: + alicloud_zone: '{{ alicloud_zone }}' + cidr_block: '{{ vsw_cidr }}' + vswitch_name: new_vswitch + vpc_id: '{{ created_vpc.vpc.id }}' + register: created_vsw + + - name: Create security group + ali_security_group: + name: new_group + vpc_id: '{{ created_vpc.vpc.id }}' + rules: + - proto: tcp + port_range: 22/22 + cidr_ip: 0.0.0.0/0 + priority: 1 + rules_egress: + - proto: tcp + port_range: 80/80 + cidr_ip: 192.168.0.54/32 + priority: 1 + register: created_group + + - name: Create a set of instances + ali_instance: + security_groups: '{{ created_group.group_id }}' + instance_type: ecs.n4.small + image_id: "{{ ami_id }}" + instance_name: "My-new-instance" + instance_tags: + Name: NewECS + Version: 0.0.1 + count: 5 + count_tag: + Name: NewECS + allocate_public_ip: true + max_bandwidth_out: 50 + vswitch_id: '{{ created_vsw.vswitch.id}}' + register: create_instance + +In the example playbook above, data about the vpc, vswitch, group, and instances created by this playbook +are saved in the variables defined by the "register" keyword in each task. + +Each Alicloud module offers a variety of parameter options. Not all options are demonstrated in the above example. +See each individual module for further details and examples. diff --git a/lib/ansible/module_utils/alicloud_ecs.py b/lib/ansible/module_utils/alicloud_ecs.py new file mode 100644 index 0000000000..31b4694c0b --- /dev/null +++ b/lib/ansible/module_utils/alicloud_ecs.py @@ -0,0 +1,158 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2017 Alibaba Group Holding Limited. He Guimin +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from ansible.module_utils.basic import env_fallback + +try: + import footmark + import footmark.ecs + import footmark.slb + import footmark.vpc + import footmark.rds + import footmark.ess + HAS_FOOTMARK = True +except ImportError: + HAS_FOOTMARK = False + + +class AnsibleACSError(Exception): + pass + + +def acs_common_argument_spec(): + return dict( + alicloud_access_key=dict(required=True, aliases=['access_key_id', 'access_key'], no_log=True, + fallback=(env_fallback, ['ALICLOUD_ACCESS_KEY', 'ALICLOUD_ACCESS_KEY_ID'])), + alicloud_secret_key=dict(required=True, aliases=['secret_access_key', 'secret_key'], no_log=True, + fallback=(env_fallback, ['ALICLOUD_SECRET_KEY', 'ALICLOUD_SECRET_ACCESS_KEY'])), + alicloud_security_token=dict(aliases=['security_token'], no_log=True, + fallback=(env_fallback, ['ALICLOUD_SECURITY_TOKEN'])), + ) + + +def ecs_argument_spec(): + spec = acs_common_argument_spec() + spec.update( + dict( + alicloud_region=dict(required=True, aliases=['region', 'region_id'], + fallback=(env_fallback, ['ALICLOUD_REGION', 'ALICLOUD_REGION_ID'])), + ) + ) + return spec + + +def get_acs_connection_info(module): + + ecs_params = dict(acs_access_key_id=module.params.get('alicloud_access_key'), + acs_secret_access_key=module.params.get('alicloud_secret_key'), + security_token=module.params.get('alicloud_security_token'), + user_agent='Ansible-Provider-Alicloud') + + return module.params.get('alicloud_region'), ecs_params + + +def connect_to_acs(acs_module, region, **params): + conn = acs_module.connect_to_region(region, **params) + if not conn: + if region not in [acs_module_region.id for acs_module_region in acs_module.regions()]: + raise AnsibleACSError( + "Region %s does not seem to be available for acs module %s." % (region, acs_module.__name__)) + else: + raise AnsibleACSError( + "Unknown problem connecting to region %s for acs module %s." % (region, acs_module.__name__)) + return conn + + +def ecs_connect(module): + """ Return an ecs connection""" + + region, ecs_params = get_acs_connection_info(module) + # If we have a region specified, connect to its endpoint. + if region: + try: + ecs = connect_to_acs(footmark.ecs, region, **ecs_params) + except AnsibleACSError as e: + module.fail_json(msg=str(e)) + # Otherwise, no region so we fallback to the old connection method + return ecs + + +def slb_connect(module): + """ Return an slb connection""" + + region, slb_params = get_acs_connection_info(module) + # If we have a region specified, connect to its endpoint. + if region: + try: + slb = connect_to_acs(footmark.slb, region, **slb_params) + except AnsibleACSError as e: + module.fail_json(msg=str(e)) + # Otherwise, no region so we fallback to the old connection method + return slb + + +def vpc_connect(module): + """ Return an vpc connection""" + + region, vpc_params = get_acs_connection_info(module) + # If we have a region specified, connect to its endpoint. + if region: + try: + vpc = connect_to_acs(footmark.vpc, region, **vpc_params) + except AnsibleACSError as e: + module.fail_json(msg=str(e)) + # Otherwise, no region so we fallback to the old connection method + return vpc + + +def rds_connect(module): + """ Return an rds connection""" + + region, rds_params = get_acs_connection_info(module) + # If we have a region specified, connect to its endpoint. + if region: + try: + rds = connect_to_acs(footmark.rds, region, **rds_params) + except AnsibleACSError as e: + module.fail_json(msg=str(e)) + # Otherwise, no region so we fallback to the old connection method + return rds + + +def ess_connect(module): + """ Return an ess connection""" + + region, ess_params = get_acs_connection_info(module) + # If we have a region specified, connect to its endpoint. + if region: + try: + ess = connect_to_acs(footmark.ess, region, **ess_params) + except AnsibleACSError as e: + module.fail_json(msg=str(e)) + # Otherwise, no region so we fallback to the old connection method + return ess diff --git a/lib/ansible/modules/cloud/alicloud/__init__.py b/lib/ansible/modules/cloud/alicloud/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/modules/cloud/alicloud/ali_instance.py b/lib/ansible/modules/cloud/alicloud/ali_instance.py new file mode 100644 index 0000000000..1d01e4649f --- /dev/null +++ b/lib/ansible/modules/cloud/alicloud/ali_instance.py @@ -0,0 +1,777 @@ +#!/usr/bin/python +# Copyright (c) 2017 Alibaba Group Holding Limited. He Guimin +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see http://www.gnu.org/licenses/. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: ali_instance +version_added: "2.8" +short_description: Create, Start, Stop, Restart or Terminate an Instance in ECS. Add or Remove Instance to/from a Security Group. +description: + - Create, start, stop, restart, modify or terminate ecs instances. + - Add or remove ecs instances to/from security group. +options: + state: + description: + - The state of the instance after operating. + default: 'present' + choices: [ 'present', 'running', 'stopped', 'restarted', 'absent' ] + availability_zone: + description: + - Aliyun availability zone ID in which to launch the instance. + If it is not specified, it will be allocated by system automatically. + aliases: ['alicloud_zone'] + image_id: + description: + - Image ID used to launch instances. Required when C(state=present) and creating new ECS instances. + aliases: [ 'image' ] + instance_type: + description: + - Instance type used to launch instances. Required when C(state=present) and creating new ECS instances. + aliases: ['type'] + security_groups: + description: + - A list of security group IDs. + vswitch_id: + description: + - The subnet ID in which to launch the instances (VPC). + aliases: ['subnet_id'] + instance_name: + description: + - The name of ECS instance, which is a string of 2 to 128 Chinese or English characters. It must begin with an + uppercase/lowercase letter or a Chinese character and can contain numerals, ".", "_" or "-". + It cannot begin with http:// or https://. + aliases: ['name'] + description: + description: + - The description of ECS instance, which is a string of 2 to 256 characters. It cannot begin with http:// or https://. + internet_charge_type: + description: + - Internet charge type of ECS instance. + default: 'PayByBandwidth' + choices: ['PayByBandwidth', 'PayByTraffic'] + max_bandwidth_in: + description: + - Maximum incoming bandwidth from the public network, measured in Mbps (Megabits per second). + default: 200 + max_bandwidth_out: + description: + - Maximum outgoing bandwidth to the public network, measured in Mbps (Megabits per second). + default: 0 + host_name: + description: + - Instance host name. + password: + description: + - The password to login instance. After rebooting instances, modified password will take effect. + system_disk_category: + description: + - Category of the system disk. + default: 'cloud_efficiency' + choices: ['cloud_efficiency', 'cloud_ssd'] + system_disk_size: + description: + - Size of the system disk, in GB. The valid values are 40~500. + default: 40 + system_disk_name: + description: + - Name of the system disk. + system_disk_description: + description: + - Description of the system disk. + count: + description: + - The number of the new instance. An integer value which indicates how many instances that match I(count_tag) + should be running. Instances are either created or terminated based on this value. + default: 1 + count_tag: + description: + - I(count) determines how many instances based on a specific tag criteria should be present. + This can be expressed in multiple ways and is shown in the EXAMPLES section. + The specified count_tag must already exist or be passed in as the I(instance_tags) option. + If it is not specified, it will be replaced by I(instance_name). + allocate_public_ip: + description: + - Whether allocate a public ip for the new instance. + default: False + aliases: [ 'assign_public_ip' ] + type: bool + instance_charge_type: + description: + - The charge type of the instance. + choices: ['PrePaid', 'PostPaid'] + default: 'PostPaid' + period: + description: + - The charge duration of the instance, in month. Required when C(instance_charge_type=PrePaid). + - The valid value are [1-9, 12, 24, 36]. + default: 1 + auto_renew: + description: + - Whether automate renew the charge of the instance. + type: bool + default: False + auto_renew_period: + description: + - The duration of the automatic renew the charge of the instance. Required when C(auto_renew=True). + choices: [1, 2, 3, 6, 12] + instance_ids: + description: + - A list of instance ids. It is required when need to operate existing instances. + If it is specified, I(count) will lose efficacy. + force: + description: + - Whether the current operation needs to be execute forcibly. + default: False + type: bool + instance_tags: + description: + - A hash/dictionaries of instance tags, to add to the new instance or for starting/stopping instance by tag. C({"key":"value"}) + aliases: ["tags"] + key_name: + description: + - The name of key pair which is used to access ECS instance in SSH. + required: false + aliases: ['keypair'] + user_data: + description: + - User-defined data to customize the startup behaviors of an ECS instance and to pass data into an ECS instance. + It only will take effect when launching the new ECS instances. + required: false +author: + - "He Guimin (@xiaozhu36)" +requirements: + - "python >= 2.6" + - "footmark >= 1.1.16" +extends_documentation_fragment: + - alicloud +''' + +EXAMPLES = ''' +# basic provisioning example vpc network +- name: basic provisioning example + hosts: localhost + vars: + alicloud_access_key: + alicloud_secret_key: + alicloud_region: cn-beijing + image: ubuntu1404_64_40G_cloudinit_20160727.raw + instance_type: ecs.n4.small + vswitch_id: vsw-abcd1234 + assign_public_ip: True + max_bandwidth_out: 10 + host_name: myhost + password: mypassword + system_disk_category: cloud_efficiency + system_disk_size: 100 + internet_charge_type: PayByBandwidth + security_groups: ["sg-f2rwnfh23r"] + + instance_ids: ["i-abcd12346", "i-abcd12345"] + force: True + + tasks: + - name: launch ECS instance in VPC network + ali_instance: + alicloud_access_key: '{{ alicloud_access_key }}' + alicloud_secret_key: '{{ alicloud_secret_key }}' + alicloud_region: '{{ alicloud_region }}' + image: '{{ image }}' + system_disk_category: '{{ system_disk_category }}' + system_disk_size: '{{ system_disk_size }}' + instance_type: '{{ instance_type }}' + vswitch_id: '{{ vswitch_id }}' + assign_public_ip: '{{ assign_public_ip }}' + internet_charge_type: '{{ internet_charge_type }}' + max_bandwidth_out: '{{ max_bandwidth_out }}' + instance_tags: + Name: created_one + host_name: '{{ host_name }}' + password: '{{ password }}' + + - name: with count and count_tag to create a number of instances + ali_instance: + alicloud_access_key: '{{ alicloud_access_key }}' + alicloud_secret_key: '{{ alicloud_secret_key }}' + alicloud_region: '{{ alicloud_region }}' + image: '{{ image }}' + system_disk_category: '{{ system_disk_category }}' + system_disk_size: '{{ system_disk_size }}' + instance_type: '{{ instance_type }}' + assign_public_ip: '{{ assign_public_ip }}' + security_groups: '{{ security_groups }}' + internet_charge_type: '{{ internet_charge_type }}' + max_bandwidth_out: '{{ max_bandwidth_out }}' + instance_tags: + Name: created_one + Version: 0.1 + count: 2 + count_tag: + Name: created_one + host_name: '{{ host_name }}' + password: '{{ password }}' + + - name: start instance + ali_instance: + alicloud_access_key: '{{ alicloud_access_key }}' + alicloud_secret_key: '{{ alicloud_secret_key }}' + alicloud_region: '{{ alicloud_region }}' + instance_ids: '{{ instance_ids }}' + state: 'running' + + - name: reboot instance forcibly + ecs: + alicloud_access_key: '{{ alicloud_access_key }}' + alicloud_secret_key: '{{ alicloud_secret_key }}' + alicloud_region: '{{ alicloud_region }}' + instance_ids: '{{ instance_ids }}' + state: 'restarted' + force: '{{ force }}' + + - name: Add instances to an security group + ecs: + alicloud_access_key: '{{ alicloud_access_key }}' + alicloud_secret_key: '{{ alicloud_secret_key }}' + alicloud_region: '{{ alicloud_region }}' + instance_ids: '{{ instance_ids }}' + security_groups: '{{ security_groups }}' +''' + +RETURN = ''' +instances: + description: List of ECS instances + returned: always + type: complex + contains: + availability_zone: + description: The availability zone of the instance is in. + returned: always + type: string + sample: cn-beijing-a + block_device_mappings: + description: Any block device mapping entries for the instance. + returned: always + type: complex + contains: + device_name: + description: The device name exposed to the instance (for example, /dev/xvda). + returned: always + type: string + sample: /dev/xvda + attach_time: + description: The time stamp when the attachment initiated. + returned: always + type: string + sample: "2018-06-25T04:08:26Z" + delete_on_termination: + description: Indicates whether the volume is deleted on instance termination. + returned: always + type: bool + sample: true + status: + description: The attachment state. + returned: always + type: string + sample: in_use + volume_id: + description: The ID of the cloud disk. + returned: always + type: string + sample: d-2zei53pjsi117y6gf9t6 + cpu: + description: The CPU core count of the instance. + returned: always + type: int + sample: 4 + creation_time: + description: The time the instance was created. + returned: always + type: string + sample: "2018-06-25T04:08Z" + description: + description: The instance description. + returned: always + type: string + sample: "my ansible instance" + eip: + description: The attribution of EIP associated with the instance. + returned: always + type: complex + contains: + allocation_id: + description: The ID of the EIP. + returned: always + type: string + sample: eip-12345 + internet_charge_type: + description: The internet charge type of the EIP. + returned: always + type: string + sample: "paybybandwidth" + ip_address: + description: EIP address. + returned: always + type: string + sample: 42.10.2.2 + expired_time: + description: The time the instance will expire. + returned: always + type: string + sample: "2099-12-31T15:59Z" + gpu: + description: The attribution of instane GPU. + returned: always + type: complex + contains: + amount: + description: The count of the GPU. + returned: always + type: int + sample: 0 + spec: + description: The specification of the GPU. + returned: always + type: string + sample: "" + host_name: + description: The host name of the instance. + returned: always + type: string + sample: iZ2zewaoZ + id: + description: Alias of instance_id. + returned: always + type: string + sample: i-abc12345 + instance_id: + description: ECS instance resource ID. + returned: always + type: string + sample: i-abc12345 + image_id: + description: The ID of the image used to launch the instance. + returned: always + type: string + sample: m-0011223344 + inner_ip_address: + description: The inner IPv4 address of the classic instance. + returned: always + type: string + sample: 10.0.0.2 + instance_charge_type: + description: The instance charge type. + returned: always + type: string + sample: PostPaid + instance_name: + description: The name of the instance. + returned: always + type: string + sample: my-ecs + instance_type: + description: The instance type of the running instance. + returned: always + type: string + sample: ecs.sn1ne.xlarge + internet_charge_type: + description: The billing method of the network bandwidth. + returned: always + type: string + sample: PayByBandwidth + internet_max_bandwidth_in: + description: Maximum incoming bandwidth from the internet network. + returned: always + type: int + sample: 200 + internet_max_bandwidth_out: + description: Maximum incoming bandwidth from the internet network. + returned: always + type: int + sample: 20 + io_optimized: + description: Indicates whether the instance is optimized for EBS I/O. + returned: always + type: bool + sample: false + memory: + description: Memory size of the instance. + returned: always + type: int + sample: 8192 + network_interfaces: + description: One or more network interfaces for the instance. + returned: always + type: complex + contains: + mac_address: + description: The MAC address. + returned: always + type: string + sample: "00:11:22:33:44:55" + network_interface_id: + description: The ID of the network interface. + returned: always + type: string + sample: eni-01234567 + primary_ip_address: + description: The primary IPv4 address of the network interface within the vswitch. + returned: always + type: string + sample: 10.0.0.1 + osname: + description: The operation system name of the instance owned. + returned: always + type: string + sample: CentOS + ostype: + description: The operation system type of the instance owned. + returned: always + type: string + sample: linux + private_ip_address: + description: The IPv4 address of the network interface within the subnet. + returned: always + type: string + sample: 10.0.0.1 + public_ip_address: + description: The public IPv4 address assigned to the instance + returned: always + type: string + sample: 43.0.0.1 + resource_group_id: + description: The id of the resource group to which the instance belongs. + returned: always + type: string + sample: my-ecs-group + security_groups: + description: One or more security groups for the instance. + returned: always + type: complex + contains: + - group_id: + description: The ID of the security group. + returned: always + type: string + sample: sg-0123456 + - group_name: + description: The name of the security group. + returned: always + type: string + sample: my-security-group + status: + description: The current status of the instance. + returned: always + type: string + sample: running + tags: + description: Any tags assigned to the instance. + returned: always + type: dict + sample: + vswitch_id: + description: The ID of the vswitch in which the instance is running. + returned: always + type: string + sample: vsw-dew00abcdef + vpc_id: + description: The ID of the VPC the instance is in. + returned: always + type: dict + sample: vpc-0011223344 +ids: + description: List of ECS instance IDs + returned: always + type: list + sample: [i-12345er, i-3245fs] +''' + +import time +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.alicloud_ecs import ecs_argument_spec, ecs_connect + +HAS_FOOTMARK = False + +try: + from footmark.exception import ECSResponseError + HAS_FOOTMARK = True +except ImportError: + HAS_FOOTMARK = False + + +def get_instances_info(connection, ids): + result = [] + instances = connection.get_all_instances(instance_ids=ids) + if len(instances) > 0: + for inst in instances: + result.append(inst.read()) + return result + + +def create_instance(module, ecs, exact_count): + if exact_count <= 0: + return None + zone_id = module.params['availability_zone'] + image_id = module.params['image_id'] + instance_type = module.params['instance_type'] + security_groups = module.params['security_groups'] + vswitch_id = module.params['vswitch_id'] + instance_name = module.params['instance_name'] + description = module.params['description'] + internet_charge_type = module.params['internet_charge_type'] + max_bandwidth_out = module.params['max_bandwidth_out'] + max_bandwidth_in = module.params['max_bandwidth_out'] + host_name = module.params['host_name'] + password = module.params['password'] + system_disk_category = module.params['system_disk_category'] + system_disk_size = module.params['system_disk_size'] + system_disk_name = module.params['system_disk_name'] + system_disk_description = module.params['system_disk_description'] + allocate_public_ip = module.params['allocate_public_ip'] + instance_tags = module.params['instance_tags'] + period = module.params['period'] + auto_renew = module.params['auto_renew'] + instance_charge_type = module.params['instance_charge_type'] + auto_renew_period = module.params['auto_renew_period'] + user_data = module.params['user_data'] + key_name = module.params['key_name'] + + # check whether the required parameter passed or not + if not image_id: + module.fail_json(msg='image_id is required for new instance') + if not instance_type: + module.fail_json(msg='instance_type is required for new instance') + if not isinstance(security_groups, list): + module.fail_json(msg='The parameter security_groups should be a list, aborting') + if len(security_groups) <= 0: + module.fail_json(msg='Expected the parameter security_groups is non-empty when create new ECS instances, aborting') + + client_token = "Ansible-Alicloud-{0}-{1}".format(hash(str(module.params)), str(time.time())) + + try: + # call to create_instance method from footmark + instances = ecs.create_instance(image_id=image_id, instance_type=instance_type, security_group_id=security_groups[0], + zone_id=zone_id, instance_name=instance_name, description=description, + internet_charge_type=internet_charge_type, max_bandwidth_out=max_bandwidth_out, + max_bandwidth_in=max_bandwidth_in, host_name=host_name, password=password, + io_optimized='optimized', system_disk_category=system_disk_category, + system_disk_size=system_disk_size, system_disk_name=system_disk_name, + system_disk_description=system_disk_description, + vswitch_id=vswitch_id, count=exact_count, allocate_public_ip=allocate_public_ip, + instance_charge_type=instance_charge_type, period=period, auto_renew=auto_renew, + auto_renew_period=auto_renew_period, instance_tags=instance_tags, + key_pair_name=key_name, user_data=user_data, client_token=client_token) + + except Exception as e: + module.fail_json(msg='Unable to create instance, error: {0}'.format(e)) + + return instances + + +def main(): + argument_spec = ecs_argument_spec() + argument_spec.update(dict( + security_groups=dict(type='list'), + availability_zone=dict(type='str', aliases=['alicloud_zone']), + instance_type=dict(type='str', aliases=['type']), + image_id=dict(type='str', aliases=['image']), + count=dict(type='int', default=1), + count_tag=dict(type='str'), + vswitch_id=dict(type='str', aliases=['subnet_id']), + instance_name=dict(type='str', aliases=['name']), + host_name=dict(type='str'), + password=dict(type='str', no_log=True), + internet_charge_type=dict(type='str', default='PayByBandwidth', choices=['PayByBandwidth', 'PayByTraffic']), + max_bandwidth_in=dict(type='int', default=200), + max_bandwidth_out=dict(type='int', default=0), + system_disk_category=dict(type='str', default='cloud_efficiency', choices=['cloud_efficiency', 'cloud_ssd']), + system_disk_size=dict(type='int', default=40), + system_disk_name=dict(type='str'), + system_disk_description=dict(type='str'), + force=dict(type='bool', default=False), + instance_tags=dict(type='dict', aliases=['tags']), + state=dict(default='present', choices=['present', 'running', 'stopped', 'restarted', 'absent']), + description=dict(type='str'), + allocate_public_ip=dict(type='bool', aliases=['assign_public_ip'], default=False), + instance_charge_type=dict(type='str', default='PostPaid', choices=['PrePaid', 'PostPaid']), + period=dict(type='int', default=1), + auto_renew=dict(type='bool', default=False), + instance_ids=dict(type='list'), + auto_renew_period=dict(type='int', choices=[1, 2, 3, 6, 12]), + key_name=dict(type='str', aliases=['keypair']), + user_data=dict(type='str') + ) + ) + module = AnsibleModule(argument_spec=argument_spec) + + if HAS_FOOTMARK is False: + module.fail_json(msg="Package 'footmark' required for the module ali_instance.") + + ecs = ecs_connect(module) + state = module.params['state'] + instance_ids = module.params['instance_ids'] + count_tag = module.params['count_tag'] + count = module.params['count'] + instance_name = module.params['instance_name'] + force = module.params['force'] + zone_id = module.params['availability_zone'] + key_name = module.params['key_name'] + changed = False + + instances = [] + if instance_ids: + if not isinstance(instance_ids, list): + module.fail_json(msg='The parameter instance_ids should be a list, aborting') + instances = ecs.get_all_instances(zone_id=zone_id, instance_ids=instance_ids) + if not instances: + module.fail_json(msg="There are no instances in our record based on instance_ids {0}. " + "Please check it and try again.".format(instance_ids)) + elif count_tag: + instances = ecs.get_all_instances(zone_id=zone_id, instance_tags=eval(count_tag)) + elif instance_name: + instances = ecs.get_all_instances(zone_id=zone_id, instance_name=instance_name) + + ids = [] + if state == 'present': + if not instance_ids: + if len(instances) > count: + for i in range(0, len(instances) - count): + inst = instances[len(instances) - 1] + if inst.status is not 'stopped' and not force: + module.fail_json(msg="That to delete instance {0} is failed results from it is running, " + "and please stop it or set 'force' as True.".format(inst.id)) + try: + changed = inst.terminate(force=force) + except Exception as e: + module.fail_json(msg="Delete instance {0} got an error: {1}".format(inst.id, e)) + instances.pop(len(instances) - 1) + else: + try: + new_instances = create_instance(module, ecs, count - len(instances)) + if new_instances: + changed = True + instances.extend(new_instances) + except Exception as e: + module.fail_json(msg="Create new instances got an error: {0}".format(e)) + + # Security Group join/leave begin + security_groups = module.params['security_groups'] + if not isinstance(security_groups, list): + module.fail_json(msg='The parameter security_groups should be a list, aborting') + if len(security_groups) > 0: + for inst in instances: + existing = inst.security_group_ids['security_group_id'] + remove = list(set(existing).difference(set(security_groups))) + add = list(set(security_groups).difference(set(existing))) + for sg in remove: + if inst.leave_security_group(sg): + changed = True + for sg in add: + if inst.join_security_group(sg): + changed = True + # Security Group join/leave ends here + + # Attach/Detach key pair + inst_ids = [] + for inst in instances: + if key_name is not None and key_name != inst.key_name: + if key_name == "": + changed = inst.detach_key_pair() + else: + inst_ids.append(inst.id) + if inst_ids: + changed = ecs.attach_key_pair(instance_ids=inst_ids, key_pair_name=key_name) + + # Modify instance attribute + description = module.params['description'] + host_name = module.params['host_name'] + password = module.params['password'] + for inst in instances: + if not instance_name: + instance_name = inst.name + if not description: + description = inst.description + if not host_name: + host_name = inst.host_name + try: + if inst.modify(name=instance_name, description=description, host_name=host_name, password=password): + changed = True + except Exception as e: + module.fail_json(msg="Modify instance attribute {0} got an error: {1}".format(inst.id, e)) + + if inst.id not in ids: + ids.append(inst.id) + + module.exit_json(changed=changed, ids=ids, instances=get_instances_info(ecs, ids)) + + else: + if len(instances) < 1: + module.fail_json(msg='Please specify ECS instances that you want to operate by using ' + 'parameters instance_ids, instance_tags or instance_name, aborting') + force = module.params['force'] + if state == 'running': + try: + for inst in instances: + if inst.start(): + changed = True + ids.append(inst.id) + + module.exit_json(changed=changed, ids=ids, instances=get_instances_info(ecs, ids)) + except Exception as e: + module.fail_json(msg='Start instances got an error: {0}'.format(e)) + elif state == 'stopped': + try: + for inst in instances: + if inst.stop(force=force): + changed = True + ids.append(inst.id) + + module.exit_json(changed=changed, ids=ids, instances=get_instances_info(ecs, ids)) + except Exception as e: + module.fail_json(msg='Stop instances got an error: {0}'.format(e)) + elif state == 'restarted': + try: + for inst in instances: + if inst.reboot(force=module.params['force']): + changed = True + ids.append(inst.id) + + module.exit_json(changed=changed, ids=ids, instances=get_instances_info(ecs, ids)) + except Exception as e: + module.fail_json(msg='Reboot instances got an error: {0}'.format(e)) + else: + try: + for inst in instances: + if inst.status is not 'stopped' and not force: + module.fail_json(msg="Instance is running, and please stop it or set 'force' as True.") + if inst.terminate(force=module.params['force']): + changed = True + + module.exit_json(changed=changed, ids=[], instances=[]) + except Exception as e: + module.fail_json(msg='Delete instance got an error: {0}'.format(e)) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/cloud/alicloud/ali_instance_facts.py b/lib/ansible/modules/cloud/alicloud/ali_instance_facts.py new file mode 100644 index 0000000000..eb96f92f0b --- /dev/null +++ b/lib/ansible/modules/cloud/alicloud/ali_instance_facts.py @@ -0,0 +1,394 @@ +#!/usr/bin/python +# Copyright (c) 2017 Alibaba Group Holding Limited. He Guimin +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see http://www.gnu.org/licenses/. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: ali_instance_facts +version_added: "2.8" +short_description: Gather facts on instances of Alibaba Cloud ECS. +description: + - This module fetches data from the Open API in Alicloud. + The module must be called from within the ECS instance itself. + +options: + availability_zone: + description: + - Aliyun availability zone ID in which to launch the instance + aliases: ['alicloud_zone'] + instance_names: + description: + - A list of ECS instance names. + aliases: [ "names"] + instance_ids: + description: + - A list of ECS instance ids. + aliases: ["ids"] + instance_tags: + description: + - A hash/dictionaries of instance tags. C({"key":"value"}) + aliases: ["tags"] +author: + - "He Guimin (@xiaozhu36)" +requirements: + - "python >= 2.6" + - "footmark >= 1.1.16" +extends_documentation_fragment: + - alicloud +''' + +EXAMPLES = ''' +# Fetch instances details according to setting different filters +- name: fetch instances details example + hosts: localhost + vars: + alicloud_access_key: + alicloud_secret_key: + alicloud_region: cn-beijing + availability_zone: cn-beijing-a + + tasks: + - name: Find all instances in the specified region + ali_instance_facts: + register: all_instances + - name: Find all instances based on the specified ids + ali_instance_facts: + instance_ids: + - "i-35b333d9" + - "i-ddav43kd" + register: instances_by_ids + - name: Find all instances based on the specified names/name-prefixes + ali_instance_facts: + instance_names: + - "ecs_instance-1" + - "ecs_instance_2" + register: instances_by_ids + +''' + +RETURN = ''' +instances: + description: List of ECS instances + returned: always + type: complex + contains: + availability_zone: + description: The availability zone of the instance is in. + returned: always + type: string + sample: cn-beijing-a + block_device_mappings: + description: Any block device mapping entries for the instance. + returned: always + type: complex + contains: + device_name: + description: The device name exposed to the instance (for example, /dev/xvda). + returned: always + type: string + sample: /dev/xvda + attach_time: + description: The time stamp when the attachment initiated. + returned: always + type: string + sample: "2018-06-25T04:08:26Z" + delete_on_termination: + description: Indicates whether the volume is deleted on instance termination. + returned: always + type: bool + sample: true + status: + description: The attachment state. + returned: always + type: string + sample: in_use + volume_id: + description: The ID of the cloud disk. + returned: always + type: string + sample: d-2zei53pjsi117y6gf9t6 + cpu: + description: The CPU core count of the instance. + returned: always + type: int + sample: 4 + creation_time: + description: The time the instance was created. + returned: always + type: string + sample: "2018-06-25T04:08Z" + description: + description: The instance description. + returned: always + type: string + sample: "my ansible instance" + eip: + description: The attribution of EIP associated with the instance. + returned: always + type: complex + contains: + allocation_id: + description: The ID of the EIP. + returned: always + type: string + sample: eip-12345 + internet_charge_type: + description: The internet charge type of the EIP. + returned: always + type: string + sample: "paybybandwidth" + ip_address: + description: EIP address. + returned: always + type: string + sample: 42.10.2.2 + expired_time: + description: The time the instance will expire. + returned: always + type: string + sample: "2099-12-31T15:59Z" + gpu: + description: The attribution of instane GPU. + returned: always + type: complex + contains: + amount: + description: The count of the GPU. + returned: always + type: int + sample: 0 + spec: + description: The specification of the GPU. + returned: always + type: string + sample: "" + host_name: + description: The host name of the instance. + returned: always + type: string + sample: iZ2zewaoZ + id: + description: Alias of instance_id. + returned: always + type: string + sample: i-abc12345 + instance_id: + description: ECS instance resource ID. + returned: always + type: string + sample: i-abc12345 + image_id: + description: The ID of the image used to launch the instance. + returned: always + type: string + sample: m-0011223344 + inner_ip_address: + description: The inner IPv4 address of the classic instance. + returned: always + type: string + sample: 10.0.0.2 + instance_charge_type: + description: The instance charge type. + returned: always + type: string + sample: PostPaid + instance_name: + description: The name of the instance. + returned: always + type: string + sample: my-ecs + instance_type: + description: The instance type of the running instance. + returned: always + type: string + sample: ecs.sn1ne.xlarge + internet_charge_type: + description: The billing method of the network bandwidth. + returned: always + type: string + sample: PayByBandwidth + internet_max_bandwidth_in: + description: Maximum incoming bandwidth from the internet network. + returned: always + type: int + sample: 200 + internet_max_bandwidth_out: + description: Maximum incoming bandwidth from the internet network. + returned: always + type: int + sample: 20 + io_optimized: + description: Indicates whether the instance is optimized for EBS I/O. + returned: always + type: bool + sample: false + memory: + description: Memory size of the instance. + returned: always + type: int + sample: 8192 + network_interfaces: + description: One or more network interfaces for the instance. + returned: always + type: complex + contains: + mac_address: + description: The MAC address. + returned: always + type: string + sample: "00:11:22:33:44:55" + network_interface_id: + description: The ID of the network interface. + returned: always + type: string + sample: eni-01234567 + primary_ip_address: + description: The primary IPv4 address of the network interface within the vswitch. + returned: always + type: string + sample: 10.0.0.1 + osname: + description: The operation system name of the instance owned. + returned: always + type: string + sample: CentOS + ostype: + description: The operation system type of the instance owned. + returned: always + type: string + sample: linux + private_ip_address: + description: The IPv4 address of the network interface within the subnet. + returned: always + type: string + sample: 10.0.0.1 + public_ip_address: + description: The public IPv4 address assigned to the instance + returned: always + type: string + sample: 43.0.0.1 + resource_group_id: + description: The id of the resource group to which the instance belongs. + returned: always + type: string + sample: my-ecs-group + security_groups: + description: One or more security groups for the instance. + returned: always + type: complex + contains: + - group_id: + description: The ID of the security group. + returned: always + type: string + sample: sg-0123456 + - group_name: + description: The name of the security group. + returned: always + type: string + sample: my-security-group + status: + description: The current status of the instance. + returned: always + type: string + sample: running + tags: + description: Any tags assigned to the instance. + returned: always + type: dict + sample: + vswitch_id: + description: The ID of the vswitch in which the instance is running. + returned: always + type: string + sample: vsw-dew00abcdef + vpc_id: + description: The ID of the VPC the instance is in. + returned: always + type: dict + sample: vpc-0011223344 +ids: + description: List of ECS instance IDs + returned: always + type: list + sample: [i-12345er, i-3245fs] +''' + +# import time +# import sys +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.alicloud_ecs import get_acs_connection_info, ecs_argument_spec, ecs_connect + +HAS_FOOTMARK = False + +try: + from footmark.exception import ECSResponseError + HAS_FOOTMARK = True +except ImportError: + HAS_FOOTMARK = False + + +def main(): + argument_spec = ecs_argument_spec() + argument_spec.update(dict( + availability_zone=dict(aliases=['alicloud_zone']), + instance_ids=dict(type='list', aliases=['ids']), + instance_names=dict(type='list', aliases=['names']), + instance_tags=dict(type='list', aliases=['tags']), + ) + ) + module = AnsibleModule(argument_spec=argument_spec) + + if HAS_FOOTMARK is False: + module.fail_json(msg='footmark required for the module ali_instance_facts') + + ecs = ecs_connect(module) + + instances = [] + instance_ids = [] + ids = module.params['instance_ids'] + names = module.params['instance_names'] + zone_id = module.params['availability_zone'] + if ids and (not isinstance(ids, list) or len(ids) < 1): + module.fail_json(msg='instance_ids should be a list of instances, aborting') + + if names and (not isinstance(names, list) or len(names) < 1): + module.fail_json(msg='instance_ids should be a list of instances, aborting') + + if names: + for name in names: + for inst in ecs.get_all_instances(zone_id=zone_id, instance_ids=ids, instance_name=name): + instances.append(inst.read()) + instance_ids.append(inst.id) + else: + for inst in ecs.get_all_instances(zone_id=zone_id, instance_ids=ids): + instances.append(inst.read()) + instance_ids.append(inst.id) + + module.exit_json(changed=False, ids=instance_ids, instances=instances) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/utils/module_docs_fragments/alicloud.py b/lib/ansible/utils/module_docs_fragments/alicloud.py new file mode 100644 index 0000000000..e94f4fed00 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/alicloud.py @@ -0,0 +1,61 @@ +# !/usr/bin/python +# Copyright (c) 2017 Alibaba Group Holding Limited. He Guimin +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + + +class ModuleDocFragment(object): + + # Alicloud only documentation fragment + DOCUMENTATION = """ +options: + alicloud_access_key: + description: + - Aliyun Cloud access key. If not set then the value of environment variable C(ALICLOUD_ACCESS_KEY), + C(ALICLOUD_ACCESS_KEY_ID) will be used instead. + aliases: ['access_key_id', 'access_key'] + alicloud_secret_key: + description: + - Aliyun Cloud secret key. If not set then the value of environment variable C(ALICLOUD_SECRET_KEY), + C(ALICLOUD_SECRET_ACCESS_KEY) will be used instead. + aliases: ['secret_access_key', 'secret_key'] + alicloud_region: + description: + - The Aliyun Cloud region to use. If not specified then the value of environment variable + C(ALICLOUD_REGION), C(ALICLOUD_REGION_ID) will be used instead. + aliases: ['region', 'region_id'] + alicloud_security_token: + description: + - The Aliyun Cloud security token. If not specified then the value of environment variable + C(ALICLOUD_SECURITY_TOKEN) will be used instead. + aliases: ['security_token'] +author: + - "He Guimin (@xiaozhu36)" +requirements: + - "python >= 2.6" +extends_documentation_fragment: + - alicloud +notes: + - If parameters are not set within the module, the following + environment variables can be used in decreasing order of precedence + C(ALICLOUD_ACCESS_KEY) or C(ALICLOUD_ACCESS_KEY_ID), + C(ALICLOUD_SECRET_KEY) or C(ALICLOUD_SECRET_ACCESS_KEY), + C(ALICLOUD_REGION) or C(ALICLOUD_REGION_ID), + C(ALICLOUD_SECURITY_TOKEN) + - C(ALICLOUD_REGION) or C(ALICLOUD_REGION_ID) can be typically be used to specify the + ALICLOUD region, when required, but this can also be configured in the footmark config file +"""