cuelang.org/go@v0.13.0/internal/ci/base/gerrithub.cue (about) 1 package base 2 3 // This file contains gerrithub related definitions etc 4 5 import ( 6 "encoding/json" 7 "strings" 8 "cue.dev/x/githubactions" 9 ) 10 11 // trybotWorkflows is a template for trybot-based repos 12 trybotWorkflows: { 13 (trybot.key): githubactions.#Workflow & { 14 on: workflow_dispatch: {} 15 } 16 "\(trybot.key)_dispatch": trybotDispatchWorkflow 17 "push_tip_to_\(trybot.key)": pushTipToTrybotWorkflow 18 "evict_caches": evictCaches 19 } 20 21 #dispatch: { 22 type: string 23 CL: int 24 patchset: int 25 targetBranch: *defaultBranch | string 26 27 let p = strings.Split("\(CL)", "") 28 let rightMostTwo = p[len(p)-2] + p[len(p)-1] 29 ref: *"refs/changes/\(rightMostTwo)/\(CL)/\(patchset)" | string 30 } 31 32 trybotDispatchWorkflow: bashWorkflow & { 33 #dummyDispatch?: #dispatch 34 name: "Dispatch \(trybot.key)" 35 on: { 36 repository_dispatch: {} 37 push: { 38 // To enable testing of the dispatch itself 39 branches: [testDefaultBranch] 40 } 41 } 42 jobs: { 43 (trybot.key): { 44 "runs-on": linuxMachine 45 46 let goodDummyData = [if json.Marshal(#dummyDispatch) != _|_ {true}, false][0] 47 48 // We set the "on" conditions above, but this would otherwise mean we 49 // run for all dispatch events. 50 if: "${{ (\(isTestDefaultBranch) && \(goodDummyData)) || github.event.client_payload.type == '\(trybot.key)' }}" 51 52 // See the comment below about the need for cases 53 let cases = [ 54 { 55 condition: "!=" 56 expr: "fromJSON(steps.payload.outputs.value)" 57 nameSuffix: "fake data" 58 }, 59 { 60 condition: "==" 61 expr: "github.event.client_payload" 62 nameSuffix: "repository_dispatch payload" 63 }, 64 ] 65 66 steps: [ 67 writeNetrcFile, 68 69 githubactions.#Step & { 70 name: "Write fake payload" 71 id: "payload" 72 if: "github.repository == '\(githubRepositoryPath)' && \(isTestDefaultBranch)" 73 74 // Use bash heredocs so that JSON's use of double quotes does 75 // not get interpreted as shell. Both in the running of the 76 // command itself, which itself is the echo-ing of a command to 77 // $GITHUB_OUTPUT. 78 run: #""" 79 cat <<EOD >> $GITHUB_OUTPUT 80 value<<DOE 81 \#(*json.Marshal(#dummyDispatch) | "null") 82 DOE 83 EOD 84 """# 85 }, 86 87 // GitHub does not allow steps with the same ID, even if (by virtue 88 // of runtime 'if' expressions) both would not actually run. So 89 // we have to duplciate the steps that follow with those same 90 // runtime expressions 91 // 92 // Hence we have to create two steps, one to trigger if the 93 // repository_dispatch payload is set, and one if not (i.e. we use 94 // the fake payload). 95 for v in cases { 96 let localBranchExpr = "local_${{ \(v.expr).targetBranch }}" 97 let targetBranchExpr = "${{ \(v.expr).targetBranch }}" 98 githubactions.#Step & { 99 name: "Trigger \(trybot.name) (\(v.nameSuffix))" 100 if: "github.event.client_payload.type \(v.condition) '\(trybot.key)'" 101 run: """ 102 mkdir tmpgit 103 cd tmpgit 104 git init -b initialbranch 105 git config user.name \(botGitHubUser) 106 git config user.email \(botGitHubUserEmail) 107 git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n \(botGitHubUser):${{ secrets.\(botGitHubUserTokenSecretsKey) }} | base64)" 108 git remote add origin \(gerritHubRepositoryURL) 109 110 git fetch origin ${{ \(v.expr).ref }} 111 git checkout -b \(localBranchExpr) FETCH_HEAD 112 113 # Error if we already have dispatchTrailer according to git log logic. 114 # See earlier check for GitHub expression logic check. 115 x="$(git log -1 --pretty='%(trailers:key=\(dispatchTrailer),valueonly)')" 116 if [[ "$x" != "" ]] 117 then 118 echo "Ref ${{ \(v.expr).ref }} already has a \(dispatchTrailer)" 119 exit 1 120 fi 121 122 # Add the trailer because we don't have it yet. GitHub expressions do not have a 123 # substitute or quote capability. So we do that in shell. We also strip out the 124 # indenting added by toJSON. We ensure that the type field is first in order 125 # that we can safely check for specific types of dispatch trailer. 126 # 127 # Use bash heredoc so that JSON's use of double quotes does 128 # not get interpreted as shell. 129 trailer="$(cat <<EOD | jq -r -c '{type} + .' 130 ${{ toJSON(\(v.expr)) }} 131 EOD 132 )" 133 git log -1 --format=%B | git interpret-trailers --trailer "\(dispatchTrailer): $trailer" | git commit --amend -F - 134 git log -1 135 136 success=false 137 for try in {1..20}; do 138 echo "Push to trybot try $try" 139 if git push -f \(trybotRepositoryURL) \(localBranchExpr):\(targetBranchExpr); then 140 success=true 141 break 142 fi 143 sleep 1 144 done 145 if ! $success; then 146 echo "Giving up" 147 exit 1 148 fi 149 """ 150 } 151 }, 152 ] 153 } 154 } 155 } 156 157 pushTipToTrybotWorkflow: bashWorkflow & { 158 on: { 159 push: branches: protectedBranchPatterns 160 } 161 jobs: push: { 162 "runs-on": linuxMachine 163 if: "${{github.repository == '\(githubRepositoryPath)'}}" 164 } 165 166 name: "Push tip to \(trybot.key)" 167 168 concurrency: "push_tip_to_trybot" 169 170 jobs: push: { 171 steps: [ 172 writeNetrcFile, 173 githubactions.#Step & { 174 name: "Push tip to trybot" 175 run: """ 176 mkdir tmpgit 177 cd tmpgit 178 git init -b initialbranch 179 git config user.name \(botGitHubUser) 180 git config user.email \(botGitHubUserEmail) 181 git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n \(botGitHubUser):${{ secrets.\(botGitHubUserTokenSecretsKey) }} | base64)" 182 git remote add origin \(gerritHubRepositoryURL) 183 git remote add trybot \(trybotRepositoryURL) 184 185 git fetch origin "${{ github.ref }}" 186 187 success=false 188 for try in {1..20}; do 189 echo "Push to trybot try $try" 190 if git push -f trybot "FETCH_HEAD:${{ github.ref }}"; then 191 success=true 192 break 193 fi 194 sleep 1 195 done 196 if ! $success; then 197 echo "Giving up" 198 exit 1 199 fi 200 """ 201 }, 202 ] 203 } 204 205 } 206 207 // evictCaches removes "old" GitHub actions caches from the main repo and the 208 // accompanying trybot The job is only run in the main repo, because 209 // that is the only place where the credentials exist. 210 // 211 // The GitHub actions caches in the main and trybot repos can get large. So 212 // large in fact we got the following warning from GitHub: 213 // 214 // "Approaching total cache storage limit (34.5 GB of 10 GB Used)" 215 // 216 // Yes, you did read that right. 217 // 218 // Not only does this have the effect of causing us to breach "limits" it also 219 // means that we can't be sure that individual caches are not bloated. 220 // 221 // Fix that by purging the actions caches on a daily basis at 0200, followed 15 222 // mins later by a re-run of the tip trybots to repopulate the caches so they 223 // are warm and minimal. 224 // 225 // In testing with @mvdan, this resulted in cache sizes for Linux dropping from 226 // ~1GB to ~125MB. This is a considerable saving. 227 // 228 // Note this currently removes all cache entries, regardless of whether they 229 // are go-related or not. We should revisit this later. 230 evictCaches: bashWorkflow & { 231 name: "Evict caches" 232 233 on: { 234 schedule: [ 235 {cron: "0 2 * * *"}, 236 ] 237 } 238 239 jobs: { 240 test: { 241 // We only want to run this in the main repo 242 if: "${{github.repository == '\(githubRepositoryPath)'}}" 243 "runs-on": linuxMachine 244 steps: [ 245 for v in checkoutCode {v}, 246 247 githubactions.#Step & { 248 name: "Delete caches" 249 run: """ 250 echo ${{ secrets.\(botGitHubUserTokenSecretsKey) }} | gh auth login --with-token 251 for i in \(githubRepositoryURL) \(trybotRepositoryURL) 252 do 253 echo "Evicting caches for $i" 254 gh cache delete --repo $i --all --succeed-on-no-caches 255 done 256 """ 257 }, 258 259 githubactions.#Step & { 260 name: "Trigger workflow runs to repopulate caches" 261 let branchPatterns = strings.Join(protectedBranchPatterns, " ") 262 263 run: """ 264 # Prepare git for pushes to trybot repo. Note 265 # because we have already checked out code we don't 266 # need origin. Fetch origin default branch for later use 267 git config user.name \(botGitHubUser) 268 git config user.email \(botGitHubUserEmail) 269 git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n \(botGitHubUser):${{ secrets.\(botGitHubUserTokenSecretsKey) }} | base64)" 270 git remote add trybot \(trybotRepositoryURL) 271 272 # Now trigger the most recent workflow run on each of the default branches. 273 # We do this by listing all the branches on the main repo and finding those 274 # which match the protected branch patterns (globs). 275 for j in $(\(curlGitHubAPI) -f https://api.github.com/repos/\(githubRepositoryPath)/branches | jq -r '.[] | .name') 276 do 277 for i in \(branchPatterns) 278 do 279 if [[ "$j" != $i ]]; then 280 continue 281 fi 282 283 echo Branch: $j 284 sha=$(\(curlGitHubAPI) "https://api.github.com/repos/\(githubRepositoryPath)/commits/$j" | jq -r '.sha') 285 echo Latest commit: $sha 286 287 echo "Trigger workflow on \(githubRepositoryPath)" 288 \(curlGitHubAPI) --fail-with-body -X POST https://api.github.com/repos/\(githubRepositoryPath)/actions/workflows/\(trybot.key+workflowFileExtension)/dispatches -d "{\\"ref\\":\\"$j\\"}" 289 290 # Ensure that the trybot repo has the latest commit for 291 # this branch. If the force-push results in a commit 292 # being pushed, that will trigger the trybot workflows 293 # so we don't need to do anything, otherwise we need to 294 # trigger the most recent commit on that branch 295 git remote -v 296 git fetch origin refs/heads/$j 297 git log -1 FETCH_HEAD 298 299 success=false 300 for try in {1..20}; do 301 echo "Push to trybot try $try" 302 exitCode=0; push="$(git push -f trybot FETCH_HEAD:$j 2>&1)" || exitCode=$? 303 echo "$push" 304 if [[ $exitCode -eq 0 ]]; then 305 success=true 306 break 307 fi 308 sleep 1 309 done 310 if ! $success; then 311 echo "Giving up" 312 exit 1 313 fi 314 315 if echo "$push" | grep up-to-date 316 then 317 # We are up-to-date, i.e. the push did nothing, hence we need to trigger a workflow_dispatch 318 # in the trybot repo. 319 echo "Trigger workflow on \(trybotRepositoryPath)" 320 \(curlGitHubAPI) --fail-with-body -X POST https://api.github.com/repos/\(trybotRepositoryPath)/actions/workflows/\(trybot.key+workflowFileExtension)/dispatches -d "{\\"ref\\":\\"$j\\"}" 321 else 322 echo "Force-push to \(trybotRepositoryPath) did work; nothing to do" 323 fi 324 done 325 done 326 """ 327 }, 328 ] 329 } 330 } 331 } 332 333 writeNetrcFile: githubactions.#Step & { 334 name: "Write netrc file for \(botGerritHubUser) Gerrithub" 335 run: """ 336 cat <<EOD > ~/.netrc 337 machine \(gerritHubHostname) 338 login \(botGerritHubUser) 339 password ${{ secrets.\(botGerritHubUserPasswordSecretsKey) }} 340 EOD 341 chmod 600 ~/.netrc 342 """ 343 }