HEX
Server: Apache
System: Linux sg241.singhost.net 2.6.32-896.16.1.lve1.4.51.el6.x86_64 #1 SMP Wed Jan 17 13:19:23 EST 2018 x86_64
User: honghock (909)
PHP: 8.0.30
Disabled: passthru,system,shell_exec,show_source,exec,popen,proc_open
Upload Files
File: //usr/lib/python2.7/site-packages/salt/cloud/clouds/aliyun.py
# -*- coding: utf-8 -*-
'''
AliYun ECS Cloud Module
=======================

.. versionadded:: 2014.7.0

The Aliyun cloud module is used to control access to the aliyun ECS.
http://www.aliyun.com/

Use of this module requires the ``id`` and ``key`` parameter to be set.
Set up the cloud configuration at ``/etc/salt/cloud.providers`` or
``/etc/salt/cloud.providers.d/aliyun.conf``:

.. code-block:: yaml

    my-aliyun-config:
      # aliyun Access Key ID
      id: wFGEwgregeqw3435gDger
      # aliyun Access Key Secret
      key: GDE43t43REGTrkilg43934t34qT43t4dgegerGEgg
      location: cn-qingdao
      driver: aliyun

:depends: requests
'''

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import time
import pprint
import logging
import hmac
import uuid
import sys
import base64
from hashlib import sha1

# Import Salt libs
from salt.ext.six.moves.urllib.parse import quote as _quote  # pylint: disable=import-error,no-name-in-module

# Import salt cloud libs
import salt.utils.cloud
import salt.utils.data
import salt.utils.json
import salt.config as config
from salt.exceptions import (
    SaltCloudNotFound,
    SaltCloudSystemExit,
    SaltCloudExecutionFailure,
    SaltCloudExecutionTimeout
)
from salt.utils.stringutils import to_bytes

# Import 3rd-party libs
from salt.ext import six
try:
    import requests
    HAS_REQUESTS = True
except ImportError:
    HAS_REQUESTS = False

# Get logging started
log = logging.getLogger(__name__)

ALIYUN_LOCATIONS = {
    # 'us-west-2': 'ec2_us_west_oregon',
    'cn-hangzhou': 'AliYun HangZhou Region',
    'cn-beijing': 'AliYun BeiJing Region',
    'cn-hongkong': 'AliYun HongKong Region',
    'cn-qingdao': 'AliYun QingDao Region',
    'cn-shanghai': 'AliYun ShangHai Region',
    'cn-shenzhen': 'AliYun ShenZheng Region',
    'ap-northeast-1': 'AliYun DongJing Region',
    'ap-southeast-1': 'AliYun XinJiaPo Region',
    'ap-southeast-2': 'AliYun XiNi Region',
    'eu-central-1': 'EU FalaKeFu Region',
    'me-east-1': 'ME DiBai Region',
    'us-east-1': 'US FuJiNiYa Region',
    'us-west-1': 'US GuiGu Region',
}
DEFAULT_LOCATION = 'cn-hangzhou'

DEFAULT_ALIYUN_API_VERSION = '2014-05-26'

__virtualname__ = 'aliyun'


# Only load in this module if the aliyun configurations are in place
def __virtual__():
    '''
    Check for aliyun configurations
    '''
    if get_configured_provider() is False:
        return False

    if get_dependencies() is False:
        return False

    return __virtualname__


def get_configured_provider():
    '''
    Return the first configured instance.
    '''
    return config.is_provider_configured(
        __opts__,
        __active_provider_name__ or __virtualname__,
        ('id', 'key')
    )


def get_dependencies():
    '''
    Warn if dependencies aren't met.
    '''
    return config.check_driver_dependencies(
        __virtualname__,
        {'requests': HAS_REQUESTS}
    )


def avail_locations(call=None):
    '''
    Return a dict of all available VM locations on the cloud provider with
    relevant data
    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'The avail_locations function must be called with '
            '-f or --function, or with the --list-locations option'
        )

    params = {'Action': 'DescribeRegions'}
    items = query(params=params)

    ret = {}
    for region in items['Regions']['Region']:
        ret[region['RegionId']] = {}
        for item in region:
            ret[region['RegionId']][item] = six.text_type(region[item])

    return ret


def avail_images(kwargs=None, call=None):
    '''
    Return a list of the images that are on the provider
    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'The avail_images function must be called with '
            '-f or --function, or with the --list-images option'
        )

    if not isinstance(kwargs, dict):
        kwargs = {}

    provider = get_configured_provider()
    location = provider.get('location', DEFAULT_LOCATION)

    if 'location' in kwargs:
        location = kwargs['location']

    params = {
        'Action': 'DescribeImages',
        'RegionId': location,
        'PageSize': '100',
    }
    items = query(params=params)

    ret = {}
    for image in items['Images']['Image']:
        ret[image['ImageId']] = {}
        for item in image:
            ret[image['ImageId']][item] = six.text_type(image[item])

    return ret


