pr_check.yml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. name: PR-check
  2. on:
  3. pull_request_target:
  4. branches:
  5. - 'main'
  6. - 'stable-*'
  7. - 'prestable-*'
  8. - 'stream-nb-*'
  9. - '*-stable-*'
  10. - 'dev-*'
  11. types:
  12. - 'opened'
  13. - 'synchronize'
  14. - 'reopened'
  15. - 'labeled'
  16. concurrency:
  17. group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  18. cancel-in-progress: true
  19. jobs:
  20. check-running-allowed:
  21. if: ${{vars.CHECKS_SWITCH != '' && fromJSON(vars.CHECKS_SWITCH).pr_check == true}}
  22. runs-on: ubuntu-latest
  23. timeout-minutes: 600
  24. outputs:
  25. result: ${{ steps.check-ownership-membership.outputs.result == 'true' && steps.check-is-mergeable.outputs.result == 'true' }}
  26. commit_sha: ${{ steps.check-is-mergeable.outputs.commit_sha }}
  27. steps:
  28. - name: Reset integrated status
  29. run: |
  30. curl -L -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{github.token}}" -H "X-GitHub-Api-Version: 2022-11-28" \
  31. https://api.github.com/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
  32. -d '{"state":"pending","description":"Waiting for relevant checks to complete","context":"checks_integrated"}'
  33. - name: Check if running tests is allowed
  34. id: check-ownership-membership
  35. uses: actions/github-script@v7
  36. with:
  37. github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
  38. script: |
  39. const labels = context.payload.pull_request.labels;
  40. const okToTestLabel = labels.find(
  41. label => label.name == 'ok-to-test'
  42. );
  43. console.log("okToTestLabel=%o", okToTestLabel !== undefined);
  44. if (okToTestLabel !== undefined) {
  45. return true;
  46. }
  47. // This is used primarily in forks. Repository owner
  48. // should be allowed to run anything.
  49. const userLogin = context.payload.pull_request.user.login;
  50. // How to interpret membership status code:
  51. // https://docs.github.com/rest/collaborators/collaborators#check-if-a-user-is-a-repository-collaborator
  52. const isRepoCollaborator = async function () {
  53. try {
  54. const response = await github.rest.repos.checkCollaborator({
  55. owner: context.payload.repository.owner.login,
  56. repo: context.payload.repository.name,
  57. username: userLogin,
  58. });
  59. return response.status == 204;
  60. } catch (error) {
  61. if (error.status && error.status == 404) {
  62. return false;
  63. }
  64. throw error;
  65. }
  66. }
  67. if (context.payload.repository.owner.login == userLogin) {
  68. console.log("You are the repository owner!");
  69. return true;
  70. }
  71. if (await isRepoCollaborator()) {
  72. console.log("You are a collaborator!");
  73. return true;
  74. }
  75. return false;
  76. - name: comment-if-waiting-on-ok
  77. if: steps.check-ownership-membership.outputs.result == 'false' &&
  78. github.event.action == 'opened'
  79. uses: actions/github-script@v7
  80. with:
  81. script: |
  82. let externalContributorLabel = 'external';
  83. github.rest.issues.createComment({
  84. issue_number: context.issue.number,
  85. owner: context.repo.owner,
  86. repo: context.repo.repo,
  87. body: 'Hi! Thank you for contributing!\nThe tests on this PR will run after a maintainer adds an `ok-to-test` label to this PR manually. Thank you for your patience!'
  88. });
  89. github.rest.issues.addLabels({
  90. ...context.repo,
  91. issue_number: context.issue.number,
  92. labels: [externalContributorLabel]
  93. });
  94. - name: cleanup-test-label
  95. uses: actions/github-script@v7
  96. with:
  97. script: |
  98. let labelsToRemove = ['ok-to-test', 'rebase-and-check'];
  99. const prNumber = context.payload.pull_request.number;
  100. const prLabels = new Set(context.payload.pull_request.labels.map(l => l.name));
  101. for await (const label of labelsToRemove.filter(l => prLabels.has(l))) {
  102. core.info(`remove label=${label} for pr=${prNumber}`);
  103. try {
  104. const result = await github.rest.issues.removeLabel({
  105. ...context.repo,
  106. issue_number: prNumber,
  107. name: label
  108. });
  109. } catch(error) {
  110. // ignore the 404 error that arises
  111. // when the label did not exist for the
  112. // organization member
  113. if (error.status && error.status != 404) {
  114. throw error;
  115. }
  116. }
  117. }
  118. - name: check is mergeable
  119. id: check-is-mergeable
  120. if: steps.check-ownership-membership.outputs.result == 'true'
  121. uses: actions/github-script@v7
  122. with:
  123. result-encoding: string
  124. script: |
  125. let pr = context.payload.pull_request;
  126. const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  127. const header = `<!-- merge pr=${pr.number} -->\n`;
  128. const fail_msg = header + ':red_circle: Unable to merge your PR into the base branch. '
  129. + 'Please rebase or merge it with the base branch.'
  130. let i = 0;
  131. while (pr.mergeable == null && i < 60) {
  132. console.log("get pull-request status");
  133. let result = await github.rest.pulls.get({
  134. ...context.repo,
  135. pull_number: pr.number
  136. })
  137. pr = result.data;
  138. if (pr.mergeable == null) {
  139. await delay(5000);
  140. }
  141. i += 1;
  142. }
  143. console.log("pr.mergeable=%o", pr.mergeable);
  144. if (pr.mergeable === null) {
  145. core.setFailed("Unable to check if the PR is mergeable, please re-run the check.");
  146. return false;
  147. }
  148. const { data: comments } = await github.rest.issues.listComments({
  149. issue_number: context.issue.number,
  150. owner: context.repo.owner,
  151. repo: context.repo.repo
  152. });
  153. const commentToUpdate = comments.find(comment => comment.body.startsWith(header));
  154. if (!pr.mergeable) {
  155. let commentParams = {
  156. ...context.repo,
  157. issue_number: context.issue.number,
  158. body: fail_msg
  159. };
  160. if (commentToUpdate) {
  161. await github.rest.issues.updateComment({
  162. ...commentParams,
  163. comment_id: commentToUpdate.id,
  164. });
  165. } else {
  166. await github.rest.issues.createComment({...commentParams});
  167. }
  168. core.setFailed("Merge conflict detected");
  169. return false;
  170. } else if (commentToUpdate) {
  171. await github.rest.issues.deleteComment({
  172. ...context.repo,
  173. issue_number: context.issue.number,
  174. comment_id: commentToUpdate.id,
  175. });
  176. }
  177. core.info(`commit_sha=${pr.commit_sha}`);
  178. core.setOutput('commit_sha', pr.merge_commit_sha);
  179. return true;
  180. build_and_test:
  181. needs:
  182. - check-running-allowed
  183. if: needs.check-running-allowed.outputs.result == 'true' && needs.check-running-allowed.outputs.commit_sha != ''
  184. strategy:
  185. fail-fast: false
  186. matrix:
  187. include:
  188. - build_preset: relwithdebinfo
  189. threads_count: 52
  190. timeout: 240
  191. build_target: "ydb/"
  192. test_size: small,medium
  193. - build_preset: release-asan
  194. threads_count: 52
  195. timeout: 240
  196. build_target: "ydb/"
  197. test_size: small,medium
  198. - build_preset: release-msan
  199. threads_count: 20
  200. timeout: 480
  201. build_target: "ydb/"
  202. test_size: small,medium
  203. - build_preset: release-tsan
  204. threads_count: 20
  205. timeout: 600
  206. build_target: "ydb/"
  207. test_size: small,medium
  208. runs-on: [ self-hosted, auto-provisioned, "${{ format('build-preset-{0}', matrix.build_preset) }}" ]
  209. name: Build and test ${{ matrix.build_preset }}
  210. steps:
  211. - name: Checkout
  212. uses: actions/checkout@v4
  213. with:
  214. ref: ${{ needs.check-running-allowed.outputs.commit_sha }}
  215. fetch-depth: 2
  216. - name: Setup ydb access
  217. uses: ./.github/actions/setup_ci_ydb_service_account_key_file_credentials
  218. with:
  219. ci_ydb_service_account_key_file_credentials: ${{ secrets.CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS }}
  220. - name: Build and test
  221. if: |
  222. (matrix.build_preset == 'release-asan') ||
  223. (matrix.build_preset == 'relwithdebinfo') ||
  224. (matrix.build_preset == 'release-tsan' &&
  225. contains(github.event.pull_request.labels.*.name, 'run-tsan-tests') ||
  226. contains(github.event.pull_request.labels.*.name, 'run-sanitizer-tests')) ||
  227. (matrix.build_preset == 'release-msan' &&
  228. contains(github.event.pull_request.labels.*.name, 'run-msan-tests') ||
  229. contains(github.event.pull_request.labels.*.name, 'run-sanitizer-tests'))
  230. uses: ./.github/actions/build_and_test_ya
  231. with:
  232. build_preset: ${{ matrix.build_preset }}
  233. build_target: ${{ matrix.build_target }}
  234. increment: true
  235. run_tests: ${{ contains(fromJSON('["relwithdebinfo", "release-asan", "release-tsan", "release-msan"]'), matrix.build_preset) }}
  236. test_size: ${{ matrix.test_size }}
  237. test_threads: ${{ matrix.threads_count }}
  238. put_build_results_to_cache: true
  239. additional_ya_make_args: -DDEBUGINFO_LINES_ONLY # we don't need full symbols in CI checks
  240. secs: ${{ format('{{"TESTMO_TOKEN2":"{0}","AWS_KEY_ID":"{1}","AWS_KEY_VALUE":"{2}","REMOTE_CACHE_USERNAME":"{3}","REMOTE_CACHE_PASSWORD":"{4}"}}',
  241. secrets.TESTMO_TOKEN2, secrets.AWS_KEY_ID, secrets.AWS_KEY_VALUE, secrets.REMOTE_CACHE_USERNAME, secrets.REMOTE_CACHE_PASSWORD ) }}
  242. vars: ${{ format('{{"AWS_BUCKET":"{0}","AWS_ENDPOINT":"{1}","REMOTE_CACHE_URL":"{2}","TESTMO_URL":"{3}","TESTMO_PROJECT_ID":"{4}"}}',
  243. vars.AWS_BUCKET, vars.AWS_ENDPOINT, vars.REMOTE_CACHE_URL_YA, vars.TESTMO_URL, vars.TESTMO_PROJECT_ID ) }}
  244. update_integrated_status:
  245. runs-on: ubuntu-latest
  246. needs: build_and_test
  247. if: always()
  248. steps:
  249. - name: Gather required checks results
  250. shell: bash
  251. run: |
  252. successbuilds=$(curl -L -X GET -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{github.token}}" -H "X-GitHub-Api-Version: 2022-11-28" \
  253. https://api.github.com/repos/${{github.repository}}/commits/${{github.event.pull_request.head.sha}}/status | \
  254. jq -cr '.statuses | .[] | select(.state=="success") | select(.context | (startswith("build_relwithdebinfo") or startswith("build_release-asan") or startswith("test_relwithdebinfo")) ) | .context' | \
  255. wc -l )
  256. if [[ $successbuilds == "3" ]];then
  257. integrated_status="success"
  258. else
  259. integrated_status="failure"
  260. fi
  261. curl -L -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{github.token}}" -H "X-GitHub-Api-Version: 2022-11-28" \
  262. https://api.github.com/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
  263. -d '{"state":"'$integrated_status'","description":"All checks completed","context":"checks_integrated"}'