def __init__(self, argument_spec, direct_params=None, error_callback=None, warn_callback=None, **kwargs): kwargs['supports_check_mode'] = True super(TowerAPIModule, self).__init__(argument_spec=argument_spec, direct_params=direct_params, error_callback=error_callback, warn_callback=warn_callback, **kwargs) self.session = Request(cookies=CookieJar(), validate_certs=self.verify_ssl) if 'update_secrets' in self.params: self.update_secrets = self.params.pop('update_secrets') else: self.update_secrets = True
def __init__(self, headers=None, use_proxy=True, force=False, timeout=120, validate_certs=True, url_username=None, url_password=None, http_agent=None, force_basic_auth=False, follow_redirects='urllib2', client_cert=None, client_key=None, cookies=None): self.request = Request( headers=headers, use_proxy=use_proxy, force=force, timeout=timeout, validate_certs=validate_certs, url_username=url_username, url_password=url_password, http_agent=http_agent, force_basic_auth=force_basic_auth, follow_redirects=follow_redirects, client_cert=client_cert, client_key=client_key, cookies=cookies ) self.last_url = None
def test_Request_open_netrc(urlopen_mock, install_opener_mock, monkeypatch): here = os.path.dirname(__file__) monkeypatch.setenv('NETRC', os.path.join(here, 'fixtures/netrc')) r = Request().open('GET', 'http://ansible.com/') args = urlopen_mock.call_args[0] req = args[0] assert req.headers.get('Authorization') == b'Basic dXNlcjpwYXNzd2Q=' r = Request().open('GET', 'http://foo.ansible.com/') args = urlopen_mock.call_args[0] req = args[0] assert 'Authorization' not in req.headers monkeypatch.setenv('NETRC', os.path.join(here, 'fixtures/netrc.nonexistant')) r = Request().open('GET', 'http://ansible.com/') args = urlopen_mock.call_args[0] req = args[0] assert 'Authorization' not in req.headers
def test_Request_open_force(urlopen_mock, install_opener_mock): r = Request().open('GET', 'https://ansible.com/', force=True, last_mod_time=datetime.datetime.now()) args = urlopen_mock.call_args[0] req = args[0] assert req.headers.get('Cache-control') == 'no-cache' assert 'If-modified-since' not in req.headers
def run(self, terms, variables=None, **kwargs): base_url = "https://%s/api/v2" % variables['tower_host'] headers = {'Content-type': 'application/json'} username = variables['tower_username'] password = variables['tower_password'] verify_ssl = variables.get('tower_verify_ssl') req = Request(headers=headers, url_username=username, url_password=password, validate_certs=verify_ssl, force_basic_auth=True) url = "%s/%s" % (base_url, terms[0]) resp = req.get(url) resp = json.loads(resp.read()) return resp['results']
def test_Request_open_not_gzip(urlopen_mock): h = http_client.HTTPResponse( Sock(RESP), method='GET', ) h.begin() if PY3: urlopen_mock.return_value = h else: urlopen_mock.return_value = addinfourl( h.fp, h.msg, 'http://ansible.com/', h.status, ) urlopen_mock.return_value.msg = h.reason r = Request().open('GET', 'https://ansible.com/') assert not isinstance(r.fp, GzipDecodedReader) assert r.read() == JSON_DATA
def test_Request_open_no_proxy(urlopen_mock, install_opener_mock, mocker): build_opener_mock = mocker.patch('ansible.module_utils.urls.urllib_request.build_opener') r = Request().open('GET', 'http://ansible.com/', use_proxy=False) handlers = build_opener_mock.call_args[0] found_handlers = [] for handler in handlers: if isinstance(handler, urllib_request.ProxyHandler): found_handlers.append(handler) assert len(found_handlers) == 1
def test_Request_open_unix_socket(urlopen_mock, install_opener_mock): r = Request().open('GET', 'http://ansible.com/', unix_socket='/foo/bar/baz.sock') args = urlopen_mock.call_args[0] opener = install_opener_mock.call_args[0][0] handlers = opener.handlers found_handlers = [] for handler in handlers: if isinstance(handler, UnixHTTPHandler): found_handlers.append(handler) assert len(found_handlers) == 1
def test_Request_open_cookies(urlopen_mock, install_opener_mock): r = Request().open('GET', 'https://ansible.com/', cookies=cookiejar.CookieJar()) opener = install_opener_mock.call_args[0][0] handlers = opener.handlers cookies_handler = None for handler in handlers: if isinstance(handler, urllib_request.HTTPCookieProcessor): cookies_handler = handler break assert cookies_handler is not None
def __init__(self, server, user, password, server_port=8443, use_ssl=True, ignore_ssl_validation=True): # Set api endpoint url if use_ssl: self.api_endpoint_url = self.api_endpoint_url + 'https://' else: self.api_endpoint_url = self.api_endpoint_url + 'http://' self.__api_password = password self.__api_user = user self.__ignore_ssl_validation = ignore_ssl_validation self.__request = Request() api_fetch_result, api_version_string = self.get_latest_api_version if not api_fetch_result: # TODO:: What we do if we got an error on the API Request # should we throw exception, but then it will not be pretty else: self.api_endpoint_url = self.api_endpoint_url + \ server + ':' + str(server_port) + "/wsg/api/public/" + api_version_string
def test_Request_open_http(urlopen_mock, install_opener_mock): r = Request().open('GET', 'http://ansible.com/') args = urlopen_mock.call_args[0] opener = install_opener_mock.call_args[0][0] handlers = opener.handlers found_handlers = [] for handler in handlers: if isinstance(handler, SSLValidationHandler): found_handlers.append(handler) assert len(found_handlers) == 0
def parse(self, inventory, loader, path, cache=True): # Plugin interface (2) super(InventoryModule, self).parse(inventory, loader, path) self._read_config_data(path) host = self.get_option('host') ping_url = urljoin(host, 'api/v2/ping') try: response = Request().get(ping_url) text = response.read() data = json.loads(text) except (ConnectionError, urllib_error.URLError) as e: raise AnsibleParserError( "Unable to connect Tower or AWX server %s: %s" % (host, e)) except (ValueError, TypeError) as e: raise AnsibleParserError( 'Failed to parse json data from host, error: %s, data: %s' % (e, text)) for instance_data in data['instances']: self.inventory.add_host( instance_data['node']) # Inventory interface (1) self.inventory.set_variable( instance_data['node'], 'capacity', instance_data['capacity']) # inventory interface (2) for group_data in data['instance_groups']: group_name = self.inventory.add_group( group_data['name']) # Inventory interface (3) self.inventory.set_variable( group_name, 'group_capacity', group_data['capacity']) # Inventory interface (2) for instance in group_data['instances']: self.inventory.add_child(group_name, instance) # Inventory interface (4)
def __init__(self, server, user, password, server_port=8443, use_ssl=True, ignore_ssl_validation=True): # Set api endpoint url if use_ssl: self.api_endpoint_url = self.api_endpoint_url + 'https://' else: self.api_endpoint_url = self.api_endpoint_url + 'http://' self.api_endpoint_url = self.api_endpoint_url + \ server + ':' + str(server_port) + "/wsg/api/public" self.__api_password = password self.__api_user = user self.__ignore_ssl_validation = ignore_ssl_validation self.__request = Request()
def read_tower_inventory(self, tower_host, tower_user, tower_pass, inventory, verify_ssl=True): if not re.match('(?:http|https)://', tower_host): tower_host = 'https://{tower_host}'.format(tower_host=tower_host) inventory_id = inventory.replace('/', '') inventory_url = '/api/v2/inventories/{inv_id}/script/?hostvars=1&towervars=1&all=1'.format( inv_id=inventory_id) inventory_url = urljoin(tower_host, inventory_url) request_handler = Request(url_username=tower_user, url_password=tower_pass, force_basic_auth=True, validate_certs=verify_ssl) try: response = request_handler.get(inventory_url) except (ConnectionError, urllib_error.URLError, socket.error, httplib.HTTPException) as e: error_msg = 'Connection to remote host failed: {err}'.format(err=e) # If Tower gives a readable error message, display that message to the user. if callable(getattr(e, 'read', None)): error_msg += ' with message: {err_msg}'.format( err_msg=e.read()) raise AnsibleParserError(to_native(error_msg)) # Attempt to parse JSON. try: return json.loads(response.read()) except (ValueError, TypeError) as e: # If the JSON parse fails, print the ValueError raise AnsibleParserError( to_native( 'Failed to parse json from host: {err}'.format(err=e)))
class Connection: def __init__(self, address): self._address = address.rstrip("/") self._headers = {} self._client = Request() def _request(self, method, path, payload=None): headers = self._headers.copy() data = None if payload: data = json.dumps(payload) headers["Content-Type"] = "application/json" url = self._address + path try: r = self._client.open(method, url, data=data, headers=headers) r_status = r.getcode() r_headers = dict(r.headers) data = r.read().decode("utf-8") r_data = json.loads(data) if data else {} except HTTPError as e: r_status = e.code r_headers = {} r_data = dict(msg=str(e.reason)) except (ConnectionError, URLError) as e: raise AnsibleConnectionFailure( "Could not connect to {0}: {1}".format(url, e.reason)) return r_status, r_headers, r_data def get(self, path): return self._request("GET", path) def post(self, path, payload=None): return self._request("POST", path, payload) def delete(self, path): return self._request("DELETE", path) def login(self, username, password): status, headers, _ = self.post( "/tokens", dict(username=username, password=password), ) self._headers["x-auth-token"] = headers["x-auth-token"] def logout(self): if "x-auth-token" in self._headers: self.delete("/tokens/" + self._headers["x-auth-token"]) del self._headers["x-auth-token"]
def request(self, method, path, headers=None, data=None): query_args = dict(follow_redirects=True, validate_certs=self.validate_certs) headers = headers if headers is not None else {} data = data if data is not None else {} headers.update({'Content-type': 'application/json'}) query_args["headers"] = headers query_args["data"] = json.dumps(data).encode('utf-8') authentication = "none" if self.barn_token: authentication = "token" query_args["headers"]["x-access-tokens"] = self.barn_token elif self.barn_user and self.barn_password: authentication = "userpass" query_args["url_username"] = self.barn_user query_args["url_password"] = self.barn_password query_args["force_basic_auth"] = True try: resp = Request().open(method.upper(), "%s/%s" % (self.barn_url, path.lstrip('/')), **query_args) result = BarnResult.from_response(resp) result["request"] = dict(url="%s/%s" % (self.barn_url, path.lstrip('/')), method=method, authentication=authentication) return result except (urllib_error.HTTPError, HTTPError) as e: try: result = BarnResult.from_dict(json.loads(e.read())) result["status"] = int(getattr(e, 'code', -1)) result["request"] = dict(url="%s/%s" % (self.barn_url, path.lstrip('/')), method=method) return result except AttributeError: result = BarnResult.from_dict( dict(status=None, failed=True, changed=False)) result["request"] = dict(url="%s/%s" % (self.barn_url, path.lstrip('/')), method=method) result.set_main_message( "Can't parse API response to json response") return result return None
def test_Request_open_username_in_url(urlopen_mock, install_opener_mock): r = Request().open('GET', 'http://[email protected]/') opener = install_opener_mock.call_args[0][0] handlers = opener.handlers expected_handlers = ( urllib_request.HTTPBasicAuthHandler, urllib_request.HTTPDigestAuthHandler, ) found_handlers = [] for handler in handlers: if isinstance(handler, expected_handlers): found_handlers.append(handler) assert found_handlers[0].passwd.passwd[None] == {(('ansible.com', '/'),): ('user2', '')}
def test_Request_open_https_unix_socket(urlopen_mock, install_opener_mock): r = Request().open('GET', 'https://ansible.com/', unix_socket='/foo/bar/baz.sock') args = urlopen_mock.call_args[0] opener = install_opener_mock.call_args[0][0] handlers = opener.handlers found_handlers = [] for handler in handlers: if isinstance(handler, HTTPSClientAuthHandler): found_handlers.append(handler) assert len(found_handlers) == 1 inst = found_handlers[0]._build_https_connection('foo') assert isinstance(inst, UnixHTTPSConnection)
def main(): argument_spec = dict( state=dict(default="present", type="str", choices=["absent", "present"]), url=dict(required=True, type="str"), token=dict(required=True, type="str"), project_id=dict(required=True, type="int"), stage_id=dict(required=True, type="int"), webhook_url=dict(type="str", default=""), username=dict(type="str"), password=dict(type="str", no_log=True), ) module = AnsibleModule( argument_spec=argument_spec, required_if=[["state", "present", ["stage_id", "webhook_url"]]], ) state = module.params["state"] token = module.params["token"] params = module.params.copy() del params["url"] del params["state"] del params["token"] del params["project_id"] params["url"] = module.params["webhook_url"] del params["webhook_url"] http_client = Request( headers={ "Authorization": "Bearer {}".format(token), "Content-Type": "application/json", }) project = find_project_by_id(http_client, module.params["url"], module.params["project_id"]) base_url = "/".join([ module.params["url"], "projects", project["permalink"], "outbound_webhooks" ]) if state == "present": create(module, http_client, base_url, params) elif state == "absent": delete(module, http_client, base_url, params)
def test_Request_open_auth_in_netloc(urlopen_mock, install_opener_mock): r = Request().open('GET', 'http://*****:*****@ansible.com/') args = urlopen_mock.call_args[0] req = args[0] assert req.get_full_url() == 'http://ansible.com/' opener = install_opener_mock.call_args[0][0] handlers = opener.handlers expected_handlers = ( urllib_request.HTTPBasicAuthHandler, urllib_request.HTTPDigestAuthHandler, ) found_handlers = [] for handler in handlers: if isinstance(handler, expected_handlers): found_handlers.append(handler) assert len(found_handlers) == 2
def test_Request_open_no_validate_certs(urlopen_mock, install_opener_mock): r = Request().open('GET', 'https://ansible.com/', validate_certs=False) opener = install_opener_mock.call_args[0][0] handlers = opener.handlers ssl_handler = None for handler in handlers: if isinstance(handler, HTTPSClientAuthHandler): ssl_handler = handler break assert ssl_handler is not None context = ssl_handler._context assert context.protocol == ssl.PROTOCOL_SSLv23 if ssl.OP_NO_SSLv2: assert context.options & ssl.OP_NO_SSLv2 assert context.options & ssl.OP_NO_SSLv3 assert context.verify_mode == ssl.CERT_NONE assert context.check_hostname is False
def test_Request_open_username_force_basic(urlopen_mock, install_opener_mock): r = Request().open('GET', 'http://ansible.com/', url_username='******', url_password='******', force_basic_auth=True) opener = install_opener_mock.call_args[0][0] handlers = opener.handlers expected_handlers = ( urllib_request.HTTPBasicAuthHandler, urllib_request.HTTPDigestAuthHandler, ) found_handlers = [] for handler in handlers: if isinstance(handler, expected_handlers): found_handlers.append(handler) assert len(found_handlers) == 0 args = urlopen_mock.call_args[0] req = args[0] assert req.headers.get('Authorization') == b'Basic dXNlcjpwYXNzd2Q='
def main(): argument_spec = dict( state=dict(default="present", type="str", choices=["absent", "present"]), url=dict(required=True, type="str"), token=dict(required=True, type="str"), permalink=dict(required=True, type="str"), name=dict(type="str"), env_value=dict(required=False, type="str"), environment_id=dict(required=True, type="int"), ) module = AnsibleModule(argument_spec=argument_spec, required_if=[["state", "present", ["name"]]]) validate_permalink(module) base_url = "/".join([module.params["url"], "deploy_groups"]) state = module.params["state"] params = dict( (k, module.params[k]) for k in ("permalink", "name", "env_value", "environment_id")) params = dict((k, v) for k, v in params.items() if v) http_client = Request( headers={ "Authorization": "Bearer {}".format(module.params["token"]), "Content-Type": "application/json", }) if state == "present": upsert_using_html(module, http_client, base_url, params, "deploy_group") if state == "absent": delete_entity(module, http_client, base_url, params, json_suffix=False)
def parse(self, inventory, loader, path, cache=True): super(InventoryModule, self).parse(inventory, loader, path) if not self.no_config_file_supplied and os.path.isfile(path): self._read_config_data(path) # Read inventory from tower server. # Note the environment variables will be handled automatically by InventoryManager. tower_host = self.get_option('host') if not re.match('(?:http|https)://', tower_host): tower_host = 'https://{tower_host}'.format(tower_host=tower_host) request_handler = Request( url_username=self.get_option('username'), url_password=self.get_option('password'), force_basic_auth=True, validate_certs=self.get_option('validate_certs')) # validate type of inventory_id because we allow two types as special case inventory_id = self.get_option('inventory_id') if isinstance(inventory_id, int): inventory_id = to_text(inventory_id, nonstring='simplerepr') else: try: inventory_id = ensure_type(inventory_id, 'str') except ValueError as e: raise AnsibleOptionsError( 'Invalid type for configuration option inventory_id, ' 'not integer, and cannot convert to string: {err}'.format( err=to_native(e))) inventory_id = inventory_id.replace('/', '') inventory_url = '/api/v2/inventories/{inv_id}/script/?hostvars=1&towervars=1&all=1'.format( inv_id=inventory_id) inventory_url = urljoin(tower_host, inventory_url) inventory = self.make_request(request_handler, inventory_url) # To start with, create all the groups. for group_name in inventory: if group_name != '_meta': self.inventory.add_group(group_name) # Then, create all hosts and add the host vars. all_hosts = inventory['_meta']['hostvars'] for host_name, host_vars in six.iteritems(all_hosts): self.inventory.add_host(host_name) for var_name, var_value in six.iteritems(host_vars): self.inventory.set_variable(host_name, var_name, var_value) # Lastly, create to group-host and group-group relationships, and set group vars. for group_name, group_content in six.iteritems(inventory): if group_name != 'all' and group_name != '_meta': # First add hosts to groups for host_name in group_content.get('hosts', []): self.inventory.add_host(host_name, group_name) # Then add the parent-children group relationships. for child_group_name in group_content.get('children', []): self.inventory.add_child(group_name, child_group_name) # Set the group vars. Note we should set group var for 'all', but not '_meta'. if group_name != '_meta': for var_name, var_value in six.iteritems( group_content.get('vars', {})): self.inventory.set_variable(group_name, var_name, var_value) # Fetch extra variables if told to do so if self.get_option('include_metadata'): config_url = urljoin(tower_host, '/api/v2/config/') config_data = self.make_request(request_handler, config_url) server_data = {} server_data['license_type'] = config_data.get( 'license_info', {}).get('license_type', 'unknown') for key in ('version', 'ansible_version'): server_data[key] = config_data.get(key, 'unknown') self.inventory.set_variable('all', 'tower_metadata', server_data) # Clean up the inventory. self.inventory.reconcile_inventory()
class AHAPIModule(AnsibleModule): """Ansible module for managing private automation hub servers.""" AUTH_ARGSPEC = dict( ah_host=dict(required=False, aliases=["ah_hostname"], fallback=(env_fallback, ["AH_HOST"])), ah_username=dict(required=False, fallback=(env_fallback, ["AH_USERNAME"])), ah_password=dict(no_log=True, required=False, fallback=(env_fallback, ["AH_PASSWORD"])), ah_path_prefix=dict(required=False, fallback=(env_fallback, ["GALAXY_API_PATH_PREFIX" ])), validate_certs=dict(type="bool", aliases=["ah_verify_ssl"], required=False, fallback=(env_fallback, ["AH_VERIFY_SSL"])), ) short_params = { "host": "ah_host", "username": "******", "password": "******", "verify_ssl": "validate_certs", "path_prefix": "ah_path_prefix", } host = "127.0.0.1" username = None password = None verify_ssl = True path_prefix = "galaxy" authenticated = False def __init__(self, argument_spec, **kwargs): """Initialize the object.""" full_argspec = {} full_argspec.update(AHAPIModule.AUTH_ARGSPEC) full_argspec.update(argument_spec) super(AHAPIModule, self).__init__(argument_spec=full_argspec, **kwargs) # Update the current object with the provided parameters for short_param, long_param in self.short_params.items(): direct_value = self.params.get(long_param) if direct_value is not None: setattr(self, short_param, direct_value) # Perform some basic validation if not re.match("^https{0,1}://", self.host): self.host = "https://{host}".format(host=self.host) # Try to parse the hostname as a url try: self.host_url = urlparse(self.host) except Exception as e: self.fail_json( msg="Unable to parse ah_host as a URL ({host}): {error}". format(host=self.host, error=e)) # Try to resolve the hostname try: socket.gethostbyname(self.host_url.hostname) except Exception as e: self.fail_json(msg="Unable to resolve ah_host ({host}): {error}". format(host=self.host_url.hostname, error=e)) self.headers = { "referer": self.host, "Content-Type": "application/json", "Accept": "application/json" } self.session = Request(validate_certs=self.verify_ssl, headers=self.headers) # Define the API paths self.galaxy_path_prefix = "/api/{prefix}".format( prefix=self.path_prefix.strip("/")) self.ui_path_prefix = "{galaxy_prefix}/_ui/v1".format( galaxy_prefix=self.galaxy_path_prefix) self.pulp_path_prefix = "/pulp/api/v3" def _build_url(self, prefix, endpoint=None, query_params=None): """Return a URL from the given prefix and endpoint. The URL is build as follows:: https://<host>/<prefix>/[<endpoint>]/[?<query>] :param prefix: Prefix to add to the endpoint. :type prefix: str :param endpoint: Usually the API object name ("users", "groups", ...) :type endpoint: str :param query_params: The optional query to append to the URL :type query_params: dict :return: The full URL built from the given prefix and endpoint. :rtype: :py:class:``urllib.parse.ParseResult`` """ if endpoint is None: api_path = "/{base}/".format(base=prefix.strip("/")) else: api_path = "{base}/{endpoint}/".format( base=prefix, endpoint=endpoint.strip("/")) url = self.host_url._replace(path=api_path) if query_params: url = url._replace(query=urlencode(query_params)) return url def build_ui_url(self, endpoint, query_params=None): """Return the URL of the given endpoint in the UI API. :param endpoint: Usually the API object name ("users", "groups", ...) :type endpoint: str :return: The full URL built from the given endpoint. :rtype: :py:class:``urllib.parse.ParseResult`` """ return self._build_url(self.ui_path_prefix, endpoint, query_params) def build_pulp_url(self, endpoint, query_params=None): """Return the URL of the given endpoint in the Pulp API. :param endpoint: Usually the API object name ("users", "groups", ...) :type endpoint: str :return: The full URL built from the given endpoint. :rtype: :py:class:``urllib.parse.ParseResult`` """ return self._build_url(self.pulp_path_prefix, endpoint, query_params) def make_request_raw_reponse(self, method, url, **kwargs): """Perform an API call and return the retrieved data. :param method: GET, PUT, POST, or DELETE :type method: str :param url: URL to the API endpoint :type url: :py:class:``urllib.parse.ParseResult`` :param kwargs: Additionnal parameter to pass to the API (headers, data for PUT and POST requests, ...) :raises AHAPIModuleError: The API request failed. :return: The reponse from the API call :rtype: :py:class:``http.client.HTTPResponse`` """ # In case someone is calling us directly; make sure we were given a method, let's not just assume a GET if not method: raise Exception("The HTTP method must be defined") # Extract the provided headers and data headers = kwargs.get("headers", {}) data = json.dumps(kwargs.get("data", {})) #set default response response = {} try: response = self.session.open(method, url.geturl(), headers=headers, data=data) except SSLValidationError as ssl_err: raise AHAPIModuleError( "Could not establish a secure connection to {host}: {error}.". format(host=url.netloc, error=ssl_err)) except ConnectionError as con_err: raise AHAPIModuleError( "Network error when trying to connect to {host}: {error}.". format(host=url.netloc, error=con_err)) except HTTPError as he: # Sanity check: Did the server send back some kind of internal error? if he.code >= 500: raise AHAPIModuleError( "The host sent back a server error: {path}: {error}. Please check the logs and try again later" .format(path=url.path, error=he)) # Sanity check: Did we fail to authenticate properly? If so, fail out now; this is always a failure. elif he.code == 401: raise AHAPIModuleError( "Invalid authentication credentials for {path} (HTTP 401)." .format(path=url.path)) # Sanity check: Did we get a forbidden response, which means that the user isn't allowed to do this? Report that. elif he.code == 403: raise AHAPIModuleError( "You do not have permission to {method} {path} (HTTP 403)." .format(method=method, path=url.path)) # Sanity check: Did we get a 404 response? # Requests with primary keys will return a 404 if there is no response, and we want to consistently trap these. elif he.code == 404: raise AHAPIModuleError( "The requested object could not be found at {path}.". format(path=url.path)) # Sanity check: Did we get a 405 response? # A 405 means we used a method that isn't allowed. Usually this is a bad request, but it requires special treatment because the # API sends it as a logic error in a few situations (e.g. trying to cancel a job that isn't running). elif he.code == 405: raise AHAPIModuleError( "Cannot make a {method} request to this endpoint {path}". format(method=method, path=url.path)) # Sanity check: Did we get some other kind of error? If so, write an appropriate error message. elif he.code >= 400: # We are going to return a 400 so the module can decide what to do with it pass elif he.code == 204 and method == "DELETE": # A 204 is a normal response for a delete function pass else: raise AHAPIModuleError( "Unexpected return code when calling {url}: {error}". format(url=url.geturl(), error=he)) except Exception as e: raise AHAPIModuleError( "There was an unknown error when trying to connect to {name}: {error} {url}" .format(name=type(e).__name__, error=e, url=url.geturl())) return response def make_request(self, method, url, wait_for_task=True, **kwargs): """Perform an API call and return the data. :param method: GET, PUT, POST, or DELETE :type method: str :param url: URL to the API endpoint :type url: :py:class:``urllib.parse.ParseResult`` :param kwargs: Additionnal parameter to pass to the API (headers, data for PUT and POST requests, ...) :raises AHAPIModuleError: The API request failed. :return: A dictionnary with two entries: ``status_code`` provides the API call returned code and ``json`` provides the returned data in JSON format. :rtype: dict """ response = self.make_request_raw_reponse(method, url, **kwargs) try: response_body = response.read() except Exception as e: raise AHAPIModuleError( "Failed to read response body: {error}".format(error=e)) response_json = {} if response_body: try: response_json = json.loads(response_body) except Exception as e: raise AHAPIModuleError( "Failed to parse the response json: {0}".format(e)) # A background task has been triggered. Check if the task is completed if response.status == 202 and "task" in response_json and wait_for_task: url = url._replace(path=response_json["task"], query="") for _ in range(5): time.sleep(3) bg_task = self.make_request("GET", url) if "state" in bg_task["json"] and bg_task["json"][ "state"].lower().startswith("complete"): break else: if "state" in bg_task["json"]: raise AHAPIModuleError( "Failed to get the status of the remote task: {task}: last status: {status}" .format(task=response_json["task"], status=bg_task["json"]["state"])) raise AHAPIModuleError( "Failed to get the status of the remote task: {task}". format(task=response_json["task"])) return {"status_code": response.status, "json": response_json} def extract_error_msg(self, response): """Return the error message provided in the API response. Example of messages returned by the API call: { "errors": [ { "status":"400", "code":"invalid", "title":"Invalid input.", "detail":"Permission matching query does not exist." } ] } { "errors": [ { "status":"404", "code":"not_found", "title":"Not found." } ] } { "detail":"Not found." } :param response: The response message from the API. This dictionary has two keys: ``status_code`` provides the API call returned code and ``json`` provides the returned data in JSON format. :type response: dict :return: The error message or an empty string if the reponse does not provide a message. :rtype: str """ if not response or "json" not in response: return "" if "errors" in response["json"] and len(response["json"]["errors"]): if "detail" in response["json"]["errors"][0]: return response["json"]["errors"][0]["detail"] if "title" in response["json"]["errors"][0]: return response["json"]["errors"][0]["title"] if "detail" in response["json"]: return response["json"]["detail"] return "" def authenticate(self): """Authenticate with the API.""" # curl -k -i -X GET -H "Accept: application/json" -H "Content-Type: application/json" https://hub.lab.example.com/api/galaxy/_ui/v1/auth/login/ # HTTP/1.1 204 No Content # Server: nginx/1.18.0 # Date: Tue, 10 Aug 2021 07:33:37 GMT # Content-Length: 0 # Connection: keep-alive # Vary: Accept, Cookie # Allow: GET, POST, HEAD, OPTIONS # X-Frame-Options: SAMEORIGIN # Set-Cookie: csrftoken=jvdb...kKHo; expires=Tue, 09 Aug 2022 07:33:37 GMT; Max-Age=31449600; Path=/; SameSite=Lax # Strict-Transport-Security: max-age=15768000 url = self.build_ui_url("auth/login") try: response = self.make_request_raw_reponse("GET", url) except AHAPIModuleError as e: self.fail_json(msg="Authentication error: {error}".format(error=e)) # Set-Cookie: csrftoken=jvdb...kKHo; expires=Tue, 09 Aug 2022 07:33:37 GMT for h in response.getheaders(): if h[0].lower() == "set-cookie": k, v = h[1].split("=", 1) if k.lower() == "csrftoken": header = {"X-CSRFToken": v.split(";", 1)[0]} break else: header = {} # curl -k -i -X POST -H 'referer: https://hub.lab.example.com' -H "Accept: application/json" -H "Content-Type: application/json" # -H 'X-CSRFToken: jvdb...kKHo' --cookie 'csrftoken=jvdb...kKHo' -d '{"username":"******","password":"******"}' # https://hub.lab.example.com/api/galaxy/_ui/v1/auth/login/ # HTTP/1.1 204 No Content # Server: nginx/1.18.0 # Date: Tue, 10 Aug 2021 07:35:33 GMT # Content-Length: 0 # Connection: keep-alive # Vary: Accept, Cookie # Allow: GET, POST, HEAD, OPTIONS # X-Frame-Options: SAMEORIGIN # Set-Cookie: csrftoken=6DVP...at9a; expires=Tue, 09 Aug 2022 07:35:33 GMT; Max-Age=31449600; Path=/; SameSite=Lax # Set-Cookie: sessionid=87b0iw12wyvy0353rk5fwci0loy5s615; expires=Tue, 24 Aug 2021 07:35:33 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax # Strict-Transport-Security: max-age=15768000 try: response = self.make_request_raw_reponse("POST", url, data={ "username": self.username, "password": self.password }, headers=header) except AHAPIModuleError as e: self.fail_json(msg="Authentication error: {error}".format(error=e)) for h in response.getheaders(): if h[0].lower() == "set-cookie": k, v = h[1].split("=", 1) if k.lower() == "csrftoken": header = {"X-CSRFToken": v.split(";", 1)[0]} break else: header = {} self.headers.update(header) self.authenticated = True def logout(self): if not self.authenticated: return url = self.build_ui_url("auth/logout") try: self.make_request_raw_reponse("POST", url) except AHAPIModuleError: pass self.headers = { "referer": self.host, "Content-Type": "application/json", "Accept": "application/json" } self.session = Request(validate_certs=self.verify_ssl, headers=self.headers) self.authenticated = False def fail_json(self, **kwargs): self.logout() super(AHAPIModule, self).fail_json(**kwargs) def exit_json(self, **kwargs): self.logout() super(AHAPIModule, self).exit_json(**kwargs) def get_server_version(self): """Return the automation hub/galaxy server version. :return: the server version ("4.2.5" for example) or an empty string if that information is not available. :rtype: str """ url = self._build_url(self.galaxy_path_prefix) try: response = self.make_request("GET", url) except AHAPIModuleError as e: self.fail_json( msg="Error while getting server version: {error}".format( error=e)) if response["status_code"] != 200: error_msg = self.extract_error_msg(response) if error_msg: fail_msg = "Unable to get server version: {code}: {error}".format( code=response["status_code"], error=error_msg) else: fail_msg = "Unable to get server version: {code}".format( code=response["status_code"]) self.fail_json(msg=fail_msg) return response["json"][ "server_version"] if "server_version" in response["json"] else ""
def __init__(self, address): self._address = address.rstrip("/") self._headers = {} self._client = Request()
def test_Request_init_headers_not_dict(urlopen_mock, install_opener_mock): with pytest.raises(ValueError): Request(headers=['bob'])
def test_Request_open_headers_not_dict(urlopen_mock, install_opener_mock): with pytest.raises(ValueError): Request().open('GET', 'https://ansible.com/', headers=['bob'])
def test_Request_open_headers(urlopen_mock, install_opener_mock): r = Request().open('GET', 'http://ansible.com/', headers={'Foo': 'bar'}) args = urlopen_mock.call_args[0] req = args[0] assert req.headers == {'Foo': 'bar'}
def test_Request_open_ftp(urlopen_mock, install_opener_mock, mocker): mocker.patch('ansible.module_utils.urls.ParseResultDottedDict.as_list', side_effect=AssertionError) # Using ftp scheme should prevent the AssertionError side effect to fire r = Request().open('GET', 'ftp://[email protected]/')