使用持续集成 (CI) 进行发布

概述

持续集成 (CI) 指的是自动从版本控制系统中签入的代码发布内容的实践。虽然使用 CI 进行发布配置起来稍微复杂一些,但它有几个好处,包括:

  • 每当源代码发生变化时,内容会自动发布(你不需要记住显式地渲染)。

  • 在另一系统上进行渲染确保了你的代码是可复现的(但请注意,如果渲染有特殊要求,这可能是一把双刃剑——请参阅下面的 CI 渲染 讨论)。

  • 不将渲染输出签入版本控制使得差异更小,减少了合并冲突。

本文介绍了如何使用 GitHub Actions(由 GitHub 运行的服务)、普通 shell 命令(可以与任何 CI 服务配合使用)以及 Posit Connect 实现 Quarto 的 CI。

CI 渲染

在开始使用 CI 服务器之前,你需要考虑你希望可执行代码(例如 R、Python 或 Julia 代码)在哪里运行,以及你希望 quarto render 在哪里运行。你可能会本能地假设你总是希望在 CI 服务器上运行所有内容,然而这样做会引入许多复杂性:

  1. 你需要确保 CI 环境中存在适当版本的 Quarto。

  2. 你需要在 CI 环境中重新构建所有依赖项(所需的 R、Python 或 Julia 包)。

  3. 如果你的代码需要任何特殊权限(例如数据库或网络访问),这些权限也需要在 CI 服务器上存在。

  4. 你的项目可能包含无法轻松执行的文档(例如几年前的博客文章,使用了较旧版本的包)。

鉴于上述情况,你可以将渲染视为一个从在本地环境中运行所有内容(包括 quarto render)一直到在 CI 上远程运行所有内容的连续体:

  • 本地执行和渲染 —— 在本地环境中运行所有内容,然后将输出(例如 _site 目录)签入版本控制。在这种情况下,CI 服务器只是确保每次提交时签入的内容被复制/部署到正确的位置。你可能会选择这种方法,以对 CI 服务器上需要存在的软件提出最小的要求。

  • 本地执行与 CI 渲染 —— 在本地执行 R、Python 或 Julia 代码,并使用 Quarto 的 冻结计算输出 功能将计算结果保存到 _freeze 目录中。在 CI 服务器上渲染站点(将使用存储在 _freeze 中的计算结果)。当难以在 CI 服务器上完全重新执行代码时,使用这种方法。

  • CI 执行和渲染 —— 在 CI 服务器上执行所有代码并进行渲染。虽然这是自动化和可复现性的黄金标准,但它将要求你捕获 R、Python 或 Julia 依赖项(例如在 renv.lock 文件或 requirements.txt 文件中),并安排它们在 CI 服务器上安装。你还需要确保 CI 服务器上存在代码所需的权限(例如数据库访问)。

下面我们将描述如何使用 GitHub Actions、普通 Shell 命令(你应该能够将其适应任何 CI 环境)或 Posit Connect 实现这些策略。

GitHub Actions

GitHub Actions 是 GitHub 提供的持续集成服务,如果你的源代码已经托管在 GitHub 仓库中,这是一个极好的选择。Quarto 提供了一组 标准 GitHub Actions,使得安装 Quarto 并渲染和发布内容变得容易。

在这里了解如何将 GitHub Actions 与各种发布服务一起使用:

如果你想在其他工作流程中使用标准的 Quarto 操作,请参阅 Quarto 的 GitHub Actions 仓库。

Posit Connect

如果你将内容的源代码版本发布到 Posit Connect,可以配置 Connect 从 Git 仓库获取代码,然后在 Connect 服务器上渲染和执行。

要了解更多信息,请参阅 Posit Connect 的 Git 支持的内容 文档。

Shell 命令

本节介绍如何在无法进行用户交互的服务器上使用 quarto publish 命令。这涉及以下步骤:

  1. 渲染你的内容。
  2. 指定发布位置(服务/服务器、发布目标 ID 等)。
  3. 提供适当的发布凭证。

例如,这里有一个基于项目根目录下的 _publish.yml 文件信息发布到 Netlify 的 shell 脚本:

Terminal
# 来自 https://app.netlify.com/user/applications 的凭证
export NETLIFY_AUTH_TOKEN="45fd6ae56c"

# 发布到 _publish.yml 中提供的 Netlify 站点 ID
quarto publish netlify

以下是 _publish.yml 的内容:

- source: project
  netlify:
    - id: "5f3abafe-68f9-4c1d-835b-9d668b892001"
      url: "https://tubular-unicorn-97bb3c.netlify.app"

以下是另一种在命令行中提供发布目标的变体:

Terminal
quarto publish netlify --id 5f3abafe-68f9-4c1d-835b-9d668b892001

