Github Actions:如何在脚本中使用策略/矩阵

22

我有一个需要在步骤中循环的工作流,使用策略/矩阵非常完美。

唯一的问题是策略/矩阵需要由常数设置。

是否可能使用脚本输出来使用策略矩阵?

name: tests
on: [push]

jobs:
  test:
    runs-on: ${{ ubuntu-latest }}
    strategy:
      fail-fast: false
      matrix:
        versions: $(./script.py)

    steps:
    - uses: actions/checkout@v2
 .......
3个回答

52
你可以在一个作业中生成JSON矩阵,并将其设置为第二个作业。
GitHub于四月份添加了此功能:https://github.blog/changelog/2020-04-15-github-actions-new-workflow-features/ 工作流示例
name: build
on: push
jobs:
  job1:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
    - id: set-matrix
      run: echo "::set-output name=matrix::{\"include\":[{\"project\":\"foo\",\"config\":\"Debug\"},{\"project\":\"bar\",\"config\":\"Release\"}]}"
  job2:
    needs: job1
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{fromJson(needs.job1.outputs.matrix)}}
    steps:
    - run: echo ${{ matrix.project }}
    - run: echo ${{ matrix.config }}

首先,该作业将输出变量matrix设置为包含两个配置的JSON。

{
  "include": [
    {
      "project": "foo",
      "config": "Debug"
    },
    {
      "project": "bar",
      "config": "Release"
    }
  ]
}

.yml 中的等效语句:

  job2:
    strategy:
      matrix:
        include:
        - project: foo
          config: Debug
        - project: bar
          config: Release

不要忘记转义引号\"并将JSON打印在一行中。

更复杂的工作流程示例

检测已更改的文件并对更改的目录运行构建作业。如果目录名称以操作系统名称开头,则使用该名称作为运行位置。

name: Build
on: [push, pull_request]

jobs:

  generate-matrix:
    name: Generate matrix for build
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v2
      - name: Check changed files
        id: diff
        run: |
          # See https://github.community/t/check-pushed-file-changes-with-git-diff-tree-in-github-actions/17220/10
          if [ $GITHUB_BASE_REF ]; then
            # Pull Request
            git fetch origin $GITHUB_BASE_REF --depth=1
            export DIFF=$( git diff --name-only origin/$GITHUB_BASE_REF $GITHUB_SHA )
            echo "Diff between origin/$GITHUB_BASE_REF and $GITHUB_SHA"
          else
            # Push
            git fetch origin ${{ github.event.before }} --depth=1
            export DIFF=$( git diff --name-only ${{ github.event.before }} $GITHUB_SHA )
            echo "Diff between ${{ github.event.before }} and $GITHUB_SHA"
          fi
          echo "$DIFF"
          # Escape newlines (replace \n with %0A)
          echo "diff=$( echo "$DIFF" | sed ':a;N;$!ba;s/\n/%0A/g' )" >> $GITHUB_OUTPUT
      - name: Set matrix for build
        id: set-matrix
        run: |
          # See https://dev59.com/S1IH5IYBdhLWcg3wWcvO#62953566
          DIFF="${{ steps.diff.outputs.diff }}"
          JSON="{\"include\":["

          # Loop by lines
          while read path; do
            # Set $directory to substring before /
            directory="$( echo $path | cut -d'/' -f1 -s )"

            if [ -z "$directory" ]; then
              continue # Exclude root directory
            elif [ "$directory" == docs ]; then
              continue # Exclude docs directory
            elif [ "$path" == *.rst ]; then
              continue # Exclude *.rst files
            fi

            # Set $os. "ubuntu-latest" by default. if directory starts with windows, then "windows-latest"
            os="ubuntu-latest"
            if [ "$directory" == windows* ]; then
              os="windows-latest"
            fi

            # Add build to the matrix only if it is not already included
            JSONline="{\"directory\": \"$directory\", \"os\": \"$os\"},"
            if [[ "$JSON" != *"$JSONline"* ]]; then
              JSON="$JSON$JSONline"
            fi
          done <<< "$DIFF"

          # Remove last "," and add closing brackets
          if [[ $JSON == *, ]]; then
            JSON="${JSON%?}"
          fi
          JSON="$JSON]}"
          echo $JSON

          # Set output
          echo "matrix=$( echo "$JSON" )" >> $GITHUB_OUTPUT

  build:
    name: Build "${{ matrix.directory }}" on ${{ matrix.os }}
    needs: generate-matrix
    strategy:
      matrix: ${{fromJson(needs.generate-matrix.outputs.matrix)}}
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - name: Build
        run: |
          cd ${{ matrix.directory }}
          echo "${{ matrix.directory }} ${{ matrix.os }}"

