Ejemplo n.º 1
0
def axapi_call_v3(module, url, method=None, body=None, signature=None):
    '''
    Returns a datastructure based on the result of the API call
    '''
    if signature:
        headers = {'content-type': 'application/json', 'Authorization': 'A10 %s' % signature}
    else:
        headers = {'content-type': 'application/json'}
    rsp, info = fetch_url(module, url, method=method, data=body, headers=headers)
    if not rsp or info['status'] >= 400:
        module.fail_json(msg="failed to connect (status code %s), error was %s" % (info['status'], info.get('msg', 'no error given')))
    try:
        raw_data = rsp.read()
        data = json.loads(raw_data)
    except ValueError:
        # at least one API call (system.action.write_config) returns
        # XML even when JSON is requested, so do some minimal handling
        # here to prevent failing even when the call succeeded
        if 'status="ok"' in raw_data.lower():
            data = {"response": {"status": "OK"}}
        else:
            data = {"response": {"status": "fail", "err": {"msg": raw_data}}}
    except Exception:
        module.fail_json(msg="could not read the result from the host")
    finally:
        rsp.close()
    return data
Ejemplo n.º 2
0
def request(module,
            api_url,
            project,
            path,
            access_token,
            private_token,
            rawdata='',
            method='GET'):
    url = "%s/v4/projects/%s%s" % (api_url, quote_plus(project), path)
    headers = {}
    if access_token:
        headers['Authorization'] = "Bearer %s" % access_token
    else:
        headers['Private-Token'] = private_token

    headers['Accept'] = "application/json"
    headers['Content-Type'] = "application/json"

    response, info = fetch_url(module=module,
                               url=url,
                               headers=headers,
                               data=rawdata,
                               method=method)
    status = info['status']
    content = ""
    if response:
        content = response.read()
    if status == 204:
        return True, content
    elif status == 200 or status == 201:
        return True, json.loads(content)
    else:
        return False, str(status) + ": " + content
Ejemplo n.º 3
0
def fetch_url_json(module, url, method='GET', timeout=10, data=None, headers=None, accept_errors=None):
    '''
    Make general request to Hetzner's JSON robot API.
    '''
    module.params['url_username'] = module.params['hetzner_user']
    module.params['url_password'] = module.params['hetzner_password']
    resp, info = fetch_url(module, url, method=method, timeout=timeout, data=data, headers=headers)
    try:
        content = resp.read()
    except AttributeError:
        content = info.pop('body', None)

    if not content:
        module.fail_json(msg='Cannot retrieve content from {0}'.format(url))

    try:
        result = module.from_json(content.decode('utf8'))
        if 'error' in result:
            if accept_errors:
                if result['error']['code'] in accept_errors:
                    return result, result['error']['code']
            module.fail_json(msg='Request failed: {0} {1} ({2})'.format(
                result['error']['status'],
                result['error']['code'],
                result['error']['message']
            ))
        return result, None
    except ValueError:
        module.fail_json(msg='Cannot decode content retrieved from {0}'.format(url))
Ejemplo n.º 4
0
    def send_request(self, commands, output='text'):
        commands = to_list(commands)

        if self._enable:
            commands.insert(0, self._enable)

        body = self._request_builder(commands, output)
        data = self._module.jsonify(body)

        headers = {'Content-Type': 'application/json-rpc'}
        timeout = self._module.params['provider']['timeout']
        use_proxy = self._module.params['provider']['use_proxy']

        response, headers = fetch_url(
            self._module, self._url, data=data, headers=headers,
            method='POST', timeout=timeout, use_proxy=use_proxy
        )

        if headers['status'] != 200:
            self._module.fail_json(**headers)

        try:
            data = response.read()
            response = self._module.from_json(to_text(data, errors='surrogate_then_replace'))
        except ValueError:
            self._module.fail_json(msg='unable to load response from device', data=data)

        if self._enable and 'result' in response:
            response['result'].pop(0)

        return response
Ejemplo n.º 5
0
 def get_nonce(self, resource=None):
     url = self.directory_root if self.version == 1 else self.directory['newNonce']
     if resource is not None:
         url = resource
     dummy, info = fetch_url(self.module, url, method='HEAD')
     if info['status'] not in (200, 204):
         raise ModuleFailException("Failed to get replay-nonce, got status {0}".format(info['status']))
     return info['replay-nonce']
Ejemplo n.º 6
0
    def send(self, method, path, data=None, headers=None):
        url = self._url_builder(path)
        data = self.module.jsonify(data)

        resp, info = fetch_url(self.module,
                               url,
                               data=data,
                               headers=self.headers,
                               method=method)

        return Response(resp, info)
