跳至内容

使用可信发布者发布

在 PyPI 上配置可信发布者(无论是“待定”还是“正常”)后,您就可以通过相关平台上的可信发布者进行发布。以下选项卡描述了每个受支持的可信发布者的设置过程。

简单方法

您可以使用 PyPA 的 pypi-publish 操作发布您的包。

这看起来与正常操作几乎完全相同,不同之处在于您不需要任何显式用户名、密码或 API 令牌:GitHub 的 OIDC 身份提供者将为您处理所有操作。

jobs:
  pypi-publish:
    name: upload release to PyPI
    runs-on: ubuntu-latest
    # Specifying a GitHub environment is optional, but strongly encouraged
    environment: release
    permissions:
      # IMPORTANT: this permission is mandatory for trusted publishing
      id-token: write
    steps:
      # retrieve your distributions here

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

如果您正在从基于密码或 API 令牌的身份验证流程迁移,那么您的差异可能看起来像这样。

jobs:
  pypi-publish:
    name: upload release to PyPI
    runs-on: ubuntu-latest
+    # Specifying a GitHub environment is optional, but strongly encouraged
+    environment: release
+    permissions:
+      # IMPORTANT: this permission is mandatory for trusted publishing
+      id-token: write
    steps:
      # retrieve your distributions here

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
-        with:
-          username: __token__
-          password: ${{ secrets.PYPI_TOKEN }}

请注意 id-token: write 权限:您**必须**在作业级别(**强烈建议**)或工作流级别(**不建议**)提供此权限。没有它,发布操作将没有足够的权限来向 PyPI 标识自己。

注意

强烈建议在作业级别使用此权限,因为它可以减少不必要的凭据泄露。

发布到除 PyPI 之外的索引

PyPA 的 pypi-publish 操作还支持与其他(非 PyPI)索引一起使用可信发布,前提是它们启用了可信发布(并且您已在其上配置了可信发布者)。例如,以下是如何在 TestPyPI 上使用可信发布。

- name: Publish package distributions to TestPyPI
  uses: pypa/gh-action-pypi-publish@release/v1
  with:
    repository-url: https://test.pypi.org/legacy/

手动方式

警告

停止!您可能不需要此部分;它仅用于提供有关 GitHub Actions 和 PyPI 如何使用 OIDC 协调的一些内部详细信息。如果您是普通用户,强烈建议您使用 PyPA 的 pypi-publish 操作。

警告

下面描述的许多详细信息是特定于实现的,既不受标准化流程约束,也不受兼容性保证约束。它们不是公共接口的一部分,可能会随时更改。对于稳定的公共接口,您**必须**使用 pypi-publish 操作。

使用 OIDC 发布者的过程是

  1. 从 OIDC 身份提供者检索OIDC 令牌
  2. 将该令牌提交到 PyPI,它将返回一个短期 API 密钥;
  3. 像往常一样使用该 API 密钥(例如,使用 twine

以下所有代码都假设它在具有 id-token: write 权限的 GitHub Actions 工作流运行器中运行。该权限至关重要;没有它,GitHub Actions 将拒绝为您提供 OIDC 令牌。

首先,让我们从 GitHub Actions 中获取 OIDC 令牌。

resp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
    "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")

注意

使用 audience=pypi 仅适用于 PyPI。对于 TestPyPI,正确的受众是 testpypi。更一般地说,您可以通过 {index}/_/oidc/audience 端点访问任何实例的预期 OIDC 受众。

$ curl https://pypi.ac.cn/_/oidc/audience
{"audience":"pypi"}

对此的响应将是一个 JSON 块,其中包含 OIDC 令牌。我们可以使用 jq 将其提取出来。

oidc_token=$(jq '.value' <<< "${resp}")

最后,我们可以将该令牌提交到 PyPI 并获取一个短期 API 密钥。

