65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Python 更新 Confluence Wiki 页面

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2017年6月13日

CPOL

8分钟阅读

viewsIcon

35860

downloadIcon

497

如何以编程方式编辑 Confluence Wiki 页面

引言

如果您的组织正在使用 Confluence-Wiki 服务器产品,并希望以编程方式更改特定页面,那么您可能会对本文感兴趣。不幸的是,该代码不能直接用于基于订阅的 SAAS Confluence 产品。虽然有一个命令行程序允许您进行此类更新,但该 CLI 需要在 Confluence 服务器上付费插件。幸运的是,Confluence 提供了一个 REST API,允许我们操作服务器上的内容。Confluence 还支持旧的 XMLRPC Web 标准,但现在人们倾向于喜欢使用 REST。

背景

就我而言,我尝试在 Confluence 上实现一种自动日志记录。为什么要用 Confluence?Confluence 允许用户订阅他们感兴趣的特定页面。如果内容发生更改,用户会收到电子邮件。这样可以使利益相关者随时了解情况。我们有一个程序,要求所有对特定 IT 系统的配置更改都必须发布。我们想知道谁进行了更改、更改是什么以及为什么。IT 配置更改有时会产生意想不到的后果,因此让利益相关者知道“某人”更改了系统很有好处。

我冒着听起来像广告(不,我没有收到任何钱来写这个)的风险——自引入 Confluence 以来,它已成为对我们组织最有益的工具。页面易于编写,截图、代码片段、图表——一切都运行良好,用户似乎也很喜欢并贡献内容。

在我们的案例中,涉及的 IT 系统是 TeamCity——一个持续集成/构建服务器。我们有兴趣跟踪对构建代理所做的配置更改。问题在于,有时人们会进行更改——但会忘记在 Confluence 页面上记录这些更改。该页面只是一个简单的表格,每行代表一次更改——最新的更改位于顶部。这使得利益相关者可以轻松快速地扫描表格,了解谁进行了更改、何时进行的更改、简要描述以及完整的更改描述。但如果更改未记录,日志页面就毫无用处。

我想编写一个程序,每次启动配置更改时都可以运行。用户必须输入配置更改的原因。然后程序将启动。它将下载“更改日志”页面,提取 HTML,在表格 #2 的顶部插入一个额外的行(我的页面上有两个表格,日志是第二个)。然后该工具将保存 HTML,并通过 Confluence 的 REST API 将更改上传回 Confluence。听起来很简单,对吧?

为什么选择 Python?

通常有很多方法可以解决问题,那么为什么我选择为这个小项目使用 Python 呢?主要原因是 Nick Hilton 已经 发布了一些使用 REST 更新 Confluence 页面的 Python 代码。我有一个选择是重写,还是直接重用。我大部分的经验是 C#,但我以前也接触过 Python,所以我认为这是一个通过实际做有用的事情来学习更多的机会。

Nick Hilton 的 Confluence 更新代码是如何工作的?

如果您查看下面的代码,通过 REST 更新页面比最初预期的要复杂一些。如果您想更新 Confluence 页面,通常需要先进行身份验证,但您还需要知道您感兴趣的页面的内部页面 ID。如何获取页面 ID?我通常只是在 Firefox 中访问 Confluence 上的页面,右键单击并选择“查看页面信息”。

页面 ID 显示为 `ajs-page-id` 属性。页面的更新分几个步骤进行——首先,进行 REST 调用以获取有关页面的更多信息——特别是,我们对版本号感兴趣。每个 Confluence 页面都经过版本控制——每次保存都会保留另一个版本。这允许恢复到任何先前版本,以及比较页面的不同修订。在这里,我们必须计算下一个版本号,只需在原始版本的基础上加一。在调用保存页面时需要此项。另一个调用用于获取“`ancestors`”对象,在调用保存页面之前对其进行清理。构建一个 JSON 有效负载,更新后的 HTML 位于 `['body']['storage']['value']` 下。调用完成后,页面的 HTML 将被替换为传入的内容。完美!。

def get_page_ancestors(auth, pageid):

    # Get basic page information plus the ancestors property

    url = '{base}/{pageid}?expand=ancestors'.format(
        base = BASE_URL,
        pageid = pageid)

    r = requests.get(url, auth = auth)

    r.raise_for_status()

    return r.json()['ancestors']


def get_page_info(auth, pageid):

    url = '{base}/{pageid}'.format(
        base = BASE_URL,
        pageid = pageid)

    r = requests.get(url, auth = auth)

    r.raise_for_status()

    return r.json()


def write_data(auth, html, pageid, title = None):

    info = get_page_info(auth, pageid)

    ver = int(info['version']['number']) + 1

    ancestors = get_page_ancestors(auth, pageid)

    anc = ancestors[-1]
    del anc['_links']
    del anc['_expandable']
    del anc['extensions']

    if title is not None:
        info['title'] = title

    data = {
        'id' : str(pageid),
        'type' : 'page',
        'title' : info['title'],
        'version' : {'number' : ver},
        'ancestors' : [anc],
        'body'  : {
            'storage' :
            {
                'representation' : 'storage',
                'value' : str(html),
            }
        }
    }

    data = json.dumps(data)

    url = '{base}/{pageid}'.format(base = BASE_URL, pageid = pageid)

    r = requests.put(
        url,
        data = data,
        auth = auth,
        headers = { 'Content-Type' : 'application/json' }
    )

    r.raise_for_status()

    print "Wrote '%s' version %d" % (info['title'], ver)
    print "URL: %s%d" % (VIEW_URL, pageid)

解决 REST 400 错误