你能提供适用于多行JSON和矩阵转换的PowerShell替代方案吗? - wehelpdox

2

新增一个例子,这是一个非常有帮助的答案。感谢 @ArtemSBulgakov !

这个例子使用 Github Actions 的 strategy.matrixfromJson 来仅收集 Pull Request 中更改的目录,并使用 https://github.com/dflook/terraform-github-actions 进行 Terraform 的语法和格式审查。

---
name: Check Syntax
on: [pull_request]

jobs:
  generate-matrix:
    name: Generate matrix for build
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Check changed files
        id: diff
        run: |
          # See https://github.community/t/check-pushed-file-changes-with-git-diff-tree-in-github-actions/17220/10
          export DIFF=$( git diff --dirstat=files,0,cumulative ${{ github.event.pull_request.base.sha }} | awk -F ' ' '{print $2}' )
          echo "$DIFF"
          # Escape newlines (replace \n with %0A)
          echo "::set-output name=diff::$( echo "$DIFF" | sed ':a;N;$!ba;s/\n/%0A/g' )"
      - name: Set matrix for build
        id: set-matrix
        run: |
          # See https://dev59.com/S1IH5IYBdhLWcg3wWcvO#62953566
          DIFF="${{ steps.diff.outputs.diff }}"
          JSON="{\"tfpaths\":["

          # Loop by lines
          while read path; do
          # Add item to the matrix only if it is not already included
          JSONline="\"$path\","
          if [[ "$JSON" != *"$JSONline"* ]]; then
          JSON="$JSON$JSONline"
          fi
          done <<< "$DIFF"

          # Remove last "," and add closing brackets
          if [[ $JSON == *, ]]; then
          JSON="${JSON%?}"
          fi
          JSON="$JSON]}"
          echo $JSON
          # Set output
          echo "::set-output name=matrix::$( echo "$JSON" )"

  validate:
    name: Check Terraform syntax on "${{ matrix.tfpaths }}"
    needs: generate-matrix
    strategy:
      matrix: ${{fromJson(needs.generate-matrix.outputs.matrix)}}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: terraform validate
        uses: dflook/terraform-validate@v1
        with:
          path: ${{ matrix.tfpaths }}

  check-format:
    name: Check Terraform format on "${{ matrix.tfpaths }}"
    needs: generate-matrix
    strategy:
      matrix: ${{fromJson(needs.generate-matrix.outputs.matrix)}}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: terraform fmt
        uses: dflook/terraform-fmt-check@v1
        with:
          path: ${{ matrix.tfpaths }}


0

我使用了@ArtemSBulgakov的解决方案作为起点,但在尝试从其他输出生成matrix时遇到了困难,而不是显式的JSON字符串。

如果您和我一样想要以其他outputs的方式提供matrix,请参见下文。


在这个例子中,我想使用octokit/request-action操作从GitHub获取最新的拉取请求,然后对每个拉取请求进行一些检查。听起来很简单,但是将输出(即steps.fetch.outputs.data)转换成像这样的东西...
{
  "includes": [{ "number": 1, "title": "my first pr " }]
}

...证明比我预期的要困难得多。您可能更擅长使用GitHub提供的其中一个可用的shell,但是您仍然必须以某种方式将output值传递给run脚本,然后再次传出。如果有人知道一种简单的方法来做到这一点,我会很高兴看到它。

因此,我决定创建{{link2:nickofthyme/object-remap}} GitHub操作,以使这个过程变得更容易。我不会介绍所有用法(请参阅README.md),但使用Object filters(即.*.)设置matrix.includes的示例用法如下...

name: 'PR Check'

on:
  schedule:
    - cron:  '0 0 * * *' # once a day