Ejemplo n.º 7
0
    def _delete(self, api_call):
        resp, info = fetch_url(self._module,
                               API_URL + api_call,
                               headers=self._auth_header,
                               method='DELETE',
                               timeout=self._module.params['api_timeout'])

        if info['status'] == 204:
            return None
        else:
            self._module.fail_json(
                msg=
                'Failure while calling the cloudscale.ch API with DELETE for '
                '"%s".' % api_call,
                fetch_url_info=info)
Ejemplo n.º 8
0
    def _get(self, api_call):
        resp, info = fetch_url(self._module,
                               API_URL + api_call,
                               headers=self._auth_header,
                               timeout=self._module.params['api_timeout'])

        if info['status'] == 200:
            return self._module.from_json(
                to_text(resp.read(), errors='surrogate_or_strict'))
        elif info['status'] == 404:
            return None
        else:
            self._module.fail_json(
                msg='Failure while calling the cloudscale.ch API with GET for '
                '"%s".' % api_call,
                fetch_url_info=info)
Ejemplo n.º 9
0
    def get_request(self, uri, parse_json_result=True, headers=None, get_only=False, fail_on_error=True):
        '''
        Perform a GET-like request. Will try POST-as-GET for ACMEv2, with fallback
        to GET if server replies with a status code of 405.
        '''
        if not get_only and self.version != 1:
            # Try POST-as-GET
            content, info = self.send_signed_request(uri, None, parse_json_result=False)
            if info['status'] == 405:
                # Instead, do unauthenticated GET
                get_only = True
        else:
            # Do unauthenticated GET
            get_only = True

        if get_only:
            # Perform unauthenticated GET
            resp, info = fetch_url(self.module, uri, method='GET', headers=headers)

            _assert_fetch_url_success(resp, info)

            try:
                content = resp.read()
            except AttributeError:
                content = info.pop('body', None)

        # Process result
        if parse_json_result:
            result = {}
            if content:
                if info['content-type'].startswith('application/json'):
                    try:
                        result = self.module.from_json(content.decode('utf8'))
                    except ValueError:
                        raise ModuleFailException("Failed to parse the ACME response: {0} {1}".format(uri, content))
                else:
                    result = content
        else:
            result = content

        if fail_on_error and (info['status'] < 200 or info['status'] >= 400):
            raise ModuleFailException("ACME request failed: CODE: {0} RESULT: {1}".format(info['status'], result))
        return result, info
Ejemplo n.º 10
0
    def login(self):
        ''' Log in to MSO '''

        # Perform login request
        self.url = urljoin(self.baseuri, 'auth/login')
        payload = {'username': self.params.get('username'), 'password': self.params.get('password')}
        resp, auth = fetch_url(self.module,
                               self.url,
                               data=json.dumps(payload),
                               method='POST',
                               headers=self.headers,
                               timeout=self.params.get('timeout'),
                               use_proxy=self.params.get('use_proxy'))

        # Handle MSO response
        if auth.get('status') != 201:
            self.response = auth.get('msg')
            self.status = auth.get('status')
            self.fail_json(msg='Authentication failed: {msg}'.format(**auth))

        payload = json.loads(resp.read())

        self.headers['Authorization'] = 'Bearer {token}'.format(**payload)
Ejemplo n.º 11
0
    def _post_or_patch(self, api_call, method, data):
        # This helps with tags when we have the full API resource href to update.
        if API_URL not in api_call:
            api_endpoint = API_URL + api_call
        else:
            api_endpoint = api_call

        headers = self._auth_header.copy()
        if data is not None:
            # Sanitize data dictionary
            # Deepcopy: Duplicate the data object for iteration, because
            # iterating an object and changing it at the same time is insecure
            for k, v in deepcopy(data).items():
                if v is None:
                    del data[k]

            data = self._module.jsonify(data)
            headers['Content-type'] = 'application/json'

        resp, info = fetch_url(self._module,
                               api_endpoint,
                               headers=headers,
                               method=method,
                               data=data,
                               timeout=self._module.params['api_timeout'])

        if info['status'] in (200, 201):
            return self._module.from_json(
                to_text(resp.read(), errors='surrogate_or_strict'))
        elif info['status'] == 204:
            return None
        else:
            self._module.fail_json(
                msg='Failure while calling the cloudscale.ch API with %s for '
                '"%s".' % (method, api_call),
                fetch_url_info=info)
