def _do_http(opts, profile='default'): ''' Make the http request and return the data ''' ret = {} url = __salt__['config.get']('modjk:{0}:url'.format(profile), '') user = __salt__['config.get']('modjk:{0}:user'.format(profile), '') passwd = __salt__['config.get']('modjk:{0}:pass'.format(profile), '') realm = __salt__['config.get']('modjk:{0}:realm'.format(profile), '') timeout = __salt__['config.get']('modjk:{0}:timeout'.format(profile), '') if not url: raise Exception('missing url in profile {0}'.format(profile)) if user and passwd: auth = _auth(url=url, realm=realm, user=user, passwd=passwd) _install_opener(auth) url += '?{0}'.format(_urlencode(opts)) for line in _urlopen(url, timeout=timeout).read().splitlines(): splt = line.split('=', 1) if splt[0] in ret: ret[splt[0]] += ',{0}'.format(splt[1]) else: ret[splt[0]] = splt[1] return ret
def _query(url, username=None, api_key=None, auth=False, args=None, header_dict=None, method='GET', ): ''' Statuscake object method function to construct and execute on the API URL. :param username: The Statuscake username. :param api_key: The Statuscake api key. :param function: The Statuscake api function to perform. :param method: The HTTP method, e.g. GET or POST. :param data: The data to be sent for POST method. :return: The json response from the API call or False. ''' if auth: test = _check_api_key(api_key) if not test['res']: return test api_key = test['data'] test = _check_api_username(username) if not test['res']: return test username = test['data'] if header_dict is None: header_dict = {} if method in ['POST', 'PUT']: header_dict['Content-Type'] = 'application/x-www-form-urlencoded' if auth: if 'API' not in header_dict: header_dict['API'] = api_key if 'Username' not in header_dict: header_dict['Username'] = username if args: args = _urlencode(args) result = salt.utils.http.query( url, method, data=args, decode=True, status=True, header_dict=header_dict, opts=__opts__, ) if method == 'GET': return _handle_get_result(result) else: return _handle_generic_result(result)
def post_message(channel, message, from_name, api_key=None): """ Send a message to a Slack channel. :param channel: The channel name, either will work. :param message: The message to send to the Slack channel. :param from_name: Specify who the message is from. :param api_key: The Slack api key, if not specified in the configuration. :return: Boolean if message was sent successfully. CLI Example: .. code-block:: bash salt '*' slack.post_message channel="Development Room" message="Build is done" from_name="Build Server" """ if not api_key: api_key = _get_api_key() if not channel: log.error("channel is a required option.") # channel must start with a hash if not channel.startswith("#"): channel = "#{0}".format(channel) if not from_name: log.error("from_name is a required option.") if not message: log.error("message is a required option.") if not from_name: log.error("from_name is a required option.") parameters = {"channel": channel, "username": from_name, "text": message} # Slack wants the body on POST to be urlencoded. result = salt.utils.slack.query( function="message", api_key=api_key, method="POST", header_dict={"Content-Type": "application/x-www-form-urlencoded"}, data=_urlencode(parameters), opts=__opts__, ) if result["res"]: return True else: return result
def _post_message(user, device, message, title, priority, expire, retry, sound, api_version=1, token=None): ''' Send a message to a Pushover user or group. :param user: The user or group to send to, must be key of user or group not email address. :param message: The message to send to the PushOver user or group. :param title: Specify who the message is from. :param priority The priority of the message, defaults to 0. :param api_version: The PushOver API version, if not specified in the configuration. :param notify: Whether to notify the room, default: False. :param token: The PushOver token, if not specified in the configuration. :return: Boolean if message was sent successfully. ''' user_validate = salt.utils.pushover.validate_user(user, device, token) if not user_validate['result']: return user_validate parameters = dict() parameters['user'] = user parameters['device'] = device parameters['token'] = token parameters['title'] = title parameters['priority'] = priority parameters['expire'] = expire parameters['retry'] = retry parameters['message'] = message if sound: sound_validate = salt.utils.pushover.validate_sound(sound, token) if sound_validate['res']: parameters['sound'] = sound result = salt.utils.pushover.query(function='message', method='POST', header_dict={'Content-Type': 'application/x-www-form-urlencoded'}, data=_urlencode(parameters), opts=__opts__) return result
def validate_user(user, device, token): ''' Send a message to a Pushover user or group. :param user: The user or group name, either will work. :param device: The device for the user. :param token: The PushOver token. ''' res = { 'message': 'User key is invalid', 'result': False } parameters = dict() parameters['user'] = user parameters['token'] = token if device: parameters['device'] = device response = query(function='validate_user', method='POST', header_dict={'Content-Type': 'application/x-www-form-urlencoded'}, data=_urlencode(parameters)) if response['res']: if 'message' in response: _message = response.get('message', '') if 'status' in _message: if _message.get('dict', {}).get('status', None) == 1: res['result'] = True res['message'] = 'User key is valid.' else: res['result'] = False res['message'] = ''.join(_message.get('dict', {}).get('errors')) return res
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 ssh_keys = config.get_cloud_config_value( 'ssh_key_names', get_configured_provider(), __opts__, search_global=False, ) key_filename = config.get_cloud_config_value( 'ssh_key_file', vm_, __opts__, search_global=False, default=None ) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( 'The defined key_filename \'{0}\' does not exist'.format( key_filename ) ) log.debug("Using SSH Key file: '%s'", key_filename) log.debug("Selected server SSH keys to install: '%s'", ssh_keys) kwargs = { "label": vm_["name"], "OSID": osid, "VPSPLANID": vpsplanid, "DCID": dcid, "hostname": vm_["name"], "enable_private_network": enable_private_network, } if ssh_keys: keyids, available_keys = get_keyids_for_keys_names(ssh_keys) if not keyids: # None of the requested key names were found. raise SaltCloudConfigError( 'The specified ssh key names were not found in the vultr key list. Desired key names: \'{0}\'. Available keys: \'{1}\''.format( ssh_keys, available_keys ) ) kwargs['SSHKEYID'] = keyids 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: # pylint: disable=broad-except 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 password 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"] # Maximum delay if we don't see the server # reach the 'ok' state. install_timeout = 60 * 5 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) # So vultr somehow sits in the 'installingbooting' state for many, MANY minutes after # the install has actually finished. I think it might be just a timer somewhere. if six.text_type(data.get('server_state', '')) == 'ok': return data['default_password'] if six.text_type(data.get('server_state', '')) == 'installingbooting': nonlocal install_timeout install_timeout -= 1 log.debug('Install delay: %s seconds remaining', install_timeout) if install_timeout == 0: return data['default_password'] time.sleep(1) return False 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 ), ) __utils__['cloud.wait_for_fun']( wait_for_status, 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_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, ) log.debug("VM Created. Host: '%s'", vm_['ssh_host']) log.debug("Password: '******'", vm_['password']) if key_filename: vm_['key_filename'] = key_filename # 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 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 call_hook(message, attachment=None, color='good', short=False, identifier=None, channel=None, username=None, icon_emoji=None): ''' Send message to Slack incoming webhook. :param message: The topic of message. :param attachment: The message to send to the Slacke WebHook. :param color: The color of border of left side :param short: An optional flag indicating whether the value is short enough to be displayed side-by-side with other values. :param identifier: The identifier of WebHook. :param channel: The channel to use instead of the WebHook default. :param username: Username to use instead of WebHook default. :param icon_emoji: Icon to use instead of WebHook default. :return: Boolean if message was sent successfully. CLI Example: .. code-block:: bash salt '*' slack.call_hook message='Hello, from SaltStack' ''' base_url = 'https://hooks.slack.com/services/' if not identifier: identifier = _get_hook_id() url = _urljoin(base_url, identifier) if not message: log.error('message is required option') if attachment: payload = { 'attachments': [{ 'fallback': message, 'color': color, 'pretext': message, 'fields': [{ "value": attachment, "short": short, }] }] } else: payload = { 'text': message, } if channel: payload['channel'] = channel if username: payload['username'] = username if icon_emoji: payload['icon_emoji'] = icon_emoji data = _urlencode({'payload': json.dumps(payload, ensure_ascii=False)}) result = salt.utils.http.query(url, method='POST', data=data, status=True) if result['status'] <= 201: return True else: return {'res': False, 'message': result.get('body', result['status'])}
def _query(function, api_key=None, api_version=None, room_id=None, method="GET", data=None): """ HipChat object method function to construct and execute on the API URL. :param api_key: The HipChat api key. :param function: The HipChat api function to perform. :param api_version: The HipChat api version (v1 or v2). :param method: The HTTP method, e.g. GET or POST. :param data: The data to be sent for POST method. :return: The json response from the API call or False. """ headers = {} query_params = {} if not api_key or not api_version: try: options = __salt__["config.option"]("hipchat") if not api_key: api_key = options.get("api_key") if not api_version: api_version = options.get("api_version") except (NameError, KeyError, AttributeError): log.error("No HipChat api key or version found.") return False if room_id: room_id = "room/{0}/notification".format(str(room_id)) else: room_id = "room/0/notification" hipchat_functions = { "v1": { "rooms": {"request": "rooms/list", "response": "rooms"}, "users": {"request": "users/list", "response": "users"}, "message": {"request": "rooms/message", "response": "status"}, }, "v2": { "rooms": {"request": "room", "response": "items"}, "users": {"request": "user", "response": "items"}, "message": {"request": room_id, "response": None}, }, } api_url = "https://api.hipchat.com" base_url = _urljoin(api_url, api_version + "/") path = hipchat_functions.get(api_version).get(function).get("request") url = _urljoin(base_url, path, False) if api_version == "v1": query_params["format"] = "json" query_params["auth_token"] = api_key if method == "POST": headers["Content-Type"] = "application/x-www-form-urlencoded" if data: if data.get("notify", None): data["notify"] = 1 data = _urlencode(data) elif api_version == "v2": headers["Authorization"] = "Bearer {0}".format(api_key) if data: data = json.dumps(data) if method == "POST": headers["Content-Type"] = "application/json" else: log.error("Unsupported HipChat API version") return False result = salt.utils.http.query( url, method, params=query_params, data=data, decode=True, status=True, header_dict=headers, opts=__opts__ ) if result.get("status", None) == salt.ext.six.moves.http_client.OK: response = hipchat_functions.get(api_version).get(function).get("response") return result.get("dict", {}).get(response, None) elif result.get("status", None) == salt.ext.six.moves.http_client.NO_CONTENT: return False else: log.debug(url) log.debug(query_params) log.debug(data) log.debug(result) if result.get("error"): log.error(result) return False
def post_message(user=None, device=None, message=None, title=None, priority=None, expire=None, retry=None, sound=None, api_version=1, token=None): ''' Send a message to a Pushover user or group. :param user: The user or group to send to, must be key of user or group not email address. :param message: The message to send to the PushOver user or group. :param title: Specify who the message is from. :param priority: The priority of the message, defaults to 0. :param expire: The message should expire after N number of seconds. :param retry: The number of times the message should be retried. :param sound: The sound to associate with the message. :param api_version: The PushOver API version, if not specified in the configuration. :param token: The PushOver token, if not specified in the configuration. :return: Boolean if message was sent successfully. CLI Example: .. code-block:: bash salt '*' pushover.post_message user='******' title='Message from Salt' message='Build is done' salt '*' pushover.post_message user='******' title='Message from Salt' message='Build is done' priority='2' expire='720' retry='5' ''' if not token: token = __salt__['config.get']('pushover.token') or \ __salt__['config.get']('pushover:token') if not token: raise SaltInvocationError('Pushover token is unavailable.') if not user: user = __salt__['config.get']('pushover.user') or \ __salt__['config.get']('pushover:user') if not user: raise SaltInvocationError('Pushover user key is unavailable.') if not message: raise SaltInvocationError('Required parameter "message" is missing.') user_validate = salt.utils.pushover.validate_user(user, device, token) if not user_validate['result']: return user_validate if not title: title = 'Message from SaltStack' parameters = dict() parameters['user'] = user parameters['device'] = device parameters['token'] = token parameters['title'] = title parameters['priority'] = priority parameters['expire'] = expire parameters['retry'] = retry parameters['message'] = message if sound and salt.utils.pushover.validate_sound(sound, token)['res']: parameters['sound'] = sound result = salt.utils.pushover.query( function='message', method='POST', header_dict={'Content-Type': 'application/x-www-form-urlencoded'}, data=_urlencode(parameters), opts=__opts__) if result['res']: return True else: return result
def query(url, method='GET', params=None, data=None, data_file=None, header_dict=None, header_list=None, header_file=None, username=None, password=None, auth=None, decode=False, decode_type='auto', status=False, headers=False, text=False, cookies=None, cookie_jar=None, cookie_format='lwp', persist_session=False, session_cookie_jar=None, data_render=False, data_renderer=None, header_render=False, header_renderer=None, template_dict=None, test=False, test_url=None, node='minion', port=80, opts=None, backend=None, ca_bundle=None, verify_ssl=None, cert=None, text_out=None, headers_out=None, decode_out=None, stream=False, streaming_callback=None, header_callback=None, handle=False, agent=USERAGENT, hide_fields=None, raise_error=True, **kwargs): ''' Query a resource, and decode the return data ''' ret = {} if opts is None: if node == 'master': opts = salt.config.master_config( os.path.join(salt.syspaths.CONFIG_DIR, 'master') ) elif node == 'minion': opts = salt.config.minion_config( os.path.join(salt.syspaths.CONFIG_DIR, 'minion') ) else: opts = {} if not backend: backend = opts.get('backend', 'tornado') match = re.match(r'https?://((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)($|/)', url) if not match: salt.utils.network.refresh_dns() if backend == 'requests': if HAS_REQUESTS is False: ret['error'] = ('http.query has been set to use requests, but the ' 'requests library does not seem to be installed') log.error(ret['error']) return ret else: requests_log = logging.getLogger('requests') requests_log.setLevel(logging.WARNING) # Some libraries don't support separation of url and GET parameters # Don't need a try/except block, since Salt depends on tornado url_full = tornado.httputil.url_concat(url, params) if params else url if ca_bundle is None: ca_bundle = get_ca_bundle(opts) if verify_ssl is None: verify_ssl = opts.get('verify_ssl', True) if cert is None: cert = opts.get('cert', None) if data_file is not None: data = _render( data_file, data_render, data_renderer, template_dict, opts ) # Make sure no secret fields show up in logs log_url = sanitize_url(url_full, hide_fields) log.debug('Requesting URL %s using %s method', log_url, method) log.debug("Using backend: %s", backend) if method == 'POST' and log.isEnabledFor(logging.TRACE): # Make sure no secret fields show up in logs if isinstance(data, dict): log_data = data.copy() if isinstance(hide_fields, list): for item in data: for field in hide_fields: if item == field: log_data[item] = 'XXXXXXXXXX' log.trace('Request POST Data: %s', pprint.pformat(log_data)) else: log.trace('Request POST Data: %s', pprint.pformat(data)) if header_file is not None: header_tpl = _render( header_file, header_render, header_renderer, template_dict, opts ) if isinstance(header_tpl, dict): header_dict = header_tpl else: header_list = header_tpl.splitlines() if header_dict is None: header_dict = {} if header_list is None: header_list = [] if cookie_jar is None: cookie_jar = os.path.join(opts.get('cachedir', salt.syspaths.CACHE_DIR), 'cookies.txt') if session_cookie_jar is None: session_cookie_jar = os.path.join(opts.get('cachedir', salt.syspaths.CACHE_DIR), 'cookies.session.p') if persist_session is True and HAS_MSGPACK: # TODO: This is hackish; it will overwrite the session cookie jar with # all cookies from this one connection, rather than behaving like a # proper cookie jar. Unfortunately, since session cookies do not # contain expirations, they can't be stored in a proper cookie jar. if os.path.isfile(session_cookie_jar): with salt.utils.files.fopen(session_cookie_jar, 'rb') as fh_: session_cookies = msgpack.load(fh_) if isinstance(session_cookies, dict): header_dict.update(session_cookies) else: with salt.utils.files.fopen(session_cookie_jar, 'wb') as fh_: msgpack.dump('', fh_) for header in header_list: comps = header.split(':') if len(comps) < 2: continue header_dict[comps[0].strip()] = comps[1].strip() if not auth: if username and password: auth = (username, password) if agent == USERAGENT: agent = '{0} http.query()'.format(agent) header_dict['User-agent'] = agent if backend == 'requests': sess = requests.Session() sess.auth = auth sess.headers.update(header_dict) log.trace('Request Headers: %s', sess.headers) sess_cookies = sess.cookies sess.verify = verify_ssl elif backend == 'urllib2': sess_cookies = None else: # Tornado sess_cookies = None if cookies is not None: if cookie_format == 'mozilla': sess_cookies = salt.ext.six.moves.http_cookiejar.MozillaCookieJar(cookie_jar) else: sess_cookies = salt.ext.six.moves.http_cookiejar.LWPCookieJar(cookie_jar) if not os.path.isfile(cookie_jar): sess_cookies.save() sess_cookies.load() if test is True: if test_url is None: return {} else: url = test_url ret['test'] = True if backend == 'requests': req_kwargs = {} if stream is True: if requests.__version__[0] == '0': # 'stream' was called 'prefetch' before 1.0, with flipped meaning req_kwargs['prefetch'] = False else: req_kwargs['stream'] = True # Client-side cert handling if cert is not None: if isinstance(cert, six.string_types): if os.path.exists(cert): req_kwargs['cert'] = cert elif isinstance(cert, list): if os.path.exists(cert[0]) and os.path.exists(cert[1]): req_kwargs['cert'] = cert else: log.error('The client-side certificate path that' ' was passed is not valid: %s', cert) result = sess.request( method, url, params=params, data=data, **req_kwargs ) result.raise_for_status() if stream is True: # fake a HTTP response header header_callback('HTTP/1.0 {0} MESSAGE'.format(result.status_code)) # fake streaming the content streaming_callback(result.content) return { 'handle': result, } if handle is True: return { 'handle': result, 'body': result.content, } log.debug('Final URL location of Response: %s', sanitize_url(result.url, hide_fields)) result_status_code = result.status_code result_headers = result.headers result_text = result.content result_cookies = result.cookies body = result.content if not isinstance(body, six.text_type): body = body.decode(result.encoding or 'utf-8') ret['body'] = body elif backend == 'urllib2': request = urllib_request.Request(url_full, data) handlers = [ urllib_request.HTTPHandler, urllib_request.HTTPCookieProcessor(sess_cookies) ] if url.startswith('https'): hostname = request.get_host() handlers[0] = urllib_request.HTTPSHandler(1) if not HAS_MATCHHOSTNAME: log.warning('match_hostname() not available, SSL hostname checking ' 'not available. THIS CONNECTION MAY NOT BE SECURE!') elif verify_ssl is False: log.warning('SSL certificate verification has been explicitly ' 'disabled. THIS CONNECTION MAY NOT BE SECURE!') else: if ':' in hostname: hostname, port = hostname.split(':') else: port = 443 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((hostname, int(port))) sockwrap = ssl.wrap_socket( sock, ca_certs=ca_bundle, cert_reqs=ssl.CERT_REQUIRED ) try: match_hostname(sockwrap.getpeercert(), hostname) except CertificateError as exc: ret['error'] = ( 'The certificate was invalid. ' 'Error returned was: %s', pprint.pformat(exc) ) return ret # Client-side cert handling if cert is not None: cert_chain = None if isinstance(cert, six.string_types): if os.path.exists(cert): cert_chain = (cert) elif isinstance(cert, list): if os.path.exists(cert[0]) and os.path.exists(cert[1]): cert_chain = cert else: log.error('The client-side certificate path that was ' 'passed is not valid: %s', cert) return if hasattr(ssl, 'SSLContext'): # Python >= 2.7.9 context = ssl.SSLContext.load_cert_chain(*cert_chain) handlers.append(urllib_request.HTTPSHandler(context=context)) # pylint: disable=E1123 else: # Python < 2.7.9 cert_kwargs = { 'host': request.get_host(), 'port': port, 'cert_file': cert_chain[0] } if len(cert_chain) > 1: cert_kwargs['key_file'] = cert_chain[1] handlers[0] = salt.ext.six.moves.http_client.HTTPSConnection(**cert_kwargs) opener = urllib_request.build_opener(*handlers) for header in header_dict: request.add_header(header, header_dict[header]) request.get_method = lambda: method try: result = opener.open(request) except URLError as exc: return {'Error': six.text_type(exc)} if stream is True or handle is True: return { 'handle': result, 'body': result.content, } result_status_code = result.code result_headers = dict(result.info()) result_text = result.read() if 'Content-Type' in result_headers: res_content_type, res_params = cgi.parse_header(result_headers['Content-Type']) if res_content_type.startswith('text/') and \ 'charset' in res_params and \ not isinstance(result_text, six.text_type): result_text = result_text.decode(res_params['charset']) if six.PY3 and isinstance(result_text, bytes): result_text = result_text.decode('utf-8') ret['body'] = result_text else: # Tornado req_kwargs = {} # Client-side cert handling if cert is not None: if isinstance(cert, six.string_types): if os.path.exists(cert): req_kwargs['client_cert'] = cert elif isinstance(cert, list): if os.path.exists(cert[0]) and os.path.exists(cert[1]): req_kwargs['client_cert'] = cert[0] req_kwargs['client_key'] = cert[1] else: log.error('The client-side certificate path that ' 'was passed is not valid: %s', cert) if isinstance(data, dict): data = _urlencode(data) if verify_ssl: req_kwargs['ca_certs'] = ca_bundle max_body = opts.get('http_max_body', salt.config.DEFAULT_MINION_OPTS['http_max_body']) connect_timeout = opts.get('http_connect_timeout', salt.config.DEFAULT_MINION_OPTS['http_connect_timeout']) timeout = opts.get('http_request_timeout', salt.config.DEFAULT_MINION_OPTS['http_request_timeout']) client_argspec = None proxy_host = opts.get('proxy_host', None) if proxy_host: # tornado requires a str for proxy_host, cannot be a unicode str in py2 proxy_host = salt.utils.stringutils.to_str(proxy_host) proxy_port = opts.get('proxy_port', None) proxy_username = opts.get('proxy_username', None) if proxy_username: # tornado requires a str, cannot be unicode str in py2 proxy_username = salt.utils.stringutils.to_str(proxy_username) proxy_password = opts.get('proxy_password', None) if proxy_password: # tornado requires a str, cannot be unicode str in py2 proxy_password = salt.utils.stringutils.to_str(proxy_password) no_proxy = opts.get('no_proxy', []) # Since tornado doesnt support no_proxy, we'll always hand it empty proxies or valid ones # except we remove the valid ones if a url has a no_proxy hostname in it if urlparse(url_full).hostname in no_proxy: proxy_host = None proxy_port = None # We want to use curl_http if we have a proxy defined if proxy_host and proxy_port: if HAS_CURL_HTTPCLIENT is False: ret['error'] = ('proxy_host and proxy_port has been set. This requires pycurl and tornado, ' 'but the libraries does not seem to be installed') log.error(ret['error']) return ret tornado.httpclient.AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient') client_argspec = salt.utils.args.get_function_argspec( tornado.curl_httpclient.CurlAsyncHTTPClient.initialize) else: client_argspec = salt.utils.args.get_function_argspec( tornado.simple_httpclient.SimpleAsyncHTTPClient.initialize) supports_max_body_size = 'max_body_size' in client_argspec.args req_kwargs.update({ 'method': method, 'headers': header_dict, 'auth_username': username, 'auth_password': password, 'body': data, 'validate_cert': verify_ssl, 'allow_nonstandard_methods': True, 'streaming_callback': streaming_callback, 'header_callback': header_callback, 'connect_timeout': connect_timeout, 'request_timeout': timeout, 'proxy_host': proxy_host, 'proxy_port': proxy_port, 'proxy_username': proxy_username, 'proxy_password': proxy_password, 'raise_error': raise_error, 'decompress_response': False, }) # Unicode types will cause a TypeError when Tornado's curl HTTPClient # invokes setopt. Therefore, make sure all arguments we pass which # contain strings are str types. req_kwargs = salt.utils.data.decode(req_kwargs, to_str=True) try: download_client = HTTPClient(max_body_size=max_body) \ if supports_max_body_size \ else HTTPClient() result = download_client.fetch(url_full, **req_kwargs) except tornado.httpclient.HTTPError as exc: ret['status'] = exc.code ret['error'] = six.text_type(exc) return ret except socket.gaierror as exc: if status is True: ret['status'] = 0 ret['error'] = six.text_type(exc) return ret if stream is True or handle is True: return { 'handle': result, 'body': result.body, } result_status_code = result.code result_headers = result.headers result_text = result.body if 'Content-Type' in result_headers: res_content_type, res_params = cgi.parse_header(result_headers['Content-Type']) if res_content_type.startswith('text/') and \ 'charset' in res_params and \ not isinstance(result_text, six.text_type): result_text = result_text.decode(res_params['charset']) if six.PY3 and isinstance(result_text, bytes): result_text = result_text.decode('utf-8') ret['body'] = result_text if 'Set-Cookie' in result_headers and cookies is not None: result_cookies = parse_cookie_header(result_headers['Set-Cookie']) for item in result_cookies: sess_cookies.set_cookie(item) else: result_cookies = None if isinstance(result_headers, list): result_headers_dict = {} for header in result_headers: comps = header.split(':') result_headers_dict[comps[0].strip()] = ':'.join(comps[1:]).strip() result_headers = result_headers_dict log.debug('Response Status Code: %s', result_status_code) log.trace('Response Headers: %s', result_headers) log.trace('Response Cookies: %s', sess_cookies) # log.trace("Content: %s", result_text) coding = result_headers.get('Content-Encoding', "identity") # Requests will always decompress the content, and working around that is annoying. if backend != 'requests': result_text = __decompressContent(coding, result_text) try: log.trace('Response Text: %s', result_text) except UnicodeEncodeError as exc: log.trace('Cannot Trace Log Response Text: %s. This may be due to ' 'incompatibilities between requests and logging.', exc) if text_out is not None: with salt.utils.files.fopen(text_out, 'w') as tof: tof.write(result_text) if headers_out is not None and os.path.exists(headers_out): with salt.utils.files.fopen(headers_out, 'w') as hof: hof.write(result_headers) if cookies is not None: sess_cookies.save() if persist_session is True and HAS_MSGPACK: # TODO: See persist_session above if 'set-cookie' in result_headers: with salt.utils.files.fopen(session_cookie_jar, 'wb') as fh_: session_cookies = result_headers.get('set-cookie', None) if session_cookies is not None: msgpack.dump({'Cookie': session_cookies}, fh_) else: msgpack.dump('', fh_) if status is True: ret['status'] = result_status_code if headers is True: ret['headers'] = result_headers if decode is True: if decode_type == 'auto': content_type = result_headers.get( 'content-type', 'application/json' ) if 'xml' in content_type: decode_type = 'xml' elif 'json' in content_type: decode_type = 'json' elif 'yaml' in content_type: decode_type = 'yaml' else: decode_type = 'plain' valid_decodes = ('json', 'xml', 'yaml', 'plain') if decode_type not in valid_decodes: ret['error'] = ( 'Invalid decode_type specified. ' 'Valid decode types are: {0}'.format( pprint.pformat(valid_decodes) ) ) log.error(ret['error']) return ret if decode_type == 'json': ret['dict'] = salt.utils.json.loads(result_text) elif decode_type == 'xml': ret['dict'] = [] items = ET.fromstring(result_text) for item in items: ret['dict'].append(xml.to_dict(item)) elif decode_type == 'yaml': ret['dict'] = salt.utils.data.decode(salt.utils.yaml.safe_load(result_text)) else: text = True if decode_out: with salt.utils.files.fopen(decode_out, 'w') as dof: dof.write(result_text) if text is True: ret['text'] = result_text return ret
def post_message(user=None, device=None, message=None, title=None, priority=None, expire=None, retry=None, sound=None, api_version=1, token=None): ''' Send a message to a Pushover user or group. :param user: The user or group to send to, must be key of user or group not email address. :param message: The message to send to the PushOver user or group. :param title: Specify who the message is from. :param priority: The priority of the message, defaults to 0. :param expire: The message should expire after N number of seconds. :param retry: The number of times the message should be retried. :param sound: The sound to associate with the message. :param api_version: The PushOver API version, if not specified in the configuration. :param token: The PushOver token, if not specified in the configuration. :return: Boolean if message was sent successfully. CLI Example: .. code-block:: bash salt '*' pushover.post_message user='******' title='Message from Salt' message='Build is done' salt '*' pushover.post_message user='******' title='Message from Salt' message='Build is done' priority='2' expire='720' retry='5' ''' if not token: token = __salt__['config.get']('pushover.token') or \ __salt__['config.get']('pushover:token') if not token: raise SaltInvocationError('Pushover token is unavailable.') if not user: user = __salt__['config.get']('pushover.user') or \ __salt__['config.get']('pushover:user') if not user: raise SaltInvocationError('Pushover user key is unavailable.') if not message: raise SaltInvocationError('Required parameter "message" is missing.') user_validate = salt.utils.pushover.validate_user(user, device, token) if not user_validate['result']: return user_validate if not title: title = 'Message from SaltStack' parameters = dict() parameters['user'] = user parameters['device'] = device parameters['token'] = token parameters['title'] = title parameters['priority'] = priority parameters['expire'] = expire parameters['retry'] = retry parameters['message'] = message if sound and salt.utils.pushover.validate_sound(sound, token)['res']: parameters['sound'] = sound result = salt.utils.pushover.query(function='message', method='POST', header_dict={'Content-Type': 'application/x-www-form-urlencoded'}, data=_urlencode(parameters), opts=__opts__) if result['res']: return True else: return result
def post_message(channel, message, from_name, api_key=None, icon=None): """ Send a message to a Slack channel. :param channel: The channel name, either will work. :param message: The message to send to the Slack channel. :param from_name: Specify who the message is from. :param api_key: The Slack api key, if not specified in the configuration. :param icon: URL to an image to use as the icon for this message :return: Boolean if message was sent successfully. CLI Example: .. code-block:: bash salt '*' slack.post_message channel="Development Room" message="Build is done" from_name="Build Server" """ if not api_key: api_key = _get_api_key() if not channel: log.error("channel is a required option.") # channel must start with a hash or an @ (direct-message channels) if not channel.startswith("#") and not channel.startswith("@"): log.warning( "Channel name must start with a hash or @. " 'Prepending a hash and using "#%s" as ' "channel name instead of %s", channel, channel, ) channel = "#{0}".format(channel) if not from_name: log.error("from_name is a required option.") if not message: log.error("message is a required option.") if not from_name: log.error("from_name is a required option.") parameters = {"channel": channel, "username": from_name, "text": message} if icon is not None: parameters["icon_url"] = icon # Slack wants the body on POST to be urlencoded. result = salt.utils.slack.query( function="message", api_key=api_key, method="POST", header_dict={"Content-Type": "application/x-www-form-urlencoded"}, data=_urlencode(parameters), opts=__opts__, ) if result["res"]: return True else: return result
def call_hook( message, attachment=None, color="good", short=False, identifier=None, channel=None, username=None, icon_emoji=None, ): """ Send message to Slack incoming webhook. :param message: The topic of message. :param attachment: The message to send to the Slacke WebHook. :param color: The color of border of left side :param short: An optional flag indicating whether the value is short enough to be displayed side-by-side with other values. :param identifier: The identifier of WebHook. :param channel: The channel to use instead of the WebHook default. :param username: Username to use instead of WebHook default. :param icon_emoji: Icon to use instead of WebHook default. :return: Boolean if message was sent successfully. CLI Example: .. code-block:: bash salt '*' slack.call_hook message='Hello, from SaltStack' """ base_url = "https://hooks.slack.com/services/" if not identifier: identifier = _get_hook_id() url = _urljoin(base_url, identifier) if not message: log.error("message is required option") if attachment: payload = { "attachments": [ { "fallback": message, "color": color, "pretext": message, "fields": [{"value": attachment, "short": short}], } ] } else: payload = { "text": message, } if channel: payload["channel"] = channel if username: payload["username"] = username if icon_emoji: payload["icon_emoji"] = icon_emoji data = _urlencode({"payload": salt.utils.json.dumps(payload)}) result = salt.utils.http.query(url, method="POST", data=data, status=True) if result["status"] <= 201: return True else: return {"res": False, "message": result.get("body", result["status"])}
def query(url, method="GET", params=None, data=None, data_file=None, header_dict=None, header_list=None, header_file=None, username=None, password=None, auth=None, decode=False, decode_type="auto", status=False, headers=False, text=False, cookies=None, cookie_jar=None, cookie_format="lwp", persist_session=False, session_cookie_jar=None, data_render=False, data_renderer=None, header_render=False, header_renderer=None, template_dict=None, test=False, test_url=None, node="minion", port=80, opts=None, backend=None, ca_bundle=None, verify_ssl=None, cert=None, text_out=None, headers_out=None, decode_out=None, stream=False, streaming_callback=None, header_callback=None, handle=False, agent=USERAGENT, hide_fields=None, raise_error=True, formdata=False, formdata_fieldname=None, formdata_filename=None, decode_body=True, **kwargs): """ Query a resource, and decode the return data """ ret = {} if opts is None: if node == "master": opts = salt.config.master_config( os.path.join(salt.syspaths.CONFIG_DIR, "master")) elif node == "minion": opts = salt.config.minion_config( os.path.join(salt.syspaths.CONFIG_DIR, "minion")) else: opts = {} if not backend: backend = opts.get("backend", "tornado") match = re.match( r"https?://((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)($|/)", url, ) if not match: salt.utils.network.refresh_dns() if backend == "requests": if HAS_REQUESTS is False: ret["error"] = ("http.query has been set to use requests, but the " "requests library does not seem to be installed") log.error(ret["error"]) return ret else: requests_log = logging.getLogger("requests") requests_log.setLevel(logging.WARNING) # Some libraries don't support separation of url and GET parameters # Don't need a try/except block, since Salt depends on tornado url_full = salt.ext.tornado.httputil.url_concat(url, params) if params else url if ca_bundle is None: ca_bundle = get_ca_bundle(opts) if verify_ssl is None: verify_ssl = opts.get("verify_ssl", True) if cert is None: cert = opts.get("cert", None) if data_file is not None: data = _render(data_file, data_render, data_renderer, template_dict, opts) # Make sure no secret fields show up in logs log_url = sanitize_url(url_full, hide_fields) log.debug("Requesting URL %s using %s method", log_url, method) log.debug("Using backend: %s", backend) if method == "POST" and log.isEnabledFor(logging.TRACE): # Make sure no secret fields show up in logs if isinstance(data, dict): log_data = data.copy() if isinstance(hide_fields, list): for item in data: for field in hide_fields: if item == field: log_data[item] = "XXXXXXXXXX" log.trace("Request POST Data: %s", pprint.pformat(log_data)) else: log.trace("Request POST Data: %s", pprint.pformat(data)) if header_file is not None: header_tpl = _render(header_file, header_render, header_renderer, template_dict, opts) if isinstance(header_tpl, dict): header_dict = header_tpl else: header_list = header_tpl.splitlines() if header_dict is None: header_dict = {} if header_list is None: header_list = [] if cookie_jar is None: cookie_jar = os.path.join( opts.get("cachedir", salt.syspaths.CACHE_DIR), "cookies.txt") if session_cookie_jar is None: session_cookie_jar = os.path.join( opts.get("cachedir", salt.syspaths.CACHE_DIR), "cookies.session.p") if persist_session is True and salt.utils.msgpack.HAS_MSGPACK: # TODO: This is hackish; it will overwrite the session cookie jar with # all cookies from this one connection, rather than behaving like a # proper cookie jar. Unfortunately, since session cookies do not # contain expirations, they can't be stored in a proper cookie jar. if os.path.isfile(session_cookie_jar): with salt.utils.files.fopen(session_cookie_jar, "rb") as fh_: session_cookies = salt.utils.msgpack.load(fh_) if isinstance(session_cookies, dict): header_dict.update(session_cookies) else: with salt.utils.files.fopen(session_cookie_jar, "wb") as fh_: salt.utils.msgpack.dump("", fh_) for header in header_list: comps = header.split(":") if len(comps) < 2: continue header_dict[comps[0].strip()] = comps[1].strip() if not auth: if username and password: auth = (username, password) if agent == USERAGENT: agent = "{0} http.query()".format(agent) header_dict["User-agent"] = agent if backend == "requests": sess = requests.Session() sess.auth = auth sess.headers.update(header_dict) log.trace("Request Headers: %s", sess.headers) sess_cookies = sess.cookies sess.verify = verify_ssl elif backend == "urllib2": sess_cookies = None else: # Tornado sess_cookies = None if cookies is not None: if cookie_format == "mozilla": sess_cookies = salt.ext.six.moves.http_cookiejar.MozillaCookieJar( cookie_jar) else: sess_cookies = salt.ext.six.moves.http_cookiejar.LWPCookieJar( cookie_jar) if not os.path.isfile(cookie_jar): sess_cookies.save() sess_cookies.load() if test is True: if test_url is None: return {} else: url = test_url ret["test"] = True if backend == "requests": req_kwargs = {} if stream is True: if requests.__version__[0] == "0": # 'stream' was called 'prefetch' before 1.0, with flipped meaning req_kwargs["prefetch"] = False else: req_kwargs["stream"] = True # Client-side cert handling if cert is not None: if isinstance(cert, six.string_types): if os.path.exists(cert): req_kwargs["cert"] = cert elif isinstance(cert, list): if os.path.exists(cert[0]) and os.path.exists(cert[1]): req_kwargs["cert"] = cert else: log.error( "The client-side certificate path that" " was passed is not valid: %s", cert, ) if formdata: if not formdata_fieldname: ret["error"] = "formdata_fieldname is required when formdata=True" log.error(ret["error"]) return ret result = sess.request(method, url, params=params, files={ formdata_fieldname: (formdata_filename, StringIO(data)) }, **req_kwargs) else: result = sess.request(method, url, params=params, data=data, **req_kwargs) result.raise_for_status() if stream is True: # fake a HTTP response header header_callback("HTTP/1.0 {0} MESSAGE".format(result.status_code)) # fake streaming the content streaming_callback(result.content) return { "handle": result, } if handle is True: return { "handle": result, "body": result.content, } log.debug("Final URL location of Response: %s", sanitize_url(result.url, hide_fields)) result_status_code = result.status_code result_headers = result.headers result_text = result.content result_cookies = result.cookies body = result.content if not isinstance(body, six.text_type) and decode_body: body = body.decode(result.encoding or "utf-8") ret["body"] = body elif backend == "urllib2": request = urllib_request.Request(url_full, data) handlers = [ urllib_request.HTTPHandler, urllib_request.HTTPCookieProcessor(sess_cookies), ] if url.startswith("https"): hostname = request.get_host() handlers[0] = urllib_request.HTTPSHandler(1) if not HAS_MATCHHOSTNAME: log.warning( "match_hostname() not available, SSL hostname checking " "not available. THIS CONNECTION MAY NOT BE SECURE!") elif verify_ssl is False: log.warning("SSL certificate verification has been explicitly " "disabled. THIS CONNECTION MAY NOT BE SECURE!") else: if ":" in hostname: hostname, port = hostname.split(":") else: port = 443 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((hostname, int(port))) sockwrap = ssl.wrap_socket(sock, ca_certs=ca_bundle, cert_reqs=ssl.CERT_REQUIRED) try: match_hostname(sockwrap.getpeercert(), hostname) except CertificateError as exc: ret["error"] = ( "The certificate was invalid. Error returned was: %s", pprint.pformat(exc), ) return ret # Client-side cert handling if cert is not None: cert_chain = None if isinstance(cert, six.string_types): if os.path.exists(cert): cert_chain = cert elif isinstance(cert, list): if os.path.exists(cert[0]) and os.path.exists(cert[1]): cert_chain = cert else: log.error( "The client-side certificate path that was " "passed is not valid: %s", cert, ) return if hasattr(ssl, "SSLContext"): # Python >= 2.7.9 context = ssl.SSLContext.load_cert_chain(*cert_chain) handlers.append( urllib_request.HTTPSHandler(context=context)) # pylint: disable=E1123 else: # Python < 2.7.9 cert_kwargs = { "host": request.get_host(), "port": port, "cert_file": cert_chain[0], } if len(cert_chain) > 1: cert_kwargs["key_file"] = cert_chain[1] handlers[ 0] = salt.ext.six.moves.http_client.HTTPSConnection( **cert_kwargs) opener = urllib_request.build_opener(*handlers) for header in header_dict: request.add_header(header, header_dict[header]) request.get_method = lambda: method try: result = opener.open(request) except URLError as exc: return {"Error": six.text_type(exc)} if stream is True or handle is True: return { "handle": result, "body": result.content, } result_status_code = result.code result_headers = dict(result.info()) result_text = result.read() if "Content-Type" in result_headers: res_content_type, res_params = cgi.parse_header( result_headers["Content-Type"]) if (res_content_type.startswith("text/") and "charset" in res_params and not isinstance(result_text, six.text_type)): result_text = result_text.decode(res_params["charset"]) if six.PY3 and isinstance(result_text, bytes) and decode_body: result_text = result_text.decode("utf-8") ret["body"] = result_text else: # Tornado req_kwargs = {} # Client-side cert handling if cert is not None: if isinstance(cert, six.string_types): if os.path.exists(cert): req_kwargs["client_cert"] = cert elif isinstance(cert, list): if os.path.exists(cert[0]) and os.path.exists(cert[1]): req_kwargs["client_cert"] = cert[0] req_kwargs["client_key"] = cert[1] else: log.error( "The client-side certificate path that " "was passed is not valid: %s", cert, ) if isinstance(data, dict): data = _urlencode(data) if verify_ssl: req_kwargs["ca_certs"] = ca_bundle max_body = opts.get("http_max_body", salt.config.DEFAULT_MINION_OPTS["http_max_body"]) connect_timeout = opts.get( "http_connect_timeout", salt.config.DEFAULT_MINION_OPTS["http_connect_timeout"], ) timeout = opts.get( "http_request_timeout", salt.config.DEFAULT_MINION_OPTS["http_request_timeout"], ) client_argspec = None proxy_host = opts.get("proxy_host", None) if proxy_host: # tornado requires a str for proxy_host, cannot be a unicode str in py2 proxy_host = salt.utils.stringutils.to_str(proxy_host) proxy_port = opts.get("proxy_port", None) proxy_username = opts.get("proxy_username", None) if proxy_username: # tornado requires a str, cannot be unicode str in py2 proxy_username = salt.utils.stringutils.to_str(proxy_username) proxy_password = opts.get("proxy_password", None) if proxy_password: # tornado requires a str, cannot be unicode str in py2 proxy_password = salt.utils.stringutils.to_str(proxy_password) no_proxy = opts.get("no_proxy", []) # Since tornado doesnt support no_proxy, we'll always hand it empty proxies or valid ones # except we remove the valid ones if a url has a no_proxy hostname in it if urlparse(url_full).hostname in no_proxy: proxy_host = None proxy_port = None # We want to use curl_http if we have a proxy defined if proxy_host and proxy_port: if HAS_CURL_HTTPCLIENT is False: ret["error"] = ( "proxy_host and proxy_port has been set. This requires pycurl and tornado, " "but the libraries does not seem to be installed") log.error(ret["error"]) return ret salt.ext.tornado.httpclient.AsyncHTTPClient.configure( "tornado.curl_httpclient.CurlAsyncHTTPClient") client_argspec = salt.utils.args.get_function_argspec( salt.ext.tornado.curl_httpclient.CurlAsyncHTTPClient.initialize ) else: client_argspec = salt.utils.args.get_function_argspec( salt.ext.tornado.simple_httpclient.SimpleAsyncHTTPClient. initialize) supports_max_body_size = "max_body_size" in client_argspec.args req_kwargs.update({ "method": method, "headers": header_dict, "auth_username": username, "auth_password": password, "body": data, "validate_cert": verify_ssl, "allow_nonstandard_methods": True, "streaming_callback": streaming_callback, "header_callback": header_callback, "connect_timeout": connect_timeout, "request_timeout": timeout, "proxy_host": proxy_host, "proxy_port": proxy_port, "proxy_username": proxy_username, "proxy_password": proxy_password, "raise_error": raise_error, "decompress_response": False, }) # Unicode types will cause a TypeError when Tornado's curl HTTPClient # invokes setopt. Therefore, make sure all arguments we pass which # contain strings are str types. req_kwargs = salt.utils.data.decode(req_kwargs, to_str=True) try: download_client = (HTTPClient(max_body_size=max_body) if supports_max_body_size else HTTPClient()) result = download_client.fetch(url_full, **req_kwargs) except salt.ext.tornado.httpclient.HTTPError as exc: ret["status"] = exc.code ret["error"] = six.text_type(exc) return ret except (socket.herror, socket.error, socket.timeout, socket.gaierror) as exc: if status is True: ret["status"] = 0 ret["error"] = six.text_type(exc) log.debug("Cannot perform 'http.query': {0} - {1}".format( url_full, ret["error"])) return ret if stream is True or handle is True: return { "handle": result, "body": result.body, } result_status_code = result.code result_headers = result.headers result_text = result.body if "Content-Type" in result_headers: res_content_type, res_params = cgi.parse_header( result_headers["Content-Type"]) if (res_content_type.startswith("text/") and "charset" in res_params and not isinstance(result_text, six.text_type)): result_text = result_text.decode(res_params["charset"]) if six.PY3 and isinstance(result_text, bytes) and decode_body: result_text = result_text.decode("utf-8") ret["body"] = result_text if "Set-Cookie" in result_headers and cookies is not None: result_cookies = parse_cookie_header(result_headers["Set-Cookie"]) for item in result_cookies: sess_cookies.set_cookie(item) else: result_cookies = None if isinstance(result_headers, list): result_headers_dict = {} for header in result_headers: comps = header.split(":") result_headers_dict[comps[0].strip()] = ":".join(comps[1:]).strip() result_headers = result_headers_dict log.debug("Response Status Code: %s", result_status_code) log.trace("Response Headers: %s", result_headers) log.trace("Response Cookies: %s", sess_cookies) # log.trace("Content: %s", result_text) coding = result_headers.get("Content-Encoding", "identity") # Requests will always decompress the content, and working around that is annoying. if backend != "requests": result_text = __decompressContent(coding, result_text) try: log.trace("Response Text: %s", result_text) except UnicodeEncodeError as exc: log.trace( "Cannot Trace Log Response Text: %s. This may be due to " "incompatibilities between requests and logging.", exc, ) if text_out is not None: with salt.utils.files.fopen(text_out, "w") as tof: tof.write(result_text) if headers_out is not None and os.path.exists(headers_out): with salt.utils.files.fopen(headers_out, "w") as hof: hof.write(result_headers) if cookies is not None: sess_cookies.save() if persist_session is True and salt.utils.msgpack.HAS_MSGPACK: # TODO: See persist_session above if "set-cookie" in result_headers: with salt.utils.files.fopen(session_cookie_jar, "wb") as fh_: session_cookies = result_headers.get("set-cookie", None) if session_cookies is not None: salt.utils.msgpack.dump({"Cookie": session_cookies}, fh_) else: salt.utils.msgpack.dump("", fh_) if status is True: ret["status"] = result_status_code if headers is True: ret["headers"] = result_headers if decode is True: if decode_type == "auto": content_type = result_headers.get("content-type", "application/json") if "xml" in content_type: decode_type = "xml" elif "json" in content_type: decode_type = "json" elif "yaml" in content_type: decode_type = "yaml" else: decode_type = "plain" valid_decodes = ("json", "xml", "yaml", "plain") if decode_type not in valid_decodes: ret["error"] = ("Invalid decode_type specified. " "Valid decode types are: {0}".format( pprint.pformat(valid_decodes))) log.error(ret["error"]) return ret if decode_type == "json": ret["dict"] = salt.utils.json.loads(result_text) elif decode_type == "xml": ret["dict"] = [] items = ET.fromstring(result_text) for item in items: ret["dict"].append(xml.to_dict(item)) elif decode_type == "yaml": ret["dict"] = salt.utils.data.decode( salt.utils.yaml.safe_load(result_text)) else: text = True if decode_out: with salt.utils.files.fopen(decode_out, "w") as dof: dof.write(result_text) if text is True: ret["text"] = result_text return ret
def _wget(cmd, opts=None, url='http://localhost:8080/manager', timeout=180): ''' A private function used to issue the command to tomcat via the manager webapp cmd the command to execute url The URL of the server manager webapp (example: http://localhost:8080/manager) opts a dict of arguments timeout timeout for HTTP request Return value is a dict in the from of:: { res: [True|False] msg: list of lines we got back from the manager } ''' ret = { 'res': True, 'msg': [] } # prepare authentication auth = _auth(url) if auth is False: ret['res'] = False ret['msg'] = 'missing username and password settings (grain/pillar)' return ret # prepare URL if url[-1] != '/': url += '/' url6 = url url += 'text/{0}'.format(cmd) url6 += '{0}'.format(cmd) if opts: url += '?{0}'.format(_urlencode(opts)) url6 += '?{0}'.format(_urlencode(opts)) # Make the HTTP request _install_opener(auth) try: # Trying tomcat >= 7 url ret['msg'] = _urlopen(url, timeout=timeout).read().splitlines() except Exception: try: # Trying tomcat6 url ret['msg'] = _urlopen(url6, timeout=timeout).read().splitlines() except Exception: ret['msg'] = 'Failed to create HTTP request' if not ret['msg'][0].startswith('OK'): ret['res'] = False return ret
def _query(function, api_key=None, api_version=None, room_id=None, api_url=None, method='GET', data=None): ''' HipChat object method function to construct and execute on the API URL. :param api_key: The HipChat api key. :param function: The HipChat api function to perform. :param api_version: The HipChat api version (v1 or v2). :param method: The HTTP method, e.g. GET or POST. :param data: The data to be sent for POST method. :return: The json response from the API call or False. ''' headers = {} query_params = {} if room_id: room_id = 'room/{0}/notification'.format(str(room_id)) else: room_id = 'room/0/notification' hipchat_functions = { 'v1': { 'rooms': { 'request': 'rooms/list', 'response': 'rooms', }, 'users': { 'request': 'users/list', 'response': 'users', }, 'message': { 'request': 'rooms/message', 'response': 'status', }, }, 'v2': { 'rooms': { 'request': 'room', 'response': 'items', }, 'users': { 'request': 'user', 'response': 'items', }, 'message': { 'request': room_id, 'response': None, }, }, } api_url = 'https://{0}'.format(api_url) base_url = _urljoin(api_url, api_version + '/') path = hipchat_functions.get(api_version).get(function).get('request') url = _urljoin(base_url, path, False) if api_version == 'v1': query_params['format'] = 'json' query_params['auth_token'] = api_key if method == 'POST': headers['Content-Type'] = 'application/x-www-form-urlencoded' if data: if data.get('notify'): data['notify'] = 1 else: data['notify'] = 0 data = _urlencode(data) elif api_version == 'v2': headers['Content-Type'] = 'application/json' headers['Authorization'] = 'Bearer {0}'.format(api_key) if data: data = json.dumps(data) else: log.error('Unsupported HipChat API version') return False result = salt.utils.http.query( url, method, params=query_params, data=data, decode=True, status=True, header_dict=headers, opts=__opts__, ) if result.get('status', None) == salt.ext.six.moves.http_client.OK: response = hipchat_functions.get(api_version).get(function).get('response') return result.get('dict', {}).get(response, None) elif result.get('status', None) == salt.ext.six.moves.http_client.NO_CONTENT: return False else: log.debug(url) log.debug(query_params) log.debug(data) log.debug(result) if result.get('error'): log.error(result) return False
def query(action=None, command=None, args=None, method='GET', data=None): ''' Make a web call to a Parallels provider ''' path = config.get_cloud_config_value( 'url', get_configured_provider(), __opts__, search_global=False ) auth_handler = _HTTPBasicAuthHandler() auth_handler.add_password( realm='Parallels Instance Manager', uri=path, user=config.get_cloud_config_value( 'user', get_configured_provider(), __opts__, search_global=False ), passwd=config.get_cloud_config_value( 'password', get_configured_provider(), __opts__, search_global=False ) ) opener = _build_opener(auth_handler) _install_opener(opener) if action: path += action if command: path += '/{0}'.format(command) if not type(args, dict): args = {} kwargs = {'data': data} if isinstance(data, str) and '<?xml' in data: kwargs['headers'] = { 'Content-type': 'application/xml', } if args: params = _urlencode(args) req = _Request(url='{0}?{1}'.format(path, params), **kwargs) else: req = _Request(url=path, **kwargs) req.get_method = lambda: method log.debug('{0} {1}'.format(method, req.get_full_url())) if data: log.debug(data) try: result = _urlopen(req) log.debug( 'PARALLELS Response Status Code: {0}'.format( result.getcode() ) ) if 'content-length' in result.headers: content = result.read() result.close() items = ET.fromstring(content) return items return {} except URLError as exc: log.error( 'PARALLELS Response Status Code: {0} {1}'.format( exc.code, exc.msg ) ) root = ET.fromstring(exc.read()) log.error(root) return {'error': root}
def post_message(channel, message, from_name, api_key=None, icon=None): ''' Send a message to a Slack channel. :param channel: The channel name, either will work. :param message: The message to send to the Slack channel. :param from_name: Specify who the message is from. :param api_key: The Slack api key, if not specified in the configuration. :param icon: URL to an image to use as the icon for this message :return: Boolean if message was sent successfully. CLI Example: .. code-block:: bash salt '*' slack.post_message channel="Development Room" message="Build is done" from_name="Build Server" ''' if not api_key: api_key = _get_api_key() if not channel: log.error('channel is a required option.') # channel must start with a hash or an @ (direct-message channels) if not channel.startswith('#') and not channel.startswith('@'): log.warning('Channel name must start with a hash or @. ' 'Prepending a hash and using "#%s" as ' 'channel name instead of %s', channel, channel) channel = '#{0}'.format(channel) if not from_name: log.error('from_name is a required option.') if not message: log.error('message is a required option.') if not from_name: log.error('from_name is a required option.') parameters = { 'channel': channel, 'username': from_name, 'text': message } if icon is not None: parameters['icon_url'] = icon # Slack wants the body on POST to be urlencoded. result = salt.utils.slack.query(function='message', api_key=api_key, method='POST', header_dict={'Content-Type': 'application/x-www-form-urlencoded'}, data=_urlencode(parameters), opts=__opts__) if result['res']: return True else: return result
def _query(function, api_key=None, api_version=None, room_id=None, api_url=None, method='GET', data=None): ''' HipChat object method function to construct and execute on the API URL. :param api_key: The HipChat api key. :param function: The HipChat api function to perform. :param api_version: The HipChat api version (v1 or v2). :param method: The HTTP method, e.g. GET or POST. :param data: The data to be sent for POST method. :return: The json response from the API call or False. ''' headers = {} query_params = {} if room_id: room_id = 'room/{0}/notification'.format(str(room_id)) else: room_id = 'room/0/notification' hipchat_functions = { 'v1': { 'rooms': { 'request': 'rooms/list', 'response': 'rooms', }, 'users': { 'request': 'users/list', 'response': 'users', }, 'message': { 'request': 'rooms/message', 'response': 'status', }, }, 'v2': { 'rooms': { 'request': 'room', 'response': 'items', }, 'users': { 'request': 'user', 'response': 'items', }, 'message': { 'request': room_id, 'response': None, }, }, } api_url = 'https://{0}'.format(api_url) base_url = _urljoin(api_url, api_version + '/') path = hipchat_functions.get(api_version).get(function).get('request') url = _urljoin(base_url, path, False) if api_version == 'v1': query_params['format'] = 'json' query_params['auth_token'] = api_key if method == 'POST': headers['Content-Type'] = 'application/x-www-form-urlencoded' if data: if data.get('notify'): data['notify'] = 1 else: data['notify'] = 0 data = _urlencode(data) elif api_version == 'v2': headers['Content-Type'] = 'application/json' headers['Authorization'] = 'Bearer {0}'.format(api_key) if data: data = json.dumps(data) else: log.error('Unsupported HipChat API version') return False result = salt.utils.http.query( url, method, params=query_params, data=data, decode=True, status=True, header_dict=headers, opts=__opts__, ) if result.get('status', None) == salt.ext.six.moves.http_client.OK: response = hipchat_functions.get(api_version).get(function).get( 'response') return result.get('dict', {}).get(response, None) elif result.get('status', None) == salt.ext.six.moves.http_client.NO_CONTENT: return False else: log.debug(url) log.debug(query_params) log.debug(data) log.debug(result) if result.get('error'): log.error(result) return False
def _wget(cmd, opts=None, url="http://localhost:8080/manager", timeout=180): """ A private function used to issue the command to tomcat via the manager webapp cmd the command to execute url The URL of the server manager webapp (example: http://localhost:8080/manager) opts a dict of arguments timeout timeout for HTTP request Return value is a dict in the from of:: { res: [True|False] msg: list of lines we got back from the manager } """ ret = {"res": True, "msg": []} # prepare authentication auth = _auth(url) if auth is False: ret["res"] = False ret["msg"] = "missing username and password settings (grain/pillar)" return ret # prepare URL if url[-1] != "/": url += "/" url6 = url url += "text/{}".format(cmd) url6 += "{}".format(cmd) if opts: url += "?{}".format(_urlencode(opts)) url6 += "?{}".format(_urlencode(opts)) # Make the HTTP request _install_opener(auth) try: # Trying tomcat >= 7 url ret["msg"] = _urlopen(url, timeout=timeout).read().splitlines() except Exception: # pylint: disable=broad-except try: # Trying tomcat6 url ret["msg"] = _urlopen(url6, timeout=timeout).read().splitlines() except Exception: # pylint: disable=broad-except ret["msg"] = "Failed to create HTTP request" # Force all byte strings to utf-8 strings, for python >= 3.4 for key, value in enumerate(ret["msg"]): try: ret["msg"][key] = salt.utils.stringutils.to_unicode(value, "utf-8") except (UnicodeDecodeError, AttributeError): pass if not ret["msg"][0].startswith("OK"): ret["res"] = False return ret
def _wget(cmd, opts=None, url='http://localhost:8080/manager', timeout=180): ''' A private function used to issue the command to tomcat via the manager webapp cmd the command to execute url the URL of the server manager webapp example: http://localhost:8080/manager opts a dict of arguments timeout timeout for HTTP request return value is a dict in the from of:: { res: [True|False] msg: list of lines we got back from the manager } ''' ret = { 'res': True, 'msg': [] } # prepare authentication auth = _auth(url) if auth is False: ret['res'] = False ret['msg'] = 'missing username and password settings (grain/pillar)' return ret # prepare URL if url[-1] != '/': url += '/' url6 = url url += 'text/{0}'.format(cmd) url6 += '{0}'.format(cmd) if opts: url += '?{0}'.format(_urlencode(opts)) url6 += '?{0}'.format(_urlencode(opts)) # Make the HTTP request _install_opener(auth) try: # Trying tomcat >= 7 url ret['msg'] = _urlopen(url, timeout=timeout).read().splitlines() except Exception: try: # Trying tomcat6 url ret['msg'] = _urlopen(url6, timeout=timeout).read().splitlines() except Exception: ret['msg'] = 'Failed to create HTTP request' if not ret['msg'][0].startswith('OK'): ret['res'] = False return ret
def post_message(channel, message, from_name, api_key=None, icon=None): ''' Send a message to a Slack channel. :param channel: The channel name, either will work. :param message: The message to send to the Slack channel. :param from_name: Specify who the message is from. :param api_key: The Slack api key, if not specified in the configuration. :param icon: URL to an image to use as the icon for this message :return: Boolean if message was sent successfully. CLI Example: .. code-block:: bash salt '*' slack.post_message channel="Development Room" message="Build is done" from_name="Build Server" ''' if not api_key: api_key = _get_api_key() if not channel: log.error('channel is a required option.') # channel must start with a hash or an @ (direct-message channels) if not channel.startswith('#') and not channel.startswith('@'): log.warning('Channel name must start with a hash or @. Prepending a hash and using "#{0}" as channel name instead of {1}'.format(channel, channel)) channel = '#{0}'.format(channel) if not from_name: log.error('from_name is a required option.') if not message: log.error('message is a required option.') if not from_name: log.error('from_name is a required option.') parameters = { 'channel': channel, 'username': from_name, 'text': message } if icon is not None: parameters['icon_url'] = icon # Slack wants the body on POST to be urlencoded. result = salt.utils.slack.query(function='message', api_key=api_key, method='POST', header_dict={'Content-Type': 'application/x-www-form-urlencoded'}, data=_urlencode(parameters), opts=__opts__) if result['res']: return True else: return result
def call_hook(message, attachment=None, color='good', short=False, identifier=None, channel=None, username=None, icon_emoji=None): ''' Send message to Slack incomming webhook. :param message: The topic of message. :param attachment: The message to send to the Slacke WebHook. :param color: The color of border of left side :param short: An optional flag indicating whether the value is short enough to be displayed side-by-side with other values. :param identifier: The identifier of WebHook. :param channel: The channel to use instead of the WebHook default. :param username: Username to use instead of WebHook default. :param icon_emoji: Icon to use instead of WebHook default. :return: Boolean if message was sent successfuly. CLI Example: .. code-block:: bash salt '*' slack.post_hook message='Hello, from SaltStack' ''' base_url = 'https://hooks.slack.com/services/' if not identifier: identifier = _get_hook_id() url = _urljoin(base_url, identifier) if not message: log.error('message is required option') if attachment: payload = { 'attachments': [ { 'fallback': message, 'color': color, 'pretext': message, 'fields': [ { "value": attachment, "short": short, } ] } ] } else: payload = { 'text': message, } if channel: payload['channel'] = channel if username: payload['username'] = username if icon_emoji: payload['icon_emoji'] = icon_emoji data = _urlencode( { 'payload': json.dumps(payload, ensure_ascii=False) } ) result = salt.utils.http.query(url, 'POST', data=data) if result['status'] <= 201: return True else: return { 'res': False, 'message': result.get('body', result['status']) }
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: # pylint: disable=broad-except 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) default_password = six.text_type(data.get("default_password", "")) if default_password == "" or default_password == "not supported": 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