下面我们将介绍发布脚本的各种组件,并提供一些额外的完整示例。

发布前的渲染

默认情况下,当你执行发布命令时,你的网站或文档将自动重新渲染:

Terminal
quarto publish

这通常是推荐的,因为它确保你基于源代码的最新版本进行发布。

如果你想单独渲染(或根本不渲染),可以使用 --no-render 选项:

Terminal
quarto publish --no-render

默认情况下,调用 quarto publish 将执行项目中包含的所有 R、Python 或 Julia 代码。这意味着你需要确保 CI 服务器上安装了这些工具的必要版本(以及任何所需的包)。如何做到这一点不在本文的讨论范围内——要了解更多关于保存和恢复依赖关系的信息,请参阅关于 虚拟环境 的文章。

如果你想在本地执行代码,然后在 CI 上仅进行 Markdown 渲染,可以使用 Quarto 的 冻结 功能。例如,如果你在 _quarto.yml 文件中添加以下内容:

execute:
  freeze: true

那么当你在本地渲染时,计算将运行并将结果保存在项目根目录的 _freeze 文件夹中。然后,当你在 CI 服务器上运行 quarto publishquarto render 时,这些计算不需要重新运行(服务器上只会进行 Markdown 渲染)。

发布目标

有两种方法可以为 quarto publish 命令指定发布目标:

  1. 通过从之前的发布创建的 _publish.yml 文件的内容。
  2. 使用命令行参数(例如 --id--server)。

当你执行 quarto publish 命令时,你的发布目标记录会写入与源代码一起的 _publish.yml 文件中。例如:

- source: project
  netlify:
    - id: "5f3abafe-68f9-4c1d-835b-9d668b892001"
      url: "https://tubular-unicorn-97bb3c.netlify.app"

你可以将 _publish.yml 文件检入源代码管理,以便在从 CI 服务器发布时可用。如果你在没有参数的情况下执行 quarto publish 命令,并且项目目录中有上述 _publish.yml,那么发布将针对具有指定 id 的 Netlify:

Terminal
quarto publish netlify

你也可以通过显式的命令行参数指定发布目标。例如:

Terminal
quarto publish netlify --id 5f3abafe-68f9-4c1d-835b-9d668b892001

如果你在 _publish.yml 中有多个发布目标,则可以使用 --id 选项从中选择。

发布凭证

你可以使用环境变量或命令行参数指定发布凭证。以下环境变量被各种服务识别:

服务 变量
Quarto Pub QUARTO_PUB_AUTH_TOKEN
Netlify NETLIFY_AUTH_TOKEN
Posit Connect CONNECT_SERVERCONNECT_API_KEY
Confluence CONFLUENCE_AUTH_TOKENCONFLUENCE_USER_EMAILCONFLUENCE_DOMAIN

在调用 quarto publish 之前,在你的脚本中设置这些环境变量。例如:

Terminal
export NETLIFY_AUTH_TOKEN="45fd6ae56c"
quarto publish netlify 

注意,你也可以将发布目标 --id 作为命令行参数指定。例如:

Terminal
export CONNECT_SERVER=https://connect.example.com/
export CONNECT_API_KEY=7C0947A852D8
quarto publish connect --id DDA36416-F950-4647-815C-01A24233E294

完整示例

以下是一些完整的示例,展示了编写发布 shell 脚本的各种方式:

Terminal
# 基于 _publish.yml 发布(不渲染)到 quarto pub
export QUARTO_PUB_AUTH_TOKEN="45fd6ae56c"
quarto publish quarto-pub --no-render
Terminal
# 渲染并基于 _publish.yml 发布到 netlify
export NETLIFY_AUTH_TOKEN="45fd6ae56c"
quarto publish netlify
Terminal
# 基于显式 id 发布(不渲染)到 netlify
export NETLIFY_AUTH_TOKEN="45fd6ae56c"
quarto publish netlify --id DDA36416-F950-4647-815C-01A24233E294 --no-render
Terminal
# 根据 _publish.yml 发布(不渲染)到 Connect
export CONNECT_SERVER=https://connect.example.com/
export CONNECT_API_KEY=7C0947A852D8
quarto publish connect --no-render
Terminal
# 渲染并发布到带有显式ID的Connect
export CONNECT_SERVER=https://connect.example.com/
export CONNECT_API_KEY=7C0947A852D8
quarto publish connect --id DDA36416-F950-4647-815C-01A24233E294
Terminal
# 根据_publish.yml渲染并发布到Confluence
export CONFLUENCE_AUTH_TOKEN=7C0947A852D8
export CONFLUENCE_USER_EMAIL=email@domain.com
export CONFLUENCE_DOMAIN=https://domain.atlassian.net/
quarto publish confluence --no-browser --no-prompt