GitHub-action 构建的最后一个构件的 URL

20

我使用GitHub-Action进行构建,它会生成多个带有不同名称的构件。

是否有一种方法可以预测最后一次成功构建的构件URL? 不需要知道sha1,只需要知道构件的名称和仓库?

5个回答

13
我开发了一个服务,可以将存储库的分支+工作流的最新或特定构件暴露出可预测的URL。

https://nightly.link/
https://github.com/oprypin/nightly.link

这是一个GitHub应用程序,与GitHub的通信已经得到了认证,但仅下载的用户甚至不需要登录GitHub。
实现通过 API 进行以下 3 步获取:
  • https://api.github.com/repos/:owner/:repo/actions/workflows/someworkflow.yml/runs?per_page=1&branch=master&event=push&status=success
  • https://api.github.com/repos/:owner/:repo/actions/runs/123456789/artifacts?per_page=100
  • https://api.github.com/repos/:owner/:repo/actions/artifacts/87654321/zip

最后一个链接会将您重定向到一个短暂的直接下载URL。

请注意,需要进行身份验证。对于OAuth,这是public_repo(如果适用,则为repo)。对于GitHub应用程序,这是“Actions”/“只读”。


确实没有更直接的方法来做这件事。
一些相关问题包括:

如果要求最终用户在GitHub上登录不是一个阻碍问题,您可能会对我的答案感兴趣,它仅依赖于构件的URL命名方案(将这些URL公开为提交状态)。否则,@OlehPrypin的答案是正确的选择! - ErikMD

7
目前来看,根据工作人员的评论,似乎不行,尽管未来版本的upload-artifact操作可能会改变这一点。
经过我的探索,可以使用GitHub操作API实现此功能: https://developer.github.com/v3/actions/artifacts/

GET /repos/:owner/:repo/actions/runs/:run_id/artifacts

因此,您可以接收JSON回复并遍历“artifacts”数组以获取相应的“archive_download_url”。 工作流可以像这样填写URL:

/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts


问题在于,您需要进行一些处理才能过滤出最后一个。如果您使用的是期望某些静态URL的东西,那么这将会很麻烦。 - sab
哦,这真是太痛苦了 - 直到工作流程完成之前,你甚至什么都做不了。你需要在第二个工作流中运行查询(!?),但现在几乎不可能。 - Geoff Hutchison
技术上说,我需要从另一个工作流程存储库下载该构件。 - sab

5
您可以使用命令行 JSON 处理工具 jq,结合 curl 命令来提取 URL,操作如下。
curl -s https://api.github.com/repos/<OWNER>/<REPO_NAME>/actions/artifacts\?per_page\=<NUMBER_OF_ARTIFACTS_PER_BUILD> | jq '[.artifacts[] | {name : .name, archive_download_url : .archive_download_url}]' | jq -r '.[] | select (.name == "<NAME_OF_THE_ARTIFACT>") | .archive_download_url'

例如:
curl -s https://api.github.com/repos/ballerina-platform/ballerina-distribution/actions/artifacts\?per_page\=9 | jq '[.artifacts[] | {name : .name, archive_download_url : .archive_download_url}]' | jq -r '.[] | select (.name == "Linux Installer deb") | .archive_download_url'

这里,curl -s https://api.github.com/repos/<OWNER>/<REPO_NAME>/actions/artifacts\?per_page\=<NUMBER_OF_ARTIFACTS_PER_BUILD> 返回与最新构建相关的工件数组。

jq '[.artifacts[] | {name : .name, archive_download_url : .archive_download_url}]' 提取工件数组并过滤所需数据。

jq -r '.[] | select (.name == "<NAME_OF_THE_ARTIFACT>") | .archive_download_url' 选择给定工件名称的url


非常感谢您的回答。 - Adi Roiban
@AdiRoiban 或者将 per_page\=9 替换为 per_page\=1。需要注意的是,这要求返回的构件按时间顺序降序排序,但似乎情况确实如此。 - BallpointBen

1

总结

最近我遇到了一个类似的用例,目的是通过单击提交状态(虽然需要用户登录github.com)使给定GitHub Actions工作流的构建产物更加可见

正如@geoff-hutchison他的答案中所指出的:

不过:

  • 无法从当前工作流程的作业中查询工作流程生成的工件列表;需要将请求推迟到后续工作流程
  • 相应JSON响应中包含的archive_download_url URL似乎不太实用(它们是GitHub API URL,重定向到临时 URL)。

幸运的是:

  • 浏览GitHub Actions页面中已经提供的工作流构建工件URL清晰地显示它们具有以下形式:https://github.com/${{ github.repository }}/suites/${check_suite_id}/artifacts/${artifact_id},并且这些URL是静态的(尽管仅对已登录用户和最多90天的工件过期时间有效)。

实现

