Skip to content

Commit

Permalink
internal/ci: refactor CI workflows
Browse files Browse the repository at this point in the history
Refactor our CI workflows to use commit message trailers instead of
branches to "carry" information.

Currently we use branchnames like:

    trybot/$changeID/$commitID/$CL/$patchset

when triggering trybot runs for CLs in Gerrit. We also create PRs in
order to take advantage of GitHub's action caches:

    https://github.com/actions/cache

However this approach is brittle. PRs, whilst having the advantage of
being able to leverage the actions caches, regularly cause breakages.
Workflows for a PR will not run if there are conflicts with respect to
the target branch, nor will they run if there is not connection between
the commit at the tip of the target branch and the PR itself (which can
happen in the trybot repo when the default branch is used for other
purposes).

Instead, move to an approach where all workflows that run in the trybot
repo (trybot or unity) use a force-push to the target branch. The
information that was previously carried in the branch name is now passed
in a Dispatch-Trailer such as:

    Dispatch-Trailer: {"type":"trybot","patchset":149,"CL":551352,"targetBranch":"master","ref":"refs/changes/52/551352/149"}

The #dispatch schema for the JSON payload value of this trailer is
defined in gerrithub.cue.

Related changes to the gerritstatusupdater are made in CL 551980. There
the go types for the dispatch payload are, for now, manually kept in
sync with the #dispatch schema. These changes to gerritstatusupdater
ensure that the old/new style of CI triggers is handled correctly. See
the extensive commit message in that CL for more details.

Related changes to cmd/cueckoo are presented in
cue-lang/contrib-tools#44. Similarly, the Go type
repositoryDispatchPayload is, for now, manually kept in sync with any
changes to #dispatch in this repo.

Now that all CI-related workflows run on the target branch, logic for
determing whether we are running a trybot/unity workflow or on an actual
protected branch commit has been updated.

To help with testing of this new approach prior to submitting this CL,
we also support dummy data that simulates the effect of calling
cmd/cueckoo runtrybot. This only has effect on pushes to ci/test.

This change, the related changes in other CLs, and the change to
cmd/cueckoo, all need to be made "atomically". They are intentionally
breaking. In case of rollback, all need to be rolled back.

Signed-off-by: Paul Jolly <paul@myitcv.io>
Change-Id: I8662f5eec753e3caf6a5294b61d97f667e4ff2ee
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/551352
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Chief Cueckoo <chief.cueckoo@gmail.com>
  • Loading branch information
