GitHub Actions:仅在特定分支上运行作业

243

我对GitHub Actions相对较新,有两个任务——一个运行我的测试,另一个将我的项目部署到服务器。

显然,我希望测试在每个分支上运行,但只有在推送到主分支时才能进行部署。

我正在努力找到在特定分支上运行作业的方法。我知道可以仅在特定分支上运行整个工作流程,但这意味着我会有一个“测试”工作流程和一个“部署”工作流程。

这听起来像是一种解决方案,但它们将并行运行。在理想情况下,测试将首先运行,只有在测试成功后,部署作业才会启动。但是,当使用2个单独的工作流程时,情况并非如此。

我如何能够实现这一点?是否有可能在特定分支上运行作业


我已经在下面更新了我的答案,加入了在job级别的新的if条件。https://dev59.com/gVMH5IYBdhLWcg3w0TXf#58142412 - peterevans
1
这个问题几年前就被提出来了,但是没有被采纳的答案。我可以建议作者接受下面提供的优质答案之一吗? - ScottishTapWater
13个回答

297
最近的更新中,您现在可以在作业级别上放置if条件语句。请参阅此处的文档。https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif 我测试了这个工作流,在每次推送时运行test作业,但只在主分支上运行deploy作业。
name: my workflow
on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Execute tests
        run: exit 0
  deploy:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/master'
    steps:
      - name: Deploy app
        run: exit 0

以下是我的原始答案和备选方案,如果您希望有单独的工作流,请参见以下内容。
第一个工作流适用于除了“master”之外的所有分支。在此工作流中,您只运行测试。
on:
  push:
    branches:
      - '*'
      - '!master'

第二个工作流仅适用于 master 分支,如果测试成功通过,则运行您的测试并部署。
on:
  push:
    branches:
      - master

16
如果之前的作业成功完成,你可能只想运行部署操作: if: success() && github.ref == 'refs/heads/master' - Jan Dolejsi
2
不错的回答。你能解释一下/refs/heads/...语法吗? - Roly
2
@Roly 这是 git 引用的格式。引用只是一个名称,它指向 git 中特定的提交。这就是分支和标签的工作原理。请参阅此处的文档 - peterevans
13
检查success()并不是必要的:如果你的if表达式不包含任何状态函数,它将自动返回success()。来源请见:https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#job-status-check-functions - Marcono1234
1
回复自己,在Github Actions中并不是一件简单的事情,但是它是可能的,请参见https://dev59.com/gVMH5IYBdhLWcg3w0TXf#62419599。 - goetz
显示剩余3条评论

49

大多数答案只提供了一个单个分支的解决方案。如果想要限制作业在任何特定分支集上运行,可以使用具有多个逻辑或(||)运算符的if条件语句; 但这太冗长并且不遵守DRY原则

同样的效果可以使用contains函数实现更少的重复。

使用contains