def avail_sizes(call=None):
    '''
    Return a list of the image sizes that are on the provider
    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'The avail_sizes function must be called with '
            '-f or --function, or with the --list-sizes option'
        )

    params = {'Action': 'DescribeInstanceTypes'}
    items = query(params=params)

    ret = {}
    for image in items['InstanceTypes']['InstanceType']:
        ret[image['InstanceTypeId']] = {}
        for item in image:
            ret[image['InstanceTypeId']][item] = six.text_type(image[item])

    return ret


def get_location(vm_=None):
    '''
    Return the aliyun region to use, in this order:
        - CLI parameter
        - VM parameter
        - Cloud profile setting
    '''
    return __opts__.get(
        'location',
        config.get_cloud_config_value(
            'location',
            vm_ or get_configured_provider(),
            __opts__,
            default=DEFAULT_LOCATION,
            search_global=False
        )
    )


def list_availability_zones(call=None):
    '''
    List all availability zones in the current region
    '''
    ret = {}

    params = {'Action': 'DescribeZones',
              'RegionId': get_location()}
    items = query(params)

    for zone in items['Zones']['Zone']:
        ret[zone['ZoneId']] = {}
        for item in zone:
            ret[zone['ZoneId']][item] = six.text_type(zone[item])

    return ret


def list_nodes_min(call=None):
    '''
    Return a list of the VMs that are on the provider. Only a list of VM names,
    and their state, is returned. This is the minimum amount of information
    needed to check for existing VMs.
    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'The list_nodes_min function must be called with -f or --function.'
        )

    ret = {}
    location = get_location()
    params = {
        'Action': 'DescribeInstanceStatus',
        'RegionId': location,
    }
    nodes = query(params)

    log.debug(
        'Total %s instance found in Region %s',
        nodes['TotalCount'], location
    )
    if 'Code' in nodes or nodes['TotalCount'] == 0:
        return ret

    for node in nodes['InstanceStatuses']['InstanceStatus']:
        ret[node['InstanceId']] = {}
        for item in node:
            ret[node['InstanceId']][item] = node[item]

    return ret


def list_nodes(call=None):
    '''
    Return a list of the VMs that are on the provider
    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'The list_nodes function must be called with -f or --function.'
        )

    nodes = list_nodes_full()
    ret = {}
    for instanceId in nodes:
        node = nodes[instanceId]
        ret[node['name']] = {
            'id': node['id'],
            'name': node['name'],
            'public_ips': node['public_ips'],
            'private_ips': node['private_ips'],
            'size': node['size'],
            'state': six.text_type(node['state']),
        }
    return ret


def list_nodes_full(call=None):
    '''
    Return a list of the VMs that are on the provider
    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'The list_nodes_full function must be called with -f '
            'or --function.'
        )

    ret = {}
    location = get_location()
    params = {
        'Action': 'DescribeInstanceStatus',
        'RegionId': location,
        'PageSize': '50'
    }
    result = query(params=params)

    log.debug(
        'Total %s instance found in Region %s',
        result['TotalCount'], location
    )
    if 'Code' in result or result['TotalCount'] == 0:
        return ret

    # aliyun max 100 top instance in api
    result_instancestatus = result['InstanceStatuses']['InstanceStatus']
    if result['TotalCount'] > 50:
        params['PageNumber'] = '2'
        result = query(params=params)
        result_instancestatus.update(result['InstanceStatuses']['InstanceStatus'])

    for node in result_instancestatus:

        instanceId = node.get('InstanceId', '')

        params = {
            'Action': 'DescribeInstanceAttribute',
            'InstanceId': instanceId
        }
        items = query(params=params)
        if 'Code' in items:
            log.warning('Query instance:%s attribute failed', instanceId)
            continue

        name = items['InstanceName']
        ret[name] = {
            'id': items['InstanceId'],
            'name': name,
            'image': items['ImageId'],
            'size': 'TODO',
            'state': items['Status']
        }
        for item in items:
            value = items[item]
            if value is not None:
                value = six.text_type(value)
            if item == "PublicIpAddress":
                ret[name]['public_ips'] = items[item]['IpAddress']
            if item == "InnerIpAddress" and 'private_ips' not in ret[name]:
                ret[name]['private_ips'] = items[item]['IpAddress']
            if item == 'VpcAttributes':
                vpc_ips = items[item]['PrivateIpAddress']['IpAddress']
                if vpc_ips:
                    ret[name]['private_ips'] = vpc_ips
            ret[name][item] = value

    provider = __active_provider_name__ or 'aliyun'
    if ':' in provider:
        comps = provider.split(':')
        provider = comps[0]

    __opts__['update_cachedir'] = True
    __utils__['cloud.cache_node_list'](ret, provider, __opts__)

    return ret