myitcv committed Apr 3, 2023
1 parent f881884 commit d71a76c
Show file tree
Hide file tree
Showing 8 changed files with 462 additions and 46 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,35 @@ jobs:
run: touch -t 202211302355 $(find * -type d)
- name: Restore git file modification times
uses: chetan/git-restore-mtime-action@075f9bc9d159805603419d50f794bd9f33252ebe
- id: DispatchTrailer
name: Try to extract Dispatch-Trailer
run: |-
x="$(git log -1 --pretty='%(trailers:key=Dispatch-Trailer,valueonly)')"
if [[ "$x" == "" ]]
then
x=null
fi
echo "x is $x"
echo "value<<EOD" >> $GITHUB_OUTPUT
echo "$x" >> $GITHUB_OUTPUT
echo "EOD" >> $GITHUB_OUTPUT
- if: |-
((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"')))) && (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"'))
name: Check we don't have Dispatch-Trailer on a protected branch
run: |-
echo "github.event.head_commit.message contains Dispatch-Trailer"
echo "github.event.head_commit.message value"
cat <<EOD
${{ github.event.head_commit.message }}
EOD
echo "containsDispatchTrailer expression"
cat <<EOD
(contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"'))
EOD
false
- name: Install Go
uses: actions/setup-go@v4
with:
Expand Down
53 changes: 48 additions & 5 deletions .github/workflows/trybot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ jobs:
defaults:
run:
shell: bash
if: |-
(contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"trybot"')) || ! (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"'))
steps:
- name: Checkout code
uses: actions/checkout@v3
Expand All @@ -36,6 +40,35 @@ jobs:
run: touch -t 202211302355 $(find * -type d)
- name: Restore git file modification times
uses: chetan/git-restore-mtime-action@075f9bc9d159805603419d50f794bd9f33252ebe
- id: DispatchTrailer
name: Try to extract Dispatch-Trailer
run: |-
x="$(git log -1 --pretty='%(trailers:key=Dispatch-Trailer,valueonly)')"
if [[ "$x" == "" ]]
then
x=null
fi
echo "x is $x"
echo "value<<EOD" >> $GITHUB_OUTPUT
echo "$x" >> $GITHUB_OUTPUT
echo "EOD" >> $GITHUB_OUTPUT
- if: |-
((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"')))) && (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"'))
name: Check we don't have Dispatch-Trailer on a protected branch
run: |-
echo "github.event.head_commit.message contains Dispatch-Trailer"
echo "github.event.head_commit.message value"
cat <<EOD
${{ github.event.head_commit.message }}
EOD
echo "containsDispatchTrailer expression"
cat <<EOD
(contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"'))
EOD
false
- name: Install Go
uses: actions/setup-go@v4
with:
Expand All @@ -47,23 +80,29 @@ jobs:
- id: go-cache-dir
name: Get go build/test cache directory
run: echo "dir=$(go env GOCACHE)" >> ${GITHUB_OUTPUT}
- if: (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.'))
- if: |-
((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"'))))
uses: actions/cache@v3
with:
path: |-
${{ steps.go-mod-cache-dir.outputs.dir }}/cache/download
${{ steps.go-cache-dir.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.go-version }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-${{ matrix.go-version }}
- if: '! (github.ref == ''refs/heads/master'' || startsWith(github.ref, ''refs/heads/release-branch.''))'
- if: |-
! ((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"'))))
uses: actions/cache/restore@v3
with:
path: |-
${{ steps.go-mod-cache-dir.outputs.dir }}/cache/download
${{ steps.go-cache-dir.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.go-version }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-${{ matrix.go-version }}
- if: github.repository == 'cue-lang/cue' && ((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) || github.ref == 'refs/heads/ci/test')
- if: |-
github.repository == 'cue-lang/cue' && (((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"')))) || github.ref == 'refs/heads/ci/test')
run: go clean -testcache
- if: (matrix.go-version == '1.20.x' && matrix.runner == 'ubuntu-22.04')
name: Early git and code sanity checks
Expand Down Expand Up @@ -119,12 +158,16 @@ jobs:
echo "commit author email address does not match signed-off-by trailer"
exit 1
fi
- if: (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) || (matrix.go-version == '1.20.x' && matrix.runner == 'ubuntu-22.04')
- if: |-
((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"')))) || (matrix.go-version == '1.20.x' && matrix.runner == 'ubuntu-22.04')
run: echo CUE_LONG=true >> $GITHUB_ENV
- if: (matrix.go-version == '1.20.x' && matrix.runner == 'ubuntu-22.04')
name: Generate
run: go generate ./...
- if: (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) || !(matrix.go-version == '1.20.x' && matrix.runner == 'ubuntu-22.04')
- if: |-
((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
Dispatch-Trailer: {"type":"')))) || !(matrix.go-version == '1.20.x' && matrix.runner == 'ubuntu-22.04')
name: Test
run: go test ./...
- if: (matrix.go-version == '1.20.x' && matrix.runner == 'ubuntu-22.04')
Expand Down
169 changes: 156 additions & 13 deletions .github/workflows/trybot_dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

name: Dispatch trybot
"on":
- repository_dispatch
repository_dispatch: {}
push:
branches:
- ci/test
jobs:
trybot:
runs-on: ubuntu-22.04
defaults:
run:
shell: bash
if: ${{ github.event.client_payload.type == 'trybot' }}
if: ${{ github.ref == 'refs/heads/ci/test' || github.event.client_payload.type == 'trybot' }}
steps:
- name: Write netrc file for cueckoo Gerrithub
run: |-
Expand All @@ -19,22 +22,162 @@ jobs:
password ${{ secrets.CUECKOO_GERRITHUB_PASSWORD }}
EOD
chmod 600 ~/.netrc
- id: gerrithub_ref
- id: payload
if: github.repository == 'cue-lang/cue' && github.ref == 'refs/heads/ci/test'
name: Write fake payload
run: |-
ref="$(echo ${{github.event.client_payload.payload.ref}} | sed -E 's/^refs\/changes\/[0-9]+\/([0-9]+)\/([0-9]+).*/\1\/\2/')"
echo "gerrithub_ref=$ref" >> $GITHUB_OUTPUT
- name: Trigger trybot
cat <<EOD >> $GITHUB_OUTPUT
value<<DOE
{"type":"trybot","patchset":153,"CL":551352,"targetBranch":"master","ref":"refs/changes/52/551352/153"}
DOE
EOD
- if: github.event.client_payload.type != 'trybot'
name: Trigger TryBot (fake data)
run: |-
set -x
mkdir tmpgit
cd tmpgit
git init
git config user.name cueckoo
git config user.email cueckoo@gmail.com
git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n cueckoo:${{ secrets.CUECKOO_GITHUB_PAT }} | base64)"
git remote add origin https://review.gerrithub.io/a/cue-lang/cue
# We also (temporarily) get the default branch in order that
# we can "restore" the trybot repo to a good state for the
# current (i.e. previous) implementation of trybots which
# used PRs. If the target branch in the trybot repo is not
# current, then PR creation will fail because GitHub claims
# it cannot find any link between the commit in a PR (i.e.
# the CL under test in the previous setup) and the target
# branch which, under the new setup, might well currently
# be the commit from a CL.
git fetch origin ${{ fromJSON(steps.payload.outputs.value).targetBranch }}
git fetch origin ${{ fromJSON(steps.payload.outputs.value).ref }}
git checkout -b ${{ fromJSON(steps.payload.outputs.value).targetBranch }} FETCH_HEAD
# Error if we already have dispatchTrailer according to git log logic.
# See earlier check for GitHub expression logic check.
x="$(git log -1 --pretty='%(trailers:key=Dispatch-Trailer,valueonly)')"
if [ "$x" != "" ]
then
echo "Ref ${{ fromJSON(steps.payload.outputs.value).ref }} already has a Dispatch-Trailer"
exit 1
fi
# Add the trailer because we don't have it yet. GitHub expressions do not have a
# substitute or quote capability. So we do that in shell. We also strip out the
# indenting added by toJSON. We ensure that the type field is first in order
# that we can safely check for specific types of dispatch trailer.
trailer="$(cat <<EOD | jq -c '{type} + .'
${{ toJSON(fromJSON(steps.payload.outputs.value)) }}
EOD
)"
git log -1 --format=%B | git interpret-trailers --trailer "Dispatch-Trailer: $trailer" | git commit --amend -F -
git log -1
success=false
for try in {1..20}; do
echo "Push to trybot try $try"
if git push -f https://github.com/cue-lang/cue-trybot ${{ fromJSON(steps.payload.outputs.value).targetBranch }}:${{ fromJSON(steps.payload.outputs.value).targetBranch }}; then
success=true
break
fi
sleep 1
done
if ! $success; then
echo "Giving up"
exit 1
fi
# Restore the default branch on the trybot repo to be the tip of the main repo
success=false
for try in {1..20}; do
echo "Push to trybot try $try"
if git push -f https://github.com/cue-lang/cue-trybot origin/${{ fromJSON(steps.payload.outputs.value).targetBranch }}:${{ fromJSON(steps.payload.outputs.value).targetBranch }}; then
success=true
break
fi
sleep 1
done
if ! $success; then
echo "Giving up"
exit 1
fi
- if: github.event.client_payload.type == 'trybot'
name: Trigger TryBot (repository_dispatch payload)
run: |-
set -x
mkdir tmpgit
cd tmpgit
git init
git config user.name cueckoo
git config user.email cueckoo@gmail.com
git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n cueckoo:${{ secrets.CUECKOO_GITHUB_PAT }} | base64)"
git fetch https://review.gerrithub.io/a/cue-lang/cue "${{ github.event.client_payload.payload.ref }}"
git checkout -b trybot/${{ github.event.client_payload.payload.changeID }}/${{ github.event.client_payload.payload.commit }}/${{ steps.gerrithub_ref.outputs.gerrithub_ref }} FETCH_HEAD
git remote add origin https://github.com/cue-lang/cue-trybot
git fetch origin "${{ github.event.client_payload.payload.branch }}"
git push origin trybot/${{ github.event.client_payload.payload.changeID }}/${{ github.event.client_payload.payload.commit }}/${{ steps.gerrithub_ref.outputs.gerrithub_ref }}
echo ${{ secrets.CUECKOO_GITHUB_PAT }} | gh auth login --with-token
gh pr --repo=https://github.com/cue-lang/cue-trybot create --base="${{ github.event.client_payload.payload.branch }}" --fill
git remote add origin https://review.gerrithub.io/a/cue-lang/cue
# We also (temporarily) get the default branch in order that
# we can "restore" the trybot repo to a good state for the
# current (i.e. previous) implementation of trybots which
# used PRs. If the target branch in the trybot repo is not
# current, then PR creation will fail because GitHub claims
# it cannot find any link between the commit in a PR (i.e.
# the CL under test in the previous setup) and the target
# branch which, under the new setup, might well currently
# be the commit from a CL.
git fetch origin ${{ github.event.client_payload.targetBranch }}
git fetch origin ${{ github.event.client_payload.ref }}
git checkout -b ${{ github.event.client_payload.targetBranch }} FETCH_HEAD
# Error if we already have dispatchTrailer according to git log logic.
# See earlier check for GitHub expression logic check.
x="$(git log -1 --pretty='%(trailers:key=Dispatch-Trailer,valueonly)')"
if [ "$x" != "" ]
then
echo "Ref ${{ github.event.client_payload.ref }} already has a Dispatch-Trailer"
exit 1
fi
# Add the trailer because we don't have it yet. GitHub expressions do not have a
# substitute or quote capability. So we do that in shell. We also strip out the
# indenting added by toJSON. We ensure that the type field is first in order
# that we can safely check for specific types of dispatch trailer.
trailer="$(cat <<EOD | jq -c '{type} + .'
${{ toJSON(github.event.client_payload) }}
EOD
)"
git log -1 --format=%B | git interpret-trailers --trailer "Dispatch-Trailer: $trailer" | git commit --amend -F -
git log -1
success=false
for try in {1..20}; do
echo "Push to trybot try $try"
if git push -f https://github.com/cue-lang/cue-trybot ${{ github.event.client_payload.targetBranch }}:${{ github.event.client_payload.targetBranch }}; then
success=true
break
fi
sleep 1
done
if ! $success; then
echo "Giving up"
exit 1
fi
# Restore the default branch on the trybot repo to be the tip of the main repo
success=false
for try in {1..20}; do
echo "Push to trybot try $try"
if git push -f https://github.com/cue-lang/cue-trybot origin/${{ github.event.client_payload.targetBranch }}:${{ github.event.client_payload.targetBranch }}; then
success=true
break
fi
sleep 1
done
if ! $success; then
echo "Giving up"
exit 1
fi
Loading

0 comments on commit d71a76c

Please sign in to comment.