使用可信发布者发布
在 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 发布者的过程是
- 从 OIDC 身份提供者检索OIDC 令牌;
- 将该令牌提交到 PyPI,它将返回一个短期 API 密钥;
- 像往常一样使用该 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 凭据。
首先,确保在您计划从中发布的环境中安装了 id
和 twine
。
python -m pip install -U id twine
如果您不确定服务帐户的电子邮件地址,可以使用以下命令进行验证。
python -m id pypi -d | jq 'select(.email) | .email'
从环境中生成 OIDC 令牌并将其存储起来。受众应该是 pypi
或 testpypi
,具体取决于您发布到的索引。
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 为您构建它。
- 使用 State Tool CLI 运行以下命令。将以上代码块中的占位符值替换为您的 ActiveState 组织名称 - 这通常为
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"
USERNAME-org
(ORGNAME)、包名称 (PKG_NAME) 以及您的 sdist 或源代码包的名称 (PKG_FILENAME),然后运行该命令。注意输出中的 TIMESTAMP。
注意
命名空间必须以 private/
开头,后跟您的组织名称。您也可以根据需要附加其他“文件夹”名称。
- 将您的包发布到 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
- 然后,通过在您的终端中运行
state commit
来将此构建脚本“提交”到系统中。现在您就可以发布到 PyPI 了! - 要将您的轮子发布到 PyPI,请运行:
state eval publish_receipt
。就是这样!
您已成功使用 ActiveState Platform 发布了一个 Python 轮子。
注意
构建脚本技巧
您可以将 pypi_uri
和 audience
字段留空以直接发布到主 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/*