jobs:
  fetch:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.save.outputs.json }}
    steps:
      - name: Fetch GH pulls
        id: fetch
        uses: octokit/request-action@v2.x
        with:
          route: GET /repos/{repo}/pulls?state=open
          repo: ${{ github.repository }}
        env:
          GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
      - name: Store matrix
        id: save
        uses: nickofthyme/object-remap@v1
        with:
          include.*.number: ${{ toJSON(fromJSON(steps.fetch.outputs.data).*.number) }}
          include.*.title: ${{ toJSON(fromJSON(steps.fetch.outputs.data).*.title) }}
          include.*.username: ${{ toJSON(fromJSON(steps.fetch.outputs.data).*.user.login) }}

  pr-checks:
    name: "PR #${{ matrix.number }} - ${{ matrix.title }}"
    runs-on: ubuntu-latest
    needs: fetch
    strategy:
      matrix: ${{ fromJSON(needs.fetch.outputs.matrix) }}
      fail-fast: false
    steps:
      - name: Echo pr number
        run: echo "pr number: ${{ matrix.number }}"
      - name: Echo title
        run: echo "title: ${{ matrix.title }}"
      - name: Echo username
        run: echo "username: ${{ matrix.username }}"

例如,如果在react的前2个PR上运行此工作流程。
curl https://api.github.com/repos/facebook/react/pulls?per_page=2&direction=asc&state=all

steps.fetch.outputs.data,为了简洁起见,省略了headbase_links,结果如下:

[
  {
      "url": "https://api.github.com/repos/facebook/react/pulls/1",
      "id": 6001916,
      "node_id": "MDExOlB1bGxSZXF1ZXN0NjAwMTkxNg==",
      "html_url": "https://github.com/facebook/react/pull/1",
      "diff_url": "https://github.com/facebook/react/pull/1.diff",
      "patch_url": "https://github.com/facebook/react/pull/1.patch",
      "issue_url": "https://api.github.com/repos/facebook/react/issues/1",
      "number": 1,
      "state": "closed",
      "locked": false,
      "title": "Run each test in its own <iframe>",
      "user": {
          "login": "benjamn",
          "id": 5750,
          "node_id": "MDQ6VXNlcjU3NTA=",
          "avatar_url": "https://avatars.githubusercontent.com/u/5750?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/benjamn",
          "html_url": "https://github.com/benjamn",
          "followers_url": "https://api.github.com/users/benjamn/followers",
          "following_url": "https://api.github.com/users/benjamn/following{/other_user}",
          "gists_url": "https://api.github.com/users/benjamn/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/benjamn/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/benjamn/subscriptions",
          "organizations_url": "https://api.github.com/users/benjamn/orgs",
          "repos_url": "https://api.github.com/users/benjamn/repos",
          "events_url": "https://api.github.com/users/benjamn/events{/privacy}",
          "received_events_url": "https://api.github.com/users/benjamn/received_events",
          "type": "User",
          "site_admin": false
      },
      "body": "This is not blocking the initial launch, so feel free to put it on the back-burner for now.\n\nThe Jasmine test harness still runs in the parent window and reports to PhantomJS via `window.callPhantom`, but each test `<iframe>` has its own copy of `react-test.js` and each individual test module is required in the global context of a separate `<iframe>`.\n\nThis gives us a significant approximation of the benefits of mocking, at least in terms of isolating tests from one another.\n\ncr @jeffmo @zpao\n",
      "created_at": "2013-05-29T20:20:53Z",
      "updated_at": "2014-07-16T22:39:07Z",
      "closed_at": "2013-06-03T17:58:02Z",
      "merged_at": "2013-06-03T17:58:02Z",
      "merge_commit_sha": "7a72883d48e00854a41a1cdff99a2544c1721dcc",
      "assignee": null,
      "assignees": [],
      "requested_reviewers": [],
      "requested_teams": [],
      "labels": [],
      "milestone": null,
      "draft": false,
      "commits_url": "https://api.github.com/repos/facebook/react/pulls/1/commits",
      "review_comments_url": "https://api.github.com/repos/facebook/react/pulls/1/comments",
      "review_comment_url": "https://api.github.com/repos/facebook/react/pulls/comments{/number}",
      "comments_url": "https://api.github.com/repos/facebook/react/issues/1/comments",
      "statuses_url": "https://api.github.com/repos/facebook/react/statuses/603c9ef6a8d70d3cf29ee9d0a9d7969abce48ac4",
      "head": {},
      "base": {},
      "_links": {},
      "author_association": "CONTRIBUTOR",
      "auto_merge": null,
      "active_lock_reason": null
  },
  {
      "url": "https://api.github.com/repos/facebook/react/pulls/2",
      "id": 6002192,
      "node_id": "MDExOlB1bGxSZXF1ZXN0NjAwMjE5Mg==",
      "html_url": "https://github.com/facebook/react/pull/2",
      "diff_url": "https://github.com/facebook/react/pull/2.diff",
      "patch_url": "https://github.com/facebook/react/pull/2.patch",
      "issue_url": "https://api.github.com/repos/facebook/react/issues/2",
      "number": 2,
      "state": "closed",
      "locked": false,
      "title": "[docs] Fix button links on bottom of home",
      "user": {
          "login": "paulshen",
          "id": 2266187,
          "node_id": "MDQ6VXNlcjIyNjYxODc=",
          "avatar_url": "https://avatars.githubusercontent.com/u/2266187?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/paulshen",
          "html_url": "https://github.com/paulshen",
          "followers_url": "https://api.github.com/users/paulshen/followers",
          "following_url": "https://api.github.com/users/paulshen/following{/other_user}",
          "gists_url": "https://api.github.com/users/paulshen/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/paulshen/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/paulshen/subscriptions",
          "organizations_url": "https://api.github.com/users/paulshen/orgs",
          "repos_url": "https://api.github.com/users/paulshen/repos",
          "events_url": "https://api.github.com/users/paulshen/events{/privacy}",
          "received_events_url": "https://api.github.com/users/paulshen/received_events",
          "type": "User",
          "site_admin": false
      },
      "body": "The buttons on the index were pointing at wrong paths.\n",
      "created_at": "2013-05-29T20:31:39Z",
      "updated_at": "2014-06-27T04:39:06Z",
      "closed_at": "2013-05-29T20:32:25Z",
      "merged_at": "2013-05-29T20:32:25Z",
      "merge_commit_sha": "9aa4d2bc27c38b01c9c8f3436bd729d5e656cb1b",
      "assignee": null,
      "assignees": [],
      "requested_reviewers": [],
      "requested_teams": [],
      "labels": [],
      "milestone": null,
      "draft": false,
      "commits_url": "https://api.github.com/repos/facebook/react/pulls/2/commits",
      "review_comments_url": "https://api.github.com/repos/facebook/react/pulls/2/comments",
      "review_comment_url": "https://api.github.com/repos/facebook/react/pulls/comments{/number}",
      "comments_url": "https://api.github.com/repos/facebook/react/issues/2/comments",
      "statuses_url": "https://api.github.com/repos/facebook/react/statuses/c5b4fe9e88a9a3b43cfd9b7e5383096bd9e213ef",
      "head": {},
      "base": {},
      "_links": {},
      "author_association": "CONTRIBUTOR",
      "auto_merge": null,
      "active_lock_reason": null
  }
]