def list_nodes_select(call=None):
    '''
    Return a list of the VMs that are on the provider, with select fields
    '''
    return salt.utils.cloud.list_nodes_select(
        list_nodes_full('function'), __opts__['query.selection'], call,
    )


def list_securitygroup(call=None):
    '''
    Return a list of security group
    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'The list_nodes function must be called with -f or --function.'
        )

    params = {
        'Action': 'DescribeSecurityGroups',
        'RegionId': get_location(),
        'PageSize': '50',
    }

    result = query(params)
    if 'Code' in result:
        return {}

    ret = {}
    for sg in result['SecurityGroups']['SecurityGroup']:
        ret[sg['SecurityGroupId']] = {}
        for item in sg:
            ret[sg['SecurityGroupId']][item] = sg[item]

    return ret


def get_image(vm_):
    '''
    Return the image object to use
    '''
    images = avail_images()
    vm_image = six.text_type(config.get_cloud_config_value(
        'image', vm_, __opts__, search_global=False
    ))

    if not vm_image:
        raise SaltCloudNotFound('No image specified for this VM.')

    if vm_image and six.text_type(vm_image) in images:
        return images[vm_image]['ImageId']
    raise SaltCloudNotFound(
        'The specified image, \'{0}\', could not be found.'.format(vm_image)
    )


def get_securitygroup(vm_):
    '''
    Return the security group
    '''
    sgs = list_securitygroup()
    securitygroup = config.get_cloud_config_value(
        'securitygroup', vm_, __opts__, search_global=False
    )

    if not securitygroup:
        raise SaltCloudNotFound('No securitygroup ID specified for this VM.')

    if securitygroup and six.text_type(securitygroup) in sgs:
        return sgs[securitygroup]['SecurityGroupId']
    raise SaltCloudNotFound(
        'The specified security group, \'{0}\', could not be found.'.format(
            securitygroup)
    )


def get_size(vm_):
    '''
    Return the VM's size. Used by create_node().
    '''
    sizes = avail_sizes()
    vm_size = six.text_type(config.get_cloud_config_value(
        'size', vm_, __opts__, search_global=False
    ))

    if not vm_size:
        raise SaltCloudNotFound('No size specified for this VM.')

    if vm_size and six.text_type(vm_size) in sizes:
        return sizes[vm_size]['InstanceTypeId']

    raise SaltCloudNotFound(
        'The specified size, \'{0}\', could not be found.'.format(vm_size)
    )


def __get_location(vm_):
    '''
    Return the VM's location
    '''
    locations = avail_locations()
    vm_location = six.text_type(config.get_cloud_config_value(
        'location', vm_, __opts__, search_global=False
    ))

    if not vm_location:
        raise SaltCloudNotFound('No location specified for this VM.')

    if vm_location and six.text_type(vm_location) in locations:
        return locations[vm_location]['RegionId']
    raise SaltCloudNotFound(
        'The specified location, \'{0}\', could not be found.'.format(
            vm_location
        )
    )


def start(name, call=None):
    '''
    Start a node

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a start myinstance
    '''
    if call != 'action':
        raise SaltCloudSystemExit(
            'The stop action must be called with -a or --action.'
        )

    log.info('Starting node %s', name)

    instanceId = _get_node(name)['InstanceId']

    params = {'Action': 'StartInstance',
              'InstanceId': instanceId}
    result = query(params)

    return result


def stop(name, force=False, call=None):
    '''
    Stop a node

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a stop myinstance
        salt-cloud -a stop myinstance force=True
    '''
    if call != 'action':
        raise SaltCloudSystemExit(
            'The stop action must be called with -a or --action.'
        )

    log.info('Stopping node %s', name)

    instanceId = _get_node(name)['InstanceId']

    params = {
        'Action': 'StopInstance',
        'InstanceId': instanceId,
        'ForceStop': six.text_type(force).lower()
    }
    result = query(params)

    return result


def reboot(name, call=None):
    '''
    Reboot a node

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a reboot myinstance
    '''
    if call != 'action':
        raise SaltCloudSystemExit(
            'The stop action must be called with -a or --action.'
        )

    log.info('Rebooting node %s', name)

    instance_id = _get_node(name)['InstanceId']

    params = {'Action': 'RebootInstance',
              'InstanceId': instance_id}
    result = query(params)

    return result


def create_node(kwargs):
    '''
    Convenience function to make the rest api call for node creation.
    '''
    if not isinstance(kwargs, dict):
        kwargs = {}

    # Required parameters
    params = {
        'Action': 'CreateInstance',
        'InstanceType': kwargs.get('size_id', ''),
        'RegionId': kwargs.get('region_id', DEFAULT_LOCATION),
        'ImageId': kwargs.get('image_id', ''),
        'SecurityGroupId': kwargs.get('securitygroup_id', ''),
        'InstanceName': kwargs.get('name', ''),
    }

    # Optional parameters'
    optional = [
        'InstanceName', 'InternetChargeType',
        'InternetMaxBandwidthIn', 'InternetMaxBandwidthOut',
        'HostName', 'Password', 'SystemDisk.Category', 'VSwitchId'
        # 'DataDisk.n.Size', 'DataDisk.n.Category', 'DataDisk.n.SnapshotId'
    ]

    for item in optional:
        if item in kwargs:
            params.update({item: kwargs[item]})

    # invoke web call
    result = query(params)
    return result['InstanceId']


def create(vm_):
    '''
    Create a single VM from a data dict
    '''
    try:
        # Check for required profile parameters before sending any API calls.
        if vm_['profile'] and config.is_profile_configured(__opts__,
                                                           __active_provider_name__ or 'aliyun',
                                                           vm_['profile'],
                                                           vm_=vm_) is False:
            return False
    except AttributeError:
        pass

    __utils__['cloud.fire_event'](
        'event',
        'starting create',
        'salt/cloud/{0}/creating'.format(vm_['name']),
        args=__utils__['cloud.filter_event']('creating', vm_, ['name', 'profile', 'provider', 'driver']),
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )

    log.info('Creating Cloud VM %s', vm_['name'])
    kwargs = {
        'name': vm_['name'],
        'size_id': get_size(vm_),
        'image_id': get_image(vm_),
        'region_id': __get_location(vm_),
        'securitygroup_id': get_securitygroup(vm_),
    }
    if 'vswitch_id' in vm_:
        kwargs['VSwitchId'] = vm_['vswitch_id']
    if 'internet_chargetype' in vm_:
        kwargs['InternetChargeType'] = vm_['internet_chargetype']
    if 'internet_maxbandwidthin' in vm_:
        kwargs['InternetMaxBandwidthIn'] = six.text_type(vm_['internet_maxbandwidthin'])
    if 'internet_maxbandwidthout' in vm_:
        kwargs['InternetMaxBandwidthOut'] = six.text_type(vm_['internet_maxbandwidthOut'])
    if 'hostname' in vm_:
        kwargs['HostName'] = vm_['hostname']
    if 'password' in vm_:
        kwargs['Password'] = vm_['password']
    if 'instance_name' in vm_:
        kwargs['InstanceName'] = vm_['instance_name']
    if 'systemdisk_category' in vm_:
        kwargs['SystemDisk.Category'] = vm_['systemdisk_category']

    __utils__['cloud.fire_event'](
        'event',
        'requesting instance',
        'salt/cloud/{0}/requesting'.format(vm_['name']),
        args=__utils__['cloud.filter_event']('requesting', kwargs, list(kwargs)),
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )

    try:
        ret = create_node(kwargs)
    except Exception as exc:  # pylint: disable=broad-except
        log.error(
            'Error creating %s on Aliyun ECS\n\n'
            'The following exception was thrown when trying to '
            'run the initial deployment: %s',
            vm_['name'], six.text_type(exc),
            # Show the traceback if the debug logging level is enabled
            exc_info_on_loglevel=logging.DEBUG
        )
        return False
    # repair ip address error and start vm
    time.sleep(8)
    params = {'Action': 'StartInstance',
              'InstanceId': ret}
    query(params)

    def __query_node_data(vm_name):
        data = show_instance(vm_name, call='action')
        if not data:
            # Trigger an error in the wait_for_ip function
            return False
        if data.get('PublicIpAddress', None) is not None:
            return data

    try:
        data = salt.utils.cloud.wait_for_ip(
            __query_node_data,
            update_args=(vm_['name'],),
            timeout=config.get_cloud_config_value(
                'wait_for_ip_timeout', vm_, __opts__, default=10 * 60),
            interval=config.get_cloud_config_value(
                'wait_for_ip_interval', vm_, __opts__, default=10),
        )
    except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc:
        try:
            # It might be already up, let's destroy it!
            destroy(vm_['name'])
        except SaltCloudSystemExit:
            pass
        finally:
            raise SaltCloudSystemExit(six.text_type(exc))

    if data['public_ips']:
        ssh_ip = data['public_ips'][0]
    elif data['private_ips']:
        ssh_ip = data['private_ips'][0]
    else:
        log.info('No available ip:cant connect to salt')
        return False
    log.debug('VM %s is now running', ssh_ip)
    vm_['ssh_host'] = ssh_ip

    # The instance is booted and accessible, let's Salt it!
    ret = __utils__['cloud.bootstrap'](vm_, __opts__)
    ret.update(data)

    log.info('Created Cloud VM \'%s\'', vm_['name'])
    log.debug(
        '\'%s\' VM creation details:\n%s',
        vm_['name'], pprint.pformat(data)
    )

    __utils__['cloud.fire_event'](
        'event',
        'created instance',
        'salt/cloud/{0}/created'.format(vm_['name']),
        args=__utils__['cloud.filter_event']('created', vm_, ['name', 'profile', 'provider', 'driver']),
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )

    return ret


def _compute_signature(parameters, access_key_secret):
    '''
    Generate aliyun request signature
    '''

    def percent_encode(line):
        if not isinstance(line, six.string_types):
            return line

        s = line
        if sys.stdin.encoding is None:
            s = line.decode().encode('utf8')
        else:
            s = line.decode(sys.stdin.encoding).encode('utf8')
        res = _quote(s, '')
        res = res.replace('+', '%20')
        res = res.replace('*', '%2A')
        res = res.replace('%7E', '~')
        return res

    sortedParameters = sorted(list(parameters.items()), key=lambda items: items[0])

    canonicalizedQueryString = ''
    for k, v in sortedParameters:
        canonicalizedQueryString += '&' + percent_encode(k) \
            + '=' + percent_encode(v)

    # All aliyun API only support GET method
    stringToSign = 'GET&%2F&' + percent_encode(canonicalizedQueryString[1:])

    h = hmac.new(to_bytes(access_key_secret + "&"), stringToSign, sha1)
    signature = base64.encodestring(h.digest()).strip()
    return signature


def query(params=None):
    '''
    Make a web call to aliyun ECS REST API
    '''
    path = 'https://ecs-cn-hangzhou.aliyuncs.com'

    access_key_id = config.get_cloud_config_value(
        'id', get_configured_provider(), __opts__, search_global=False
    )
    access_key_secret = config.get_cloud_config_value(
        'key', get_configured_provider(), __opts__, search_global=False
    )

    timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())

    # public interface parameters
    parameters = {
        'Format': 'JSON',
        'Version': DEFAULT_ALIYUN_API_VERSION,
        'AccessKeyId': access_key_id,
        'SignatureVersion': '1.0',
        'SignatureMethod': 'HMAC-SHA1',
        'SignatureNonce': six.text_type(uuid.uuid1()),
        'TimeStamp': timestamp,
    }

    # include action or function parameters
    if params:
        parameters.update(params)

    # Calculate the string for Signature
    signature = _compute_signature(parameters, access_key_secret)
    parameters['Signature'] = signature

    request = requests.get(path, params=parameters, verify=True)
    if request.status_code != 200:
        raise SaltCloudSystemExit(
            'An error occurred while querying aliyun ECS. HTTP Code: {0}  '
            'Error: \'{1}\''.format(
                request.status_code,
                request.text
            )
        )

    log.debug(request.url)

    content = request.text

    result = salt.utils.json.loads(content)
    if 'Code' in result:
        raise SaltCloudSystemExit(
            pprint.pformat(result.get('Message', {}))
        )
    return result


def script(vm_):
    '''
    Return the script deployment object
    '''
    deploy_script = salt.utils.cloud.os_script(
        config.get_cloud_config_value('script', vm_, __opts__),
        vm_,
        __opts__,
        salt.utils.cloud.salt_config_to_yaml(
            salt.utils.cloud.minion_config(__opts__, vm_)
        )
    )
    return deploy_script


def show_disk(name, call=None):
    '''
    Show the disk details of the instance

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a show_disk aliyun myinstance
    '''
    if call != 'action':
        raise SaltCloudSystemExit(
            'The show_disks action must be called with -a or --action.'
        )

    ret = {}
    params = {
        'Action': 'DescribeInstanceDisks',
        'InstanceId': name
    }
    items = query(params=params)

    for disk in items['Disks']['Disk']:
        ret[disk['DiskId']] = {}
        for item in disk:
            ret[disk['DiskId']][item] = six.text_type(disk[item])

    return ret


def list_monitor_data(kwargs=None, call=None):
    '''
    Get monitor data of the instance. If instance name is
    missing, will show all the instance monitor data on the region.

    CLI Examples:

    .. code-block:: bash

        salt-cloud -f list_monitor_data aliyun
        salt-cloud -f list_monitor_data aliyun name=AY14051311071990225bd
    '''
    if call != 'function':
        raise SaltCloudSystemExit(
            'The list_monitor_data must be called with -f or --function.'
        )

    if not isinstance(kwargs, dict):
        kwargs = {}

    ret = {}
    params = {
        'Action': 'GetMonitorData',
        'RegionId': get_location()
    }
    if 'name' in kwargs:
        params['InstanceId'] = kwargs['name']

    items = query(params=params)

    monitorData = items['MonitorData']

    for data in monitorData['InstanceMonitorData']:
        ret[data['InstanceId']] = {}
        for item in data:
            ret[data['InstanceId']][item] = six.text_type(data[item])

    return ret


def show_instance(name, call=None):
    '''
    Show the details from aliyun instance
    '''
    if call != 'action':
        raise SaltCloudSystemExit(
            'The show_instance action must be called with -a or --action.'
        )

    return _get_node(name)


def _get_node(name):
    attempts = 5
    while attempts >= 0:
        try:
            return list_nodes_full()[name]
        except KeyError:
            attempts -= 1
            log.debug(
                'Failed to get the data for node \'%s\'. Remaining '
                'attempts: %s', name, attempts
            )
            # Just a little delay between attempts...
            time.sleep(0.5)
    raise SaltCloudNotFound(
        'The specified instance {0} not found'.format(name)
    )


def show_image(kwargs, call=None):
    '''
    Show the details from aliyun image
    '''
    if call != 'function':
        raise SaltCloudSystemExit(
            'The show_images function must be called with '
            '-f or --function'
        )

    if not isinstance(kwargs, dict):
        kwargs = {}

    location = get_location()
    if 'location' in kwargs:
        location = kwargs['location']

    params = {
        'Action': 'DescribeImages',
        'RegionId': location,
        'ImageId': kwargs['image']
    }

    ret = {}
    items = query(params=params)
    # DescribeImages so far support input multi-image. And
    # if not found certain image, the response will include
    # blank image list other than 'not found' error message
    if 'Code' in items or not items['Images']['Image']:
        raise SaltCloudNotFound('The specified image could not be found.')

    log.debug(
        'Total %s image found in Region %s',
        items['TotalCount'], location
    )

    for image in items['Images']['Image']:
        ret[image['ImageId']] = {}
        for item in image:
            ret[image['ImageId']][item] = six.text_type(image[item])

    return ret


def destroy(name, call=None):
    '''
    Destroy a node.

    CLI Example:

    .. code-block:: bash

        salt-cloud -a destroy myinstance
        salt-cloud -d myinstance
    '''
    if call == 'function':
        raise SaltCloudSystemExit(
            'The destroy action must be called with -d, --destroy, '
            '-a or --action.'
        )

    __utils__['cloud.fire_event'](
        'event',
        'destroying instance',
        'salt/cloud/{0}/destroying'.format(name),
        args={'name': name},
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )

    instanceId = _get_node(name)['InstanceId']

    # have to stop instance before del it
    stop_params = {
        'Action': 'StopInstance',
        'InstanceId': instanceId
    }
    query(stop_params)

    params = {
        'Action': 'DeleteInstance',
        'InstanceId': instanceId
    }

    node = query(params)

    __utils__['cloud.fire_event'](
        'event',
        'destroyed instance',
        'salt/cloud/{0}/destroyed'.format(name),
        args={'name': name},
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )

    return node