contains('
  refs/heads/dev
  refs/heads/staging
  refs/heads/production
', github.ref)

与使用多个 || 相比:

github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/production' || …

完整示例:

---
on: push
jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Run tests
      run: 

  deployment:
    name: Deployment
    runs-on: ubuntu-latest
    needs: [test]
    if:
      contains('
        refs/heads/dev
        refs/heads/staging
        refs/heads/production
      ', github.ref)
    steps:
    - uses: actions/checkout@v2
    - name: Deploy
      run: …

6
我对这个回答进行了投反对票,因为存在误报的可能性。考虑一个“dev”分支和一个“device”分支。当使用"contains"方法时,“dev”也会匹配“device”,从而运行代码。即使你不想这样做。修复的方法可能是用方括号括起来,并将分支写为[refs/heads/dev]。 - sxleixer
3
这是一个很好的观察,根据contains的文档,使用数组实际上可以修复这种子字符串匹配行为。 - goetz
你应该更新你的示例来使用一个数组吗?我不认为数组字面量已经被支持了,但是你可以使用 fromJSON('["val1", "val2"]') - Michael R
1
"dry is a lie"(DRY原则是一个谎言) - airtonix

43

这是我为仅在特定分支上运行的步骤所做的工作。

- name: Publish Image
  # Develop branch only
  if: github.ref == 'refs/heads/develop'
  run: |
    ... publish commands ...

30

2021年更新

我知道可以仅在特定分支上运行整个工作流,但这意味着我需要有一个“测试”工作流和一个“部署”工作流。

这听起来像是一个解决方案,但它们会并行运行。在理想情况下,测试应该首先运行,只有当测试成功后,才会开始部署工作。但是使用两个单独的工作流不是这种情况。

您现在可以使用事件workflow_run来实现部署任务仅在测试成功后才运行(继续阅读了解详细信息):

workflow_run的文档页面

https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#workflow_run

此事件发生在请求或完成工作流运行时,并允许您根据另一个工作流的完成结果执行工作流。无论上一个工作流的结果如何,都会触发工作流运行。

例如,如果您的pull_request工作流生成构建工件,您可以创建一个新的工作流,使用workflow_run分析结果并将评论添加到原始pull请求中。

现在,考虑到OP的初始问题:

我希望测试在每个分支上运行,但只有在推送到master时才进行部署。

现在可以像这样解决此问题:

以下设置正在工作,几分钟前我刚刚在我的一个存储库中实现了相同的逻辑

Workflow <your_repo>/.github/workflows/tests.yml

name: My tests workflow

on:
  push:
    branches:
      - master
  pull_request: {}

jobs:
  test:

    # ... your implementation to run your tests

工作流程 <your_repo>/.github/workflows/deploy.yml

name: My deploy workflow

on:
  workflow_run:
    workflows: My tests workflow # Reuse the name of your tests workflow
    branches: master
    types: completed

jobs:
  deploy:
    # `if` required because a workflow run is triggered regardless of
    # the result of the previous workflow (see the documentation page)
    if: ${{ github.event.workflow_run.conclusion == 'success' }}

    # ... your implementation to deploy your project

2
还有一点需要注意,如果您将最新的工作流程推送到默认分支,那么使用workflow_run过滤并在特定分支上运行的整个过程才能正常工作。我曾经因为创建了一个名为“workflow testing”的单独分支并在那里进行测试,但是无论在哪个分支上都会触发特定的工作流程而苦苦挣扎了数小时,直到我将其推送到默认的“main”分支。 - leonaugust
1
如果我正确阅读了文档,这仅适用于公共存储库。 - abergmeier
@abergmeier 似乎也适用于私有仓库。 - Manuel van Rijn

13

虽然这个讨论非常古老,但最近我遇到了同样的问题,并进行了一些微小的添加。检查分支是否为main的if条件是有效的,但是如果有人推送他们的分支并更新工作流yml文件以删除if条件,该怎么办呢?部署作业将被触发,而他们的分支甚至没有在main中被审核或合并,可能会破坏生产环境。这可能是开源项目中所关注的问题。

我无法在任何地方找到答案,所以想分享我的发现。希望这是正确的主题。

为了确保没有办法触发除特定分支外的作业,可以使用environments。部署作业很可能具有一些api密钥,用于连接到目标服务器,这些密钥可能存储在secrets中。我们应该将它们存储在相应的环境中,而不是存储在可全局访问的存储库secrets中。

关于环境的官方文档包含详细的解释和示例脚本,但这里分享一个简单的例子。假设我们只想在更新main时运行生产部署

  1. 从存储库设置中创建一个production环境
  2. Deployment Branches下拉菜单中选择Selected Branches,并将main添加到模式中
  3. 将api密钥添加到生产环境私密内容中

在工作流yml中,我们只需要添加环境信息environment: production,如下所示(使用@peterevans答案中的脚本)

name: my workflow
on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Execute tests
        run: exit 0
  deploy:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - name: Deploy app
        run: exit 0

环境信息指示密钥的读取位置。如果当前分支名称与“选择的分支”中提供的模式不匹配,则作业将立即失败并显示错误。由于我们设置了仅在“main”上运行此作业的条件,通常情况下这不会困扰我们,因为该作业将在其他分支上跳过。但是,如果有人错误地或恶意地修改yml文件并在推送他们的分支之前移除该条件,他们将收到错误提示。因此,至少从此处威胁来看,我们的系统保持安全。

希望这对任何想了解相关信息的人有所帮助。


8

对于步骤或作业,您还可以使用github.ref_name,它是触发工作流运行的分支或标签名称。

name: my workflow
on: push
jobs:
  if: github.ref_name == 'main'
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Execute tests
        run: exit 0

要了解有关 GitHub 上下文检查的更多信息,请单击这里


4
以下是我在工作中使用的方法:
如果PR目标分支属于以下任意一种:
if: contains(github.base_ref, 'staging')
  || contains(github.base_ref, 'production')
当拉取请求的源分支是以下之一时:
if: contains(github.head_ref, 'feature')
  || contains(github.head_ref, 'release')

4

虽然目前无法在作业级别上设置条件,但可以通过在步骤级别上添加条件来实现 - 请参阅GitHub操作的上下文和表达式语法

要获取分支名称,当前解决方案是检查GITHUB_REF环境变量 - 请参阅默认环境变量此问题以了解详细信息。

将所有内容组合起来 - 如果决定使用上面链接中的被接受的答案,您的工作流可能如下所示:

jobs:

 test:
  runs-on: ubuntu-latest
  steps:
  - name: Run tests
    run: ./my-tests.sh

 deploy:
  runs-on: ubuntu-latest
  needs: test
  steps:
  - name: Extract branch name
    shell: bash
    run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})"
    id: extract_branch
  - name: Deploy
    run: ./deploy.sh
    if: steps.extract_branch.outputs.branch == 'master'

如果您更喜欢将所有内容保留在工作流文件中而不是分离的脚本中,您可以在给定作业的每个步骤中添加if。我希望这只是一个临时解决方案/变通方法,并且作业条件将在beta结束之前添加。

3
name: CI
on: push
jobs:
  prod-check:
    if: ${{ github.ref == 'refs/heads/main' }}
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying to production server on branch $GITHUB_REF"

根据文档,if条件语句的格式如下:if: ${{ github.ref == 'refs/heads/main' }}

1

您需要将这两个脚本添加到工作流中

//main.yml
name: main
on:
  push:
    branches:
      - main


//size.yml     
name: size
on:
  pull_request:
    branches:
      - main

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