Nick 的代码对于我创建的任意 HTML 实际上效果很好。要使内容正常工作,HTML 必须是普通、有效的 HTML,没有任何特殊标签或宏。在我的情况下,我需要进行往返处理,也就是说,我需要从 Confluence 读取现有的 HTML,进行一些修改,然后上传修改后的版本。当我尝试这样做时,我总是收到 400 错误——请求无效。查看服务器日志没有帮助,而且由于普通 HTML 可以正常工作,问题可能出在哪里?查看 Confluence REST API 文档,我找到了“`contentbody/convert/{to}`”方法。挠头之后,我明白了。Confluence 以存储格式存储页面——在这种格式下,链接、宏等未展开。HTML 实际上包含对普通 HTML 而言没有意义的元素。因此,要获取 HTML 并进行编辑和保存,需要执行以下操作:

  1. 读取页面 HTML - HTML 处于“存储”格式。
  2. 将 HTML 转换为“查看”格式。在此格式下,链接和宏已展开。
  3. 以编程方式编辑 HTML。
  4. 将“查看”HTML 转换回“存储”HTML。
  5. 使用编辑后的“存储”HTML 更新页面。

采用这种方法可以避免 400 错误。下面显示了用于执行转换的 Python 方法。

def convert_db_to_view(auth2, html):
    url = 'http://server/rest/api/contentbody/convert/view'

    data2 = {
        'value': html,
        'representation': 'storage'
    }

    r = requests.post(url,
                      data=json.dumps(data2),
                      auth=auth2,
                      headers={'Content-Type': 'application/json'}
                      )
    r.raise_for_status()
    return r.json()


def convert_view_to_db(auth2, html):
    url = 'http://server/rest/api/contentbody/convert/storage'

    data2 = {
        'value': html,
        'representation': 'editor'
    }

    r = requests.post(url,
                      data=json.dumps(data2),
                      auth=auth2,
                      headers={'Content-Type': 'application/json'}
                      )
    r.raise_for_status()
    return r.json()

HTML 必须从 JSON 响应中提取,这并不难,但您需要知道如何操作。完整的代码示例展示了如何完成此操作。

使用 Python lxml 库进行 HTML 编辑

在解决了往返问题后,我现在需要向 HTML 的表格 #2 添加一行。新行需要添加为表格的顶行。虽然可以使用普通的文本处理技术完成此类操作——但使用 HTML 解析库会更健壮。要在脚本中使用 lxml 库,您必须安装它。这通常通过以下方式完成:

pip install lxml

之后,就可以使用该库了。如果您使用 PyCharm 等 IDE,IDE 通常具有导入所需模块的功能。在 PyCharm 中,您需要使用 Preferences...-> Project:[yourproj]->Project Interpreter,然后单击包列表下方的“+”号。许多开发人员只为脚本语言使用编辑器,但我仍然偏爱可以单步调试代码的环境。请注意,您运行脚本的机器必须同时安装 Python 和依赖模块,否则将无法正常工作。

def patch_html(auth, options):
    json_text = read_data(auth, options.page_id, options.server).text
    json2 = json.loads(json_text)
    html_storage_txt = json2['body']['storage']['value']
    html_display_json = convert_db_to_view(auth, html_storage_txt, options.server)
    html_display_txt = html_display_json['value'].encode('utf-8')
    html = lxml.html.fromstring(html_display_txt)
    tables = html.findall('.//table')
    table_body = tables[1].find('.//tbody')

    insert_row_to_table(table_body, options)

    html_string = lxml.html.tostring(html, pretty_print=True)
    new_view = convert_view_to_db(auth, html_string, options.server)
    new_view_string = new_view['value'].encode('utf-8')

    return new_view_string


def insert_row_to_table(table_body, options):
    from lxml import etree
    row = etree.Element('tr')
    c1 = get_td(options.column1)  # if you want a date-stamp, use c1 = get_date()
    c2 = get_td(options.column2)
    c3 = get_td(options.column3)
    c4 = get_td(options.column4)
    row.append(c1)
    row.append(c2)
    row.append(c3)
    row.append(c4)
    table_body.insert(1, row)
    return

请注意,使用类似 XPath 的查询导航 HTML 文档是多么容易。`Element` 类允许轻松插入新元素,如上代码所示。

如何在 Windows 上使用该脚本

  1. 安装 Python 2.7 运行安装程序时,请告诉它将 Python 2.7 添加到路径(默认未启用)。
  2. 运行 `pip install lxml`
  3. 从本文的下载部分下载并解压 *python edit_confluence.py*。
  4. 在您的 Confluence 服务器上,找到您要更新的页面的 `PageId`(本文已解释)。要使用当前代码,页面应包含两个表格,第二个表格应有一个标题行和 4 列。
  5. 运行 `python edit_confluence.py -u [您的 Confluence 用户 ID] -s [http://myconfluence.com] -1 "列 1 文本 -2 "列 2 文本" -3 "列 3 文本" -4 "列 4 文本"`。如果是第一次运行,系统将提示您输入 Confluence 密码,该密码已加密存储在钥匙串中。
  6. 在浏览器中按 F5 刷新 Confluence 页面以查看更新。

注释

上述文章提供了一种我发现相当复杂的 Confluence 更新解决方案。如果您找到了更简单的更新方法,请添加评论,以便我们都能学习!由于我是 Python 新手,这段代码可能不像期望的那么 Pythonic。代码可以使用一些重构,使用一个类——这样,需要传递的参数就会少一些。Atlassian Confluence REST API 应该封装许多必需的细节,并公开一个额外的 REST API,使页面更新更加容易。不幸的是,基于云的解决方案以完全不同的方式存储页面,因此此程序不适用于该用例。

历史

  • 2017 年 6 月 15 日 - 初始修订
© . All rights reserved.