如何在GitHub Actions中运行一个步骤,即使前一步失败,同时仍然使作业失败

292
我正在尝试按照Github提供的示例使用Github Actions测试我的构建,然后将测试结果压缩并上传为artifact。但是当我的测试失败时,我不知道该怎么做。这是我的action。当我的测试通过时,一切都很顺利,我的结果被压缩并导出为artifact,但是如果我的测试失败,它会停止作业中其余的步骤,因此我的结果永远不会被发布。我尝试添加continue-on-error: true,这使得它在失败后继续并上传我的测试结果。但是,作业被标记为已通过,即使我的测试步骤失败了。是否有一种方法可以在步骤失败时上传我的artifact,同时仍将整个作业标记为失败? https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts#uploading-build-and-test-artifacts github-ci-result https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error
name: CI
on:
  pull_request:
    branches:
    - master
  push:
    branches:
      - master

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1    
    - name: Test App
      run: ./gradlew test

    - name: Archive Rest Results
      uses: actions/upload-artifact@v1
      with:
        name: test-results
        path: app/build/reports/tests

你可能还想看一下 continue-on-error。我对它没有太多的经验,但如果你想在特定的 jobstep 中不用担心错误,你可以使用 continue-on-error: true 来允许失败,并且不将整个 job / workflow 标记为失败。 - Joshua Pinter
9个回答

394

你可以添加

if: always()

即使先前的步骤失败,也可以通过以下步骤运行 https://docs.github.com/en/actions/learn-github-actions/expressions#status-check-functions

因此,对于单个步骤,它应该如下所示:

steps:
- name: Build App
  run: ./build.sh

- name: Archive Test Results
  if: always()
  uses: actions/upload-artifact@v1
  with:
    name: test-results
    path: app/build

或者您可以将其添加到作业中:

jobs:
  job1:
  job2:
    needs: job1
  job3:
    if: always()
    needs: [job1, job2]

此外,如下所指出的,加入always()会导致即使构建被取消,函数仍然运行。如果您不希望在手动取消作业时运行该函数,可以改为使用以下代码:
if: success() || failure()

或者

if: '!cancelled()'

(需要引号,以便!cancelled()不被解释为YAML标记。)

同样,如果您想仅在某些操作失败时运行函数,则可以放置:

if: failure()

另外,正如评论中所提到的,如果在if中未使用状态检查函数,比如

if: true

结果会(可能令人困惑地)表现得像

if: success() && true

这在表达式-GitHub文档中有所说明:

状态检查函数

你可以使用以下状态检查函数作为if条件语句中的表达式。除非你包含其中一个函数,否则默认的状态检查是success()


49
请注意,似乎 if: success() 总是隐含的,除非指定了 always()failure() - 即使设置了 if: true。这意味着(也许令人惊讶的是)if: xif: always() && x 不同,在于如果前面的步骤失败或作业被取消,则前者不会运行。 - coldfix
9
文档中的一个提示是:"当您在 if 条件语句中使用表达式时,您可以省略表达式语法(${{ }}),因为 GitHub 会自动将 if 条件语句作为表达式进行计算。" 换句话说,您可以简单地使用 if: always() - Taylor D. Edmiston
3
我向 GitHub 文档提交了一份 PR,以澄清这个问题:https://github.com/github/docs/pull/8411 - Vladimir Panteleev
6
请注意,这会产生一个不良影响,即您无法手动取消工作流程:https://docs.github.com/en/actions/managing-workflow-runs/canceling-a-workflow#steps-github-takes-to-cancel-a-workflow-run - depoulo
25
与“if: always()”不同,“if: success() || failure()”在仍能实现目标的同时保留了取消构建的能力。 - depoulo
显示剩余2条评论

64
另一种方法是,您可以添加continue-on-error: true。 看起来像这样
- name: Job fail
  continue-on-error: true
  run: |
    exit 1
- name: Next job
  run: |
    echo Hello

点击这里阅读更多。

修复了run中缺少冒号的代码。


34
我在原问题中描述了这个解决方案,但这在我的情况下不起作用,因为使用 continue-on-error,失败的任务仍会导致整个构建通过。我仍希望标记为失败的构建仍然是失败的,同时无论之前的任务结果如何都运行此任务。如果你不关心整体构建状态的话,这个解决方案是可行的。 - Tomer Shemesh
4
@TomerShemesh,这正是我在谷歌上搜索的答案! - Explosion
@TomerShemesh 我有完全相同的问题,但是无论是 if: always 还是 continue-on-error: true 都没有起作用。你在测试失败的情况下是否在每个跳过的步骤中都使用了 if: always - Rsaleh
1
@Rsaleh 是的,我已经在每个需要运行的步骤中添加了 if:always,即使出现故障也是如此。 - Tomer Shemesh

