Beispiel #1
0
    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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
    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']
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #10
0
    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
Beispiel #11
0
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
Beispiel #12
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)
Beispiel #13
0
    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()
Beispiel #14
0
    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)))
Beispiel #15
0
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"]
Beispiel #16
0
    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
Beispiel #17
0
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', '')}
Beispiel #18
0
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)
Beispiel #19
0
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)
Beispiel #20
0
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
Beispiel #22
0
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)
Beispiel #24
0
    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()
Beispiel #25
0
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 ""
Beispiel #26
0
 def __init__(self, address):
     self._address = address.rstrip("/")
     self._headers = {}
     self._client = Request()
Beispiel #27
0
def test_Request_init_headers_not_dict(urlopen_mock, install_opener_mock):
    with pytest.raises(ValueError):
        Request(headers=['bob'])
Beispiel #28
0
def test_Request_open_headers_not_dict(urlopen_mock, install_opener_mock):
    with pytest.raises(ValueError):
        Request().open('GET', 'https://ansible.com/', headers=['bob'])
Beispiel #29
0
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'}
Beispiel #30
0
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]/')