def put(self, key, value, data): restUrl = self.url + API_REST_BIND_PATH + '/' + key + '/' + value try: rsp = self.session.put(restUrl, json=data) except requests.exceptions.Timeout: raise ConfluenceTimeoutError(self.url) except requests.exceptions.SSLError as ex: raise ConfluenceSslError(self.url, ex) except requests.exceptions.ConnectionError as ex: raise ConfluenceBadServerUrlError(self.url, ex) if rsp.status_code == 401: raise ConfluenceAuthenticationFailedUrlError if rsp.status_code == 403: raise ConfluencePermissionError("REST PUT") if rsp.status_code == 407: raise ConfluenceProxyPermissionError if not rsp.ok: errdata = self._format_error(rsp, key) if self.verbosity > 0: errdata += "\n" errdata += json.dumps(data, indent=2) raise ConfluenceBadApiError(errdata) if not rsp.text: raise ConfluenceSeraphAuthenticationFailedUrlError try: rsp.encoding = self.CONFLUENCE_DEFAULT_ENCODING json_data = json.loads(rsp.text) except ValueError: raise ConfluenceBadServerUrlError( self.url, "REST reply did not provide valid JSON data.") return json_data
def get(self, key, params): restUrl = self.url + API_REST_BIND_PATH + '/' + key try: rsp = self.session.get(restUrl, params=params) except requests.exceptions.Timeout: raise ConfluenceTimeoutError(self.url) except requests.exceptions.SSLError as ex: raise ConfluenceSslError(self.url, ex) except requests.exceptions.ConnectionError as ex: raise ConfluenceBadServerUrlError(self.url, ex) if rsp.status_code == 401: raise ConfluenceAuthenticationFailedUrlError if rsp.status_code == 403: raise ConfluencePermissionError("REST GET") if rsp.status_code == 407: raise ConfluenceProxyPermissionError if not rsp.ok: raise ConfluenceBadApiError(self._format_error(rsp, key)) if not rsp.text: raise ConfluenceSeraphAuthenticationFailedUrlError try: rsp.encoding = self.CONFLUENCE_DEFAULT_ENCODING json_data = json.loads(rsp.text) except ValueError: raise ConfluenceBadServerUrlError( self.url, "REST reply did not provide valid JSON data.") return json_data
def delete(self, key, value): rest_url = self.url + self.bind_path + '/' + key + '/' + str(value) rsp = self.session.delete(rest_url, timeout=self.timeout) self._handle_common_request(rsp) if not rsp.ok: errdata = self._format_error(rsp, key) raise ConfluenceBadApiError(rsp.status_code, errdata)
def post(self, key, data, files=None): restUrl = self.url + API_REST_BIND_PATH + '/' + key try: headers = dict(self.session.headers) # Atlassian's documenation indicates to the security token check # when publishing attachments [1][2]. If adding files, set a # 'nocheck' value to the token. # # [1]: https://developer.atlassian.com/cloud/confluence/rest/#api-content-id-child-attachment-post # [2]: https://developer.atlassian.com/server/jira/platform/form-token-handling/ if files: headers['X-Atlassian-Token'] = 'nocheck' rsp = self.session.post(restUrl, json=data, files=files, headers=headers) except requests.exceptions.Timeout: raise ConfluenceTimeoutError(self.url) except requests.exceptions.SSLError as ex: raise ConfluenceSslError(self.url, ex) except requests.exceptions.ConnectionError as ex: raise ConfluenceBadServerUrlError(self.url, ex) if rsp.status_code == 401: raise ConfluenceAuthenticationFailedUrlError if rsp.status_code == 403: raise ConfluencePermissionError("REST POST") if rsp.status_code == 407: raise ConfluenceProxyPermissionError if not rsp.ok: errdata = self._format_error(rsp, key) if self.verbosity > 0: errdata += "\n" errdata += json.dumps(data, indent=2) raise ConfluenceBadApiError(errdata) if not rsp.text: raise ConfluenceSeraphAuthenticationFailedUrlError try: rsp.encoding = self.CONFLUENCE_DEFAULT_ENCODING json_data = json.loads(rsp.text) except ValueError: raise ConfluenceBadServerUrlError( self.url, "REST reply did not provide valid JSON data.") return json_data
def delete(self, key, value): restUrl = self.url + API_REST_BIND_PATH + '/' + key + '/' + value try: rsp = self.session.delete(restUrl) except requests.exceptions.Timeout: raise ConfluenceTimeoutError(self.url) except requests.exceptions.SSLError as ex: raise ConfluenceSslError(self.url, ex) except requests.exceptions.ConnectionError as ex: raise ConfluenceBadServerUrlError(self.url, ex) if rsp.status_code == 401: raise ConfluenceAuthenticationFailedUrlError if rsp.status_code == 403: raise ConfluencePermissionError("REST DELETE") if rsp.status_code == 407: raise ConfluenceProxyPermissionError if not rsp.ok: raise ConfluenceBadApiError(self._format_error(rsp, key))
def get(self, key, params): rest_url = self.url + self.bind_path + '/' + key rsp = self.session.get(rest_url, params=params, timeout=self.timeout) self._handle_common_request(rsp) if not rsp.ok: errdata = self._format_error(rsp, key) raise ConfluenceBadApiError(rsp.status_code, errdata) if not rsp.text: raise ConfluenceSeraphAuthenticationFailedUrlError try: rsp.encoding = self.CONFLUENCE_DEFAULT_ENCODING json_data = json.loads(rsp.text) except ValueError: raise ConfluenceBadServerUrlError(self.url, "REST reply did not provide valid JSON data.") return json_data
def put(self, key, value, data): rest_url = self.url + self.bind_path + '/' + key + '/' + str(value) rsp = self.session.put(rest_url, json=data, timeout=self.timeout) self._handle_common_request(rsp) if not rsp.ok: errdata = self._format_error(rsp, key) if self.verbosity > 0: errdata += "\n" errdata += json.dumps(data, indent=2) raise ConfluenceBadApiError(rsp.status_code, errdata) if not rsp.text: raise ConfluenceSeraphAuthenticationFailedUrlError try: rsp.encoding = self.CONFLUENCE_DEFAULT_ENCODING json_data = json.loads(rsp.text) except ValueError: raise ConfluenceBadServerUrlError(self.url, "REST reply did not provide valid JSON data.") return json_data
def storePage(self, page_name, data, parent_id=None): uploaded_page_id = None if self.config.confluence_adv_trace_data: ConfluenceLogger.trace('data', data['content']) if self.dryrun: _, page = self.getPage(page_name, 'version,ancestors') if not page: self._dryrun('adding new page ' + page_name) return None else: misc = '' if parent_id and 'ancestors' in page: if not any(a['id'] == parent_id for a in page['ancestors']): if parent_id in self._name_cache: misc += '[new parent page {} ({})]'.format( self._name_cache[parent_id], parent_id) else: misc += '[new parent page]' self._dryrun('updating existing page', page['id'], misc) return page['id'] can_labels = 'labels' not in self.config.confluence_adv_restricted expand = 'version' if can_labels and self.append_labels: expand += ',metadata.labels' _, page = self.getPage(page_name, expand=expand) if self.onlynew and page: self._onlynew('skipping existing page', page['id']) return page['id'] try: # new page if not page: newPage = { 'type': 'page', 'title': page_name, 'body': { 'storage': { 'representation': 'storage', 'value': data['content'], } }, 'space': { 'key': self.space_name }, } if can_labels: self._populate_labels(newPage, data['labels']) if parent_id: newPage['ancestors'] = [{'id': parent_id}] try: rsp = self.rest_client.post('content', newPage) if 'id' not in rsp: api_err = ('Confluence reports a successful page ' + 'creation; however, provided no ' + 'identifier.\n\n') try: api_err += 'DATA: {}'.format(json.dumps( rsp, indent=2)) except TypeError: api_err += 'DATA: <not-or-invalid-json>' raise ConfluenceBadApiError(api_err) uploaded_page_id = rsp['id'] except ConfluenceBadApiError as ex: # Check if Confluence reports that the new page request # fails, indicating it already exists. This is usually # (outside of possible permission use cases) that the page # name's casing does not match. In this case, attempt to # re-check for the page in a case-insensitive fashion. If # found, attempt to perform an update request instead. if str(ex).find('title already exists') == -1: raise ConfluenceLogger.verbose('title already exists warning ' 'for page {}'.format(page_name)) _, page = self.getPageCaseInsensitive(page_name) if not page: raise if self.onlynew: self._onlynew('skipping existing page', page['id']) return page['id'] # update existing page if page: last_version = int(page['version']['number']) updatePage = { 'id': page['id'], 'type': 'page', 'title': page_name, 'body': { 'storage': { 'representation': 'storage', 'value': data['content'], } }, 'space': { 'key': self.space_name }, 'version': { 'number': last_version + 1 }, } if can_labels: labels = list(data['labels']) if self.append_labels: labels.extend([lbl.get('name') for lbl in page.get('metadata', {}).get( 'labels', {}).get('results', {}) ]) self._populate_labels(updatePage, labels) if not self.notify: updatePage['version']['minorEdit'] = True if parent_id: updatePage['ancestors'] = [{'id': parent_id}] try: self.rest_client.put('content', page['id'], updatePage) except ConfluenceBadApiError as ex: if str(ex).find('unreconciled') != -1: raise ConfluenceUnreconciledPageError( page_name, page['id'], self.server_url, ex) # Confluence Cloud may (rarely) fail to complete a # content request with an OptimisticLockException/ # StaleObjectStateException exception. It is suspected # that this is just an instance timing/processing issue. # If this is observed, wait a moment and retry the # content request. If it happens again, the put request # will fail as it normally would. if str(ex).find('OptimisticLockException') == -1: raise ConfluenceLogger.warn( 'remote page updated failed; retrying...') time.sleep(1) self.rest_client.put('content', page['id'], updatePage) uploaded_page_id = page['id'] except ConfluencePermissionError: raise ConfluencePermissionError( """Publish user does not have permission to add page """ """content to the configured space.""" ) if not self.watch: self.rest_client.delete('user/watch/content', uploaded_page_id) return uploaded_page_id
def store_page(self, page_name, data, parent_id=None): """ request to store page information to a confluence instance Performs a request which will attempt to store the provided page information and publish it to either a new page with the provided page name or update an existing page with a matching page name. Pages will be published at the root of a Confluence space unless a provided parent page identifier is provided. Args: page_name: the page title to use on the updated page data: the page data to apply parent_id (optional): the id of the ancestor to use """ uploaded_page_id = None if self.config.confluence_adv_trace_data: logger.trace('data', data['content']) if self.dryrun: _, page = self.get_page(page_name, 'version,ancestors') if not page: self._dryrun('adding new page ' + page_name) return None else: misc = '' if parent_id and 'ancestors' in page: if not any(a['id'] == parent_id for a in page['ancestors']): if parent_id in self._name_cache: misc += '[new parent page {} ({})]'.format( self._name_cache[parent_id], parent_id) else: misc += '[new parent page]' self._dryrun('updating existing page', page['id'], misc) return page['id'] expand = 'version' if self.append_labels: expand += ',metadata.labels' _, page = self.get_page(page_name, expand=expand) if self.onlynew and page: self._onlynew('skipping existing page', page['id']) return page['id'] try: # new page if not page: new_page = self._build_page(page_name, data) self._populate_labels(new_page, data['labels']) if parent_id: new_page['ancestors'] = [{'id': parent_id}] try: rsp = self.rest_client.post('content', new_page) if 'id' not in rsp: api_err = ('Confluence reports a successful page ' + 'creation; however, provided no ' + 'identifier.\n\n') try: api_err += 'DATA: {}'.format( json.dumps(rsp, indent=2)) except TypeError: api_err += 'DATA: <not-or-invalid-json>' raise ConfluenceBadApiError(-1, api_err) uploaded_page_id = rsp['id'] # if we have labels and this is a non-cloud instance, # initial labels need to be applied in their own request labels = new_page['metadata']['labels'] if not self.cloud and labels: url = 'content/{}/label'.format(uploaded_page_id) self.rest_client.post(url, labels) except ConfluenceBadApiError as ex: # Check if Confluence reports that the new page request # fails, indicating it already exists. This is usually # (outside of possible permission use cases) that the page # name's casing does not match. In this case, attempt to # re-check for the page in a case-insensitive fashion. If # found, attempt to perform an update request instead. if str(ex).find('title already exists') == -1: raise logger.verbose('title already exists warning ' 'for page {}'.format(page_name)) _, page = self.get_page_case_insensitive(page_name) if not page: raise if self.onlynew: self._onlynew('skipping existing page', page['id']) return page['id'] # update existing page if page: self._update_page(page, page_name, data, parent_id=parent_id) uploaded_page_id = page['id'] except ConfluencePermissionError: raise ConfluencePermissionError( """Publish user does not have permission to add page """ """content to the configured space.""") if not self.watch: self.rest_client.delete('user/watch/content', uploaded_page_id) return uploaded_page_id