%PDF- %PDF-
Direktori : /proc/thread-self/root/lib/python2.7/site-packages/salt/cloud/clouds/ |
Current File : //proc/thread-self/root/lib/python2.7/site-packages/salt/cloud/clouds/vultrpy.py |
# -*- coding: utf-8 -*- ''' Vultr Cloud Module using python-vultr bindings ============================================== .. versionadded:: 2016.3.0 The Vultr cloud module is used to control access to the Vultr VPS system. Use of this module only requires the ``api_key`` parameter. Set up the cloud configuration at ``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/vultr.conf``: .. code-block:: yaml my-vultr-config: # Vultr account api key api_key: <supersecretapi_key> driver: vultr Set up the cloud profile at ``/etc/salt/cloud.profiles`` or ``/etc/salt/cloud.profiles.d/vultr.conf``: .. code-block:: yaml nyc-4gb-4cpu-ubuntu-14-04: location: 1 provider: my-vultr-config image: 160 size: 95 enable_private_network: True This driver also supports Vultr's `startup script` feature. You can list startup scripts in your account with .. code-block:: bash salt-cloud -f list_scripts <name of vultr provider> That list will include the IDs of the scripts in your account. Thus, if you have a script called 'setup-networking' with an ID of 493234 you can specify that startup script in a profile like so: .. code-block:: yaml nyc-2gb-1cpu-ubuntu-17-04: location: 1 provider: my-vultr-config image: 223 size: 13 startup_script_id: 493234 ''' # Import python libs from __future__ import absolute_import, print_function, unicode_literals import pprint import logging import time # Import salt libs import salt.config as config from salt.ext import six from salt.ext.six.moves.urllib.parse import urlencode as _urlencode # pylint: disable=E0611 from salt.exceptions import ( SaltCloudConfigError, SaltCloudSystemExit ) # Get logging started log = logging.getLogger(__name__) __virtualname__ = 'vultr' DETAILS = {} def __virtual__(): ''' Set up the Vultr functions and check for configurations ''' if get_configured_provider() 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 'vultr', ('api_key',) ) def _cache_provider_details(conn=None): ''' Provide a place to hang onto results of --list-[locations|sizes|images] so we don't have to go out to the API and get them every time. ''' DETAILS['avail_locations'] = {} DETAILS['avail_sizes'] = {} DETAILS['avail_images'] = {} locations = avail_locations(conn) images = avail_images(conn) sizes = avail_sizes(conn) for key, location in six.iteritems(locations): DETAILS['avail_locations'][location['name']] = location DETAILS['avail_locations'][key] = location for key, image in six.iteritems(images): DETAILS['avail_images'][image['name']] = image DETAILS['avail_images'][key] = image for key, vm_size in six.iteritems(sizes): DETAILS['avail_sizes'][vm_size['name']] = vm_size DETAILS['avail_sizes'][key] = vm_size def avail_locations(conn=None): ''' return available datacenter locations ''' return _query('regions/list') def avail_scripts(conn=None): ''' return available startup scripts ''' return _query('startupscript/list') def list_scripts(conn=None, call=None): ''' return list of Startup Scripts ''' return avail_scripts() def avail_sizes(conn=None): ''' Return available sizes ("plans" in VultrSpeak) ''' return _query('plans/list') def avail_images(conn=None): ''' Return available images ''' return _query('os/list') def list_nodes(**kwargs): ''' Return basic data on nodes ''' ret = {} nodes = list_nodes_full() for node in nodes: ret[node] = {} for prop in 'id', 'image', 'size', 'state', 'private_ips', 'public_ips': ret[node][prop] = nodes[node][prop] return ret def list_nodes_full(**kwargs): ''' Return all data on nodes ''' nodes = _query('server/list') ret = {} for node in nodes: name = nodes[node]['label'] ret[name] = nodes[node].copy() ret[name]['id'] = node ret[name]['image'] = nodes[node]['os'] ret[name]['size'] = nodes[node]['VPSPLANID'] ret[name]['state'] = nodes[node]['status'] ret[name]['private_ips'] = nodes[node]['internal_ip'] ret[name]['public_ips'] = nodes[node]['main_ip'] return ret def list_nodes_select(conn=None, call=None): ''' Return a list of the VMs that are on the provider, with select fields ''' return __utils__['cloud.list_nodes_select']( list_nodes_full(), __opts__['query.selection'], call, ) def destroy(name): ''' Remove a node from Vultr ''' node = show_instance(name, call='action') params = {'SUBID': node['SUBID']} result = _query('server/destroy', method='POST', decode=False, data=_urlencode(params)) # The return of a destroy call is empty in the case of a success. # Errors are only indicated via HTTP status code. Status code 200 # effetively therefore means "success". if result.get('body') == '' and result.get('text') == '': return True return result def stop(*args, **kwargs): ''' Execute a "stop" action on a VM ''' return _query('server/halt') def start(*args, **kwargs): ''' Execute a "start" action on a VM ''' return _query('server/start') def show_instance(name, call=None): ''' Show the details from the provider concerning an instance ''' if call != 'action': raise SaltCloudSystemExit( 'The show_instance action must be called with -a or --action.' ) nodes = list_nodes_full() # Find under which cloud service the name is listed, if any if name not in nodes: return {} __utils__['cloud.cache_node'](nodes[name], __active_provider_name__, __opts__) return nodes[name] def _lookup_vultrid(which_key, availkey, keyname): ''' Helper function to retrieve a Vultr ID ''' if DETAILS == {}: _cache_provider_details() which_key = six.text_type(which_key) try: return DETAILS[availkey][which_key][keyname] except KeyError: return False def create(vm_): ''' Create a single VM from a data dict ''' if 'driver' not in vm_: vm_['driver'] = vm_['provider'] private_networking = config.get_cloud_config_value( 'enable_private_network', vm_, __opts__, search_global=False, default=False, ) startup_script = config.get_cloud_config_value( 'startup_script_id', vm_, __opts__, search_global=False, default=None, ) if startup_script and str(startup_script) not in avail_scripts(): log.error('Your Vultr account does not have a startup script with ID %s', str(startup_script)) return False if private_networking is not None: if not isinstance(private_networking, bool): raise SaltCloudConfigError("'private_networking' should be a boolean value.") if private_networking is True: enable_private_network = 'yes' else: enable_private_network = 'no' __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'] ) osid = _lookup_vultrid(vm_['image'], 'avail_images', 'OSID') if not osid: log.error('Vultr does not have an image with id or name %s', vm_['image']) return False vpsplanid = _lookup_vultrid(vm_['size'], 'avail_sizes', 'VPSPLANID') if not vpsplanid: log.error('Vultr does not have a size with id or name %s', vm_['size']) return False dcid = _lookup_vultrid(vm_['location'], 'avail_locations', 'DCID') if not dcid: log.error('Vultr does not have a location with id or name %s', vm_['location']) return False kwargs = { 'label': vm_['name'], 'OSID': osid, 'VPSPLANID': vpsplanid, 'DCID': dcid, 'hostname': vm_['name'], 'enable_private_network': enable_private_network, } if startup_script: kwargs['SCRIPTID'] = startup_script log.info('Creating Cloud VM %s', vm_['name']) __utils__['cloud.fire_event']( 'event', 'requesting instance', 'salt/cloud/{0}/requesting'.format(vm_['name']), args={ 'kwargs': __utils__['cloud.filter_event']('requesting', kwargs, list(kwargs)), }, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'], ) try: data = _query('server/create', method='POST', data=_urlencode(kwargs)) if int(data.get('status', '200')) >= 300: log.error( 'Error creating %s on Vultr\n\n' 'Vultr API returned %s\n', vm_['name'], data ) log.error('Status 412 may mean that you are requesting an\n' 'invalid location, image, or size.') __utils__['cloud.fire_event']( 'event', 'instance request failed', 'salt/cloud/{0}/requesting/failed'.format(vm_['name']), args={'kwargs': kwargs}, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'], ) return False except Exception as exc: log.error( 'Error creating %s on Vultr\n\n' 'The following exception was thrown when trying to ' 'run the initial deployment:\n%s', vm_['name'], exc, # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG ) __utils__['cloud.fire_event']( 'event', 'instance request failed', 'salt/cloud/{0}/requesting/failed'.format(vm_['name']), args={'kwargs': kwargs}, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'], ) return False def wait_for_hostname(): ''' Wait for the IP address to become available ''' data = show_instance(vm_['name'], call='action') main_ip = six.text_type(data.get('main_ip', '0')) if main_ip.startswith('0'): time.sleep(3) return False return data['main_ip'] def wait_for_default_password(): ''' Wait for the IP address to become available ''' data = show_instance(vm_['name'], call='action') # print("Waiting for default password") # pprint.pprint(data) if six.text_type(data.get('default_password', '')) == '': time.sleep(1) return False return data['default_password'] def wait_for_status(): ''' Wait for the IP address to become available ''' data = show_instance(vm_['name'], call='action') # print("Waiting for status normal") # pprint.pprint(data) if six.text_type(data.get('status', '')) != 'active': time.sleep(1) return False return data['default_password'] def wait_for_server_state(): ''' Wait for the IP address to become available ''' data = show_instance(vm_['name'], call='action') # print("Waiting for server state ok") # pprint.pprint(data) if six.text_type(data.get('server_state', '')) != 'ok': time.sleep(1) return False return data['default_password'] vm_['ssh_host'] = __utils__['cloud.wait_for_fun']( wait_for_hostname, timeout=config.get_cloud_config_value( 'wait_for_fun_timeout', vm_, __opts__, default=15 * 60), ) vm_['password'] = __utils__['cloud.wait_for_fun']( wait_for_default_password, timeout=config.get_cloud_config_value( 'wait_for_fun_timeout', vm_, __opts__, default=15 * 60), ) __utils__['cloud.wait_for_fun']( wait_for_status, timeout=config.get_cloud_config_value( 'wait_for_fun_timeout', vm_, __opts__, default=15 * 60), ) __utils__['cloud.wait_for_fun']( wait_for_server_state, timeout=config.get_cloud_config_value( 'wait_for_fun_timeout', vm_, __opts__, default=15 * 60), ) __opts__['hard_timeout'] = config.get_cloud_config_value( 'hard_timeout', get_configured_provider(), __opts__, search_global=False, default=None, ) # Bootstrap ret = __utils__['cloud.bootstrap'](vm_, __opts__) ret.update(show_instance(vm_['name'], call='action')) 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 _query(path, method='GET', data=None, params=None, header_dict=None, decode=True): ''' Perform a query directly against the Vultr REST API ''' api_key = config.get_cloud_config_value( 'api_key', get_configured_provider(), __opts__, search_global=False, ) management_host = config.get_cloud_config_value( 'management_host', get_configured_provider(), __opts__, search_global=False, default='api.vultr.com' ) url = 'https://{management_host}/v1/{path}?api_key={api_key}'.format( management_host=management_host, path=path, api_key=api_key, ) if header_dict is None: header_dict = {} result = __utils__['http.query']( url, method=method, params=params, data=data, header_dict=header_dict, port=443, text=True, decode=decode, decode_type='json', hide_fields=['api_key'], opts=__opts__, ) if 'dict' in result: return result['dict'] return result