Exemplo n.º 1
0
class DSSWikiConfluenceExporter(Runnable, WikiTransfer):
    def __init__(self, project_key, config, plugin_config):
        """
        :param project_key: the project in which the runnable executes
        :param config: the dict of the configuration of the object
        :param plugin_config: contains the plugin settings
        """
        self.project_key = project_key
        self.config = config
        confluence_login = self.config.get("confluence_login", None)
        if confluence_login is None:
            raise Exception("No Confluence login is currently set.")
        self.confluence_username = confluence_login.get(
            "confluence_username", None)
        self.assert_confluence_username()
        self.confluence_password = confluence_login.get(
            "confluence_password", None)
        self.assert_confluence_password()
        self.confluence_url = self.format_confluence_url(
            confluence_login.get("server_type", None),
            confluence_login.get("url", None),
            confluence_login.get("orgname", None))
        self.confluence_space_key = confluence_login.get(
            "confluence_space_key", None)
        self.assert_space_key()
        self.confluence_space_name = confluence_login.get(
            "confluence_space_name", self.confluence_space_key)
        if self.confluence_space_name == "":
            self.confluence_space_name = self.confluence_space_key
        self.check_space_key_format()
        self.client = dataiku.api_client()
        try:
            self.studio_external_url = self.client.get_general_settings(
            ).get_raw()['studioExternalUrl']
            assert (self.studio_external_url not in (None, ''))
        except Exception as err:
            logger.error("studioExternalUrl not set :{}".format(err))
            raise Exception(
                "Please set the DSS location URL in Administration > Settings > Notifications & Integrations > DSS Location > DSS URL"
            )
        self.wiki = DSSWiki(self.client, self.project_key)
        self.wiki_settings = self.wiki.get_settings()
        self.taxonomy = self.wiki_settings.get_taxonomy()
        self.articles = self.wiki.list_articles()
        self.space_homepage_id = None
        self.confluence = Confluence(url=self.confluence_url,
                                     username=self.confluence_username,
                                     password=self.confluence_password)
        self.assert_logged_in()
        self.progress = 0

    def get_progress_target(self):
        return (len(self.articles), 'FILES')

    def run(self, progress_callback):
        self.progress_callback = progress_callback

        space = self.confluence.get_space(self.confluence_space_key)
        if space is None:
            raise Exception(
                'Empty answer from server. Please check the Confluence server address.'
            )

        if "id" not in space:
            space = self.confluence.create_space(self.confluence_space_key,
                                                 self.confluence_space_name)

        if space is None:
            space = self.confluence.get_space(self.confluence_space_key)
            if u'statusCode' in space and space[u'statusCode'] == 404:
                raise Exception(
                    'Could not create the "' + self.confluence_space_key +
                    '" space. It probably exists but you don\'t have permission to view it, or the casing is wrong.'
                )

        if space is not None and "homepage" in space:
            self.space_homepage_id = space['homepage']['id']
        else:
            self.space_homepage_id = None

        self.recurse_taxonomy(self.taxonomy, self.space_homepage_id)

        if self.space_homepage_id is not None:
            self.update_landing_page(self.space_homepage_id)

        return self.confluence_url + "/display/" + self.confluence_space_key

    def assert_logged_in(self):
        try:
            user_details = self.confluence.get_user_details_by_userkey(
                self.confluence_username)
        except Exception as err:
            logger.error("get_user_details_by_userkey failed:{}".format(err))
            raise Exception(
                'Could not connect to Confluence server. Please check the connection details'
            )
        if user_details is None:
            raise Exception(
                'No answer from the server. Please check the connection details to the Confluence server.'
            )
        if "HTTP Status 401 – Unauthorized" in user_details:
            raise Exception(
                'No valid Confluence credentials, please check login and password'
            )
        if "errorMessage" in user_details:
            raise Exception(
                'Error while accessing Confluence site : {}'.format(
                    user_details["errorMessage"]))
        if "status-code" in user_details and user_details["status-code"] > 400:
            if "message" in user_details:
                raise Exception(
                    'Error while accessing Confluence site : {}'.format(
                        user_details["message"]))
            else:
                raise Exception(
                    'Error {} while accessing Confluence site'.format(
                        user_details["status-code"]))

    def assert_space_key(self):
        space_name_format = re.compile(r'^[a-zA-Z0-9]+$')
        if self.confluence_space_key is None or space_name_format.match(
                self.confluence_space_key) is None:
            raise Exception(
                'The space key does not match Confluence requirements ([a-z], [A-Z], [0-9], not space)'
            )

    def assert_confluence_username(self):
        username_format = re.compile(r'^[a-z0-9-.@]+$')
        if self.confluence_username is None or username_format.match(
                self.confluence_username) is None:
            raise Exception('The Confluence user name is not valid')

    def assert_confluence_password(self):
        if self.confluence_password is None or self.confluence_password == "":
            raise Exception('Please set your Confluence login password')