Ejemplo n.º 12
0
    def api_query(self, resource="/domains", method="GET", data=None):
        url = EXO_DNS_BASEURL + resource
        if data:
            data = self.module.jsonify(data)

        response, info = fetch_url(
            module=self.module,
            url=url,
            data=data,
            method=method,
            headers=self.headers,
            timeout=self.module.params.get('api_timeout'),
        )

        if info['status'] not in (200, 201, 204):
            self.module.fail_json(msg="%s returned %s, with body: %s" %
                                  (url, info['status'], info['msg']))

        try:
            return self.module.from_json(to_text(response.read()))

        except Exception as e:
            self.module.fail_json(
                msg="Could not process response into json: %s" % to_native(e))
Ejemplo n.º 13
0
    def request(self, path, method=None, data=None, qs=None):
        ''' Generic HTTP method for MSO requests. '''
        self.path = path

        if method is not None:
            self.method = method

        # If we PATCH with empty operations, return
        if method == 'PATCH' and not data:
            return {}

        self.url = urljoin(self.baseuri, path)

        if qs is not None:
            self.url = self.url + update_qs(qs)

        resp, info = fetch_url(self.module,
                               self.url,
                               headers=self.headers,
                               data=json.dumps(data),
                               method=self.method,
                               timeout=self.params.get('timeout'),
                               use_proxy=self.params.get('use_proxy'),
                               )
        self.response = info.get('msg')
        self.status = info.get('status')

        # self.result['info'] = info

        # Get change status from HTTP headers
        if 'modified' in info:
            self.has_modified = True
            if info.get('modified') == 'false':
                self.result['changed'] = False
            elif info.get('modified') == 'true':
                self.result['changed'] = True

        # 200: OK, 201: Created, 202: Accepted, 204: No Content
        if self.status in (200, 201, 202, 204):
            output = resp.read()
            if output:
                return json.loads(output)

        # 404: Not Found
        elif self.method == 'DELETE' and self.status == 404:
            return {}

        # 400: Bad Request, 401: Unauthorized, 403: Forbidden,
        # 405: Method Not Allowed, 406: Not Acceptable
        # 500: Internal Server Error, 501: Not Implemented
        elif self.status >= 400:
            try:
                output = resp.read()
                payload = json.loads(output)
            except (ValueError, AttributeError):
                try:
                    payload = json.loads(info['body'])
                except Exception:
                    self.fail_json(msg='MSO Error:', data=data, info=info)
            if 'code' in payload:
                self.fail_json(msg='MSO Error {code}: {message}'.format(**payload), data=data, info=info, payload=payload)
            else:
                self.fail_json(msg='MSO Error:'.format(**payload), data=data, info=info, payload=payload)

        return {}
Ejemplo n.º 14
0
    def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True, encode_payload=True):
        '''
        Sends a JWS signed HTTP POST request to the ACME server and returns
        the response as dictionary
        https://tools.ietf.org/html/rfc8555#section-6.2

        If payload is None, a POST-as-GET is performed.
        (https://tools.ietf.org/html/rfc8555#section-6.3)
        '''
        key_data = key_data or self.key_data
        jws_header = jws_header or self.jws_header
        failed_tries = 0
        while True:
            protected = copy.deepcopy(jws_header)
            protected["nonce"] = self.directory.get_nonce()
            if self.version != 1:
                protected["url"] = url

            self._log('URL', url)
            self._log('protected', protected)
            self._log('payload', payload)
            data = self.sign_request(protected, payload, key_data, encode_payload=encode_payload)
            if self.version == 1:
                data["header"] = jws_header.copy()
                for k, v in protected.items():
                    hv = data["header"].pop(k, None)
            self._log('signed request', data)
            data = self.module.jsonify(data)

            headers = {
                'Content-Type': 'application/jose+json',
            }
            resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST')
            _assert_fetch_url_success(resp, info)
            result = {}
            try:
                content = resp.read()
            except AttributeError:
                content = info.pop('body', None)

            if content or not parse_json_result:
                if (parse_json_result and info['content-type'].startswith('application/json')) or 400 <= info['status'] < 600:
                    try:
                        decoded_result = self.module.from_json(content.decode('utf8'))
                        self._log('parsed result', decoded_result)
                        # In case of badNonce error, try again (up to 5 times)
                        # (https://tools.ietf.org/html/rfc8555#section-6.7)
                        if (400 <= info['status'] < 600 and
                                decoded_result.get('type') == 'urn:ietf:params:acme:error:badNonce' and
                                failed_tries <= 5):
                            failed_tries += 1
                            continue
                        if parse_json_result:
                            result = decoded_result
                        else:
                            result = content
                    except ValueError:
                        raise ModuleFailException("Failed to parse the ACME response: {0} {1}".format(url, content))
                else:
                    result = content

            return result, info