resp=$(curl -X POST https://pypi.ac.cn/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
api_token=$(jq -r '.token' <<< "${resp}")

# tell GitHub Actions to mask the token in any console logs,
# to avoid leaking it
echo "::add-mask::${api_token}"

此 API 密钥可以馈送到 twine 或任何其他上传客户端。

TWINE_USERNAME=__token__ TWINE_PASSWORD=${api_token} twine upload dist/*

所有这些都可以整合到一个 GitHub Actions 工作流中。

on:
  release:
    types:
      - published

name: release

jobs:
  pypi:
    name: upload release to PyPI
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-python@v4
        with:
          python-version: "3.x"

      - name: deps
        run: python -m pip install -U build

      - name: build
        run: python -m build

      - name: mint API token
        id: mint-token
        run: |
          # retrieve the ambient OIDC token
          resp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")
          oidc_token=$(jq -r '.value' <<< "${resp}")

          # exchange the OIDC token for an API token
          resp=$(curl -X POST https://pypi.ac.cn/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
          api_token=$(jq -r '.token' <<< "${resp}")

          # mask the newly minted API token, so that we don't accidentally leak it
          echo "::add-mask::${api_token}"

          # see the next step in the workflow for an example of using this step output
          echo "api-token=${api_token}" >> "${GITHUB_OUTPUT}"

      - name: publish
        # gh-action-pypi-publish uses TWINE_PASSWORD automatically
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          password: ${{ steps.mint-token.outputs.api-token }}

您可以使用 https://pypi.ac.cn/project/id/ 工具在 Google Cloud 服务上自动检测和生成 OIDC 凭据。

首先,确保在您计划从中发布的环境中安装了 idtwine

python -m pip install -U id twine

如果您不确定服务帐户的电子邮件地址,可以使用以下命令进行验证。

python -m id pypi -d | jq 'select(.email) | .email'

从环境中生成 OIDC 令牌并将其存储起来。受众应该是 pypitestpypi,具体取决于您发布到的索引。

oidc_token=$(python -m id pypi)

注意

pypi 仅适用于 PyPI。对于 TestPyPI,正确的受众是 testpypi。更一般地说,您可以通过 {index}/_/oidc/audience 端点访问任何实例的预期 OIDC 受众。

$ curl https://pypi.ac.cn/_/oidc/audience
{"audience":"pypi"}

最后,我们可以将该令牌提交到 PyPI 并获取一个短期 API 密钥。

resp=$(curl -X POST https://pypi.ac.cn/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
api_token=$(jq -r '.token' <<< "${resp}")

注意

这是 PyPI 的 URL。对于 TestPyPI,正确的域名应该是 test.pypi.org

此 API 密钥可以馈送到 twine 或任何其他上传客户端。

TWINE_USERNAME=__token__ TWINE_PASSWORD=${api_token} twine upload dist/*

ActiveState 的 Platform 作为您依赖项的零配置 CI 解决方案,可以自动构建 PyPI 项目的跨平台轮子。在 Platform 上设置并链接您的 PyPI 项目后,您就可以发布了。有关 ActiveState 入门的更多信息,请访问 此处。要开始

将您的包发布到 ActiveState 的目录中。这将允许 ActiveState 的 Platform 为您构建它。

  1. 使用 State Tool CLI 运行以下命令。
    state publish \
      --namespace private/ORGNAME \
      --name PKG_NAME PKG_FILENAME \
      --depend "builder/python-module-builder@>=0" \
      --depend "language/python@>=3" \
      --depend "language/python/setuptools@>=43.0.0" \
      --depend "language/python/wheel@>=0"
    
    将以上代码块中的占位符值替换为您的 ActiveState 组织名称 - 这通常为 USERNAME-org (ORGNAME)、包名称 (PKG_NAME) 以及您的 sdist 或源代码包的名称 (PKG_FILENAME),然后运行该命令。注意输出中的 TIMESTAMP。

注意

命名空间必须以 private/ 开头,后跟您的组织名称。您也可以根据需要附加其他“文件夹”名称。

  1. 将您的包发布到 ActiveState 后,您需要创建一个构建脚本文件 (buildscript.as) 来将其构建成轮子并发布到 PyPI。以下显示了一个示例脚本。在您的 activestate.yaml 文件所在的同一个文件夹中创建一个新的构建脚本文件,并将其命名为 buildscript.as。粘贴以下代码,将占位符值替换为您的项目中的值:您刚刚发布的包的时间戳 (PUBLISHED_TIMESTAMP)、命名空间的名称(即您发布该成分的文件夹,它看起来像 private/USERNAME-org)(NAMESPACE)、您的包的名称 (PKG_NAME) 以及您要发布的版本 (VERSION)。保存对该文件的更改。
    at_time = "PUBLISHED_TIMESTAMP"
    
    publish_receipt = pypi_publisher(
      attempt = 1,
      audience = "testpypi",
      pypi_uri = "test.pypi.org",
      src = wheels
    )
    runtime = state_tool_artifacts(
      build_flags = [
      ],
      src = sources
    )
    sources = solve(
      at_time = at_time,
      platforms = [
        "7c998ec2-7491-4e75-be4d-8885800ef5f2"
      ],
      requirements = [
        Req(namespace = "language", name = "python", version = Eq(value="3.10.13")),
        Req(namespace = "NAMESPACE", name = "PKG_NAME", version = Eq(value="VERSION"))
      ],
      solver_version = null
    )
    wheel_srcs = select_ingredient(
      namespace = "NAMESPACE",
      name = "PKG_NAME",
      src = sources
    )
    wheels = make_wheel(
      at_time = at_time,
      python_version = "3.10.13",
      src = wheel_srcs
    )
    
    main = runtime
    
  2. 然后,通过在您的终端中运行 state commit 来将此构建脚本“提交”到系统中。现在您就可以发布到 PyPI 了!
  3. 要将您的轮子发布到 PyPI,请运行:state eval publish_receipt。就是这样!

您已成功使用 ActiveState Platform 发布了一个 Python 轮子。

注意

构建脚本技巧

您可以将 pypi_uriaudience 字段留空以直接发布到主 PyPI 存储库。

如果您遇到网络超时或其他瞬态错误,您可以增加 attempt 参数以重试。

platforms = [ 之后的字符串是您要为其构建轮子的支持平台的 UUID。可以在 此处 找到所有受支持平台的列表。从提供的列表中选择适用于您的项目的所有平台。

注意

如果您想在发布之前测试您的轮子,请在运行 state eval publish_receipt 之前执行以下步骤:1. 要单独构建您的轮子,请运行 state eval wheels 2. 构建轮子后,运行 state builds --all 查看所有可用的构建。注意新轮子的 HASH_ID。3. 运行 state builds dl <HASH_ID> 下载并测试您构建的轮子。

这是一个示例 GitLab 工作流,它使用可信发布构建并发布一个包到 PyPI。与普通工作流的关键区别在于部署步骤 (publish-job)

  • 关键字 id_tokens 用于从 GitLab 请求一个名为 PYPI_ID_TOKEN 且受众为 pypi 的 OIDC 令牌。
  • 此 OIDC 令牌使用 id 包从 CI/CD 环境中提取。
  • 然后将 OIDC 令牌发送到 PyPI 以换取 PyPI API 令牌,然后使用 twine 发布该包。
build-job:
  stage: build
  image: python:3-bookworm
  script:
    - python -m pip install -U build
    - cd python_pkg && python -m build
  artifacts:
    paths:
      - "python_pkg/dist/"

publish-job:
  stage: deploy
  image: python:3-bookworm
  dependencies:
    - build-job
  id_tokens:
    PYPI_ID_TOKEN:
      # Use "testpypi" if uploading to TestPyPI
      aud: pypi
  script:
    # Install dependencies
    - apt update && apt install -y jq
    - python -m pip install -U twine id

    # Retrieve the OIDC token from GitLab CI/CD, and exchange it for a PyPI API token
    - oidc_token=$(python -m id PYPI)
    # Replace "https://pypi.ac.cn/*" with "https://test.pypi.org/*" if uploading to TestPyPI
    - resp=$(curl -X POST https://pypi.ac.cn/_/oidc/mint-token -d "{\"token\":\"${oidc_token}\"}")
    - api_token=$(jq --raw-output '.token' <<< "${resp}")

    # Upload to PyPI authenticating via the newly-minted token
    # Add "--repository testpypi" if uploading to TestPyPI
    - twine upload -u __token__ -p "${api_token}" python_pkg/dist/*