def req_stream(self, path): ''' A thin wrapper to get a response from saltstack api. The body of the response will not be downloaded immediately. Make sure to close the connection after use. api = Pepper('http://ipaddress/api/') print(api.login('salt','salt','pam')) response = api.req_stream('/events') :param path: The path to the salt api resource :return: :class:`Response <Response>` object :rtype: requests.Response ''' import requests headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', } if self.auth and 'token' in self.auth and self.auth['token']: headers.setdefault('X-Auth-Token', self.auth['token']) else: raise PepperException('Authentication required') return params = { 'url': self._construct_url(path), 'headers': headers, 'verify': self._ssl_verify is True, 'stream': True } try: resp = requests.get(**params) if resp.status_code == 401: raise PepperException( str(resp.status_code) + ':Authentication denied') return if resp.status_code == 500: raise PepperException(str(resp.status_code) + ':Server error.') return if resp.status_code == 404: raise PepperException( str(resp.status_code) + ' :This request returns nothing.') return except PepperException as e: print(e) return return resp
def __init__(self, api_url='https://localhost:8000', debug_http=False, ignore_ssl_errors=False): ''' Initialize the class with the URL of the API :param api_url: Host or IP address of the salt-api URL; include the port number :param debug_http: Add a flag to urllib2 to output the HTTP exchange :param ignore_ssl_errors: Add a flag to urllib2 to ignore invalid SSL certificates :raises PepperException: if the api_url is misformed ''' split = urlparse.urlsplit(api_url) if split.scheme not in ['http', 'https']: raise PepperException( "salt-api URL missing HTTP(s) protocol: {0}".format(api_url)) self.api_url = api_url self.debug_http = int(debug_http) self._ssl_verify = not ignore_ssl_errors self.auth = {}
def req_get(self, path): ''' A thin wrapper from get http method of saltstack api api = Pepper('http://ipaddress/api/') print(api.login('salt','salt','pam')) print(api.req_get('/keys')) ''' import requests headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', } if self.auth and 'token' in self.auth and self.auth['token']: headers.setdefault('X-Auth-Token', self.auth['token']) else: raise PepperException('Authentication required') return params = { 'url': self._construct_url(path), 'headers': headers, 'verify': self._ssl_verify is True, } try: resp = requests.get(**params) if resp.status_code == 401: raise PepperException( str(resp.status_code) + ':Authentication denied') return if resp.status_code == 500: raise PepperException(str(resp.status_code) + ':Server error.') return if resp.status_code == 404: raise PepperException( str(resp.status_code) + ' :This request returns nothing.') return except PepperException as e: print(e) return return resp.json()
def req_requests(self, path, data=None): ''' A thin wrapper around request and request_kerberos to send requests and return the response If the current instance contains an authentication token it will be attached to the request as a custom header. :rtype: dictionary ''' import requests from requests_kerberos import HTTPKerberosAuth, OPTIONAL auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL) headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', } if self.auth and 'token' in self.auth and self.auth['token']: headers.setdefault('X-Auth-Token', self.auth['token']) # Optionally toggle SSL verification params = { 'url': self._construct_url(path), 'headers': headers, 'verify': self._ssl_verify is True, 'auth': auth, 'data': json.dumps(data), } logger.debug('postdata {0}'.format(params)) resp = requests.post(**params) if resp.status_code == 401: # TODO should be resp.raise_from_status raise PepperException('Authentication denied') if resp.status_code == 500: # TODO should be resp.raise_from_status raise PepperException('Server error.') if not self.salt_version and 'x-salt-version' in resp.headers: self._parse_salt_version(resp.headers['x-salt-version']) return resp.json()
def parse_cmd(self): ''' Extract the low data for a command from the passed CLI params ''' # Short-circuit if JSON was given. if self.options.json_input: try: return json.loads(self.options.json_input) except JSONDecodeError: raise PepperArgumentsException("Invalid JSON given.") if self.options.json_file: try: with open(self.options.json_file, 'r') as json_content: try: return json.load(json_content) except JSONDecodeError: raise PepperArgumentsException("Invalid JSON given.") except FileNotFoundError: raise PepperArgumentsException('Cannot open file: %s', self.options.json_file) args = list(self.args) client = self.options.client if not self.options.batch else 'local_batch' low = {'client': client} if client.startswith('local'): if len(args) < 2: self.parser.error("Command or target not specified") low['tgt_type'] = self.options.expr_form low['tgt'] = args.pop(0) low['fun'] = args.pop(0) low['batch'] = self.options.batch low['arg'] = args elif client.startswith('runner'): low['fun'] = args.pop(0) for arg in args: if '=' in arg: key, value = arg.split('=', 1) try: low[key] = json.loads(value) except JSONDecodeError: low[key] = value else: low.setdefault('args', []).append(arg) elif client.startswith('wheel'): low['fun'] = args.pop(0) for arg in args: if '=' in arg: key, value = arg.split('=', 1) try: low[key] = json.loads(value) except JSONDecodeError: low[key] = value else: low.setdefault('args', []).append(arg) elif client.startswith('ssh'): if len(args) < 2: self.parser.error("Command or target not specified") low['tgt_type'] = self.options.expr_form low['tgt'] = args.pop(0) low['fun'] = args.pop(0) low['batch'] = self.options.batch low['arg'] = args else: raise PepperException('Client not implemented: {0}'.format(client)) return [low]
def req(self, path, data=None): ''' A thin wrapper around urllib2 to send requests and return the response If the current instance contains an authentication token it will be attached to the request as a custom header. :rtype: dictionary ''' if ((hasattr(data, 'get') and data.get('eauth') == 'kerberos') or self.auth.get('eauth') == 'kerberos'): return self.req_requests(path, data) headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', } opener = build_opener() for handler in opener.handlers: if isinstance(handler, HTTPHandler): handler.set_http_debuglevel(self.debug_http) if isinstance(handler, HTTPSHandler): handler.set_http_debuglevel(self.debug_http) install_opener(opener) # Build POST data if data is not None: postdata = json.dumps(data).encode() clen = len(postdata) else: postdata = None # Create request object url = self._construct_url(path) req = Request(url, postdata, headers) # Add POST data to request if data is not None: req.add_header('Content-Length', clen) # Add auth header to request if self.auth and 'token' in self.auth and self.auth['token']: req.add_header('X-Auth-Token', self.auth['token']) # Send request try: if not (self._ssl_verify): con = ssl.SSLContext(ssl.PROTOCOL_SSLv23) f = urlopen(req, context=con) else: f = urlopen(req) content = f.read().decode('utf-8') if (self.debug_http): logger.debug('Response: %s', content) ret = json.loads(content) except (HTTPError, URLError) as exc: logger.debug('Error with request', exc_info=True) status = getattr(exc, 'code', None) if status == 401: raise PepperException('Authentication denied') if status == 500: raise PepperException('Server error.') logger.error('Error with request: {0}'.format(exc)) raise except AttributeError: logger.debug('Error converting response from JSON', exc_info=True) raise PepperException('Unable to parse the server response.') return ret
def parse_cmd(self, api): ''' Extract the low data for a command from the passed CLI params ''' # Short-circuit if JSON was given. if self.options.json_input: try: return json.loads(self.options.json_input) except JSONDecodeError: raise PepperArgumentsException("Invalid JSON given.") if self.options.json_file: try: with open(self.options.json_file, 'r') as json_content: try: return json.load(json_content) except JSONDecodeError: raise PepperArgumentsException("Invalid JSON given.") except FileNotFoundError: raise PepperArgumentsException('Cannot open file: %s', self.options.json_file) args = list(self.args) client = self.options.client if not self.options.batch else 'local_batch' low = {'client': client} if client.startswith('local'): if len(args) < 2: self.parser.error("Command or target not specified") low['tgt_type'] = self.options.expr_form low['tgt'] = args.pop(0) low['fun'] = args.pop(0) low['batch'] = self.options.batch low['arg'] = args elif client.startswith('runner'): low['fun'] = args.pop(0) # post https://github.com/saltstack/salt/pull/50124, kwargs can be # passed as is in foo=bar form, splitting and deserializing will # happen in salt-api. additionally, the presence of salt-version header # means we are neon or newer, so don't need a finer grained check if api.salt_version: low['arg'] = args else: for arg in args: if '=' in arg: key, value = arg.split('=', 1) try: low[key] = json.loads(value) except JSONDecodeError: low[key] = value else: low.setdefault('arg', []).append(arg) elif client.startswith('wheel'): low['fun'] = args.pop(0) # see above comment in runner arg handling if api.salt_version: low['arg'] = args else: for arg in args: if '=' in arg: key, value = arg.split('=', 1) try: low[key] = json.loads(value) except JSONDecodeError: low[key] = value else: low.setdefault('arg', []).append(arg) elif client.startswith('ssh'): if len(args) < 2: self.parser.error("Command or target not specified") low['tgt_type'] = self.options.expr_form low['tgt'] = args.pop(0) low['fun'] = args.pop(0) low['batch'] = self.options.batch low['arg'] = args else: raise PepperException('Client not implemented: {0}'.format(client)) return [low]