pr_check.yml 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. name: PR-check
  2. on:
  3. pull_request_target:
  4. branches:
  5. - 'main'
  6. - 'stable-*'
  7. paths-ignore:
  8. - 'ydb/docs/**'
  9. - '*'
  10. types:
  11. - 'opened'
  12. - 'synchronize'
  13. - 'reopened'
  14. - 'labeled'
  15. concurrency:
  16. group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  17. cancel-in-progress: true
  18. jobs:
  19. check-running-allowed:
  20. if: ${{vars.CHECKS_SWITCH != '' && fromJSON(vars.CHECKS_SWITCH).pr_check == true}}
  21. runs-on: ubuntu-latest
  22. outputs:
  23. result: ${{ steps.check-ownership-membership.outputs.result == 'true' && steps.check-is-mergeable.outputs.result == 'true' }}
  24. commit_sha: ${{ steps.check-is-mergeable.outputs.commit_sha }}
  25. steps:
  26. - name: Check if running tests is allowed
  27. id: check-ownership-membership
  28. uses: actions/github-script@v6
  29. with:
  30. github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
  31. script: |
  32. const labels = context.payload.pull_request.labels;
  33. const okToTestLabel = labels.find(
  34. label => label.name == 'ok-to-test'
  35. );
  36. console.log("okToTestLabel=%o", okToTestLabel !== undefined);
  37. if (okToTestLabel !== undefined) {
  38. return true;
  39. }
  40. // This is used primarily in forks. Repository owner
  41. // should be allowed to run anything.
  42. const userLogin = context.payload.pull_request.user.login;
  43. // How to interpret membership status code:
  44. // https://docs.github.com/rest/collaborators/collaborators#check-if-a-user-is-a-repository-collaborator
  45. const isRepoCollaborator = async function () {
  46. try {
  47. const response = await github.rest.repos.checkCollaborator({
  48. owner: context.payload.repository.owner.login,
  49. repo: context.payload.repository.name,
  50. username: userLogin,
  51. });
  52. return response.status == 204;
  53. } catch (error) {
  54. if (error.status && error.status == 404) {
  55. return false;
  56. }
  57. throw error;
  58. }
  59. }
  60. if (context.payload.repository.owner.login == userLogin) {
  61. console.log("You are the repository owner!");
  62. return true;
  63. }
  64. if (await isRepoCollaborator()) {
  65. console.log("You are a collaborator!");
  66. return true;
  67. }
  68. return false;
  69. - name: comment-if-waiting-on-ok
  70. if: steps.check-ownership-membership.outputs.result == 'false' &&
  71. github.event.action == 'opened'
  72. uses: actions/github-script@v6
  73. with:
  74. script: |
  75. github.rest.issues.createComment({
  76. issue_number: context.issue.number,
  77. owner: context.repo.owner,
  78. repo: context.repo.repo,
  79. 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!'
  80. });
  81. - name: cleanup-test-label
  82. uses: actions/github-script@v6
  83. with:
  84. script: |
  85. let labelsToRemove = ['ok-to-test', 'rebase-and-check'];
  86. const prNumber = context.payload.pull_request.number;
  87. const prLabels = new Set(context.payload.pull_request.labels.map(l => l.name));
  88. for await (const label of labelsToRemove.filter(l => prLabels.has(l))) {
  89. core.info(`remove label=${label} for pr=${prNumber}`);
  90. try {
  91. const result = await github.rest.issues.removeLabel({
  92. ...context.repo,
  93. issue_number: prNumber,
  94. name: label
  95. });
  96. } catch(error) {
  97. // ignore the 404 error that arises
  98. // when the label did not exist for the
  99. // organization member
  100. if (error.status && error.status != 404) {
  101. throw error;
  102. }
  103. }
  104. }
  105. - name: check is mergeable
  106. id: check-is-mergeable
  107. if: steps.check-ownership-membership.outputs.result == 'true'
  108. uses: actions/github-script@v6
  109. with:
  110. result-encoding: string
  111. script: |
  112. let pr = context.payload.pull_request;
  113. const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  114. const header = `<!-- merge pr=${pr.number} -->\n`;
  115. const fail_msg = header + ':red_circle: Unable to merge your PR into the `main` branch. '
  116. + 'Please rebase or merge it with the `main` branch.'
  117. let i = 0;
  118. while (pr.mergeable == null || i >= 60) {
  119. console.log("get pull-request status");
  120. let result = await github.rest.pulls.get({
  121. ...context.repo,
  122. pull_number: pr.number
  123. })
  124. pr = result.data;
  125. if (pr.mergeable == null) {
  126. await delay(5000);
  127. }
  128. i += 1;
  129. }
  130. console.log("pr.mergeable=%o", pr.mergeable);
  131. const { data: comments } = await github.rest.issues.listComments({
  132. issue_number: context.issue.number,
  133. owner: context.repo.owner,
  134. repo: context.repo.repo
  135. });
  136. const commentToUpdate = comments.find(comment => comment.body.startsWith(header));
  137. if (pr.mergeable === false) {
  138. let commentParams = {
  139. ...context.repo,
  140. issue_number: context.issue.number,
  141. body: fail_msg
  142. };
  143. if (commentToUpdate) {
  144. await github.rest.issues.updateComment({
  145. ...commentParams,
  146. comment_id: commentToUpdate.id,
  147. });
  148. } else {
  149. await github.rest.issues.createComment({...commentParams});
  150. }
  151. core.setFailed("Merge conflict detected");
  152. return false;
  153. } else if (commentToUpdate) {
  154. await github.rest.issues.deleteComment({
  155. ...context.repo,
  156. issue_number: context.issue.number,
  157. comment_id: commentToUpdate.id,
  158. });
  159. }
  160. core.info(`commit_sha=${pr.commit_sha}`);
  161. core.setOutput('commit_sha', pr.merge_commit_sha);
  162. return true;
  163. build_and_test:
  164. needs:
  165. - check-running-allowed
  166. if: needs.check-running-allowed.outputs.result == 'true' && needs.check-running-allowed.outputs.commit_sha != ''
  167. strategy:
  168. fail-fast: false
  169. matrix:
  170. build_preset: ["relwithdebinfo", "release-asan"]
  171. name: Build and test ${{ matrix.build_preset }}
  172. uses: ./.github/workflows/build_and_test_ya_provisioned.yml
  173. with:
  174. build_preset: ${{ matrix.build_preset }}
  175. build_target: "ydb/"
  176. test_size: "small,medium"
  177. test_type: "unittest,py3test,py2test,pytest"
  178. test_threads: 52
  179. runner_label: auto-provisioned
  180. put_build_results_to_cache: true
  181. commit_sha: ${{ needs.check-running-allowed.outputs.commit_sha }}
  182. secrets: inherit