Exemplo n.º 2
0
class ConfluenceAdapter(object):
    """
    Adapter for Atlassian Confluence class.
    Encapsulates content retrieve and update functionality.
    """
    NAMESPACES = {
        "atlassian-content": "http://atlassian.com/content",
        "ac": "http://atlassian.com/content",
        "ri": "http://atlassian.com/content",
        "atlassian-template": "http://atlassian.com/template",
        "at": "http://atlassian.com/template",
    }

    def __init__(self, url, username, password, space_key):
        self.confluence = Confluence(url=url, username=username, password=password)
        self.space_key = space_key
        self.get_space_or_create()

    def get_space_or_create(self):
        """
        Check whether space exists or not. If it doesn't, then create the space.

        :rtype: dict
        :return: Space data.
        """
        space = self.confluence.get_space(self.space_key)
        if type(space) is not dict:
            raise WikiUpdateException("Can't retrieve valid information about Confluence space."
                                      " Please check configurations. Data: {}".format(space))

        if space.get('statusCode', None) == 404:
            space = self.confluence.create_space(self.space_key, self.space_key)
        return space

    def get_page_or_create(self, page_title):
        """
        Get page content, if no such page then create it.

        :type page_title: str
        :param page_title: Title of the page which should be retrieved.

        :raises: WikiUpdateException

        :rtype: tuple(int, lxml.etree._Element)
        :returns: Tuple where first element is the id of the page. The second is the parsed content data.
        """
        data = self.confluence.get_page_by_title(title=page_title,
                                                 space=self.space_key,
                                                 expand="body.storage,version")
        if not data:
            # No such page exist. Then create such page.
            data = self.create_page(page_title)

        try:
            content_xml = data['body']['storage']['value']
        except KeyError:
            raise WikiUpdateException("Can't get partial-devices page content.")

        body_xml = render_to_string('wrapper.xml', {
            'content': content_xml
        })
        return data['id'], etree.fromstring(body_xml)

    def create_page(self, page_title):
        """
        Create new page.+

        :type page_title: str
        :param page_title: Title of the page which should be created.

        :raises: WikiUpdateException

        :rtype: dict
        :return: Data of newly created page.
        """
        data = self.confluence.create_page(self.space_key, page_title, body="")
        if not data or 'id' not in data:
            raise WikiUpdateException("Page `{}` could not be created. Response data: {}".format(page_title, data))
        return data

    def update_page_content(self, page_id, page_title, body):
        """
        Update existing page with new body.

        :type page_id: int
        :param page_id: Page id which should be modified.

        :type page_title: str
        :param page_title: Title of the page which should be modified.

        :type body: lxml.etree._Element
        :param body: Page content data.

        :raises: WikiUpdateException

        :rtype: dict
        :returns: Data of newly updated page.
        """
        # Get raw xml.
        body_xml = etree.tostring(body).decode('utf-8')

        # <xml xmlns:******> <p></p>***<ac:structured-macro>***</ac:structured-macro> </xml>
        #                  ^                                                        ^
        # Take the content starting right after `>` of the opening xml tag and till `<` of xml closing tag.
        content_xml = body_xml[body_xml.find('>') + 1:body_xml.rfind('<')]

        data = self.confluence.update_existing_page(page_id, page_title, content_xml)
        if not data or 'id' not in data:
            raise WikiUpdateException("Page `{}` could not be updated. Response data: {}".format(page_title, data))
        return data

    @classmethod
    def get_field_element(cls, element, field):
        """
        Get element from tree by field name.

        :rtype: lxml.etree._Element
        :returns: Element data.
        """
        return element.xpath('//ac:structured-macro/ac:parameter'
                             '[@ac:name="MultiExcerptName" and translate(normalize-space(text()), " ", "")="{}"]'
                                '/following-sibling::ac:rich-text-body/*[local-name()="p"]'.format(field.name),
                             namespaces=cls.NAMESPACES)

    @classmethod
    def update_content_for_field(cls, page_content, field):
        """
        Update content for field.

        :type page_content: lxml.etree._Element
        :param page_content: Wiki page content.

        :type field: AbstractLinkedField
        :param field: Field for which the page_content should be updated.

        :rtype: lxml.etree._Element
        :returns: Page content data.
        """
        field_elements = cls.get_field_element(page_content, field)
        if not field_elements:
            # If element does not exist then create it.
            macro_id = uuid.uuid4()
            data = render_to_string('multiexcerpt.xml', {
                "macro_id": macro_id,
                "field_name": field.name,
                "field_value": field.provide_value()
            })
            element = etree.fromstring(data)
            page_content.insert(-1, element)
            # Can leave without this return, but `Explicit is better than implicit.` (C) Python Zen.
            return page_content

        for field_element in field_elements:
            field_element.text = field.provide_value()

        return page_content