28

即使前面的步骤失败了,也要运行 Github Actions 步骤。

如果您只需要在步骤成功或失败时才执行它,则:

steps:
- name: Build App
  run: ./build.sh

- name: Archive Test Results
  if: success() || failure()
  uses: actions/upload-artifact@v1
  with:
    name: test-results
    path: app/build

为什么要使用success() || failure()而非always()

阅读Github上的状态检查函数文档:

always

导致步骤始终执行,并返回true,即使被取消。当关键性失败阻止任务运行时,作业或步骤将不会运行。例如,如果获取源代码失败。

这意味着作业将在被取消时仍然运行,如果这正是您想要的,那么可以继续使用。否则,success() || failure()会更合适。

注意 - 感谢Vladimir Panteleev提交了以下PR以明确说明文档:Github Docs PR #8411


1
仅为了给予荣誉...文档中的“导致步骤始终执行”的措辞是由弗拉基米尔·潘捷列夫在回应这个确切的SO问题时添加的 - 请参见他在上面被接受的答案中的评论,其中链接到他的PR: https://github.com/github/docs/pull/8411/files - Matt

12

插件:如果您遇到以下情况。在某些情况下,即使用输入参数的workflow_dispatch,您可能希望跳过build步骤并继续进行deploy,这需要经过2个步骤,即build> deploy。同时,当build失败时,您可能希望跳过deploy
逻辑上,您可以将skipped or not failed作为deploy条件。
if: always()不能解决问题,因为它会始终触发deploy,即使build失败。
解决方案非常简单:
if: ${{ !failure() }}
请注意,在if:中否定时不能省略括号,因为这会报告语法错误。


if 隐式地添加括号。问题在于 ! 在 yaml 中不允许以未引用的字符串开头。相反,if: '!failure()'if: "!failure()" 可以正常工作。 - Cameron Tacklind

12
这里的其他答案都很好,可以运行,但您可能需要更细致的控制。例如,只有在./test运行成功后才上传./upload
但是,如果其他问题导致测试无法运行,则不要上传。
      # ... Other steps
      - run: ./test
        id: test
      - run: ./upload
        if: success() || steps.test.conclusion == 'failure'

steps.*.conclusion将是successfailurecancelledskipped
successfailure表示步骤已运行。cancelledskipped表示未运行。

请注意,必须在if中测试至少一个success()failure()有一个重要的警告
if: steps.test.conclusion == 'success' || steps.test.conclusion == 'failure'不能按预期工作。


6

2
如果有人想知道,您可以将always()与另一个条件组合,以便在该步骤处理之前发生故障时,仍然执行该步骤。
    steps:

      - name: Exit with error
        run: |
          echo "Erroring"
          exit 1

      - name: Output report 1
        if: true
        run: |
          echo "Report 1"

      - name: Output report 2
        if: always() && true
        run: |
          echo "Report 2"

输出如下: github workflow report

1

.status属性对我无效,我现在使用新的API来访问步骤的结果。新方法能更有效地处理步骤失败。

为了实现这一点,你可以使用if条件语句和failure()表达式。以下是一个示例,展示如何在前一个步骤失败时运行后续步骤:

jobs:
  my-job:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        id: demo
        run: echo "This step will fail" && exit 1
      
      - name: Step 2
        if: ${{ failure() && steps.demo.conclusion == 'failure' }}
        run: echo "This step will run even if the previous step fails"

在上面的例子中,无论步骤1成功与否,步骤2都会执行。if: ${{ failure() }} 条件检查前一步骤是否失败,并允许步骤2继续进行。
您可以参考GitHub Actions文档,了解更多关于使用条件处理步骤失败的详细信息。

-6

你可以在命令中添加 || true。 例如:

 jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1    
    - name: Test App
      run: ./gradlew test || true

4
这并不是一个好的解决方案,因为即使测试失败,测试步骤仍然会被标记为通过。这通常不是我们想要的结果。你仍然希望测试步骤本身失败并将构建标记为失败,但仍希望运行任务,如存档和发布测试结果。无论测试步骤本身是否通过,你都想要执行此操作。按照你的建议做会导致所有构建始终通过,而忽略了测试失败的情况。 - Tomer Shemesh

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