integration-testing.yaml 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. name: Run OpenCost Integration Tests
  2. on:
  3. schedule:
  4. - cron: '0 14 * * *'
  5. push:
  6. branches:
  7. - develop
  8. pull_request:
  9. branches:
  10. - develop
  11. merge_group:
  12. types: [checks_requested]
  13. concurrency:
  14. group: ${{ github.event.merge_group.head.sha || github.ref }}-intg-tests
  15. cancel-in-progress: false
  16. jobs:
  17. check_actor_permissions:
  18. runs-on: ubuntu-latest
  19. outputs:
  20. ismaintainer: ${{ steps.check_permissions.outputs.ismaintainer }}
  21. steps:
  22. - name: Check if actor is a maintainer
  23. id: check_permissions
  24. uses: actions/github-script@v7
  25. with:
  26. script: |
  27. const { data: collaborators } = await github.rest.repos.listCollaborators({
  28. owner: context.repo.owner,
  29. repo: context.repo.repo,
  30. permission: 'push'
  31. });
  32. const writers = collaborators.map(collaborator => collaborator.login);
  33. const isActorMaintainer = writers.includes(context.actor);
  34. console.log(`Actor: ${context.actor}`);
  35. console.log(`Repository writers: ${writers.join(', ')}`);
  36. console.log(`Is actor a maintainer? ${isActorMaintainer}`);
  37. core.setOutput('ismaintainer', isActorMaintainer.toString());
  38. noop-tests:
  39. needs: check_actor_permissions
  40. runs-on: Kubecost-Linux-Small-x86
  41. if: ${{ always() && !cancelled() && github.event_name != 'merge_group' && github.ref != 'refs/heads/develop' && needs.check_actor_permissions.outputs.ismaintainer == 'false' }}
  42. outputs:
  43. is_noop: ${{ steps.noop-tests.outputs.is_noop }}
  44. steps:
  45. - name: Tests Not Needed
  46. id: noop-tests
  47. run: |
  48. echo "integration tests not running because you are not a maintainer. they will run automatically when a PR is merged."
  49. echo "is_noop=true" >> $GITHUB_OUTPUT
  50. wait_for_image_ready:
  51. runs-on: ubuntu-latest
  52. needs: check_actor_permissions
  53. if: ${{ (always() && !cancelled()) && ( github.event_name == 'merge_group' || github.ref != 'refs/heads/develop' || needs.check_actor_permissions.outputs.ismaintainer == 'true') }}
  54. outputs:
  55. IMAGE_TAG: ${{ steps.set_image_tags.outputs.IMAGE_TAG }}
  56. NAMESPACE: ${{ steps.set_image_tags.outputs.NAMESPACE }}
  57. MAINBRANCH: ${{ steps.set_image_tags.outputs.mainbranch }}
  58. passed: ${{ steps.wait_for_image_ready.outputs.passed }}
  59. steps:
  60. - uses: actions/checkout@v4
  61. with:
  62. ref: ${{ github.event.merge_group.head.sha || github.ref }}
  63. - name: Set OC SHA
  64. run: |
  65. echo "OC_SHORTHASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
  66. - name: Set image tags
  67. id: set_image_tags
  68. run: |
  69. echo "github.event_name: ${{ github.event_name }}"
  70. if [[ "${{ github.event_name }}" == "merge_group" ]]; then
  71. echo "IMAGE_TAG=ghcr.io/${{ github.repository_owner }}/opencost:test-${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
  72. echo "NAMESPACE=merge-queue-${{ github.event.number }}-oc-${{ env.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
  73. echo "mainbranch=false" >> $GITHUB_OUTPUT
  74. else
  75. echo "building on develop branch"
  76. echo "IMAGE_TAG=ghcr.io/${{ github.repository_owner }}/opencost:develop-${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
  77. echo "NAMESPACE=develop-oc-${{ env.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
  78. echo "mainbranch=true" >> $GITHUB_OUTPUT
  79. fi
  80. - name: Log into ghcr.io
  81. uses: docker/login-action@v3
  82. with:
  83. registry: ghcr.io
  84. username: ${{ github.actor }}
  85. password: ${{ secrets.GITHUB_TOKEN }}
  86. - name: wait for docker image to be ready
  87. id: wait_for_image_ready
  88. run: |
  89. max_attempts=100
  90. # Loop until the Docker image can be pulled
  91. until docker manifest inspect ${{ steps.set_image_tags.outputs.IMAGE_TAG }}; do
  92. echo "Waiting for Docker image ${{ steps.set_image_tags.outputs.IMAGE_TAG }} to be available, $max_attempts tries remain..."
  93. sleep 6
  94. max_attempts=$((max_attempts - 1))
  95. if [[ $max_attempts -eq 0 ]]; then
  96. echo "Docker image ${{ steps.set_image_tags.outputs.IMAGE_TAG }} is not available after 10 minutes. Exiting..."
  97. exit 1
  98. fi
  99. done
  100. echo "Docker image ${{ steps.set_image_tags.outputs.IMAGE_TAG }} is ready!"
  101. echo "passed=true" >> $GITHUB_OUTPUT
  102. build-test-stack:
  103. needs: wait_for_image_ready
  104. uses: opencost/opencost-infra/.github/workflows/build-stack.yaml@master
  105. secrets: inherit
  106. with:
  107. oc-container-version: "${{ needs.wait_for_image_ready.outputs.IMAGE_TAG }}"
  108. namespace: "${{ needs.wait_for_image_ready.outputs.NAMESPACE }}"
  109. wait-for-dns:
  110. needs: [wait_for_image_ready, build-test-stack]
  111. runs-on: ubuntu-latest
  112. steps:
  113. - name: Wait for DNS to resolve
  114. run: |
  115. echo "Waiting for ${{ needs.wait_for_image_ready.outputs.NAMESPACE }}.infra.opencost.io to resolve in DNS..."
  116. max_attempts=60
  117. until host ${{ needs.wait_for_image_ready.outputs.NAMESPACE }}.infra.opencost.io; do
  118. echo "DNS not yet resolved for ${{ needs.wait_for_image_ready.outputs.NAMESPACE }}.infra.opencost.io, $max_attempts tries remain..."
  119. sleep 10
  120. max_attempts=$((max_attempts - 1))
  121. if [[ $max_attempts -eq 0 ]]; then
  122. echo "DNS resolution failed for ${{ needs.wait_for_image_ready.outputs.NAMESPACE }}.infra.opencost.io after 10 minutes. Exiting..."
  123. exit 1
  124. fi
  125. done
  126. echo "DNS resolved successfully for ${{ needs.wait_for_image_ready.outputs.NAMESPACE }}.infra.opencost.io!"
  127. run-tests:
  128. needs: [wait_for_image_ready, build-test-stack, wait-for-dns]
  129. uses: opencost/opencost-infra/.github/workflows/test-stack.yaml@master
  130. secrets: inherit
  131. with:
  132. namespace: "${{ needs.wait_for_image_ready.outputs.NAMESPACE }}"
  133. target_branch: "${{ github.event.pull_request.head.ref || 'main' }}"
  134. teardown-test-stack:
  135. needs: [wait_for_image_ready, run-tests]
  136. uses: opencost/opencost-infra/.github/workflows/destroy-stack.yaml@master
  137. if: always()
  138. secrets: inherit
  139. with:
  140. namespace: "${{ needs.wait_for_image_ready.outputs.NAMESPACE }}"
  141. check-success:
  142. needs: [noop-tests, run-tests]
  143. runs-on: ubuntu-latest
  144. if: ${{ always() }}
  145. steps:
  146. - name: Check success
  147. run: |
  148. if [[ "${{ needs.noop-tests.outputs.is_noop }}" == "true" ]]; then
  149. echo "No-op tests, skipping success check"
  150. exit 0
  151. fi
  152. if [[ "${{ needs.run-tests.outputs.passed }}" != "true" ]]; then
  153. echo "One or more integration tests failed"
  154. exit 1
  155. fi
  156. echo "All integration tests passed"
  157. exit 0
  158. set-labels:
  159. needs: [wait_for_image_ready, run-tests]
  160. runs-on: ubuntu-latest
  161. steps:
  162. - name: label integration tests failing
  163. if: ${{ always() && contains(needs.*.result, 'failure') && !cancelled()}}
  164. uses: andymckay/labeler@1.0.4
  165. with:
  166. add-labels: "integration tests failed"
  167. - uses: mondeja/remove-labels-gh-action@v2
  168. if: ${{ always() && contains(needs.*.result, 'failure') && !cancelled()}}
  169. with:
  170. token: ${{ secrets.GITHUB_TOKEN }}
  171. labels: |
  172. integration tests passed
  173. - name: Label integration tests passing
  174. if: ${{ always() && !contains(needs.*.result, 'failure') && !cancelled()}}
  175. uses: andymckay/labeler@1.0.4
  176. with:
  177. add-labels: "integration tests passed"
  178. - uses: mondeja/remove-labels-gh-action@v2
  179. if: ${{ always() && !contains(needs.*.result, 'failure') && !cancelled()}}
  180. with:
  181. token: ${{ secrets.GITHUB_TOKEN }}
  182. labels: |
  183. integration tests failed