steps.save.outputs.json(又名needs.fetch.outputs.matrix)的值将是...


{ 
  "includes": [
    {
        "state": "closed",
        "title": "Run each test in its own <iframe>",
        "username": "benjamn"
    },
    {
        "number": 2,
        "title": "[docs] Fix button links on bottom of home",
        "username": "paulshen"
    }
  ]
}

...可以轻松传递给jobs.<job_id>.strategy.matrix,这将触发两个pr-checks作业。

最后注意一点:我尝试只将矩阵值数组传递给jobs.<job_id>.strategy.matrix.includes,但这会失败,因为matrix.includes不接受GitHub 表达式作为值。因此,嵌套值在includes中是正确的方法!


你能提供适用于多行JSON和矩阵转换的PowerShell替代方案吗? - wehelpdox
我不熟悉Powershell,但是你是指Windows机器上的“object-remap”操作无法正常工作吗?如果是,请在GH上开一个问题,如果不是,请详细说明。 - Nickofthyme
运行于:[自托管,X64,开发版,Windows] 输出: 矩阵:${{ steps.parsejson.outputs.matrix }} 步骤:
  • 名称:解析 JSON 并读取输入变量定义 ID:parsejson 运行: $content= (Get-Content "${{github.workspace}}${{env.input_json}}" -Raw) echo "::set-output name=matrix::$( echo "$content" )"
- wehelpdox
运行: | echo "${{fromJson(steps.parsejson.outputs.matrix).postcheckout}}" -> 这里出现了错误 错误: .github/workflows/start.yml (第50行, 第13列): 警告: 在评估显示名称${{ format('echo "{0}" ', fromJson(steps.parsejson.outputs.matrix).postcheckout) }}时遇到错误。该模板无效。.github/workflows/start.yml (第50行, 第13列): 从JsonReader读取JObject时出错。路径'', 行1, 位置1。 错误: .github/workflows/start.yml (第50行, 第13列): 错误: 模板无效。.github/workflows/start.yml (第50行, 第 - wehelpdox
1
这个代码库是公共的吗?你有链接吗?否则请在新的SO问题中打开它,并把链接放在这里让我看一下。 - Nickofthyme

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