因此,我开发了以下通用代码(根据MIT许可证)来将给定工作流的所有工件固定在提交状态中(只需替换3个TODO字符串):

.github/workflows/pin-artifacts.yml

name: Pin artifacts
on:
  workflow_run:
    workflows:
      - "TODO-Name Of Existing Workflow"
    types: ["completed"]
jobs:
  # Make artifacts links more visible for the upstream project
  pin-artifacts:
    permissions:
      statuses: write
    name: Add artifacts links to commit statuses
    if: ${{ github.event.workflow_run.conclusion == 'success' && github.repository == 'TODO-orga/TODO-repo' }}
    runs-on: ubuntu-latest
    steps:
      - name: Add artifacts links to commit status
        run: |
          set -x
          workflow_id=${{ github.event.workflow_run.workflow_id }}
          run_id=${{ github.event.workflow_run.id }}  # instead of ${{ github.run_id }}
          run_number=${{ github.event.workflow_run.run_number }}
          head_branch=${{ github.event.workflow_run.head_branch }}
          head_sha=${{ github.event.workflow_run.head_sha }}  # instead of ${{ github.event.pull_request.head.sha }} (or ${{ github.sha }})
          check_suite_id=${{ github.event.workflow_run.check_suite_id }}
          set +x

          curl \
          -H "Accept: application/vnd.github+json" \
          "https://api.github.com/repos/${{ github.repository }}/actions/runs/${run_id}/artifacts" \
          | jq '[.artifacts | .[] | {"id": .id, "name": .name, "created_at": .created_at, "expires_at": .expires_at, "archive_download_url": .archive_download_url}] | sort_by(.name)' \
          > artifacts.json

          cat artifacts.json

          < artifacts.json jq -r ".[] | \
            .name + \"§\" + \
            ( .id | tostring | \"https://github.com/${{ github.repository }}/suites/${check_suite_id}/artifacts/\" + . ) + \"§\" + \
            ( \"Link to \" + .name + \".zip [\" + ( .created_at | sub(\"T.*\"; \"→\") ) + ( .expires_at | sub(\"T.*\"; \"] (you must be logged)\" ) ) )" \
          | while IFS="§" read name url descr; do
              curl \
              -X POST \
              -H "Accept: application/vnd.github+json" \
              -H "Authorization: Bearer ${{ github.token }}" \
              "https://api.github.com/repos/${{ github.repository }}/statuses/${head_sha}" \
              -d "$( printf '{"state":"%s","target_url":"%s","description":"%s","context":"%s"}' "${{ github.event.workflow_run.conclusion }}" "$url" "$descr" "$name (artifact)" )"
          done

(如有需要,还可以查看我的PR ocaml-sf/learn-ocaml#501以查看实现示例和此GHA工作流程的屏幕截图。)


0

我不是GitHub和jq大师。可能有更优化的解决方案。

jq playground链接https://jqplay.org/s/Gm0kRcv63C - 用于测试我的解决方案和其他可能的想法。我删除了一些无关的字段以缩小示例JSON的大小(例如:node_id,size_in_bytes,created_at...)下面的代码示例中有更多详细信息。

####### You can get the max date of your artifacts.
####### Then you need to choose the artifact entry by this date.
#######
####### NOTE: I just pre-formatted the first command "line".
####### 2nd "line" has a very similar, but simplified structure.
####### (at least easy to copy-paste into jq playground)

####### NOTE: ASSUMPTION:
####### First "page" of json response contains the most recent entries
#######   AND includes artifact(s) with that specific name.
#######
####### (if other artifacts flood your API response, you can increase
#######  the page size of it or do iteration on pages as a workaround)

bash$ cat artifact_response.json | \
  jq '
    (
      [
        .artifacts[]
        | select(.name == "my-artifact" and .expired == false)
        | .updated_at
      ]
      | max
    ) as $max_date
    | { $max_date }'

####### output
{ "max_date": "2021-04-29T11:22:20Z" }

另一种方式:

####### Latest ID of non-expired artifacts with a specific name.
####### Probably this is better solution than the above since you
####### can use the "id" instantly in your download url construction:
#######
####### "https://api.github.com/repos/blabla.../actions/artifacts/92394368/zip"
#######
####### ASSUMPTION: higher "id" means higher "update date" in your workflow
####### (there is no post-update of artifacts aka creation and
#######  update dates are identical for an artifact)


cat artifact_response.json | \
jq '[ .artifacts[] | select(.name == "my-artifact" and .expired == false) | .id ] | max'

####### output
92394368

更紧凑的过滤器,假设API响应中按日期或ID的相反顺序排列:
####### no full command line, just jq filter string
#######
####### no "max" function, only pick the first element by index
#######
'[ .artifacts[] | select(.name == "my-artifact" and .expired == false) | .id ][0]'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接