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  }