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