Merge branch 'master' into patch-1
This commit is contained in:
		
						commit
						a4e2558329
					
				|  | @ -1,9 +1,9 @@ | ||||||
| name: 'Setup ARC E2E Test Action' | name: "Setup ARC E2E Test Action" | ||||||
| description: 'Build controller image, create kind cluster, load the image, and exchange ARC configure token.' | description: "Build controller image, create kind cluster, load the image, and exchange ARC configure token." | ||||||
| 
 | 
 | ||||||
| inputs: | inputs: | ||||||
|   app-id: |   app-id: | ||||||
|     description: 'GitHub App Id for exchange access token' |     description: "GitHub App Id for exchange access token" | ||||||
|     required: true |     required: true | ||||||
|   app-pk: |   app-pk: | ||||||
|     description: "GitHub App private key for exchange access token" |     description: "GitHub App private key for exchange access token" | ||||||
|  | @ -20,14 +20,14 @@ inputs: | ||||||
| 
 | 
 | ||||||
| outputs: | outputs: | ||||||
|   token: |   token: | ||||||
|     description: 'Token to use for configure ARC' |     description: "Token to use for configure ARC" | ||||||
|     value: ${{steps.config-token.outputs.token}} |     value: ${{steps.config-token.outputs.token}} | ||||||
| 
 | 
 | ||||||
| runs: | runs: | ||||||
|   using: "composite" |   using: "composite" | ||||||
|   steps: |   steps: | ||||||
|     - name: Set up Docker Buildx |     - name: Set up Docker Buildx | ||||||
|       uses: docker/setup-buildx-action@v3 |       uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 | ||||||
|       with: |       with: | ||||||
|         # Pinning v0.9.1 for Buildx and BuildKit v0.10.6 |         # Pinning v0.9.1 for Buildx and BuildKit v0.10.6 | ||||||
|         # BuildKit v0.11 which has a bug causing intermittent |         # BuildKit v0.11 which has a bug causing intermittent | ||||||
|  | @ -36,7 +36,8 @@ runs: | ||||||
|         driver-opts: image=moby/buildkit:v0.10.6 |         driver-opts: image=moby/buildkit:v0.10.6 | ||||||
| 
 | 
 | ||||||
|     - name: Build controller image |     - name: Build controller image | ||||||
|       uses: docker/build-push-action@v5 |       # https://github.com/docker/build-push-action/releases/tag/v6.15.0 | ||||||
|  |       uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 | ||||||
|       with: |       with: | ||||||
|         file: Dockerfile |         file: Dockerfile | ||||||
|         platforms: linux/amd64 |         platforms: linux/amd64 | ||||||
|  | @ -56,6 +57,7 @@ runs: | ||||||
| 
 | 
 | ||||||
|     - name: Get configure token |     - name: Get configure token | ||||||
|       id: config-token |       id: config-token | ||||||
|  |       # https://github.com/peter-murray/workflow-application-token-action/releases/tag/v3.0.0 | ||||||
|       uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 |       uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 | ||||||
|       with: |       with: | ||||||
|         application_id: ${{ inputs.app-id }} |         application_id: ${{ inputs.app-id }} | ||||||
|  |  | ||||||
|  | @ -24,23 +24,27 @@ runs: | ||||||
|       shell: bash |       shell: bash | ||||||
| 
 | 
 | ||||||
|     - name: Set up QEMU |     - name: Set up QEMU | ||||||
|       uses: docker/setup-qemu-action@v3 |       # https://github.com/docker/setup-qemu-action/releases/tag/v3.6.0 | ||||||
|  |       uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 | ||||||
| 
 | 
 | ||||||
|     - name: Set up Docker Buildx |     - name: Set up Docker Buildx | ||||||
|       uses: docker/setup-buildx-action@v3 |       # https://github.com/docker/setup-buildx-action/releases/tag/v3.10.0 | ||||||
|  |       uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 | ||||||
|       with: |       with: | ||||||
|         version: latest |         version: latest | ||||||
| 
 | 
 | ||||||
|     - name: Login to DockerHub |     - name: Login to DockerHub | ||||||
|       if: ${{ github.event_name == 'release' || github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.password != ''  }} |       if: ${{ github.event_name == 'release' || github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.password != ''  }} | ||||||
|       uses: docker/login-action@v3 |       # https://github.com/docker/login-action/releases/tag/v3.4.0 | ||||||
|  |       uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 | ||||||
|       with: |       with: | ||||||
|         username: ${{ inputs.username }} |         username: ${{ inputs.username }} | ||||||
|         password: ${{ inputs.password }} |         password: ${{ inputs.password }} | ||||||
| 
 | 
 | ||||||
|     - name: Login to GitHub Container Registry |     - name: Login to GitHub Container Registry | ||||||
|       if: ${{ github.event_name == 'release' || github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.ghcr_password != ''  }} |       if: ${{ github.event_name == 'release' || github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.ghcr_password != ''  }} | ||||||
|       uses: docker/login-action@v3 |       # https://github.com/docker/login-action/releases/tag/v3.4.0 | ||||||
|  |       uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 | ||||||
|       with: |       with: | ||||||
|         registry: ghcr.io |         registry: ghcr.io | ||||||
|         username: ${{ inputs.ghcr_username }} |         username: ${{ inputs.ghcr_username }} | ||||||
|  |  | ||||||
|  | @ -7,16 +7,16 @@ on: | ||||||
|     branches: |     branches: | ||||||
|       - master |       - master | ||||||
|     paths: |     paths: | ||||||
|     - 'charts/**' |       - "charts/**" | ||||||
|     - '.github/workflows/arc-publish-chart.yaml' |       - ".github/workflows/arc-publish-chart.yaml" | ||||||
|     - '!charts/actions-runner-controller/docs/**' |       - "!charts/actions-runner-controller/docs/**" | ||||||
|     - '!charts/gha-runner-scale-set-controller/**' |       - "!charts/gha-runner-scale-set-controller/**" | ||||||
|     - '!charts/gha-runner-scale-set/**' |       - "!charts/gha-runner-scale-set/**" | ||||||
|     - '!**.md' |       - "!**.md" | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|     inputs: |     inputs: | ||||||
|       force: |       force: | ||||||
|         description: 'Force publish even if the chart version is not bumped' |         description: "Force publish even if the chart version is not bumped" | ||||||
|         type: boolean |         type: boolean | ||||||
|         required: true |         required: true | ||||||
|         default: false |         default: false | ||||||
|  | @ -45,6 +45,7 @@ jobs: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
| 
 | 
 | ||||||
|       - name: Set up Helm |       - name: Set up Helm | ||||||
|  |         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2.0 | ||||||
|         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 |         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 | ||||||
|         with: |         with: | ||||||
|           version: ${{ env.HELM_VERSION }} |           version: ${{ env.HELM_VERSION }} | ||||||
|  | @ -60,10 +61,11 @@ jobs: | ||||||
|       # python is a requirement for the chart-testing action below (supports yamllint among other tests) |       # python is a requirement for the chart-testing action below (supports yamllint among other tests) | ||||||
|       - uses: actions/setup-python@v5 |       - uses: actions/setup-python@v5 | ||||||
|         with: |         with: | ||||||
|         python-version: '3.11' |           python-version: "3.11" | ||||||
| 
 | 
 | ||||||
|       - name: Set up chart-testing |       - name: Set up chart-testing | ||||||
|       uses: helm/chart-testing-action@v2.6.0 |         # https://github.com/helm/chart-testing-action/releases/tag/v2.7.0 | ||||||
|  |         uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b | ||||||
| 
 | 
 | ||||||
|       - name: Run chart-testing (list-changed) |       - name: Run chart-testing (list-changed) | ||||||
|         id: list-changed |         id: list-changed | ||||||
|  | @ -79,7 +81,8 @@ jobs: | ||||||
| 
 | 
 | ||||||
|       - name: Create kind cluster |       - name: Create kind cluster | ||||||
|         if: steps.list-changed.outputs.changed == 'true' |         if: steps.list-changed.outputs.changed == 'true' | ||||||
|       uses: helm/kind-action@v1.4.0 |         # https://github.com/helm/kind-action/releases/tag/v1.12.0 | ||||||
|  |         uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 | ||||||
| 
 | 
 | ||||||
|       # We need cert-manager already installed in the cluster because we assume the CRDs exist |       # We need cert-manager already installed in the cluster because we assume the CRDs exist | ||||||
|       - name: Install cert-manager |       - name: Install cert-manager | ||||||
|  | @ -145,6 +148,7 @@ jobs: | ||||||
| 
 | 
 | ||||||
|       - name: Get Token |       - name: Get Token | ||||||
|         id: get_workflow_token |         id: get_workflow_token | ||||||
|  |         # https://github.com/peter-murray/workflow-application-token-action/releases/tag/v3.0.0 | ||||||
|         uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 |         uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 | ||||||
|         with: |         with: | ||||||
|           application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} |           application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} | ||||||
|  | @ -152,7 +156,7 @@ jobs: | ||||||
|           organization: ${{ env.CHART_TARGET_ORG }} |           organization: ${{ env.CHART_TARGET_ORG }} | ||||||
| 
 | 
 | ||||||
|       - name: Install chart-releaser |       - name: Install chart-releaser | ||||||
|       uses: helm/chart-releaser-action@v1.4.1 |         uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f | ||||||
|         with: |         with: | ||||||
|           install_only: true |           install_only: true | ||||||
|           install_dir: ${{ github.workspace }}/bin |           install_dir: ${{ github.workspace }}/bin | ||||||
|  |  | ||||||
|  | @ -9,10 +9,10 @@ on: | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|     inputs: |     inputs: | ||||||
|       release_tag_name: |       release_tag_name: | ||||||
|         description: 'Tag name of the release to publish' |         description: "Tag name of the release to publish" | ||||||
|         required: true |         required: true | ||||||
|       push_to_registries: |       push_to_registries: | ||||||
|         description: 'Push images to registries' |         description: "Push images to registries" | ||||||
|         required: true |         required: true | ||||||
|         type: boolean |         type: boolean | ||||||
|         default: false |         default: false | ||||||
|  | @ -43,7 +43,7 @@ jobs: | ||||||
| 
 | 
 | ||||||
|       - uses: actions/setup-go@v5 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: 'go.mod' |           go-version-file: "go.mod" | ||||||
| 
 | 
 | ||||||
|       - name: Install tools |       - name: Install tools | ||||||
|         run: | |         run: | | ||||||
|  | @ -73,6 +73,7 @@ jobs: | ||||||
| 
 | 
 | ||||||
|       - name: Get Token |       - name: Get Token | ||||||
|         id: get_workflow_token |         id: get_workflow_token | ||||||
|  |         # https://github.com/peter-murray/workflow-application-token-action/releases/tag/v3.0.0 | ||||||
|         uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 |         uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 | ||||||
|         with: |         with: | ||||||
|           application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} |           application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} | ||||||
|  |  | ||||||
|  | @ -7,10 +7,10 @@ on: | ||||||
|   # are available to the workflow run |   # are available to the workflow run | ||||||
|   push: |   push: | ||||||
|     branches: |     branches: | ||||||
|       - 'master' |       - "master" | ||||||
|     paths: |     paths: | ||||||
|       - 'runner/VERSION' |       - "runner/VERSION" | ||||||
|       - '.github/workflows/arc-release-runners.yaml' |       - ".github/workflows/arc-release-runners.yaml" | ||||||
| 
 | 
 | ||||||
| env: | env: | ||||||
|   # Safeguard to prevent pushing images to registeries after build |   # Safeguard to prevent pushing images to registeries after build | ||||||
|  | @ -39,6 +39,7 @@ jobs: | ||||||
| 
 | 
 | ||||||
|       - name: Get Token |       - name: Get Token | ||||||
|         id: get_workflow_token |         id: get_workflow_token | ||||||
|  |         # https://github.com/peter-murray/workflow-application-token-action/releases/tag/v3.0.0 | ||||||
|         uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 |         uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 | ||||||
|         with: |         with: | ||||||
|           application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} |           application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} | ||||||
|  |  | ||||||
|  | @ -5,20 +5,20 @@ on: | ||||||
|     branches: |     branches: | ||||||
|       - master |       - master | ||||||
|     paths: |     paths: | ||||||
|       - 'charts/**' |       - "charts/**" | ||||||
|       - '.github/workflows/arc-validate-chart.yaml' |       - ".github/workflows/arc-validate-chart.yaml" | ||||||
|       - '!charts/actions-runner-controller/docs/**' |       - "!charts/actions-runner-controller/docs/**" | ||||||
|       - '!**.md' |       - "!**.md" | ||||||
|       - '!charts/gha-runner-scale-set-controller/**' |       - "!charts/gha-runner-scale-set-controller/**" | ||||||
|       - '!charts/gha-runner-scale-set/**' |       - "!charts/gha-runner-scale-set/**" | ||||||
|   push: |   push: | ||||||
|     paths: |     paths: | ||||||
|       - 'charts/**' |       - "charts/**" | ||||||
|       - '.github/workflows/arc-validate-chart.yaml' |       - ".github/workflows/arc-validate-chart.yaml" | ||||||
|       - '!charts/actions-runner-controller/docs/**' |       - "!charts/actions-runner-controller/docs/**" | ||||||
|       - '!**.md' |       - "!**.md" | ||||||
|       - '!charts/gha-runner-scale-set-controller/**' |       - "!charts/gha-runner-scale-set-controller/**" | ||||||
|       - '!charts/gha-runner-scale-set/**' |       - "!charts/gha-runner-scale-set/**" | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
| env: | env: | ||||||
|   KUBE_SCORE_VERSION: 1.10.0 |   KUBE_SCORE_VERSION: 1.10.0 | ||||||
|  | @ -45,34 +45,19 @@ jobs: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
| 
 | 
 | ||||||
|       - name: Set up Helm |       - name: Set up Helm | ||||||
|         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2 |         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2.0 | ||||||
|         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 |         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 | ||||||
|         with: |         with: | ||||||
|           version: ${{ env.HELM_VERSION }} |           version: ${{ env.HELM_VERSION }} | ||||||
| 
 | 
 | ||||||
|       - name: Set up kube-score |  | ||||||
|         run: | |  | ||||||
|           wget https://github.com/zegl/kube-score/releases/download/v${{ env.KUBE_SCORE_VERSION }}/kube-score_${{ env.KUBE_SCORE_VERSION }}_linux_amd64 -O kube-score |  | ||||||
|           chmod 755 kube-score |  | ||||||
| 
 |  | ||||||
|       - name: Kube-score generated manifests |  | ||||||
|         run: helm template  --values charts/.ci/values-kube-score.yaml charts/* | ./kube-score score - |  | ||||||
|               --ignore-test pod-networkpolicy |  | ||||||
|               --ignore-test deployment-has-poddisruptionbudget |  | ||||||
|               --ignore-test deployment-has-host-podantiaffinity |  | ||||||
|               --ignore-test container-security-context |  | ||||||
|               --ignore-test pod-probes |  | ||||||
|               --ignore-test container-image-tag |  | ||||||
|               --enable-optional-test container-security-context-privileged |  | ||||||
|               --enable-optional-test container-security-context-readonlyrootfilesystem |  | ||||||
| 
 |  | ||||||
|       # python is a requirement for the chart-testing action below (supports yamllint among other tests) |       # python is a requirement for the chart-testing action below (supports yamllint among other tests) | ||||||
|       - uses: actions/setup-python@v5 |       - uses: actions/setup-python@v5 | ||||||
|         with: |         with: | ||||||
|           python-version: '3.11' |           python-version: "3.11" | ||||||
| 
 | 
 | ||||||
|       - name: Set up chart-testing |       - name: Set up chart-testing | ||||||
|         uses: helm/chart-testing-action@v2.6.0 |         # https://github.com/helm/chart-testing-action/releases/tag/v2.7.0 | ||||||
|  |         uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b | ||||||
| 
 | 
 | ||||||
|       - name: Run chart-testing (list-changed) |       - name: Run chart-testing (list-changed) | ||||||
|         id: list-changed |         id: list-changed | ||||||
|  | @ -87,7 +72,8 @@ jobs: | ||||||
|           ct lint --config charts/.ci/ct-config.yaml |           ct lint --config charts/.ci/ct-config.yaml | ||||||
| 
 | 
 | ||||||
|       - name: Create kind cluster |       - name: Create kind cluster | ||||||
|         uses: helm/kind-action@v1.4.0 |         # https://github.com/helm/kind-action/releases/tag/v1.12.0 | ||||||
|  |         uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 | ||||||
|         if: steps.list-changed.outputs.changed == 'true' |         if: steps.list-changed.outputs.changed == 'true' | ||||||
| 
 | 
 | ||||||
|       # We need cert-manager already installed in the cluster because we assume the CRDs exist |       # We need cert-manager already installed in the cluster because we assume the CRDs exist | ||||||
|  |  | ||||||
|  | @ -3,11 +3,11 @@ name: Validate ARC Runners | ||||||
| on: | on: | ||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - '**' |       - "**" | ||||||
|     paths: |     paths: | ||||||
|       - 'runner/**' |       - "runner/**" | ||||||
|       - 'test/startup/**' |       - "test/startup/**" | ||||||
|       - '!**.md' |       - "!**.md" | ||||||
| 
 | 
 | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  | @ -25,21 +25,9 @@ jobs: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: shellcheck |       - name: "Run shellcheck" | ||||||
|         uses: reviewdog/action-shellcheck@v1 |         run: make shellcheck | ||||||
|         with: | 
 | ||||||
|           github_token: ${{ secrets.GITHUB_TOKEN }} |  | ||||||
|           path: "./runner" |  | ||||||
|           pattern: | |  | ||||||
|             *.sh |  | ||||||
|             *.bash |  | ||||||
|             update-status |  | ||||||
|           # Make this consistent with `make shellsheck` |  | ||||||
|           shellcheck_flags: "--shell bash --source-path runner" |  | ||||||
|           exclude: "./.git/*" |  | ||||||
|           check_all_files_with_shebangs: "false" |  | ||||||
|           # Set this to "true" once we addressed all the shellcheck findings |  | ||||||
|           fail_on_error: "false" |  | ||||||
|   test-runner-entrypoint: |   test-runner-entrypoint: | ||||||
|     name: Test entrypoint |     name: Test entrypoint | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|  | @ -4,27 +4,27 @@ on: | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|     inputs: |     inputs: | ||||||
|       ref: |       ref: | ||||||
|         description: 'The branch, tag or SHA to cut a release from' |         description: "The branch, tag or SHA to cut a release from" | ||||||
|         required: false |         required: false | ||||||
|         type: string |         type: string | ||||||
|         default: '' |         default: "" | ||||||
|       release_tag_name: |       release_tag_name: | ||||||
|         description: 'The name to tag the controller image with' |         description: "The name to tag the controller image with" | ||||||
|         required: true |         required: true | ||||||
|         type: string |         type: string | ||||||
|         default: 'canary' |         default: "canary" | ||||||
|       push_to_registries: |       push_to_registries: | ||||||
|         description: 'Push images to registries' |         description: "Push images to registries" | ||||||
|         required: true |         required: true | ||||||
|         type: boolean |         type: boolean | ||||||
|         default: false |         default: false | ||||||
|       publish_gha_runner_scale_set_controller_chart: |       publish_gha_runner_scale_set_controller_chart: | ||||||
|         description: 'Publish new helm chart for gha-runner-scale-set-controller' |         description: "Publish new helm chart for gha-runner-scale-set-controller" | ||||||
|         required: true |         required: true | ||||||
|         type: boolean |         type: boolean | ||||||
|         default: false |         default: false | ||||||
|       publish_gha_runner_scale_set_chart: |       publish_gha_runner_scale_set_chart: | ||||||
|         description: 'Publish new helm chart for gha-runner-scale-set' |         description: "Publish new helm chart for gha-runner-scale-set" | ||||||
|         required: true |         required: true | ||||||
|         type: boolean |         type: boolean | ||||||
|         default: false |         default: false | ||||||
|  | @ -72,10 +72,11 @@ jobs: | ||||||
|           echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT |           echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | ||||||
| 
 | 
 | ||||||
|       - name: Set up QEMU |       - name: Set up QEMU | ||||||
|         uses: docker/setup-qemu-action@v3 |         # https://github.com/docker/setup-qemu-action/releases/tag/v3.6.0 | ||||||
|  |         uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 | ||||||
| 
 | 
 | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3 |         uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 | ||||||
|         with: |         with: | ||||||
|           # Pinning v0.9.1 for Buildx and BuildKit v0.10.6 |           # Pinning v0.9.1 for Buildx and BuildKit v0.10.6 | ||||||
|           # BuildKit v0.11 which has a bug causing intermittent |           # BuildKit v0.11 which has a bug causing intermittent | ||||||
|  | @ -84,14 +85,16 @@ jobs: | ||||||
|           driver-opts: image=moby/buildkit:v0.10.6 |           driver-opts: image=moby/buildkit:v0.10.6 | ||||||
| 
 | 
 | ||||||
|       - name: Login to GitHub Container Registry |       - name: Login to GitHub Container Registry | ||||||
|         uses: docker/login-action@v3 |         # https://github.com/docker/login-action/releases/tag/v3.4.0 | ||||||
|  |         uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 | ||||||
|         with: |         with: | ||||||
|           registry: ghcr.io |           registry: ghcr.io | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN }} |           password: ${{ secrets.GITHUB_TOKEN }} | ||||||
| 
 | 
 | ||||||
|       - name: Build & push controller image |       - name: Build & push controller image | ||||||
|         uses: docker/build-push-action@v5 |         # https://github.com/docker/build-push-action/releases/tag/v6.15.0 | ||||||
|  |         uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 | ||||||
|         with: |         with: | ||||||
|           file: Dockerfile |           file: Dockerfile | ||||||
|           platforms: linux/amd64,linux/arm64 |           platforms: linux/amd64,linux/arm64 | ||||||
|  | @ -140,7 +143,7 @@ jobs: | ||||||
|           echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT |           echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | ||||||
| 
 | 
 | ||||||
|       - name: Set up Helm |       - name: Set up Helm | ||||||
|         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2 |         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2.0 | ||||||
|         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 |         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 | ||||||
|         with: |         with: | ||||||
|           version: ${{ env.HELM_VERSION }} |           version: ${{ env.HELM_VERSION }} | ||||||
|  | @ -188,7 +191,7 @@ jobs: | ||||||
|           echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT |           echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | ||||||
| 
 | 
 | ||||||
|       - name: Set up Helm |       - name: Set up Helm | ||||||
|         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2 |         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2.0 | ||||||
|         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 |         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 | ||||||
|         with: |         with: | ||||||
|           version: ${{ env.HELM_VERSION }} |           version: ${{ env.HELM_VERSION }} | ||||||
|  |  | ||||||
|  | @ -5,16 +5,16 @@ on: | ||||||
|     branches: |     branches: | ||||||
|       - master |       - master | ||||||
|     paths: |     paths: | ||||||
|       - 'charts/**' |       - "charts/**" | ||||||
|       - '.github/workflows/gha-validate-chart.yaml' |       - ".github/workflows/gha-validate-chart.yaml" | ||||||
|       - '!charts/actions-runner-controller/**' |       - "!charts/actions-runner-controller/**" | ||||||
|       - '!**.md' |       - "!**.md" | ||||||
|   push: |   push: | ||||||
|     paths: |     paths: | ||||||
|       - 'charts/**' |       - "charts/**" | ||||||
|       - '.github/workflows/gha-validate-chart.yaml' |       - ".github/workflows/gha-validate-chart.yaml" | ||||||
|       - '!charts/actions-runner-controller/**' |       - "!charts/actions-runner-controller/**" | ||||||
|       - '!**.md' |       - "!**.md" | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
| env: | env: | ||||||
|   KUBE_SCORE_VERSION: 1.16.1 |   KUBE_SCORE_VERSION: 1.16.1 | ||||||
|  | @ -41,7 +41,7 @@ jobs: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
| 
 | 
 | ||||||
|       - name: Set up Helm |       - name: Set up Helm | ||||||
|         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2 |         # Using https://github.com/Azure/setup-helm/releases/tag/v4.2.0 | ||||||
|         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 |         uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 | ||||||
|         with: |         with: | ||||||
|           version: ${{ env.HELM_VERSION }} |           version: ${{ env.HELM_VERSION }} | ||||||
|  | @ -49,10 +49,11 @@ jobs: | ||||||
|       # python is a requirement for the chart-testing action below (supports yamllint among other tests) |       # python is a requirement for the chart-testing action below (supports yamllint among other tests) | ||||||
|       - uses: actions/setup-python@v5 |       - uses: actions/setup-python@v5 | ||||||
|         with: |         with: | ||||||
|           python-version: '3.11' |           python-version: "3.11" | ||||||
| 
 | 
 | ||||||
|       - name: Set up chart-testing |       - name: Set up chart-testing | ||||||
|         uses: helm/chart-testing-action@v2.6.0 |         # https://github.com/helm/chart-testing-action/releases/tag/v2.7.0 | ||||||
|  |         uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b | ||||||
| 
 | 
 | ||||||
|       - name: Run chart-testing (list-changed) |       - name: Run chart-testing (list-changed) | ||||||
|         id: list-changed |         id: list-changed | ||||||
|  | @ -68,13 +69,14 @@ jobs: | ||||||
|           ct lint --config charts/.ci/ct-config-gha.yaml |           ct lint --config charts/.ci/ct-config-gha.yaml | ||||||
| 
 | 
 | ||||||
|       - name: Set up docker buildx |       - name: Set up docker buildx | ||||||
|         uses: docker/setup-buildx-action@v3 |         uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 | ||||||
|         if: steps.list-changed.outputs.changed == 'true' |         if: steps.list-changed.outputs.changed == 'true' | ||||||
|         with: |         with: | ||||||
|           version: latest |           version: latest | ||||||
| 
 | 
 | ||||||
|       - name: Build controller image |       - name: Build controller image | ||||||
|         uses: docker/build-push-action@v5 |         # https://github.com/docker/build-push-action/releases/tag/v6.15.0 | ||||||
|  |         uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 | ||||||
|         if: steps.list-changed.outputs.changed == 'true' |         if: steps.list-changed.outputs.changed == 'true' | ||||||
|         with: |         with: | ||||||
|           file: Dockerfile |           file: Dockerfile | ||||||
|  | @ -89,7 +91,8 @@ jobs: | ||||||
|           cache-to: type=gha,mode=max |           cache-to: type=gha,mode=max | ||||||
| 
 | 
 | ||||||
|       - name: Create kind cluster |       - name: Create kind cluster | ||||||
|         uses: helm/kind-action@v1.4.0 |         # https://github.com/helm/kind-action/releases/tag/v1.12.0 | ||||||
|  |         uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 | ||||||
|         if: steps.list-changed.outputs.changed == 'true' |         if: steps.list-changed.outputs.changed == 'true' | ||||||
|         with: |         with: | ||||||
|           cluster_name: chart-testing |           cluster_name: chart-testing | ||||||
|  |  | ||||||
|  | @ -7,30 +7,30 @@ on: | ||||||
|     branches: |     branches: | ||||||
|       - master |       - master | ||||||
|     paths-ignore: |     paths-ignore: | ||||||
|       - '**.md' |       - "**.md" | ||||||
|       - '.github/actions/**' |       - ".github/actions/**" | ||||||
|       - '.github/ISSUE_TEMPLATE/**' |       - ".github/ISSUE_TEMPLATE/**" | ||||||
|       - '.github/workflows/e2e-test-dispatch-workflow.yaml' |       - ".github/workflows/e2e-test-dispatch-workflow.yaml" | ||||||
|       - '.github/workflows/gha-e2e-tests.yaml' |       - ".github/workflows/gha-e2e-tests.yaml" | ||||||
|       - '.github/workflows/arc-publish.yaml' |       - ".github/workflows/arc-publish.yaml" | ||||||
|       - '.github/workflows/arc-publish-chart.yaml' |       - ".github/workflows/arc-publish-chart.yaml" | ||||||
|       - '.github/workflows/gha-publish-chart.yaml' |       - ".github/workflows/gha-publish-chart.yaml" | ||||||
|       - '.github/workflows/arc-release-runners.yaml' |       - ".github/workflows/arc-release-runners.yaml" | ||||||
|       - '.github/workflows/global-run-codeql.yaml' |       - ".github/workflows/global-run-codeql.yaml" | ||||||
|       - '.github/workflows/global-run-first-interaction.yaml' |       - ".github/workflows/global-run-first-interaction.yaml" | ||||||
|       - '.github/workflows/global-run-stale.yaml' |       - ".github/workflows/global-run-stale.yaml" | ||||||
|       - '.github/workflows/arc-update-runners-scheduled.yaml' |       - ".github/workflows/arc-update-runners-scheduled.yaml" | ||||||
|       - '.github/workflows/validate-arc.yaml' |       - ".github/workflows/validate-arc.yaml" | ||||||
|       - '.github/workflows/arc-validate-chart.yaml' |       - ".github/workflows/arc-validate-chart.yaml" | ||||||
|       - '.github/workflows/gha-validate-chart.yaml' |       - ".github/workflows/gha-validate-chart.yaml" | ||||||
|       - '.github/workflows/arc-validate-runners.yaml' |       - ".github/workflows/arc-validate-runners.yaml" | ||||||
|       - '.github/dependabot.yml' |       - ".github/dependabot.yml" | ||||||
|       - '.github/RELEASE_NOTE_TEMPLATE.md' |       - ".github/RELEASE_NOTE_TEMPLATE.md" | ||||||
|       - 'runner/**' |       - "runner/**" | ||||||
|       - '.gitignore' |       - ".gitignore" | ||||||
|       - 'PROJECT' |       - "PROJECT" | ||||||
|       - 'LICENSE' |       - "LICENSE" | ||||||
|       - 'Makefile' |       - "Makefile" | ||||||
| 
 | 
 | ||||||
| # https://docs.github.com/en/rest/overview/permissions-required-for-github-apps | # https://docs.github.com/en/rest/overview/permissions-required-for-github-apps | ||||||
| permissions: | permissions: | ||||||
|  | @ -59,6 +59,7 @@ jobs: | ||||||
| 
 | 
 | ||||||
|       - name: Get Token |       - name: Get Token | ||||||
|         id: get_workflow_token |         id: get_workflow_token | ||||||
|  |         # https://github.com/peter-murray/workflow-application-token-action/releases/tag/v3.0.0 | ||||||
|         uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 |         uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 | ||||||
|         with: |         with: | ||||||
|           application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} |           application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} | ||||||
|  | @ -93,7 +94,8 @@ jobs: | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v4 | ||||||
| 
 | 
 | ||||||
|       - name: Login to GitHub Container Registry |       - name: Login to GitHub Container Registry | ||||||
|         uses: docker/login-action@v3 |         # https://github.com/docker/login-action/releases/tag/v3.4.0 | ||||||
|  |         uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 | ||||||
|         with: |         with: | ||||||
|           registry: ghcr.io |           registry: ghcr.io | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|  | @ -110,16 +112,19 @@ jobs: | ||||||
|           echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT |           echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | ||||||
| 
 | 
 | ||||||
|       - name: Set up QEMU |       - name: Set up QEMU | ||||||
|         uses: docker/setup-qemu-action@v3 |         # https://github.com/docker/setup-qemu-action/releases/tag/v3.6.0 | ||||||
|  |         uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 | ||||||
| 
 | 
 | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3 |         # https://github.com/docker/setup-buildx-action/releases/tag/v3.10.0 | ||||||
|  |         uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 | ||||||
|         with: |         with: | ||||||
|           version: latest |           version: latest | ||||||
| 
 | 
 | ||||||
|       # Unstable builds - run at your own risk |       # Unstable builds - run at your own risk | ||||||
|       - name: Build and Push |       - name: Build and Push | ||||||
|         uses: docker/build-push-action@v5 |         # https://github.com/docker/build-push-action/releases/tag/v6.15.0 | ||||||
|  |         uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           file: ./Dockerfile |           file: ./Dockerfile | ||||||
|  |  | ||||||
|  | @ -4,16 +4,16 @@ on: | ||||||
|     branches: |     branches: | ||||||
|       - master |       - master | ||||||
|     paths: |     paths: | ||||||
|       - '.github/workflows/go.yaml' |       - ".github/workflows/go.yaml" | ||||||
|       - '**.go' |       - "**.go" | ||||||
|       - 'go.mod' |       - "go.mod" | ||||||
|       - 'go.sum' |       - "go.sum" | ||||||
|   pull_request: |   pull_request: | ||||||
|     paths: |     paths: | ||||||
|       - '.github/workflows/go.yaml' |       - ".github/workflows/go.yaml" | ||||||
|       - '**.go' |       - "**.go" | ||||||
|       - 'go.mod' |       - "go.mod" | ||||||
|       - 'go.sum' |       - "go.sum" | ||||||
| 
 | 
 | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  | @ -32,7 +32,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-go@v5 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: 'go.mod' |           go-version-file: "go.mod" | ||||||
|           cache: false |           cache: false | ||||||
|       - name: fmt |       - name: fmt | ||||||
|         run: go fmt ./... |         run: go fmt ./... | ||||||
|  | @ -45,13 +45,14 @@ jobs: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-go@v5 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: 'go.mod' |           go-version-file: "go.mod" | ||||||
|           cache: false |           cache: false | ||||||
|       - name: golangci-lint |       - name: golangci-lint | ||||||
|         uses: golangci/golangci-lint-action@v6 |         # https://github.com/golangci/golangci-lint-action/releases/tag/v7.0.0 | ||||||
|  |         uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd | ||||||
|         with: |         with: | ||||||
|           only-new-issues: true |           only-new-issues: true | ||||||
|           version: v1.55.2 |           version: v2.1.2 | ||||||
| 
 | 
 | ||||||
|   generate: |   generate: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  | @ -59,7 +60,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-go@v5 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: 'go.mod' |           go-version-file: "go.mod" | ||||||
|           cache: false |           cache: false | ||||||
|       - name: Generate |       - name: Generate | ||||||
|         run: make generate |         run: make generate | ||||||
|  | @ -72,7 +73,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-go@v5 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: 'go.mod' |           go-version-file: "go.mod" | ||||||
|       - run: make manifests |       - run: make manifests | ||||||
|       - name: Check diff |       - name: Check diff | ||||||
|         run: git diff --exit-code |         run: git diff --exit-code | ||||||
|  |  | ||||||
|  | @ -1,19 +1,14 @@ | ||||||
|  | version: "2" | ||||||
| run: | run: | ||||||
|   timeout: 3m |   timeout: 5m | ||||||
| output: | linters: | ||||||
|   formats: |   settings: | ||||||
|     - format: github-actions |  | ||||||
|       path: stdout |  | ||||||
| linters-settings: |  | ||||||
|     errcheck: |     errcheck: | ||||||
|       exclude-functions: |       exclude-functions: | ||||||
|         - (net/http.ResponseWriter).Write |         - (net/http.ResponseWriter).Write | ||||||
|         - (*net/http.Server).Shutdown |         - (*net/http.Server).Shutdown | ||||||
|         - (*github.com/actions/actions-runner-controller/simulator.VisibleRunnerGroups).Add |         - (*github.com/actions/actions-runner-controller/simulator.VisibleRunnerGroups).Add | ||||||
|         - (*github.com/actions/actions-runner-controller/testing.Kind).Stop |         - (*github.com/actions/actions-runner-controller/testing.Kind).Stop | ||||||
| issues: |   exclusions: | ||||||
|   exclude-rules: |     presets: | ||||||
|     - path: controllers/suite_test.go |       - std-error-handling | ||||||
|       linters: |  | ||||||
|         - staticcheck |  | ||||||
|       text: "SA1019" |  | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| # Build the manager binary | # Build the manager binary | ||||||
| FROM --platform=$BUILDPLATFORM golang:1.24.0 as builder | FROM --platform=$BUILDPLATFORM golang:1.24.3 AS builder | ||||||
| 
 | 
 | ||||||
| WORKDIR /workspace | WORKDIR /workspace | ||||||
| 
 | 
 | ||||||
|  | @ -30,7 +30,7 @@ ARG TARGETPLATFORM TARGETOS TARGETARCH TARGETVARIANT VERSION=dev COMMIT_SHA=dev | ||||||
| # to avoid https://github.com/moby/buildkit/issues/2334 | # to avoid https://github.com/moby/buildkit/issues/2334 | ||||||
| # We can use docker layer cache so the build is fast enogh anyway | # We can use docker layer cache so the build is fast enogh anyway | ||||||
| # We also use per-platform GOCACHE for the same reason. | # We also use per-platform GOCACHE for the same reason. | ||||||
| ENV GOCACHE /build/${TARGETPLATFORM}/root/.cache/go-build | ENV GOCACHE="/build/${TARGETPLATFORM}/root/.cache/go-build" | ||||||
| 
 | 
 | ||||||
| # Build | # Build | ||||||
| RUN --mount=target=. \ | RUN --mount=target=. \ | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										10
									
								
								Makefile
								
								
								
								
							|  | @ -6,7 +6,7 @@ endif | ||||||
| DOCKER_USER ?= $(shell echo ${DOCKER_IMAGE_NAME} | cut -d / -f1) | DOCKER_USER ?= $(shell echo ${DOCKER_IMAGE_NAME} | cut -d / -f1) | ||||||
| VERSION ?= dev | VERSION ?= dev | ||||||
| COMMIT_SHA = $(shell git rev-parse HEAD) | COMMIT_SHA = $(shell git rev-parse HEAD) | ||||||
| RUNNER_VERSION ?= 2.323.0 | RUNNER_VERSION ?= 2.325.0 | ||||||
| TARGETPLATFORM ?= $(shell arch) | TARGETPLATFORM ?= $(shell arch) | ||||||
| RUNNER_NAME ?= ${DOCKER_USER}/actions-runner | RUNNER_NAME ?= ${DOCKER_USER}/actions-runner | ||||||
| RUNNER_TAG  ?= ${VERSION} | RUNNER_TAG  ?= ${VERSION} | ||||||
|  | @ -20,7 +20,7 @@ KUBECONTEXT ?= kind-acceptance | ||||||
| CLUSTER ?= acceptance | CLUSTER ?= acceptance | ||||||
| CERT_MANAGER_VERSION ?= v1.1.1 | CERT_MANAGER_VERSION ?= v1.1.1 | ||||||
| KUBE_RBAC_PROXY_VERSION ?= v0.11.0 | KUBE_RBAC_PROXY_VERSION ?= v0.11.0 | ||||||
| SHELLCHECK_VERSION ?= 0.8.0 | SHELLCHECK_VERSION ?= 0.10.0 | ||||||
| 
 | 
 | ||||||
| # Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
 | ||||||
| CRD_OPTIONS ?= "crd:generateEmbeddedObjectMeta=true,allowDangerousTypes=true" | CRD_OPTIONS ?= "crd:generateEmbeddedObjectMeta=true,allowDangerousTypes=true" | ||||||
|  | @ -68,7 +68,7 @@ endif | ||||||
| all: manager | all: manager | ||||||
| 
 | 
 | ||||||
| lint: | lint: | ||||||
| 	docker run --rm -v $(PWD):/app -w /app golangci/golangci-lint:v1.57.2 golangci-lint run | 	docker run --rm -v $(PWD):/app -w /app golangci/golangci-lint:v2.1.2 golangci-lint run | ||||||
| 
 | 
 | ||||||
| GO_TEST_ARGS ?= -short | GO_TEST_ARGS ?= -short | ||||||
| 
 | 
 | ||||||
|  | @ -204,7 +204,7 @@ generate: controller-gen | ||||||
| 
 | 
 | ||||||
| # Run shellcheck on runner scripts
 | # Run shellcheck on runner scripts
 | ||||||
| shellcheck: shellcheck-install | shellcheck: shellcheck-install | ||||||
| 	$(TOOLS_PATH)/shellcheck --shell bash --source-path runner runner/*.sh hack/*.sh | 	$(TOOLS_PATH)/shellcheck --shell bash --source-path runner runner/*.sh runner/update-status hack/*.sh | ||||||
| 
 | 
 | ||||||
| docker-buildx: | docker-buildx: | ||||||
| 	export DOCKER_CLI_EXPERIMENTAL=enabled ;\
 | 	export DOCKER_CLI_EXPERIMENTAL=enabled ;\
 | ||||||
|  | @ -310,7 +310,7 @@ github-release: release | ||||||
| # Otherwise we get errors like the below:
 | # Otherwise we get errors like the below:
 | ||||||
| #   Error: failed to install CRD crds/actions.summerwind.dev_runnersets.yaml: CustomResourceDefinition.apiextensions.k8s.io "runnersets.actions.summerwind.dev" is invalid: [spec.validation.openAPIV3Schema.properties[spec].properties[template].properties[spec].properties[containers].items.properties[ports].items.properties[protocol].default: Required value: this property is in x-kubernetes-list-map-keys, so it must have a default or be a required property, spec.validation.openAPIV3Schema.properties[spec].properties[template].properties[spec].properties[initContainers].items.properties[ports].items.properties[protocol].default: Required value: this property is in x-kubernetes-list-map-keys, so it must have a default or be a required property]
 | #   Error: failed to install CRD crds/actions.summerwind.dev_runnersets.yaml: CustomResourceDefinition.apiextensions.k8s.io "runnersets.actions.summerwind.dev" is invalid: [spec.validation.openAPIV3Schema.properties[spec].properties[template].properties[spec].properties[containers].items.properties[ports].items.properties[protocol].default: Required value: this property is in x-kubernetes-list-map-keys, so it must have a default or be a required property, spec.validation.openAPIV3Schema.properties[spec].properties[template].properties[spec].properties[initContainers].items.properties[ports].items.properties[protocol].default: Required value: this property is in x-kubernetes-list-map-keys, so it must have a default or be a required property]
 | ||||||
| #
 | #
 | ||||||
| # Note that controller-gen newer than 0.6.2 is needed due to https://github.com/kubernetes-sigs/controller-tools/issues/448
 | # Note that controller-gen newer than 0.7.0 is needed due to https://github.com/kubernetes-sigs/controller-tools/issues/448
 | ||||||
| # Otherwise ObjectMeta embedded in Spec results in empty on the storage.
 | # Otherwise ObjectMeta embedded in Spec results in empty on the storage.
 | ||||||
| controller-gen: | controller-gen: | ||||||
| ifeq (, $(shell which controller-gen)) | ifeq (, $(shell which controller-gen)) | ||||||
|  |  | ||||||
|  | @ -10,16 +10,17 @@ env: | ||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   assume-role-in-runner-test: |   assume-role-in-runner-test: | ||||||
|     runs-on: ['self-hosted', 'Linux'] |     runs-on: ["self-hosted", "Linux"] | ||||||
|     steps: |     steps: | ||||||
|       - name: Test aws-actions/configure-aws-credentials Action |       - name: Test aws-actions/configure-aws-credentials Action | ||||||
|         uses: aws-actions/configure-aws-credentials@v1 |         # https://github.com/aws-actions/configure-aws-credentials/releases/tag/v4.1.0 | ||||||
|  |         uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 | ||||||
|         with: |         with: | ||||||
|           aws-region: ${{ env.AWS_REGION }} |           aws-region: ${{ env.AWS_REGION }} | ||||||
|           role-to-assume: ${{ env.ASSUME_ROLE_ARN }} |           role-to-assume: ${{ env.ASSUME_ROLE_ARN }} | ||||||
|           role-duration-seconds: 900 |           role-duration-seconds: 900 | ||||||
|   assume-role-in-container-test: |   assume-role-in-container-test: | ||||||
|     runs-on: ['self-hosted', 'Linux'] |     runs-on: ["self-hosted", "Linux"] | ||||||
|     container: |     container: | ||||||
|       image: amazon/aws-cli |       image: amazon/aws-cli | ||||||
|       env: |       env: | ||||||
|  | @ -29,7 +30,8 @@ jobs: | ||||||
|         - /var/run/secrets/eks.amazonaws.com/serviceaccount/token:/var/run/secrets/eks.amazonaws.com/serviceaccount/token |         - /var/run/secrets/eks.amazonaws.com/serviceaccount/token:/var/run/secrets/eks.amazonaws.com/serviceaccount/token | ||||||
|     steps: |     steps: | ||||||
|       - name: Test aws-actions/configure-aws-credentials Action in container |       - name: Test aws-actions/configure-aws-credentials Action in container | ||||||
|         uses: aws-actions/configure-aws-credentials@v1 |         # https://github.com/aws-actions/configure-aws-credentials/releases/tag/v4.1.0 | ||||||
|  |         uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 | ||||||
|         with: |         with: | ||||||
|           aws-region: ${{ env.AWS_REGION }} |           aws-region: ${{ env.AWS_REGION }} | ||||||
|           role-to-assume: ${{ env.ASSUME_ROLE_ARN }} |           role-to-assume: ${{ env.ASSUME_ROLE_ARN }} | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ env: | ||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   run-step-in-container-test: |   run-step-in-container-test: | ||||||
|     runs-on: ['self-hosted', 'Linux'] |     runs-on: ["self-hosted", "Linux"] | ||||||
|     container: |     container: | ||||||
|       image: alpine |       image: alpine | ||||||
|     steps: |     steps: | ||||||
|  | @ -21,7 +21,7 @@ jobs: | ||||||
|               exit 1 |               exit 1 | ||||||
|           fi |           fi | ||||||
|   setup-python-test: |   setup-python-test: | ||||||
|     runs-on: ['self-hosted', 'Linux'] |     runs-on: ["self-hosted", "Linux"] | ||||||
|     steps: |     steps: | ||||||
|       - name: Print native Python environment |       - name: Print native Python environment | ||||||
|         run: | |         run: | | ||||||
|  | @ -41,11 +41,11 @@ jobs: | ||||||
|             echo "Python version detected : $(python --version 2>&1)" |             echo "Python version detected : $(python --version 2>&1)" | ||||||
|           fi |           fi | ||||||
|   setup-node-test: |   setup-node-test: | ||||||
|     runs-on: ['self-hosted', 'Linux'] |     runs-on: ["self-hosted", "Linux"] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/setup-node@v2 |       - uses: actions/setup-node@v2 | ||||||
|         with: |         with: | ||||||
|           node-version: '12' |           node-version: "12" | ||||||
|       - name: Test actions/setup-node works |       - name: Test actions/setup-node works | ||||||
|         run: | |         run: | | ||||||
|           VERSION=$(node --version | cut -c 2- | cut -d '.' -f1) |           VERSION=$(node --version | cut -c 2- | cut -d '.' -f1) | ||||||
|  | @ -57,9 +57,10 @@ jobs: | ||||||
|             echo "Node version detected : $(node --version 2>&1)" |             echo "Node version detected : $(node --version 2>&1)" | ||||||
|           fi |           fi | ||||||
|   setup-ruby-test: |   setup-ruby-test: | ||||||
|     runs-on: ['self-hosted', 'Linux'] |     runs-on: ["self-hosted", "Linux"] | ||||||
|     steps: |     steps: | ||||||
|       - uses: ruby/setup-ruby@v1 |       # https://github.com/ruby/setup-ruby/releases/tag/v1.227.0 | ||||||
|  |       - uses: ruby/setup-ruby@1a615958ad9d422dd932dc1d5823942ee002799f | ||||||
|         with: |         with: | ||||||
|           ruby-version: 3.0 |           ruby-version: 3.0 | ||||||
|           bundler-cache: true |           bundler-cache: true | ||||||
|  | @ -74,7 +75,7 @@ jobs: | ||||||
|               echo "Ruby version detected : $(ruby --version 2>&1)" |               echo "Ruby version detected : $(ruby --version 2>&1)" | ||||||
|           fi |           fi | ||||||
|   python-shell-test: |   python-shell-test: | ||||||
|     runs-on: ['self-hosted', 'Linux'] |     runs-on: ["self-hosted", "Linux"] | ||||||
|     steps: |     steps: | ||||||
|       - name: Test Python shell works |       - name: Test Python shell works | ||||||
|         run: | |         run: | | ||||||
|  |  | ||||||
|  | @ -119,7 +119,7 @@ type EphemeralRunnerStatus struct { | ||||||
| 	RunnerJITConfig string `json:"runnerJITConfig,omitempty"` | 	RunnerJITConfig string `json:"runnerJITConfig,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// +optional
 | 	// +optional
 | ||||||
| 	Failures map[string]bool `json:"failures,omitempty"` | 	Failures map[string]metav1.Time `json:"failures,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// +optional
 | 	// +optional
 | ||||||
| 	JobRequestId int64 `json:"jobRequestId,omitempty"` | 	JobRequestId int64 `json:"jobRequestId,omitempty"` | ||||||
|  | @ -137,6 +137,20 @@ type EphemeralRunnerStatus struct { | ||||||
| 	JobDisplayName string `json:"jobDisplayName,omitempty"` | 	JobDisplayName string `json:"jobDisplayName,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (s *EphemeralRunnerStatus) LastFailure() metav1.Time { | ||||||
|  | 	var maxTime metav1.Time | ||||||
|  | 	if len(s.Failures) == 0 { | ||||||
|  | 		return maxTime | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, ts := range s.Failures { | ||||||
|  | 		if ts.After(maxTime.Time) { | ||||||
|  | 			maxTime = ts | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return maxTime | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // +kubebuilder:object:root=true
 | // +kubebuilder:object:root=true
 | ||||||
| 
 | 
 | ||||||
| // EphemeralRunnerList contains a list of EphemeralRunner
 | // EphemeralRunnerList contains a list of EphemeralRunner
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,72 @@ | ||||||
|  | package v1alpha1 | ||||||
|  | 
 | ||||||
|  | import "strings" | ||||||
|  | 
 | ||||||
|  | func IsVersionAllowed(resourceVersion, buildVersion string) bool { | ||||||
|  | 	if buildVersion == "dev" || resourceVersion == buildVersion || strings.HasPrefix(buildVersion, "canary-") { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	rv, ok := parseSemver(resourceVersion) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	bv, ok := parseSemver(buildVersion) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return rv.major == bv.major && rv.minor == bv.minor | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type semver struct { | ||||||
|  | 	major string | ||||||
|  | 	minor string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseSemver(v string) (p semver, ok bool) { | ||||||
|  | 	if v == "" { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	p.major, v, ok = parseInt(v) | ||||||
|  | 	if !ok { | ||||||
|  | 		return p, false | ||||||
|  | 	} | ||||||
|  | 	if v == "" { | ||||||
|  | 		p.minor = "0" | ||||||
|  | 		return p, true | ||||||
|  | 	} | ||||||
|  | 	if v[0] != '.' { | ||||||
|  | 		return p, false | ||||||
|  | 	} | ||||||
|  | 	p.minor, v, ok = parseInt(v[1:]) | ||||||
|  | 	if !ok { | ||||||
|  | 		return p, false | ||||||
|  | 	} | ||||||
|  | 	if v == "" { | ||||||
|  | 		return p, true | ||||||
|  | 	} | ||||||
|  | 	if v[0] != '.' { | ||||||
|  | 		return p, false | ||||||
|  | 	} | ||||||
|  | 	if _, _, ok = parseInt(v[1:]); !ok { | ||||||
|  | 		return p, false | ||||||
|  | 	} | ||||||
|  | 	return p, true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseInt(v string) (t, rest string, ok bool) { | ||||||
|  | 	if v == "" { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if v[0] < '0' || '9' < v[0] { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	i := 1 | ||||||
|  | 	for i < len(v) && '0' <= v[i] && v[i] <= '9' { | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	if v[0] == '0' && i != 1 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	return v[:i], v[i:], true | ||||||
|  | } | ||||||
|  | @ -0,0 +1,60 @@ | ||||||
|  | package v1alpha1_test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestIsVersionAllowed(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  | 	tt := map[string]struct { | ||||||
|  | 		resourceVersion string | ||||||
|  | 		buildVersion    string | ||||||
|  | 		want            bool | ||||||
|  | 	}{ | ||||||
|  | 		"dev should always be allowed": { | ||||||
|  | 			resourceVersion: "0.11.0", | ||||||
|  | 			buildVersion:    "dev", | ||||||
|  | 			want:            true, | ||||||
|  | 		}, | ||||||
|  | 		"resourceVersion is not semver": { | ||||||
|  | 			resourceVersion: "dev", | ||||||
|  | 			buildVersion:    "0.11.0", | ||||||
|  | 			want:            false, | ||||||
|  | 		}, | ||||||
|  | 		"buildVersion is not semver": { | ||||||
|  | 			resourceVersion: "0.11.0", | ||||||
|  | 			buildVersion:    "NA", | ||||||
|  | 			want:            false, | ||||||
|  | 		}, | ||||||
|  | 		"major version mismatch": { | ||||||
|  | 			resourceVersion: "0.11.0", | ||||||
|  | 			buildVersion:    "1.11.0", | ||||||
|  | 			want:            false, | ||||||
|  | 		}, | ||||||
|  | 		"minor version mismatch": { | ||||||
|  | 			resourceVersion: "0.11.0", | ||||||
|  | 			buildVersion:    "0.10.0", | ||||||
|  | 			want:            false, | ||||||
|  | 		}, | ||||||
|  | 		"patch version mismatch": { | ||||||
|  | 			resourceVersion: "0.11.1", | ||||||
|  | 			buildVersion:    "0.11.0", | ||||||
|  | 			want:            true, | ||||||
|  | 		}, | ||||||
|  | 		"arbitrary version match": { | ||||||
|  | 			resourceVersion: "abc", | ||||||
|  | 			buildVersion:    "abc", | ||||||
|  | 			want:            true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for name, tc := range tt { | ||||||
|  | 		t.Run(name, func(t *testing.T) { | ||||||
|  | 			got := v1alpha1.IsVersionAllowed(tc.resourceVersion, tc.buildVersion) | ||||||
|  | 			assert.Equal(t, tc.want, got) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -22,6 +22,7 @@ package v1alpha1 | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"k8s.io/api/core/v1" | 	"k8s.io/api/core/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -459,9 +460,9 @@ func (in *EphemeralRunnerStatus) DeepCopyInto(out *EphemeralRunnerStatus) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
| 	if in.Failures != nil { | 	if in.Failures != nil { | ||||||
| 		in, out := &in.Failures, &out.Failures | 		in, out := &in.Failures, &out.Failures | ||||||
| 		*out = make(map[string]bool, len(*in)) | 		*out = make(map[string]metav1.Time, len(*in)) | ||||||
| 		for key, val := range *in { | 		for key, val := range *in { | ||||||
| 			(*out)[key] = val | 			(*out)[key] = *val.DeepCopy() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -215,10 +215,10 @@ func (rs *RunnerSpec) validateRepository() error { | ||||||
| 		foundCount += 1 | 		foundCount += 1 | ||||||
| 	} | 	} | ||||||
| 	if foundCount == 0 { | 	if foundCount == 0 { | ||||||
| 		return errors.New("Spec needs enterprise, organization or repository") | 		return errors.New("spec needs enterprise, organization or repository") | ||||||
| 	} | 	} | ||||||
| 	if foundCount > 1 { | 	if foundCount > 1 { | ||||||
| 		return errors.New("Spec cannot have many fields defined enterprise, organization and repository") | 		return errors.New("spec cannot have many fields defined enterprise, organization and repository") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| # This file defines the config for "ct" (chart tester) used by the helm linting GitHub workflow | # This file defines the config for "ct" (chart tester) used by the helm linting GitHub workflow | ||||||
|  | remote: origin | ||||||
|  | target-branch: master | ||||||
| lint-conf: charts/.ci/lint-config.yaml | lint-conf: charts/.ci/lint-config.yaml | ||||||
| chart-repos: | chart-repos: | ||||||
|   - jetstack=https://charts.jetstack.io |   - jetstack=https://charts.jetstack.io | ||||||
| check-version-increment: false # Disable checking that the chart version has been bumped | check-version-increment: false # Disable checking that the chart version has been bumped | ||||||
| charts: | charts: | ||||||
| - charts/gha-runner-scale-set-controller |   - charts/gha-runner-scale-set-controller | ||||||
| - charts/gha-runner-scale-set |   - charts/gha-runner-scale-set | ||||||
| skip-clean-up: true | skip-clean-up: true | ||||||
|  |  | ||||||
|  | @ -1,7 +1,9 @@ | ||||||
| # This file defines the config for "ct" (chart tester) used by the helm linting GitHub workflow | # This file defines the config for "ct" (chart tester) used by the helm linting GitHub workflow | ||||||
|  | remote: origin | ||||||
|  | target-branch: master | ||||||
| lint-conf: charts/.ci/lint-config.yaml | lint-conf: charts/.ci/lint-config.yaml | ||||||
| chart-repos: | chart-repos: | ||||||
|   - jetstack=https://charts.jetstack.io |   - jetstack=https://charts.jetstack.io | ||||||
| check-version-increment: false # Disable checking that the chart version has been bumped | check-version-increment: false # Disable checking that the chart version has been bumped | ||||||
| charts: | charts: | ||||||
| - charts/actions-runner-controller |   - charts/actions-runner-controller | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| for chart in `ls charts`; | for chart in `ls charts`; | ||||||
| do | do | ||||||
| helm template --values charts/$chart/ci/ci-values.yaml charts/$chart | kube-score score - \ | helm template --values charts/$chart/ci/ci-values.yaml charts/$chart | kube-score score - \ | ||||||
|  |  | ||||||
|  | @ -44,7 +44,7 @@ All additional docs are kept in the `docs/` folder, this README is solely for do | ||||||
| | `image.pullPolicy`                                        | The pull policy of the controller image                                                                                                   | IfNotPresent                                                                                    | | | `image.pullPolicy`                                        | The pull policy of the controller image                                                                                                   | IfNotPresent                                                                                    | | ||||||
| | `metrics.serviceMonitor.enable`                           | Deploy serviceMonitor kind for for use with prometheus-operator CRDs                                                                      | false                                                                                           | | | `metrics.serviceMonitor.enable`                           | Deploy serviceMonitor kind for for use with prometheus-operator CRDs                                                                      | false                                                                                           | | ||||||
| | `metrics.serviceMonitor.interval`                         | Configure the interval that Prometheus should scrap the controller's metrics                                                              | 1m                                                                                              | | | `metrics.serviceMonitor.interval`                         | Configure the interval that Prometheus should scrap the controller's metrics                                                              | 1m                                                                                              | | ||||||
| | `metrics.serviceMonitor.namespace                         | Namespace which Prometheus is running in                                                                                                  | `Release.Namespace` (the default namespace of the helm chart).                                  | | | `metrics.serviceMonitor.namespace`                        | Namespace which Prometheus is running in                                                                                                  | `Release.Namespace` (the default namespace of the helm chart).                                  | | ||||||
| | `metrics.serviceMonitor.timeout`                          | Configure the timeout the timeout of Prometheus scrapping.                                                                                | 30s                                                                                             | | | `metrics.serviceMonitor.timeout`                          | Configure the timeout the timeout of Prometheus scrapping.                                                                                | 30s                                                                                             | | ||||||
| | `metrics.serviceAnnotations`                              | Set annotations for the provisioned metrics service resource                                                                              |                                                                                                 | | | `metrics.serviceAnnotations`                              | Set annotations for the provisioned metrics service resource                                                                              |                                                                                                 | | ||||||
| | `metrics.port`                                            | Set port of metrics service                                                                                                               | 8443                                                                                            | | | `metrics.port`                                            | Set port of metrics service                                                                                                               | 8443                                                                                            | | ||||||
|  |  | ||||||
|  | @ -7794,7 +7794,8 @@ spec: | ||||||
|               properties: |               properties: | ||||||
|                 failures: |                 failures: | ||||||
|                   additionalProperties: |                   additionalProperties: | ||||||
|                     type: boolean |                     format: date-time | ||||||
|  |                     type: string | ||||||
|                   type: object |                   type: object | ||||||
|                 jobDisplayName: |                 jobDisplayName: | ||||||
|                   type: string |                   type: string | ||||||
|  |  | ||||||
|  | @ -106,6 +106,9 @@ env: | ||||||
|     value: "123" |     value: "123" | ||||||
| securityContext: | securityContext: | ||||||
|   privileged: true |   privileged: true | ||||||
|  | {{- if (ge (.Capabilities.KubeVersion.Minor | int) 29) }} | ||||||
|  | restartPolicy: Always | ||||||
|  | {{- end }} | ||||||
| volumeMounts: | volumeMounts: | ||||||
|   - name: work |   - name: work | ||||||
|     mountPath: /home/runner/_work |     mountPath: /home/runner/_work | ||||||
|  |  | ||||||
|  | @ -149,6 +149,10 @@ spec: | ||||||
|       - name: init-dind-externals |       - name: init-dind-externals | ||||||
|         {{- include "gha-runner-scale-set.dind-init-container" . | nindent 8 }} |         {{- include "gha-runner-scale-set.dind-init-container" . | nindent 8 }} | ||||||
|         {{- end }} |         {{- end }} | ||||||
|  |         {{- if (ge (.Capabilities.KubeVersion.Minor | int) 29) }} | ||||||
|  |       - name: dind | ||||||
|  |         {{- include "gha-runner-scale-set.dind-container" . | nindent 8 }} | ||||||
|  |         {{- end }} | ||||||
|         {{- with .Values.template.spec.initContainers }} |         {{- with .Values.template.spec.initContainers }} | ||||||
|       {{- toYaml . | nindent 6 }} |       {{- toYaml . | nindent 6 }} | ||||||
|         {{- end }} |         {{- end }} | ||||||
|  | @ -157,8 +161,10 @@ spec: | ||||||
|       {{- if eq $containerMode.type "dind" }} |       {{- if eq $containerMode.type "dind" }} | ||||||
|       - name: runner |       - name: runner | ||||||
|         {{- include "gha-runner-scale-set.dind-runner-container" . | nindent 8 }} |         {{- include "gha-runner-scale-set.dind-runner-container" . | nindent 8 }} | ||||||
|  |         {{- if not (ge (.Capabilities.KubeVersion.Minor | int) 29) }} | ||||||
|       - name: dind |       - name: dind | ||||||
|         {{- include "gha-runner-scale-set.dind-container" . | nindent 8 }} |         {{- include "gha-runner-scale-set.dind-container" . | nindent 8 }} | ||||||
|  |         {{- end }} | ||||||
|       {{- include "gha-runner-scale-set.non-runner-non-dind-containers" . | nindent 6 }} |       {{- include "gha-runner-scale-set.non-runner-non-dind-containers" . | nindent 6 }} | ||||||
|       {{- else if eq $containerMode.type "kubernetes" }} |       {{- else if eq $containerMode.type "kubernetes" }} | ||||||
|       - name: runner |       - name: runner | ||||||
|  |  | ||||||
|  | @ -728,20 +728,20 @@ func TestTemplateRenderedAutoScalingRunnerSet_DinD_ExtraInitContainers(t *testin | ||||||
| 	var ars v1alpha1.AutoscalingRunnerSet | 	var ars v1alpha1.AutoscalingRunnerSet | ||||||
| 	helm.UnmarshalK8SYaml(t, output, &ars) | 	helm.UnmarshalK8SYaml(t, output, &ars) | ||||||
| 
 | 
 | ||||||
| 	assert.Len(t, ars.Spec.Template.Spec.InitContainers, 3, "InitContainers should be 3") | 	assert.Len(t, ars.Spec.Template.Spec.InitContainers, 4, "InitContainers should be 4") | ||||||
| 	assert.Equal(t, "kube-init", ars.Spec.Template.Spec.InitContainers[1].Name, "InitContainers[1] Name should be kube-init") | 	assert.Equal(t, "kube-init", ars.Spec.Template.Spec.InitContainers[2].Name, "InitContainers[1] Name should be kube-init") | ||||||
| 	assert.Equal(t, "runner-image:latest", ars.Spec.Template.Spec.InitContainers[1].Image, "InitContainers[1] Image should be runner-image:latest") | 	assert.Equal(t, "runner-image:latest", ars.Spec.Template.Spec.InitContainers[2].Image, "InitContainers[1] Image should be runner-image:latest") | ||||||
| 	assert.Equal(t, "sudo", ars.Spec.Template.Spec.InitContainers[1].Command[0], "InitContainers[1] Command[0] should be sudo") | 	assert.Equal(t, "sudo", ars.Spec.Template.Spec.InitContainers[2].Command[0], "InitContainers[1] Command[0] should be sudo") | ||||||
| 	assert.Equal(t, "chown", ars.Spec.Template.Spec.InitContainers[1].Command[1], "InitContainers[1] Command[1] should be chown") | 	assert.Equal(t, "chown", ars.Spec.Template.Spec.InitContainers[2].Command[1], "InitContainers[1] Command[1] should be chown") | ||||||
| 	assert.Equal(t, "-R", ars.Spec.Template.Spec.InitContainers[1].Command[2], "InitContainers[1] Command[2] should be -R") | 	assert.Equal(t, "-R", ars.Spec.Template.Spec.InitContainers[2].Command[2], "InitContainers[1] Command[2] should be -R") | ||||||
| 	assert.Equal(t, "1001:123", ars.Spec.Template.Spec.InitContainers[1].Command[3], "InitContainers[1] Command[3] should be 1001:123") | 	assert.Equal(t, "1001:123", ars.Spec.Template.Spec.InitContainers[2].Command[3], "InitContainers[1] Command[3] should be 1001:123") | ||||||
| 	assert.Equal(t, "/home/runner/_work", ars.Spec.Template.Spec.InitContainers[1].Command[4], "InitContainers[1] Command[4] should be /home/runner/_work") | 	assert.Equal(t, "/home/runner/_work", ars.Spec.Template.Spec.InitContainers[2].Command[4], "InitContainers[1] Command[4] should be /home/runner/_work") | ||||||
| 	assert.Equal(t, "work", ars.Spec.Template.Spec.InitContainers[1].VolumeMounts[0].Name, "InitContainers[1] VolumeMounts[0] Name should be work") | 	assert.Equal(t, "work", ars.Spec.Template.Spec.InitContainers[2].VolumeMounts[0].Name, "InitContainers[1] VolumeMounts[0] Name should be work") | ||||||
| 	assert.Equal(t, "/home/runner/_work", ars.Spec.Template.Spec.InitContainers[1].VolumeMounts[0].MountPath, "InitContainers[1] VolumeMounts[0] MountPath should be /home/runner/_work") | 	assert.Equal(t, "/home/runner/_work", ars.Spec.Template.Spec.InitContainers[2].VolumeMounts[0].MountPath, "InitContainers[1] VolumeMounts[0] MountPath should be /home/runner/_work") | ||||||
| 
 | 
 | ||||||
| 	assert.Equal(t, "ls", ars.Spec.Template.Spec.InitContainers[2].Name, "InitContainers[2] Name should be ls") | 	assert.Equal(t, "ls", ars.Spec.Template.Spec.InitContainers[3].Name, "InitContainers[2] Name should be ls") | ||||||
| 	assert.Equal(t, "ubuntu:latest", ars.Spec.Template.Spec.InitContainers[2].Image, "InitContainers[2] Image should be ubuntu:latest") | 	assert.Equal(t, "ubuntu:latest", ars.Spec.Template.Spec.InitContainers[3].Image, "InitContainers[2] Image should be ubuntu:latest") | ||||||
| 	assert.Equal(t, "ls", ars.Spec.Template.Spec.InitContainers[2].Command[0], "InitContainers[2] Command[0] should be ls") | 	assert.Equal(t, "ls", ars.Spec.Template.Spec.InitContainers[3].Command[0], "InitContainers[2] Command[0] should be ls") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestTemplateRenderedAutoScalingRunnerSet_DinD_ExtraVolumes(t *testing.T) { | func TestTemplateRenderedAutoScalingRunnerSet_DinD_ExtraVolumes(t *testing.T) { | ||||||
|  | @ -860,13 +860,26 @@ func TestTemplateRenderedAutoScalingRunnerSet_EnableDinD(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	assert.NotNil(t, ars.Spec.Template.Spec, "Template.Spec should not be nil") | 	assert.NotNil(t, ars.Spec.Template.Spec, "Template.Spec should not be nil") | ||||||
| 
 | 
 | ||||||
| 	assert.Len(t, ars.Spec.Template.Spec.InitContainers, 1, "Template.Spec should have 1 init container") | 	assert.Len(t, ars.Spec.Template.Spec.InitContainers, 2, "Template.Spec should have 2 init container") | ||||||
| 	assert.Equal(t, "init-dind-externals", ars.Spec.Template.Spec.InitContainers[0].Name) | 	assert.Equal(t, "init-dind-externals", ars.Spec.Template.Spec.InitContainers[0].Name) | ||||||
| 	assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.InitContainers[0].Image) | 	assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.InitContainers[0].Image) | ||||||
| 	assert.Equal(t, "cp", ars.Spec.Template.Spec.InitContainers[0].Command[0]) | 	assert.Equal(t, "cp", ars.Spec.Template.Spec.InitContainers[0].Command[0]) | ||||||
| 	assert.Equal(t, "-r /home/runner/externals/. /home/runner/tmpDir/", strings.Join(ars.Spec.Template.Spec.InitContainers[0].Args, " ")) | 	assert.Equal(t, "-r /home/runner/externals/. /home/runner/tmpDir/", strings.Join(ars.Spec.Template.Spec.InitContainers[0].Args, " ")) | ||||||
| 
 | 
 | ||||||
| 	assert.Len(t, ars.Spec.Template.Spec.Containers, 2, "Template.Spec should have 2 container") | 	assert.Equal(t, "dind", ars.Spec.Template.Spec.InitContainers[1].Name) | ||||||
|  | 	assert.Equal(t, "docker:dind", ars.Spec.Template.Spec.InitContainers[1].Image) | ||||||
|  | 	assert.True(t, *ars.Spec.Template.Spec.InitContainers[1].SecurityContext.Privileged) | ||||||
|  | 	assert.Len(t, ars.Spec.Template.Spec.InitContainers[1].VolumeMounts, 3, "The dind container should have 3 volume mounts, dind-sock, work and externals") | ||||||
|  | 	assert.Equal(t, "work", ars.Spec.Template.Spec.InitContainers[1].VolumeMounts[0].Name) | ||||||
|  | 	assert.Equal(t, "/home/runner/_work", ars.Spec.Template.Spec.InitContainers[1].VolumeMounts[0].MountPath) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.InitContainers[1].VolumeMounts[1].Name) | ||||||
|  | 	assert.Equal(t, "/var/run", ars.Spec.Template.Spec.InitContainers[1].VolumeMounts[1].MountPath) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, "dind-externals", ars.Spec.Template.Spec.InitContainers[1].VolumeMounts[2].Name) | ||||||
|  | 	assert.Equal(t, "/home/runner/externals", ars.Spec.Template.Spec.InitContainers[1].VolumeMounts[2].MountPath) | ||||||
|  | 
 | ||||||
|  | 	assert.Len(t, ars.Spec.Template.Spec.Containers, 1, "Template.Spec should have 1 container") | ||||||
| 	assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name) | 	assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name) | ||||||
| 	assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.Containers[0].Image) | 	assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.Containers[0].Image) | ||||||
| 	assert.Len(t, ars.Spec.Template.Spec.Containers[0].Env, 2, "The runner container should have 2 env vars, DOCKER_HOST and RUNNER_WAIT_FOR_DOCKER_IN_SECONDS") | 	assert.Len(t, ars.Spec.Template.Spec.Containers[0].Env, 2, "The runner container should have 2 env vars, DOCKER_HOST and RUNNER_WAIT_FOR_DOCKER_IN_SECONDS") | ||||||
|  | @ -883,19 +896,6 @@ func TestTemplateRenderedAutoScalingRunnerSet_EnableDinD(t *testing.T) { | ||||||
| 	assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name) | 	assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name) | ||||||
| 	assert.Equal(t, "/var/run", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath) | 	assert.Equal(t, "/var/run", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath) | ||||||
| 
 | 
 | ||||||
| 	assert.Equal(t, "dind", ars.Spec.Template.Spec.Containers[1].Name) |  | ||||||
| 	assert.Equal(t, "docker:dind", ars.Spec.Template.Spec.Containers[1].Image) |  | ||||||
| 	assert.True(t, *ars.Spec.Template.Spec.Containers[1].SecurityContext.Privileged) |  | ||||||
| 	assert.Len(t, ars.Spec.Template.Spec.Containers[1].VolumeMounts, 3, "The dind container should have 3 volume mounts, dind-sock, work and externals") |  | ||||||
| 	assert.Equal(t, "work", ars.Spec.Template.Spec.Containers[1].VolumeMounts[0].Name) |  | ||||||
| 	assert.Equal(t, "/home/runner/_work", ars.Spec.Template.Spec.Containers[1].VolumeMounts[0].MountPath) |  | ||||||
| 
 |  | ||||||
| 	assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Containers[1].VolumeMounts[1].Name) |  | ||||||
| 	assert.Equal(t, "/var/run", ars.Spec.Template.Spec.Containers[1].VolumeMounts[1].MountPath) |  | ||||||
| 
 |  | ||||||
| 	assert.Equal(t, "dind-externals", ars.Spec.Template.Spec.Containers[1].VolumeMounts[2].Name) |  | ||||||
| 	assert.Equal(t, "/home/runner/externals", ars.Spec.Template.Spec.Containers[1].VolumeMounts[2].MountPath) |  | ||||||
| 
 |  | ||||||
| 	assert.Len(t, ars.Spec.Template.Spec.Volumes, 3, "Volumes should be 3") | 	assert.Len(t, ars.Spec.Template.Spec.Volumes, 3, "Volumes should be 3") | ||||||
| 	assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Volumes[0].Name, "Volume name should be dind-sock") | 	assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Volumes[0].Name, "Volume name should be dind-sock") | ||||||
| 	assert.Equal(t, "dind-externals", ars.Spec.Template.Spec.Volumes[1].Name, "Volume name should be dind-externals") | 	assert.Equal(t, "dind-externals", ars.Spec.Template.Spec.Volumes[1].Name, "Volume name should be dind-externals") | ||||||
|  | @ -1178,7 +1178,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) { | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			require.NotNil(t, volume) | 			require.NotNil(t, volume) | ||||||
| 			assert.Equal(t, "certs-configmap", volume.ConfigMap.LocalObjectReference.Name) | 			assert.Equal(t, "certs-configmap", volume.ConfigMap.Name) | ||||||
| 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key) | 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key) | ||||||
| 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path) | 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path) | ||||||
| 
 | 
 | ||||||
|  | @ -1238,7 +1238,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) { | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			require.NotNil(t, volume) | 			require.NotNil(t, volume) | ||||||
| 			assert.Equal(t, "certs-configmap", volume.ConfigMap.LocalObjectReference.Name) | 			assert.Equal(t, "certs-configmap", volume.ConfigMap.Name) | ||||||
| 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key) | 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key) | ||||||
| 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path) | 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path) | ||||||
| 
 | 
 | ||||||
|  | @ -1298,7 +1298,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) { | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			require.NotNil(t, volume) | 			require.NotNil(t, volume) | ||||||
| 			assert.Equal(t, "certs-configmap", volume.ConfigMap.LocalObjectReference.Name) | 			assert.Equal(t, "certs-configmap", volume.ConfigMap.Name) | ||||||
| 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key) | 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key) | ||||||
| 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path) | 			assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path) | ||||||
| 
 | 
 | ||||||
|  | @ -1826,7 +1826,7 @@ func TestTemplateRenderedAutoScalingRunnerSet_DinDMergePodSpec(t *testing.T) { | ||||||
| 	var ars v1alpha1.AutoscalingRunnerSet | 	var ars v1alpha1.AutoscalingRunnerSet | ||||||
| 	helm.UnmarshalK8SYaml(t, output, &ars) | 	helm.UnmarshalK8SYaml(t, output, &ars) | ||||||
| 
 | 
 | ||||||
| 	assert.Len(t, ars.Spec.Template.Spec.Containers, 2, "There should be 2 containers") | 	assert.Len(t, ars.Spec.Template.Spec.Containers, 1, "There should be 1 containers") | ||||||
| 	assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name, "Container name should be runner") | 	assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name, "Container name should be runner") | ||||||
| 	assert.Equal(t, "250m", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().String(), "CPU Limit should be set") | 	assert.Equal(t, "250m", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().String(), "CPU Limit should be set") | ||||||
| 	assert.Equal(t, "64Mi", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String(), "Memory Limit should be set") | 	assert.Equal(t, "64Mi", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String(), "Memory Limit should be set") | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ githubConfigSecret: | ||||||
| ## (Variation B) When using a GitHub App, the syntax is as follows: | ## (Variation B) When using a GitHub App, the syntax is as follows: | ||||||
| # githubConfigSecret: | # githubConfigSecret: | ||||||
| #   # NOTE: IDs MUST be strings, use quotes | #   # NOTE: IDs MUST be strings, use quotes | ||||||
|  | #   # The github_app_id can be an app_id or the client_id | ||||||
| #   github_app_id: "" | #   github_app_id: "" | ||||||
| #   github_app_installation_id: "" | #   github_app_installation_id: "" | ||||||
| #   github_app_private_key: | | #   github_app_private_key: | | ||||||
|  |  | ||||||
|  | @ -18,7 +18,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| type Config struct { | type Config struct { | ||||||
| 	ConfigureUrl string `json:"configure_url"` | 	ConfigureUrl string `json:"configure_url"` | ||||||
| 	AppID                       int64                   `json:"app_id"` | 	// AppID can be an ID of the app or the client ID
 | ||||||
|  | 	AppID                       string                  `json:"app_id"` | ||||||
| 	AppInstallationID           int64                   `json:"app_installation_id"` | 	AppInstallationID           int64                   `json:"app_installation_id"` | ||||||
| 	AppPrivateKey               string                  `json:"app_private_key"` | 	AppPrivateKey               string                  `json:"app_private_key"` | ||||||
| 	Token                       string                  `json:"token"` | 	Token                       string                  `json:"token"` | ||||||
|  | @ -62,26 +63,26 @@ func (c *Config) Validate() error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(c.EphemeralRunnerSetNamespace) == 0 || len(c.EphemeralRunnerSetName) == 0 { | 	if len(c.EphemeralRunnerSetNamespace) == 0 || len(c.EphemeralRunnerSetName) == 0 { | ||||||
| 		return fmt.Errorf("EphemeralRunnerSetNamespace '%s' or EphemeralRunnerSetName '%s' is missing", c.EphemeralRunnerSetNamespace, c.EphemeralRunnerSetName) | 		return fmt.Errorf("EphemeralRunnerSetNamespace %q or EphemeralRunnerSetName %q is missing", c.EphemeralRunnerSetNamespace, c.EphemeralRunnerSetName) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if c.RunnerScaleSetId == 0 { | 	if c.RunnerScaleSetId == 0 { | ||||||
| 		return fmt.Errorf("RunnerScaleSetId '%d' is missing", c.RunnerScaleSetId) | 		return fmt.Errorf(`RunnerScaleSetId "%d" is missing`, c.RunnerScaleSetId) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if c.MaxRunners < c.MinRunners { | 	if c.MaxRunners < c.MinRunners { | ||||||
| 		return fmt.Errorf("MinRunners '%d' cannot be greater than MaxRunners '%d'", c.MinRunners, c.MaxRunners) | 		return fmt.Errorf(`MinRunners "%d" cannot be greater than MaxRunners "%d"`, c.MinRunners, c.MaxRunners) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	hasToken := len(c.Token) > 0 | 	hasToken := len(c.Token) > 0 | ||||||
| 	hasPrivateKeyConfig := c.AppID > 0 && c.AppPrivateKey != "" | 	hasPrivateKeyConfig := len(c.AppID) > 0 && c.AppPrivateKey != "" | ||||||
| 
 | 
 | ||||||
| 	if !hasToken && !hasPrivateKeyConfig { | 	if !hasToken && !hasPrivateKeyConfig { | ||||||
| 		return fmt.Errorf("GitHub auth credential is missing, token length: '%d', appId: '%d', installationId: '%d', private key length: '%d", len(c.Token), c.AppID, c.AppInstallationID, len(c.AppPrivateKey)) | 		return fmt.Errorf(`GitHub auth credential is missing, token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(c.Token), c.AppID, c.AppInstallationID, len(c.AppPrivateKey)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if hasToken && hasPrivateKeyConfig { | 	if hasToken && hasPrivateKeyConfig { | ||||||
| 		return fmt.Errorf("only one GitHub auth method supported at a time. Have both PAT and App auth: token length: '%d', appId: '%d', installationId: '%d', private key length: '%d", len(c.Token), c.AppID, c.AppInstallationID, len(c.AppPrivateKey)) | 		return fmt.Errorf(`only one GitHub auth method supported at a time. Have both PAT and App auth: token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(c.Token), c.AppID, c.AppInstallationID, len(c.AppPrivateKey)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ func TestConfigValidationMinMax(t *testing.T) { | ||||||
| 		Token:                       "token", | 		Token:                       "token", | ||||||
| 	} | 	} | ||||||
| 	err := config.Validate() | 	err := config.Validate() | ||||||
| 	assert.ErrorContains(t, err, "MinRunners '5' cannot be greater than MaxRunners '2", "Expected error about MinRunners > MaxRunners") | 	assert.ErrorContains(t, err, `MinRunners "5" cannot be greater than MaxRunners "2"`, "Expected error about MinRunners > MaxRunners") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestConfigValidationMissingToken(t *testing.T) { | func TestConfigValidationMissingToken(t *testing.T) { | ||||||
|  | @ -29,13 +29,17 @@ func TestConfigValidationMissingToken(t *testing.T) { | ||||||
| 		RunnerScaleSetId:            1, | 		RunnerScaleSetId:            1, | ||||||
| 	} | 	} | ||||||
| 	err := config.Validate() | 	err := config.Validate() | ||||||
| 	expectedError := fmt.Sprintf("GitHub auth credential is missing, token length: '%d', appId: '%d', installationId: '%d', private key length: '%d", len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey)) | 	expectedError := fmt.Sprintf(`GitHub auth credential is missing, token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey)) | ||||||
| 	assert.ErrorContains(t, err, expectedError, "Expected error about missing auth") | 	assert.ErrorContains(t, err, expectedError, "Expected error about missing auth") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestConfigValidationAppKey(t *testing.T) { | func TestConfigValidationAppKey(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  | 
 | ||||||
|  | 	t.Run("app id integer", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
| 		config := &Config{ | 		config := &Config{ | ||||||
| 		AppID:                       1, | 			AppID:                       "1", | ||||||
| 			AppInstallationID:           10, | 			AppInstallationID:           10, | ||||||
| 			ConfigureUrl:                "github.com/some_org/some_repo", | 			ConfigureUrl:                "github.com/some_org/some_repo", | ||||||
| 			EphemeralRunnerSetNamespace: "namespace", | 			EphemeralRunnerSetNamespace: "namespace", | ||||||
|  | @ -43,13 +47,29 @@ func TestConfigValidationAppKey(t *testing.T) { | ||||||
| 			RunnerScaleSetId:            1, | 			RunnerScaleSetId:            1, | ||||||
| 		} | 		} | ||||||
| 		err := config.Validate() | 		err := config.Validate() | ||||||
| 	expectedError := fmt.Sprintf("GitHub auth credential is missing, token length: '%d', appId: '%d', installationId: '%d', private key length: '%d", len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey)) | 		expectedError := fmt.Sprintf(`GitHub auth credential is missing, token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey)) | ||||||
| 		assert.ErrorContains(t, err, expectedError, "Expected error about missing auth") | 		assert.ErrorContains(t, err, expectedError, "Expected error about missing auth") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("app id as client id", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  | 		config := &Config{ | ||||||
|  | 			AppID:                       "Iv23f8doAlphaNumer1c", | ||||||
|  | 			AppInstallationID:           10, | ||||||
|  | 			ConfigureUrl:                "github.com/some_org/some_repo", | ||||||
|  | 			EphemeralRunnerSetNamespace: "namespace", | ||||||
|  | 			EphemeralRunnerSetName:      "deployment", | ||||||
|  | 			RunnerScaleSetId:            1, | ||||||
|  | 		} | ||||||
|  | 		err := config.Validate() | ||||||
|  | 		expectedError := fmt.Sprintf(`GitHub auth credential is missing, token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey)) | ||||||
|  | 		assert.ErrorContains(t, err, expectedError, "Expected error about missing auth") | ||||||
|  | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestConfigValidationOnlyOneTypeOfCredentials(t *testing.T) { | func TestConfigValidationOnlyOneTypeOfCredentials(t *testing.T) { | ||||||
| 	config := &Config{ | 	config := &Config{ | ||||||
| 		AppID:                       1, | 		AppID:                       "1", | ||||||
| 		AppInstallationID:           10, | 		AppInstallationID:           10, | ||||||
| 		AppPrivateKey:               "asdf", | 		AppPrivateKey:               "asdf", | ||||||
| 		Token:                       "asdf", | 		Token:                       "asdf", | ||||||
|  | @ -59,7 +79,7 @@ func TestConfigValidationOnlyOneTypeOfCredentials(t *testing.T) { | ||||||
| 		RunnerScaleSetId:            1, | 		RunnerScaleSetId:            1, | ||||||
| 	} | 	} | ||||||
| 	err := config.Validate() | 	err := config.Validate() | ||||||
| 	expectedError := fmt.Sprintf("only one GitHub auth method supported at a time. Have both PAT and App auth: token length: '%d', appId: '%d', installationId: '%d', private key length: '%d", len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey)) | 	expectedError := fmt.Sprintf(`only one GitHub auth method supported at a time. Have both PAT and App auth: token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey)) | ||||||
| 	assert.ErrorContains(t, err, expectedError, "Expected error about missing auth") | 	assert.ErrorContains(t, err, expectedError, "Expected error about missing auth") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -287,7 +287,7 @@ func (e *exporter) ListenAndServe(ctx context.Context) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *exporter) setGauge(name string, allLabels prometheus.Labels, val float64) { | func (e *exporter) setGauge(name string, allLabels prometheus.Labels, val float64) { | ||||||
| 	m, ok := e.metrics.gauges[name] | 	m, ok := e.gauges[name] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -299,7 +299,7 @@ func (e *exporter) setGauge(name string, allLabels prometheus.Labels, val float6 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *exporter) incCounter(name string, allLabels prometheus.Labels) { | func (e *exporter) incCounter(name string, allLabels prometheus.Labels) { | ||||||
| 	m, ok := e.metrics.counters[name] | 	m, ok := e.counters[name] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -311,7 +311,7 @@ func (e *exporter) incCounter(name string, allLabels prometheus.Labels) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *exporter) observeHistogram(name string, allLabels prometheus.Labels, val float64) { | func (e *exporter) observeHistogram(name string, allLabels prometheus.Labels, val float64) { | ||||||
| 	m, ok := e.metrics.histograms[name] | 	m, ok := e.histograms[name] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -339,7 +339,7 @@ func (e *exporter) PublishJobStarted(msg *actions.JobStarted) { | ||||||
| 	l := e.startedJobLabels(msg) | 	l := e.startedJobLabels(msg) | ||||||
| 	e.incCounter(MetricStartedJobsTotal, l) | 	e.incCounter(MetricStartedJobsTotal, l) | ||||||
| 
 | 
 | ||||||
| 	startupDuration := msg.JobMessageBase.RunnerAssignTime.Unix() - msg.JobMessageBase.ScaleSetAssignTime.Unix() | 	startupDuration := msg.RunnerAssignTime.Unix() - msg.ScaleSetAssignTime.Unix() | ||||||
| 	e.observeHistogram(MetricJobStartupDurationSeconds, l, float64(startupDuration)) | 	e.observeHistogram(MetricJobStartupDurationSeconds, l, float64(startupDuration)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -347,7 +347,7 @@ func (e *exporter) PublishJobCompleted(msg *actions.JobCompleted) { | ||||||
| 	l := e.completedJobLabels(msg) | 	l := e.completedJobLabels(msg) | ||||||
| 	e.incCounter(MetricCompletedJobsTotal, l) | 	e.incCounter(MetricCompletedJobsTotal, l) | ||||||
| 
 | 
 | ||||||
| 	executionDuration := msg.JobMessageBase.FinishTime.Unix() - msg.JobMessageBase.RunnerAssignTime.Unix() | 	executionDuration := msg.FinishTime.Unix() - msg.RunnerAssignTime.Unix() | ||||||
| 	e.observeHistogram(MetricJobExecutionDurationSeconds, l, float64(executionDuration)) | 	e.observeHistogram(MetricJobExecutionDurationSeconds, l, float64(executionDuration)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7794,7 +7794,8 @@ spec: | ||||||
|               properties: |               properties: | ||||||
|                 failures: |                 failures: | ||||||
|                   additionalProperties: |                   additionalProperties: | ||||||
|                     type: boolean |                     format: date-time | ||||||
|  |                     type: string | ||||||
|                   type: object |                   type: object | ||||||
|                 jobDisplayName: |                 jobDisplayName: | ||||||
|                   type: string |                   type: string | ||||||
|  |  | ||||||
|  | @ -77,7 +77,7 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl. | ||||||
| 		return ctrl.Result{}, client.IgnoreNotFound(err) | 		return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !autoscalingListener.ObjectMeta.DeletionTimestamp.IsZero() { | 	if !autoscalingListener.DeletionTimestamp.IsZero() { | ||||||
| 		if !controllerutil.ContainsFinalizer(autoscalingListener, autoscalingListenerFinalizerName) { | 		if !controllerutil.ContainsFinalizer(autoscalingListener, autoscalingListenerFinalizerName) { | ||||||
| 			return ctrl.Result{}, nil | 			return ctrl.Result{}, nil | ||||||
| 		} | 		} | ||||||
|  | @ -139,9 +139,9 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl. | ||||||
| 
 | 
 | ||||||
| 	// Create a mirror secret in the same namespace as the AutoscalingListener
 | 	// Create a mirror secret in the same namespace as the AutoscalingListener
 | ||||||
| 	mirrorSecret := new(corev1.Secret) | 	mirrorSecret := new(corev1.Secret) | ||||||
| 	if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: scaleSetListenerSecretMirrorName(autoscalingListener)}, mirrorSecret); err != nil { | 	if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: autoscalingListener.Name}, mirrorSecret); err != nil { | ||||||
| 		if !kerrors.IsNotFound(err) { | 		if !kerrors.IsNotFound(err) { | ||||||
| 			log.Error(err, "Unable to get listener secret mirror", "namespace", autoscalingListener.Namespace, "name", scaleSetListenerSecretMirrorName(autoscalingListener)) | 			log.Error(err, "Unable to get listener secret mirror", "namespace", autoscalingListener.Namespace, "name", autoscalingListener.Name) | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -160,9 +160,9 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl. | ||||||
| 
 | 
 | ||||||
| 	// Make sure the runner scale set listener service account is created for the listener pod in the controller namespace
 | 	// Make sure the runner scale set listener service account is created for the listener pod in the controller namespace
 | ||||||
| 	serviceAccount := new(corev1.ServiceAccount) | 	serviceAccount := new(corev1.ServiceAccount) | ||||||
| 	if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: scaleSetListenerServiceAccountName(autoscalingListener)}, serviceAccount); err != nil { | 	if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: autoscalingListener.Name}, serviceAccount); err != nil { | ||||||
| 		if !kerrors.IsNotFound(err) { | 		if !kerrors.IsNotFound(err) { | ||||||
| 			log.Error(err, "Unable to get listener service accounts", "namespace", autoscalingListener.Namespace, "name", scaleSetListenerServiceAccountName(autoscalingListener)) | 			log.Error(err, "Unable to get listener service accounts", "namespace", autoscalingListener.Namespace, "name", autoscalingListener.Name) | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -175,9 +175,9 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl. | ||||||
| 
 | 
 | ||||||
| 	// Make sure the runner scale set listener role is created in the AutoscalingRunnerSet namespace
 | 	// Make sure the runner scale set listener role is created in the AutoscalingRunnerSet namespace
 | ||||||
| 	listenerRole := new(rbacv1.Role) | 	listenerRole := new(rbacv1.Role) | ||||||
| 	if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: scaleSetListenerRoleName(autoscalingListener)}, listenerRole); err != nil { | 	if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: autoscalingListener.Name}, listenerRole); err != nil { | ||||||
| 		if !kerrors.IsNotFound(err) { | 		if !kerrors.IsNotFound(err) { | ||||||
| 			log.Error(err, "Unable to get listener role", "namespace", autoscalingListener.Spec.AutoscalingRunnerSetNamespace, "name", scaleSetListenerRoleName(autoscalingListener)) | 			log.Error(err, "Unable to get listener role", "namespace", autoscalingListener.Spec.AutoscalingRunnerSetNamespace, "name", autoscalingListener.Name) | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -197,9 +197,9 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl. | ||||||
| 
 | 
 | ||||||
| 	// Make sure the runner scale set listener role binding is created
 | 	// Make sure the runner scale set listener role binding is created
 | ||||||
| 	listenerRoleBinding := new(rbacv1.RoleBinding) | 	listenerRoleBinding := new(rbacv1.RoleBinding) | ||||||
| 	if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: scaleSetListenerRoleName(autoscalingListener)}, listenerRoleBinding); err != nil { | 	if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: autoscalingListener.Name}, listenerRoleBinding); err != nil { | ||||||
| 		if !kerrors.IsNotFound(err) { | 		if !kerrors.IsNotFound(err) { | ||||||
| 			log.Error(err, "Unable to get listener role binding", "namespace", autoscalingListener.Spec.AutoscalingRunnerSetNamespace, "name", scaleSetListenerRoleName(autoscalingListener)) | 			log.Error(err, "Unable to get listener role binding", "namespace", autoscalingListener.Spec.AutoscalingRunnerSetNamespace, "name", autoscalingListener.Name) | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -294,7 +294,7 @@ func (r *AutoscalingListenerReconciler) cleanupResources(ctx context.Context, au | ||||||
| 	err = r.Get(ctx, types.NamespacedName{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, listenerPod) | 	err = r.Get(ctx, types.NamespacedName{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, listenerPod) | ||||||
| 	switch { | 	switch { | ||||||
| 	case err == nil: | 	case err == nil: | ||||||
| 		if listenerPod.ObjectMeta.DeletionTimestamp.IsZero() { | 		if listenerPod.DeletionTimestamp.IsZero() { | ||||||
| 			logger.Info("Deleting the listener pod") | 			logger.Info("Deleting the listener pod") | ||||||
| 			if err := r.Delete(ctx, listenerPod); err != nil { | 			if err := r.Delete(ctx, listenerPod); err != nil { | ||||||
| 				return false, fmt.Errorf("failed to delete listener pod: %w", err) | 				return false, fmt.Errorf("failed to delete listener pod: %w", err) | ||||||
|  | @ -312,7 +312,7 @@ func (r *AutoscalingListenerReconciler) cleanupResources(ctx context.Context, au | ||||||
| 	err = r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: scaleSetListenerConfigName(autoscalingListener)}, &secret) | 	err = r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: scaleSetListenerConfigName(autoscalingListener)}, &secret) | ||||||
| 	switch { | 	switch { | ||||||
| 	case err == nil: | 	case err == nil: | ||||||
| 		if secret.ObjectMeta.DeletionTimestamp.IsZero() { | 		if secret.DeletionTimestamp.IsZero() { | ||||||
| 			logger.Info("Deleting the listener config secret") | 			logger.Info("Deleting the listener config secret") | ||||||
| 			if err := r.Delete(ctx, &secret); err != nil { | 			if err := r.Delete(ctx, &secret); err != nil { | ||||||
| 				return false, fmt.Errorf("failed to delete listener config secret: %w", err) | 				return false, fmt.Errorf("failed to delete listener config secret: %w", err) | ||||||
|  | @ -329,7 +329,7 @@ func (r *AutoscalingListenerReconciler) cleanupResources(ctx context.Context, au | ||||||
| 		err = r.Get(ctx, types.NamespacedName{Name: proxyListenerSecretName(autoscalingListener), Namespace: autoscalingListener.Namespace}, proxySecret) | 		err = r.Get(ctx, types.NamespacedName{Name: proxyListenerSecretName(autoscalingListener), Namespace: autoscalingListener.Namespace}, proxySecret) | ||||||
| 		switch { | 		switch { | ||||||
| 		case err == nil: | 		case err == nil: | ||||||
| 			if proxySecret.ObjectMeta.DeletionTimestamp.IsZero() { | 			if proxySecret.DeletionTimestamp.IsZero() { | ||||||
| 				logger.Info("Deleting the listener proxy secret") | 				logger.Info("Deleting the listener proxy secret") | ||||||
| 				if err := r.Delete(ctx, proxySecret); err != nil { | 				if err := r.Delete(ctx, proxySecret); err != nil { | ||||||
| 					return false, fmt.Errorf("failed to delete listener proxy secret: %w", err) | 					return false, fmt.Errorf("failed to delete listener proxy secret: %w", err) | ||||||
|  | @ -343,10 +343,10 @@ func (r *AutoscalingListenerReconciler) cleanupResources(ctx context.Context, au | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	listenerRoleBinding := new(rbacv1.RoleBinding) | 	listenerRoleBinding := new(rbacv1.RoleBinding) | ||||||
| 	err = r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: scaleSetListenerRoleName(autoscalingListener)}, listenerRoleBinding) | 	err = r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: autoscalingListener.Name}, listenerRoleBinding) | ||||||
| 	switch { | 	switch { | ||||||
| 	case err == nil: | 	case err == nil: | ||||||
| 		if listenerRoleBinding.ObjectMeta.DeletionTimestamp.IsZero() { | 		if listenerRoleBinding.DeletionTimestamp.IsZero() { | ||||||
| 			logger.Info("Deleting the listener role binding") | 			logger.Info("Deleting the listener role binding") | ||||||
| 			if err := r.Delete(ctx, listenerRoleBinding); err != nil { | 			if err := r.Delete(ctx, listenerRoleBinding); err != nil { | ||||||
| 				return false, fmt.Errorf("failed to delete listener role binding: %w", err) | 				return false, fmt.Errorf("failed to delete listener role binding: %w", err) | ||||||
|  | @ -359,10 +359,10 @@ func (r *AutoscalingListenerReconciler) cleanupResources(ctx context.Context, au | ||||||
| 	logger.Info("Listener role binding is deleted") | 	logger.Info("Listener role binding is deleted") | ||||||
| 
 | 
 | ||||||
| 	listenerRole := new(rbacv1.Role) | 	listenerRole := new(rbacv1.Role) | ||||||
| 	err = r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: scaleSetListenerRoleName(autoscalingListener)}, listenerRole) | 	err = r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: autoscalingListener.Name}, listenerRole) | ||||||
| 	switch { | 	switch { | ||||||
| 	case err == nil: | 	case err == nil: | ||||||
| 		if listenerRole.ObjectMeta.DeletionTimestamp.IsZero() { | 		if listenerRole.DeletionTimestamp.IsZero() { | ||||||
| 			logger.Info("Deleting the listener role") | 			logger.Info("Deleting the listener role") | ||||||
| 			if err := r.Delete(ctx, listenerRole); err != nil { | 			if err := r.Delete(ctx, listenerRole); err != nil { | ||||||
| 				return false, fmt.Errorf("failed to delete listener role: %w", err) | 				return false, fmt.Errorf("failed to delete listener role: %w", err) | ||||||
|  | @ -376,10 +376,10 @@ func (r *AutoscalingListenerReconciler) cleanupResources(ctx context.Context, au | ||||||
| 
 | 
 | ||||||
| 	logger.Info("Cleaning up the listener service account") | 	logger.Info("Cleaning up the listener service account") | ||||||
| 	listenerSa := new(corev1.ServiceAccount) | 	listenerSa := new(corev1.ServiceAccount) | ||||||
| 	err = r.Get(ctx, types.NamespacedName{Name: scaleSetListenerServiceAccountName(autoscalingListener), Namespace: autoscalingListener.Namespace}, listenerSa) | 	err = r.Get(ctx, types.NamespacedName{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, listenerSa) | ||||||
| 	switch { | 	switch { | ||||||
| 	case err == nil: | 	case err == nil: | ||||||
| 		if listenerSa.ObjectMeta.DeletionTimestamp.IsZero() { | 		if listenerSa.DeletionTimestamp.IsZero() { | ||||||
| 			logger.Info("Deleting the listener service account") | 			logger.Info("Deleting the listener service account") | ||||||
| 			if err := r.Delete(ctx, listenerSa); err != nil { | 			if err := r.Delete(ctx, listenerSa); err != nil { | ||||||
| 				return false, fmt.Errorf("failed to delete listener service account: %w", err) | 				return false, fmt.Errorf("failed to delete listener service account: %w", err) | ||||||
|  | @ -395,7 +395,7 @@ func (r *AutoscalingListenerReconciler) cleanupResources(ctx context.Context, au | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *AutoscalingListenerReconciler) createServiceAccountForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) { | func (r *AutoscalingListenerReconciler) createServiceAccountForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) { | ||||||
| 	newServiceAccount := r.ResourceBuilder.newScaleSetListenerServiceAccount(autoscalingListener) | 	newServiceAccount := r.newScaleSetListenerServiceAccount(autoscalingListener) | ||||||
| 
 | 
 | ||||||
| 	if err := ctrl.SetControllerReference(autoscalingListener, newServiceAccount, r.Scheme); err != nil { | 	if err := ctrl.SetControllerReference(autoscalingListener, newServiceAccount, r.Scheme); err != nil { | ||||||
| 		return ctrl.Result{}, err | 		return ctrl.Result{}, err | ||||||
|  | @ -480,7 +480,7 @@ func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, a | ||||||
| 
 | 
 | ||||||
| 		logger.Info("Creating listener config secret") | 		logger.Info("Creating listener config secret") | ||||||
| 
 | 
 | ||||||
| 		podConfig, err := r.ResourceBuilder.newScaleSetListenerConfig(autoscalingListener, secret, metricsConfig, cert) | 		podConfig, err := r.newScaleSetListenerConfig(autoscalingListener, secret, metricsConfig, cert) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logger.Error(err, "Failed to build listener config secret") | 			logger.Error(err, "Failed to build listener config secret") | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
|  | @ -499,7 +499,7 @@ func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, a | ||||||
| 		return ctrl.Result{Requeue: true}, nil | 		return ctrl.Result{Requeue: true}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	newPod, err := r.ResourceBuilder.newScaleSetListenerPod(autoscalingListener, &podConfig, serviceAccount, secret, metricsConfig, envs...) | 	newPod, err := r.newScaleSetListenerPod(autoscalingListener, &podConfig, serviceAccount, secret, metricsConfig, envs...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logger.Error(err, "Failed to build listener pod") | 		logger.Error(err, "Failed to build listener pod") | ||||||
| 		return ctrl.Result{}, err | 		return ctrl.Result{}, err | ||||||
|  | @ -559,7 +559,7 @@ func (r *AutoscalingListenerReconciler) certificate(ctx context.Context, autosca | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *AutoscalingListenerReconciler) createSecretsForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) { | func (r *AutoscalingListenerReconciler) createSecretsForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) { | ||||||
| 	newListenerSecret := r.ResourceBuilder.newScaleSetListenerSecretMirror(autoscalingListener, secret) | 	newListenerSecret := r.newScaleSetListenerSecretMirror(autoscalingListener, secret) | ||||||
| 
 | 
 | ||||||
| 	if err := ctrl.SetControllerReference(autoscalingListener, newListenerSecret, r.Scheme); err != nil { | 	if err := ctrl.SetControllerReference(autoscalingListener, newListenerSecret, r.Scheme); err != nil { | ||||||
| 		return ctrl.Result{}, err | 		return ctrl.Result{}, err | ||||||
|  | @ -631,7 +631,7 @@ func (r *AutoscalingListenerReconciler) updateSecretsForListener(ctx context.Con | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *AutoscalingListenerReconciler) createRoleForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) { | func (r *AutoscalingListenerReconciler) createRoleForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) { | ||||||
| 	newRole := r.ResourceBuilder.newScaleSetListenerRole(autoscalingListener) | 	newRole := r.newScaleSetListenerRole(autoscalingListener) | ||||||
| 
 | 
 | ||||||
| 	logger.Info("Creating listener role", "namespace", newRole.Namespace, "name", newRole.Name, "rules", newRole.Rules) | 	logger.Info("Creating listener role", "namespace", newRole.Namespace, "name", newRole.Name, "rules", newRole.Rules) | ||||||
| 	if err := r.Create(ctx, newRole); err != nil { | 	if err := r.Create(ctx, newRole); err != nil { | ||||||
|  | @ -659,7 +659,7 @@ func (r *AutoscalingListenerReconciler) updateRoleForListener(ctx context.Contex | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *AutoscalingListenerReconciler) createRoleBindingForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, listenerRole *rbacv1.Role, serviceAccount *corev1.ServiceAccount, logger logr.Logger) (ctrl.Result, error) { | func (r *AutoscalingListenerReconciler) createRoleBindingForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, listenerRole *rbacv1.Role, serviceAccount *corev1.ServiceAccount, logger logr.Logger) (ctrl.Result, error) { | ||||||
| 	newRoleBinding := r.ResourceBuilder.newScaleSetListenerRoleBinding(autoscalingListener, listenerRole, serviceAccount) | 	newRoleBinding := r.newScaleSetListenerRoleBinding(autoscalingListener, listenerRole, serviceAccount) | ||||||
| 
 | 
 | ||||||
| 	logger.Info("Creating listener role binding", | 	logger.Info("Creating listener role binding", | ||||||
| 		"namespace", newRoleBinding.Namespace, | 		"namespace", newRoleBinding.Namespace, | ||||||
|  |  | ||||||
|  | @ -134,37 +134,25 @@ var _ = Describe("Test AutoScalingListener controller", func() { | ||||||
| 				autoscalingListenerTestTimeout, | 				autoscalingListenerTestTimeout, | ||||||
| 				autoscalingListenerTestInterval).Should(BeEquivalentTo(autoscalingListenerFinalizerName), "AutoScalingListener should have a finalizer") | 				autoscalingListenerTestInterval).Should(BeEquivalentTo(autoscalingListenerFinalizerName), "AutoScalingListener should have a finalizer") | ||||||
| 
 | 
 | ||||||
| 			// Check if secret is created
 |  | ||||||
| 			mirrorSecret := new(corev1.Secret) |  | ||||||
| 			Eventually( |  | ||||||
| 				func() (string, error) { |  | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerSecretMirrorName(autoscalingListener), Namespace: autoscalingListener.Namespace}, mirrorSecret) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return "", err |  | ||||||
| 					} |  | ||||||
| 					return string(mirrorSecret.Data["github_token"]), nil |  | ||||||
| 				}, |  | ||||||
| 				autoscalingListenerTestTimeout, |  | ||||||
| 				autoscalingListenerTestInterval).Should(BeEquivalentTo(autoscalingListenerTestGitHubToken), "Mirror secret should be created") |  | ||||||
| 
 |  | ||||||
| 			// Check if service account is created
 | 			// Check if service account is created
 | ||||||
| 			serviceAccount := new(corev1.ServiceAccount) | 			serviceAccount := new(corev1.ServiceAccount) | ||||||
| 			Eventually( | 			Eventually( | ||||||
| 				func() (string, error) { | 				func() (string, error) { | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerServiceAccountName(autoscalingListener), Namespace: autoscalingListener.Namespace}, serviceAccount) | 					err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, serviceAccount) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						return "", err | 						return "", err | ||||||
| 					} | 					} | ||||||
| 					return serviceAccount.Name, nil | 					return serviceAccount.Name, nil | ||||||
| 				}, | 				}, | ||||||
| 				autoscalingListenerTestTimeout, | 				autoscalingListenerTestTimeout, | ||||||
| 				autoscalingListenerTestInterval).Should(BeEquivalentTo(scaleSetListenerServiceAccountName(autoscalingListener)), "Service account should be created") | 				autoscalingListenerTestInterval, | ||||||
|  | 			).Should(BeEquivalentTo(autoscalingListener.Name), "Service account should be created") | ||||||
| 
 | 
 | ||||||
| 			// Check if role is created
 | 			// Check if role is created
 | ||||||
| 			role := new(rbacv1.Role) | 			role := new(rbacv1.Role) | ||||||
| 			Eventually( | 			Eventually( | ||||||
| 				func() ([]rbacv1.PolicyRule, error) { | 				func() ([]rbacv1.PolicyRule, error) { | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerRoleName(autoscalingListener), Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, role) | 					err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, role) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						return nil, err | 						return nil, err | ||||||
| 					} | 					} | ||||||
|  | @ -178,7 +166,7 @@ var _ = Describe("Test AutoScalingListener controller", func() { | ||||||
| 			roleBinding := new(rbacv1.RoleBinding) | 			roleBinding := new(rbacv1.RoleBinding) | ||||||
| 			Eventually( | 			Eventually( | ||||||
| 				func() (string, error) { | 				func() (string, error) { | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerRoleName(autoscalingListener), Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, roleBinding) | 					err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, roleBinding) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						return "", err | 						return "", err | ||||||
| 					} | 					} | ||||||
|  | @ -186,7 +174,7 @@ var _ = Describe("Test AutoScalingListener controller", func() { | ||||||
| 					return roleBinding.RoleRef.Name, nil | 					return roleBinding.RoleRef.Name, nil | ||||||
| 				}, | 				}, | ||||||
| 				autoscalingListenerTestTimeout, | 				autoscalingListenerTestTimeout, | ||||||
| 				autoscalingListenerTestInterval).Should(BeEquivalentTo(scaleSetListenerRoleName(autoscalingListener)), "Rolebinding should be created") | 				autoscalingListenerTestInterval).Should(BeEquivalentTo(autoscalingListener.Name), "Rolebinding should be created") | ||||||
| 
 | 
 | ||||||
| 			// Check if pod is created
 | 			// Check if pod is created
 | ||||||
| 			pod := new(corev1.Pod) | 			pod := new(corev1.Pod) | ||||||
|  | @ -248,7 +236,7 @@ var _ = Describe("Test AutoScalingListener controller", func() { | ||||||
| 			Eventually( | 			Eventually( | ||||||
| 				func() bool { | 				func() bool { | ||||||
| 					roleBinding := new(rbacv1.RoleBinding) | 					roleBinding := new(rbacv1.RoleBinding) | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerRoleName(autoscalingListener), Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, roleBinding) | 					err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, roleBinding) | ||||||
| 					return kerrors.IsNotFound(err) | 					return kerrors.IsNotFound(err) | ||||||
| 				}, | 				}, | ||||||
| 				autoscalingListenerTestTimeout, | 				autoscalingListenerTestTimeout, | ||||||
|  | @ -259,7 +247,7 @@ var _ = Describe("Test AutoScalingListener controller", func() { | ||||||
| 			Eventually( | 			Eventually( | ||||||
| 				func() bool { | 				func() bool { | ||||||
| 					role := new(rbacv1.Role) | 					role := new(rbacv1.Role) | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerRoleName(autoscalingListener), Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, role) | 					err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, role) | ||||||
| 					return kerrors.IsNotFound(err) | 					return kerrors.IsNotFound(err) | ||||||
| 				}, | 				}, | ||||||
| 				autoscalingListenerTestTimeout, | 				autoscalingListenerTestTimeout, | ||||||
|  | @ -340,7 +328,7 @@ var _ = Describe("Test AutoScalingListener controller", func() { | ||||||
| 			role := new(rbacv1.Role) | 			role := new(rbacv1.Role) | ||||||
| 			Eventually( | 			Eventually( | ||||||
| 				func() ([]rbacv1.PolicyRule, error) { | 				func() ([]rbacv1.PolicyRule, error) { | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerRoleName(autoscalingListener), Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, role) | 					err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace}, role) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						return nil, err | 						return nil, err | ||||||
| 					} | 					} | ||||||
|  | @ -397,75 +385,6 @@ var _ = Describe("Test AutoScalingListener controller", func() { | ||||||
| 				autoscalingListenerTestInterval, | 				autoscalingListenerTestInterval, | ||||||
| 			).ShouldNot(BeEquivalentTo(oldPodUID), "Pod should be re-created") | 			).ShouldNot(BeEquivalentTo(oldPodUID), "Pod should be re-created") | ||||||
| 		}) | 		}) | ||||||
| 
 |  | ||||||
| 		It("It should update mirror secrets to match secret used by AutoScalingRunnerSet", func() { |  | ||||||
| 			// Waiting for the pod is created
 |  | ||||||
| 			pod := new(corev1.Pod) |  | ||||||
| 			Eventually( |  | ||||||
| 				func() (string, error) { |  | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, pod) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return "", err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					return pod.Name, nil |  | ||||||
| 				}, |  | ||||||
| 				autoscalingListenerTestTimeout, |  | ||||||
| 				autoscalingListenerTestInterval).Should(BeEquivalentTo(autoscalingListener.Name), "Pod should be created") |  | ||||||
| 
 |  | ||||||
| 			// Update the secret
 |  | ||||||
| 			updatedSecret := configSecret.DeepCopy() |  | ||||||
| 			updatedSecret.Data["github_token"] = []byte(autoscalingListenerTestGitHubToken + "_updated") |  | ||||||
| 			err := k8sClient.Update(ctx, updatedSecret) |  | ||||||
| 			Expect(err).NotTo(HaveOccurred(), "failed to update test secret") |  | ||||||
| 
 |  | ||||||
| 			updatedPod := pod.DeepCopy() |  | ||||||
| 			// Ignore status running and consult the container state
 |  | ||||||
| 			updatedPod.Status.Phase = corev1.PodRunning |  | ||||||
| 			updatedPod.Status.ContainerStatuses = []corev1.ContainerStatus{ |  | ||||||
| 				{ |  | ||||||
| 					Name: autoscalingListenerContainerName, |  | ||||||
| 					State: corev1.ContainerState{ |  | ||||||
| 						Terminated: &corev1.ContainerStateTerminated{ |  | ||||||
| 							ExitCode: 1, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			} |  | ||||||
| 			err = k8sClient.Status().Update(ctx, updatedPod) |  | ||||||
| 			Expect(err).NotTo(HaveOccurred(), "failed to update test pod to failed") |  | ||||||
| 
 |  | ||||||
| 			// Check if mirror secret is updated with right data
 |  | ||||||
| 			mirrorSecret := new(corev1.Secret) |  | ||||||
| 			Eventually( |  | ||||||
| 				func() (map[string][]byte, error) { |  | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerSecretMirrorName(autoscalingListener), Namespace: autoscalingListener.Namespace}, mirrorSecret) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					return mirrorSecret.Data, nil |  | ||||||
| 				}, |  | ||||||
| 				autoscalingListenerTestTimeout, |  | ||||||
| 				autoscalingListenerTestInterval).Should(BeEquivalentTo(updatedSecret.Data), "Mirror secret should be updated") |  | ||||||
| 
 |  | ||||||
| 			// Check if we re-created a new pod
 |  | ||||||
| 			Eventually( |  | ||||||
| 				func() error { |  | ||||||
| 					latestPod := new(corev1.Pod) |  | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, latestPod) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return err |  | ||||||
| 					} |  | ||||||
| 					if latestPod.UID == pod.UID { |  | ||||||
| 						return fmt.Errorf("Pod should be recreated") |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					return nil |  | ||||||
| 				}, |  | ||||||
| 				autoscalingListenerTestTimeout, |  | ||||||
| 				autoscalingListenerTestInterval).Should(Succeed(), "Pod should be recreated") |  | ||||||
| 		}) |  | ||||||
| 	}) | 	}) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -99,7 +99,7 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl | ||||||
| 		return ctrl.Result{}, client.IgnoreNotFound(err) | 		return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !autoscalingRunnerSet.ObjectMeta.DeletionTimestamp.IsZero() { | 	if !autoscalingRunnerSet.DeletionTimestamp.IsZero() { | ||||||
| 		if !controllerutil.ContainsFinalizer(autoscalingRunnerSet, autoscalingRunnerSetFinalizerName) { | 		if !controllerutil.ContainsFinalizer(autoscalingRunnerSet, autoscalingRunnerSetFinalizerName) { | ||||||
| 			return ctrl.Result{}, nil | 			return ctrl.Result{}, nil | ||||||
| 		} | 		} | ||||||
|  | @ -151,7 +151,7 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl | ||||||
| 		return ctrl.Result{}, nil | 		return ctrl.Result{}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion] != build.Version { | 	if !v1alpha1.IsVersionAllowed(autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion], build.Version) { | ||||||
| 		if err := r.Delete(ctx, autoscalingRunnerSet); err != nil { | 		if err := r.Delete(ctx, autoscalingRunnerSet); err != nil { | ||||||
| 			log.Error(err, "Failed to delete autoscaling runner set on version mismatch", | 			log.Error(err, "Failed to delete autoscaling runner set on version mismatch", | ||||||
| 				"buildVersion", build.Version, | 				"buildVersion", build.Version, | ||||||
|  | @ -332,7 +332,7 @@ func (r *AutoscalingRunnerSetReconciler) cleanupListener(ctx context.Context, au | ||||||
| 	err = r.Get(ctx, client.ObjectKey{Namespace: r.ControllerNamespace, Name: scaleSetListenerName(autoscalingRunnerSet)}, &listener) | 	err = r.Get(ctx, client.ObjectKey{Namespace: r.ControllerNamespace, Name: scaleSetListenerName(autoscalingRunnerSet)}, &listener) | ||||||
| 	switch { | 	switch { | ||||||
| 	case err == nil: | 	case err == nil: | ||||||
| 		if listener.ObjectMeta.DeletionTimestamp.IsZero() { | 		if listener.DeletionTimestamp.IsZero() { | ||||||
| 			logger.Info("Deleting the listener") | 			logger.Info("Deleting the listener") | ||||||
| 			if err := r.Delete(ctx, &listener); err != nil { | 			if err := r.Delete(ctx, &listener); err != nil { | ||||||
| 				return false, fmt.Errorf("failed to delete listener: %w", err) | 				return false, fmt.Errorf("failed to delete listener: %w", err) | ||||||
|  | @ -369,7 +369,7 @@ func (r *AutoscalingRunnerSetReconciler) deleteEphemeralRunnerSets(ctx context.C | ||||||
| 	for i := range oldRunnerSets { | 	for i := range oldRunnerSets { | ||||||
| 		rs := &oldRunnerSets[i] | 		rs := &oldRunnerSets[i] | ||||||
| 		// already deleted but contains finalizer so it still exists
 | 		// already deleted but contains finalizer so it still exists
 | ||||||
| 		if !rs.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !rs.DeletionTimestamp.IsZero() { | ||||||
| 			logger.Info("Skip ephemeral runner set since it is already marked for deletion", "name", rs.Name) | 			logger.Info("Skip ephemeral runner set since it is already marked for deletion", "name", rs.Name) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | @ -622,7 +622,7 @@ func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Contex | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *AutoscalingRunnerSetReconciler) createEphemeralRunnerSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, log logr.Logger) (ctrl.Result, error) { | func (r *AutoscalingRunnerSetReconciler) createEphemeralRunnerSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, log logr.Logger) (ctrl.Result, error) { | ||||||
| 	desiredRunnerSet, err := r.ResourceBuilder.newEphemeralRunnerSet(autoscalingRunnerSet) | 	desiredRunnerSet, err := r.newEphemeralRunnerSet(autoscalingRunnerSet) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error(err, "Could not create EphemeralRunnerSet") | 		log.Error(err, "Could not create EphemeralRunnerSet") | ||||||
| 		return ctrl.Result{}, err | 		return ctrl.Result{}, err | ||||||
|  | @ -651,7 +651,7 @@ func (r *AutoscalingRunnerSetReconciler) createAutoScalingListenerForRunnerSet(c | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	autoscalingListener, err := r.ResourceBuilder.newAutoScalingListener(autoscalingRunnerSet, ephemeralRunnerSet, r.ControllerNamespace, r.DefaultRunnerScaleSetListenerImage, imagePullSecrets) | 	autoscalingListener, err := r.newAutoScalingListener(autoscalingRunnerSet, ephemeralRunnerSet, r.ControllerNamespace, r.DefaultRunnerScaleSetListenerImage, imagePullSecrets) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error(err, "Could not create AutoscalingListener spec") | 		log.Error(err, "Could not create AutoscalingListener spec") | ||||||
| 		return ctrl.Result{}, err | 		return ctrl.Result{}, err | ||||||
|  |  | ||||||
|  | @ -280,10 +280,10 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() { | ||||||
| 			// This should trigger re-creation of EphemeralRunnerSet and Listener
 | 			// This should trigger re-creation of EphemeralRunnerSet and Listener
 | ||||||
| 			patched := autoscalingRunnerSet.DeepCopy() | 			patched := autoscalingRunnerSet.DeepCopy() | ||||||
| 			patched.Spec.Template.Spec.PriorityClassName = "test-priority-class" | 			patched.Spec.Template.Spec.PriorityClassName = "test-priority-class" | ||||||
| 			if patched.ObjectMeta.Annotations == nil { | 			if patched.Annotations == nil { | ||||||
| 				patched.ObjectMeta.Annotations = make(map[string]string) | 				patched.Annotations = make(map[string]string) | ||||||
| 			} | 			} | ||||||
| 			patched.ObjectMeta.Annotations[annotationKeyValuesHash] = "test-hash" | 			patched.Annotations[annotationKeyValuesHash] = "test-hash" | ||||||
| 			err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet)) | 			err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet)) | ||||||
| 			Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet") | 			Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet") | ||||||
| 			autoscalingRunnerSet = patched.DeepCopy() | 			autoscalingRunnerSet = patched.DeepCopy() | ||||||
|  | @ -383,7 +383,7 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() { | ||||||
| 			Expect(err).NotTo(HaveOccurred(), "failed to get Listener") | 			Expect(err).NotTo(HaveOccurred(), "failed to get Listener") | ||||||
| 
 | 
 | ||||||
| 			patched = autoscalingRunnerSet.DeepCopy() | 			patched = autoscalingRunnerSet.DeepCopy() | ||||||
| 			patched.ObjectMeta.Annotations[annotationKeyValuesHash] = "hash-changes" | 			patched.Annotations[annotationKeyValuesHash] = "hash-changes" | ||||||
| 			err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet)) | 			err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet)) | ||||||
| 			Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet") | 			Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet") | ||||||
| 
 | 
 | ||||||
|  | @ -546,10 +546,10 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() { | ||||||
| 			// Patch the AutoScalingRunnerSet image which should trigger
 | 			// Patch the AutoScalingRunnerSet image which should trigger
 | ||||||
| 			// the recreation of the Listener and EphemeralRunnerSet
 | 			// the recreation of the Listener and EphemeralRunnerSet
 | ||||||
| 			patched := autoscalingRunnerSet.DeepCopy() | 			patched := autoscalingRunnerSet.DeepCopy() | ||||||
| 			if patched.ObjectMeta.Annotations == nil { | 			if patched.Annotations == nil { | ||||||
| 				patched.ObjectMeta.Annotations = make(map[string]string) | 				patched.Annotations = make(map[string]string) | ||||||
| 			} | 			} | ||||||
| 			patched.ObjectMeta.Annotations[annotationKeyValuesHash] = "testgroup2" | 			patched.Annotations[annotationKeyValuesHash] = "testgroup2" | ||||||
| 			patched.Spec.Template.Spec = corev1.PodSpec{ | 			patched.Spec.Template.Spec = corev1.PodSpec{ | ||||||
| 				Containers: []corev1.Container{ | 				Containers: []corev1.Container{ | ||||||
| 					{ | 					{ | ||||||
|  | @ -875,7 +875,7 @@ var _ = Describe("Test AutoscalingController creation failures", Ordered, func() | ||||||
| 				autoscalingRunnerSetTestInterval, | 				autoscalingRunnerSetTestInterval, | ||||||
| 			).Should(BeEquivalentTo(autoscalingRunnerSetFinalizerName), "AutoScalingRunnerSet should have a finalizer") | 			).Should(BeEquivalentTo(autoscalingRunnerSetFinalizerName), "AutoScalingRunnerSet should have a finalizer") | ||||||
| 
 | 
 | ||||||
| 			ars.ObjectMeta.Annotations = make(map[string]string) | 			ars.Annotations = make(map[string]string) | ||||||
| 			err = k8sClient.Update(ctx, ars) | 			err = k8sClient.Update(ctx, ars) | ||||||
| 			Expect(err).NotTo(HaveOccurred(), "Update autoscaling runner set without annotation should be successful") | 			Expect(err).NotTo(HaveOccurred(), "Update autoscaling runner set without annotation should be successful") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ import ( | ||||||
| 	"github.com/go-logr/logr" | 	"github.com/go-logr/logr" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	kerrors "k8s.io/apimachinery/pkg/api/errors" | 	kerrors "k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
| 	ctrl "sigs.k8s.io/controller-runtime" | 	ctrl "sigs.k8s.io/controller-runtime" | ||||||
|  | @ -50,6 +51,19 @@ type EphemeralRunnerReconciler struct { | ||||||
| 	ResourceBuilder | 	ResourceBuilder | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // precompute backoff durations for failed ephemeral runners
 | ||||||
|  | // the len(failedRunnerBackoff) must be equal to maxFailures + 1
 | ||||||
|  | var failedRunnerBackoff = []time.Duration{ | ||||||
|  | 	0, | ||||||
|  | 	5 * time.Second, | ||||||
|  | 	10 * time.Second, | ||||||
|  | 	20 * time.Second, | ||||||
|  | 	40 * time.Second, | ||||||
|  | 	80 * time.Second, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const maxFailures = 5 | ||||||
|  | 
 | ||||||
| // +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners,verbs=get;list;watch;create;update;patch;delete
 | // +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners,verbs=get;list;watch;create;update;patch;delete
 | ||||||
| // +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners/status,verbs=get;update;patch
 | // +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners/status,verbs=get;update;patch
 | ||||||
| // +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners/finalizers,verbs=get;list;watch;create;update;patch;delete
 | // +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners/finalizers,verbs=get;list;watch;create;update;patch;delete
 | ||||||
|  | @ -70,7 +84,7 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ | ||||||
| 		return ctrl.Result{}, client.IgnoreNotFound(err) | 		return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !ephemeralRunner.ObjectMeta.DeletionTimestamp.IsZero() { | 	if !ephemeralRunner.DeletionTimestamp.IsZero() { | ||||||
| 		if !controllerutil.ContainsFinalizer(ephemeralRunner, ephemeralRunnerFinalizerName) { | 		if !controllerutil.ContainsFinalizer(ephemeralRunner, ephemeralRunnerFinalizerName) { | ||||||
| 			return ctrl.Result{}, nil | 			return ctrl.Result{}, nil | ||||||
| 		} | 		} | ||||||
|  | @ -173,6 +187,29 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if len(ephemeralRunner.Status.Failures) > maxFailures { | ||||||
|  | 		log.Info(fmt.Sprintf("EphemeralRunner has failed more than %d times. Deleting ephemeral runner so it can be re-created", maxFailures)) | ||||||
|  | 		if err := r.Delete(ctx, ephemeralRunner); err != nil { | ||||||
|  | 			log.Error(fmt.Errorf("failed to delete ephemeral runner after %d failures: %w", maxFailures, err), "Failed to delete ephemeral runner") | ||||||
|  | 			return ctrl.Result{}, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return ctrl.Result{}, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	now := metav1.Now() | ||||||
|  | 	lastFailure := ephemeralRunner.Status.LastFailure() | ||||||
|  | 	backoffDuration := failedRunnerBackoff[len(ephemeralRunner.Status.Failures)] | ||||||
|  | 	nextReconciliation := lastFailure.Add(backoffDuration) | ||||||
|  | 	if !lastFailure.IsZero() && now.Before(&metav1.Time{Time: nextReconciliation}) { | ||||||
|  | 		log.Info("Backing off the next reconciliation due to failure", | ||||||
|  | 			"lastFailure", lastFailure, | ||||||
|  | 			"nextReconciliation", nextReconciliation, | ||||||
|  | 			"requeueAfter", nextReconciliation.Sub(now.Time), | ||||||
|  | 		) | ||||||
|  | 		return ctrl.Result{RequeueAfter: now.Sub(nextReconciliation)}, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	secret := new(corev1.Secret) | 	secret := new(corev1.Secret) | ||||||
| 	if err := r.Get(ctx, req.NamespacedName, secret); err != nil { | 	if err := r.Get(ctx, req.NamespacedName, secret); err != nil { | ||||||
| 		if !kerrors.IsNotFound(err) { | 		if !kerrors.IsNotFound(err) { | ||||||
|  | @ -196,21 +233,11 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ | ||||||
| 
 | 
 | ||||||
| 	pod := new(corev1.Pod) | 	pod := new(corev1.Pod) | ||||||
| 	if err := r.Get(ctx, req.NamespacedName, pod); err != nil { | 	if err := r.Get(ctx, req.NamespacedName, pod); err != nil { | ||||||
| 		switch { | 		if !kerrors.IsNotFound(err) { | ||||||
| 		case !kerrors.IsNotFound(err): |  | ||||||
| 			log.Error(err, "Failed to fetch the pod") | 			log.Error(err, "Failed to fetch the pod") | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
| 
 |  | ||||||
| 		case len(ephemeralRunner.Status.Failures) > 5: |  | ||||||
| 			log.Info("EphemeralRunner has failed more than 5 times. Marking it as failed") |  | ||||||
| 			errMessage := fmt.Sprintf("Pod has failed to start more than 5 times: %s", pod.Status.Message) |  | ||||||
| 			if err := r.markAsFailed(ctx, ephemeralRunner, errMessage, ReasonTooManyPodFailures, log); err != nil { |  | ||||||
| 				log.Error(err, "Failed to set ephemeral runner to phase Failed") |  | ||||||
| 				return ctrl.Result{}, err |  | ||||||
| 		} | 		} | ||||||
| 			return ctrl.Result{}, nil |  | ||||||
| 
 | 
 | ||||||
| 		default: |  | ||||||
| 		// Pod was not found. Create if the pod has never been created
 | 		// Pod was not found. Create if the pod has never been created
 | ||||||
| 		log.Info("Creating new EphemeralRunner pod.") | 		log.Info("Creating new EphemeralRunner pod.") | ||||||
| 		result, err := r.createPod(ctx, ephemeralRunner, secret, log) | 		result, err := r.createPod(ctx, ephemeralRunner, secret, log) | ||||||
|  | @ -230,7 +257,6 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	cs := runnerContainerStatus(pod) | 	cs := runnerContainerStatus(pod) | ||||||
| 	switch { | 	switch { | ||||||
|  | @ -319,7 +345,7 @@ func (r *EphemeralRunnerReconciler) cleanupResources(ctx context.Context, epheme | ||||||
| 	err := r.Get(ctx, types.NamespacedName{Namespace: ephemeralRunner.Namespace, Name: ephemeralRunner.Name}, pod) | 	err := r.Get(ctx, types.NamespacedName{Namespace: ephemeralRunner.Namespace, Name: ephemeralRunner.Name}, pod) | ||||||
| 	switch { | 	switch { | ||||||
| 	case err == nil: | 	case err == nil: | ||||||
| 		if pod.ObjectMeta.DeletionTimestamp.IsZero() { | 		if pod.DeletionTimestamp.IsZero() { | ||||||
| 			log.Info("Deleting the runner pod") | 			log.Info("Deleting the runner pod") | ||||||
| 			if err := r.Delete(ctx, pod); err != nil && !kerrors.IsNotFound(err) { | 			if err := r.Delete(ctx, pod); err != nil && !kerrors.IsNotFound(err) { | ||||||
| 				return fmt.Errorf("failed to delete pod: %w", err) | 				return fmt.Errorf("failed to delete pod: %w", err) | ||||||
|  | @ -339,7 +365,7 @@ func (r *EphemeralRunnerReconciler) cleanupResources(ctx context.Context, epheme | ||||||
| 	err = r.Get(ctx, types.NamespacedName{Namespace: ephemeralRunner.Namespace, Name: ephemeralRunner.Name}, secret) | 	err = r.Get(ctx, types.NamespacedName{Namespace: ephemeralRunner.Namespace, Name: ephemeralRunner.Name}, secret) | ||||||
| 	switch { | 	switch { | ||||||
| 	case err == nil: | 	case err == nil: | ||||||
| 		if secret.ObjectMeta.DeletionTimestamp.IsZero() { | 		if secret.DeletionTimestamp.IsZero() { | ||||||
| 			log.Info("Deleting the jitconfig secret") | 			log.Info("Deleting the jitconfig secret") | ||||||
| 			if err := r.Delete(ctx, secret); err != nil && !kerrors.IsNotFound(err) { | 			if err := r.Delete(ctx, secret); err != nil && !kerrors.IsNotFound(err) { | ||||||
| 				return fmt.Errorf("failed to delete secret: %w", err) | 				return fmt.Errorf("failed to delete secret: %w", err) | ||||||
|  | @ -393,7 +419,7 @@ func (r *EphemeralRunnerReconciler) cleanupRunnerLinkedPods(ctx context.Context, | ||||||
| 	var errs []error | 	var errs []error | ||||||
| 	for i := range runnerLinkedPodList.Items { | 	for i := range runnerLinkedPodList.Items { | ||||||
| 		linkedPod := &runnerLinkedPodList.Items[i] | 		linkedPod := &runnerLinkedPodList.Items[i] | ||||||
| 		if !linkedPod.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !linkedPod.DeletionTimestamp.IsZero() { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -409,7 +435,7 @@ func (r *EphemeralRunnerReconciler) cleanupRunnerLinkedPods(ctx context.Context, | ||||||
| func (r *EphemeralRunnerReconciler) cleanupRunnerLinkedSecrets(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) error { | func (r *EphemeralRunnerReconciler) cleanupRunnerLinkedSecrets(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) error { | ||||||
| 	runnerLinkedLabels := client.MatchingLabels( | 	runnerLinkedLabels := client.MatchingLabels( | ||||||
| 		map[string]string{ | 		map[string]string{ | ||||||
| 			"runner-pod": ephemeralRunner.ObjectMeta.Name, | 			"runner-pod": ephemeralRunner.Name, | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| 	var runnerLinkedSecretList corev1.SecretList | 	var runnerLinkedSecretList corev1.SecretList | ||||||
|  | @ -427,7 +453,7 @@ func (r *EphemeralRunnerReconciler) cleanupRunnerLinkedSecrets(ctx context.Conte | ||||||
| 	var errs []error | 	var errs []error | ||||||
| 	for i := range runnerLinkedSecretList.Items { | 	for i := range runnerLinkedSecretList.Items { | ||||||
| 		s := &runnerLinkedSecretList.Items[i] | 		s := &runnerLinkedSecretList.Items[i] | ||||||
| 		if !s.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !s.DeletionTimestamp.IsZero() { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -474,7 +500,7 @@ func (r *EphemeralRunnerReconciler) markAsFinished(ctx context.Context, ephemera | ||||||
| // deletePodAsFailed is responsible for deleting the pod and updating the .Status.Failures for tracking failure count.
 | // deletePodAsFailed is responsible for deleting the pod and updating the .Status.Failures for tracking failure count.
 | ||||||
| // It should not be responsible for setting the status to Failed.
 | // It should not be responsible for setting the status to Failed.
 | ||||||
| func (r *EphemeralRunnerReconciler) deletePodAsFailed(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, pod *corev1.Pod, log logr.Logger) error { | func (r *EphemeralRunnerReconciler) deletePodAsFailed(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, pod *corev1.Pod, log logr.Logger) error { | ||||||
| 	if pod.ObjectMeta.DeletionTimestamp.IsZero() { | 	if pod.DeletionTimestamp.IsZero() { | ||||||
| 		log.Info("Deleting the ephemeral runner pod", "podId", pod.UID) | 		log.Info("Deleting the ephemeral runner pod", "podId", pod.UID) | ||||||
| 		if err := r.Delete(ctx, pod); err != nil && !kerrors.IsNotFound(err) { | 		if err := r.Delete(ctx, pod); err != nil && !kerrors.IsNotFound(err) { | ||||||
| 			return fmt.Errorf("failed to delete pod with status failed: %w", err) | 			return fmt.Errorf("failed to delete pod with status failed: %w", err) | ||||||
|  | @ -484,9 +510,9 @@ func (r *EphemeralRunnerReconciler) deletePodAsFailed(ctx context.Context, ephem | ||||||
| 	log.Info("Updating ephemeral runner status to track the failure count") | 	log.Info("Updating ephemeral runner status to track the failure count") | ||||||
| 	if err := patchSubResource(ctx, r.Status(), ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) { | 	if err := patchSubResource(ctx, r.Status(), ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) { | ||||||
| 		if obj.Status.Failures == nil { | 		if obj.Status.Failures == nil { | ||||||
| 			obj.Status.Failures = make(map[string]bool) | 			obj.Status.Failures = make(map[string]metav1.Time) | ||||||
| 		} | 		} | ||||||
| 		obj.Status.Failures[string(pod.UID)] = true | 		obj.Status.Failures[string(pod.UID)] = metav1.Now() | ||||||
| 		obj.Status.Ready = false | 		obj.Status.Ready = false | ||||||
| 		obj.Status.Reason = pod.Status.Reason | 		obj.Status.Reason = pod.Status.Reason | ||||||
| 		obj.Status.Message = pod.Status.Message | 		obj.Status.Message = pod.Status.Message | ||||||
|  | @ -640,7 +666,7 @@ func (r *EphemeralRunnerReconciler) createPod(ctx context.Context, runner *v1alp | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	log.Info("Creating new pod for ephemeral runner") | 	log.Info("Creating new pod for ephemeral runner") | ||||||
| 	newPod := r.ResourceBuilder.newEphemeralRunnerPod(ctx, runner, secret, envs...) | 	newPod := r.newEphemeralRunnerPod(ctx, runner, secret, envs...) | ||||||
| 
 | 
 | ||||||
| 	if err := ctrl.SetControllerReference(runner, newPod, r.Scheme); err != nil { | 	if err := ctrl.SetControllerReference(runner, newPod, r.Scheme); err != nil { | ||||||
| 		log.Error(err, "Failed to set controller reference to a new pod") | 		log.Error(err, "Failed to set controller reference to a new pod") | ||||||
|  | @ -665,7 +691,7 @@ func (r *EphemeralRunnerReconciler) createPod(ctx context.Context, runner *v1alp | ||||||
| 
 | 
 | ||||||
| func (r *EphemeralRunnerReconciler) createSecret(ctx context.Context, runner *v1alpha1.EphemeralRunner, log logr.Logger) (*ctrl.Result, error) { | func (r *EphemeralRunnerReconciler) createSecret(ctx context.Context, runner *v1alpha1.EphemeralRunner, log logr.Logger) (*ctrl.Result, error) { | ||||||
| 	log.Info("Creating new secret for ephemeral runner") | 	log.Info("Creating new secret for ephemeral runner") | ||||||
| 	jitSecret := r.ResourceBuilder.newEphemeralRunnerJitSecret(runner) | 	jitSecret := r.newEphemeralRunnerJitSecret(runner) | ||||||
| 
 | 
 | ||||||
| 	if err := ctrl.SetControllerReference(runner, jitSecret, r.Scheme); err != nil { | 	if err := ctrl.SetControllerReference(runner, jitSecret, r.Scheme); err != nil { | ||||||
| 		return &ctrl.Result{}, fmt.Errorf("failed to set controller reference: %w", err) | 		return &ctrl.Result{}, fmt.Errorf("failed to set controller reference: %w", err) | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	ephemeralRunnerTimeout  = time.Second * 20 | 	ephemeralRunnerTimeout  = time.Second * 20 | ||||||
| 	ephemeralRunnerInterval = time.Millisecond * 250 | 	ephemeralRunnerInterval = time.Millisecond * 10 | ||||||
| 	runnerImage             = "ghcr.io/actions/actions-runner:latest" | 	runnerImage             = "ghcr.io/actions/actions-runner:latest" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -528,44 +528,26 @@ var _ = Describe("EphemeralRunner", func() { | ||||||
| 			).Should(BeEquivalentTo("")) | 			).Should(BeEquivalentTo("")) | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		It("It should not re-create pod indefinitely", func() { | 		It("It should eventually delete ephemeral runner after consecutive failures", func() { | ||||||
| 			updated := new(v1alpha1.EphemeralRunner) | 			updated := new(v1alpha1.EphemeralRunner) | ||||||
| 			pod := new(corev1.Pod) |  | ||||||
| 			Eventually( | 			Eventually( | ||||||
| 				func() (bool, error) { | 				func() error { | ||||||
| 					err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated) | 					return k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated) | ||||||
| 					if err != nil { |  | ||||||
| 						return false, err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					err = k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, pod) |  | ||||||
| 					if err != nil { |  | ||||||
| 						if kerrors.IsNotFound(err) && len(updated.Status.Failures) > 5 { |  | ||||||
| 							return true, nil |  | ||||||
| 						} |  | ||||||
| 
 |  | ||||||
| 						return false, err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, corev1.ContainerStatus{ |  | ||||||
| 						Name: v1alpha1.EphemeralRunnerContainerName, |  | ||||||
| 						State: corev1.ContainerState{ |  | ||||||
| 							Terminated: &corev1.ContainerStateTerminated{ |  | ||||||
| 								ExitCode: 1, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}) |  | ||||||
| 					err = k8sClient.Status().Update(ctx, pod) |  | ||||||
| 					Expect(err).To(BeNil(), "Failed to update pod status") |  | ||||||
| 					return false, fmt.Errorf("pod haven't failed for 5 times.") |  | ||||||
| 				}, | 				}, | ||||||
| 				ephemeralRunnerTimeout, | 				ephemeralRunnerTimeout, | ||||||
| 				ephemeralRunnerInterval, | 				ephemeralRunnerInterval, | ||||||
| 			).Should(BeEquivalentTo(true), "we should stop creating pod after 5 failures") | 			).Should(Succeed(), "failed to get ephemeral runner") | ||||||
|  | 
 | ||||||
|  | 			failEphemeralRunnerPod := func() *corev1.Pod { | ||||||
|  | 				pod := new(corev1.Pod) | ||||||
|  | 				Eventually( | ||||||
|  | 					func() error { | ||||||
|  | 						return k8sClient.Get(ctx, client.ObjectKey{Name: updated.Name, Namespace: updated.Namespace}, pod) | ||||||
|  | 					}, | ||||||
|  | 					ephemeralRunnerTimeout, | ||||||
|  | 					ephemeralRunnerInterval, | ||||||
|  | 				).Should(Succeed(), "failed to get ephemeral runner pod") | ||||||
| 
 | 
 | ||||||
| 			// In case we still have pod created due to controller-runtime cache delay, mark the container as exited
 |  | ||||||
| 			err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, pod) |  | ||||||
| 			if err == nil { |  | ||||||
| 				pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, corev1.ContainerStatus{ | 				pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, corev1.ContainerStatus{ | ||||||
| 					Name: v1alpha1.EphemeralRunnerContainerName, | 					Name: v1alpha1.EphemeralRunnerContainerName, | ||||||
| 					State: corev1.ContainerState{ | 					State: corev1.ContainerState{ | ||||||
|  | @ -576,25 +558,70 @@ var _ = Describe("EphemeralRunner", func() { | ||||||
| 				}) | 				}) | ||||||
| 				err := k8sClient.Status().Update(ctx, pod) | 				err := k8sClient.Status().Update(ctx, pod) | ||||||
| 				Expect(err).To(BeNil(), "Failed to update pod status") | 				Expect(err).To(BeNil(), "Failed to update pod status") | ||||||
|  | 
 | ||||||
|  | 				return pod | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// EphemeralRunner should failed with reason TooManyPodFailures
 | 			for i := range 5 { | ||||||
| 			Eventually(func() (string, error) { | 				pod := failEphemeralRunnerPod() | ||||||
|  | 
 | ||||||
|  | 				Eventually( | ||||||
|  | 					func() (int, error) { | ||||||
| 						err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated) | 						err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 					return "", err | 							return 0, err | ||||||
| 						} | 						} | ||||||
| 				return updated.Status.Reason, nil | 						return len(updated.Status.Failures), nil | ||||||
| 			}, ephemeralRunnerTimeout, ephemeralRunnerInterval).Should(BeEquivalentTo("TooManyPodFailures"), "Reason should be TooManyPodFailures") | 					}, | ||||||
|  | 					ephemeralRunnerTimeout, | ||||||
|  | 					ephemeralRunnerInterval, | ||||||
|  | 				).Should(BeEquivalentTo(i + 1)) | ||||||
| 
 | 
 | ||||||
| 			// EphemeralRunner should not have any pod
 | 				Eventually( | ||||||
| 			Eventually(func() (bool, error) { | 					func() error { | ||||||
| 				err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, pod) | 						nextPod := new(corev1.Pod) | ||||||
| 				if err == nil { | 						err := k8sClient.Get(ctx, client.ObjectKey{Name: pod.Name, Namespace: pod.Namespace}, nextPod) | ||||||
| 					return false, nil | 						if err != nil { | ||||||
|  | 							return err | ||||||
| 						} | 						} | ||||||
| 				return kerrors.IsNotFound(err), nil | 						if nextPod.UID != pod.UID { | ||||||
| 			}, ephemeralRunnerTimeout, ephemeralRunnerInterval).Should(BeEquivalentTo(true)) | 							return nil | ||||||
|  | 						} | ||||||
|  | 						return fmt.Errorf("pod not recreated") | ||||||
|  | 					}, | ||||||
|  | 				).WithTimeout(20*time.Second).WithPolling(10*time.Millisecond).Should(Succeed(), "pod should be recreated") | ||||||
|  | 
 | ||||||
|  | 				Eventually( | ||||||
|  | 					func() (bool, error) { | ||||||
|  | 						pod := new(corev1.Pod) | ||||||
|  | 						err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, pod) | ||||||
|  | 						if err != nil { | ||||||
|  | 							return false, err | ||||||
|  | 						} | ||||||
|  | 						for _, cs := range pod.Status.ContainerStatuses { | ||||||
|  | 							if cs.Name == v1alpha1.EphemeralRunnerContainerName { | ||||||
|  | 								return cs.State.Terminated == nil, nil | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						return true, nil | ||||||
|  | 					}, | ||||||
|  | 				).WithTimeout(20*time.Second).WithPolling(10*time.Millisecond).Should(BeEquivalentTo(true), "pod should be terminated") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			failEphemeralRunnerPod() | ||||||
|  | 
 | ||||||
|  | 			Eventually( | ||||||
|  | 				func() (bool, error) { | ||||||
|  | 					err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated) | ||||||
|  | 					if kerrors.IsNotFound(err) { | ||||||
|  | 						return true, nil | ||||||
|  | 					} | ||||||
|  | 					return false, err | ||||||
|  | 				}, | ||||||
|  | 				ephemeralRunnerTimeout, | ||||||
|  | 				ephemeralRunnerInterval, | ||||||
|  | 			).Should(BeTrue(), "Ephemeral runner should eventually be deleted") | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		It("It should re-create pod on eviction", func() { | 		It("It should re-create pod on eviction", func() { | ||||||
|  |  | ||||||
|  | @ -83,7 +83,7 @@ func (r *EphemeralRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl.R | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Requested deletion does not need reconciled.
 | 	// Requested deletion does not need reconciled.
 | ||||||
| 	if !ephemeralRunnerSet.ObjectMeta.DeletionTimestamp.IsZero() { | 	if !ephemeralRunnerSet.DeletionTimestamp.IsZero() { | ||||||
| 		if !controllerutil.ContainsFinalizer(ephemeralRunnerSet, ephemeralRunnerSetFinalizerName) { | 		if !controllerutil.ContainsFinalizer(ephemeralRunnerSet, ephemeralRunnerSetFinalizerName) { | ||||||
| 			return ctrl.Result{}, nil | 			return ctrl.Result{}, nil | ||||||
| 		} | 		} | ||||||
|  | @ -360,7 +360,7 @@ func (r *EphemeralRunnerSetReconciler) createEphemeralRunners(ctx context.Contex | ||||||
| 	// Track multiple errors at once and return the bundle.
 | 	// Track multiple errors at once and return the bundle.
 | ||||||
| 	errs := make([]error, 0) | 	errs := make([]error, 0) | ||||||
| 	for i := 0; i < count; i++ { | 	for i := 0; i < count; i++ { | ||||||
| 		ephemeralRunner := r.ResourceBuilder.newEphemeralRunner(runnerSet) | 		ephemeralRunner := r.newEphemeralRunner(runnerSet) | ||||||
| 		if runnerSet.Spec.EphemeralRunnerSpec.Proxy != nil { | 		if runnerSet.Spec.EphemeralRunnerSpec.Proxy != nil { | ||||||
| 			ephemeralRunner.Spec.ProxySecretRef = proxyEphemeralRunnerSetSecretName(runnerSet) | 			ephemeralRunner.Spec.ProxySecretRef = proxyEphemeralRunnerSetSecretName(runnerSet) | ||||||
| 		} | 		} | ||||||
|  | @ -641,7 +641,7 @@ func newEphemeralRunnerState(ephemeralRunnerList *v1alpha1.EphemeralRunnerList) | ||||||
| 		if err == nil && patchID > ephemeralRunnerState.latestPatchID { | 		if err == nil && patchID > ephemeralRunnerState.latestPatchID { | ||||||
| 			ephemeralRunnerState.latestPatchID = patchID | 			ephemeralRunnerState.latestPatchID = patchID | ||||||
| 		} | 		} | ||||||
| 		if !r.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !r.DeletionTimestamp.IsZero() { | ||||||
| 			ephemeralRunnerState.deleting = append(ephemeralRunnerState.deleting, r) | 			ephemeralRunnerState.deleting = append(ephemeralRunnerState.deleting, r) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | @ -21,6 +22,7 @@ import ( | ||||||
| 	"github.com/go-logr/logr" | 	"github.com/go-logr/logr" | ||||||
| 	. "github.com/onsi/ginkgo/v2" | 	. "github.com/onsi/ginkgo/v2" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 
 | 
 | ||||||
| 	"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" | 	"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" | ||||||
|  | @ -35,6 +37,10 @@ const ( | ||||||
| 	ephemeralRunnerSetTestGitHubToken = "gh_token" | 	ephemeralRunnerSetTestGitHubToken = "gh_token" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | func TestPrecomputedConstants(t *testing.T) { | ||||||
|  | 	require.Equal(t, len(failedRunnerBackoff), maxFailures+1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var _ = Describe("Test EphemeralRunnerSet controller", func() { | var _ = Describe("Test EphemeralRunnerSet controller", func() { | ||||||
| 	var ctx context.Context | 	var ctx context.Context | ||||||
| 	var mgr ctrl.Manager | 	var mgr ctrl.Manager | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"maps" | ||||||
| 	"math" | 	"math" | ||||||
| 	"net" | 	"net" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | @ -169,15 +170,6 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha | ||||||
| 		metricsEndpoint = metricsConfig.endpoint | 		metricsEndpoint = metricsConfig.endpoint | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var appID int64 |  | ||||||
| 	if id, ok := secret.Data["github_app_id"]; ok { |  | ||||||
| 		var err error |  | ||||||
| 		appID, err = strconv.ParseInt(string(id), 10, 64) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("failed to convert github_app_id to int: %v", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var appInstallationID int64 | 	var appInstallationID int64 | ||||||
| 	if id, ok := secret.Data["github_app_installation_id"]; ok { | 	if id, ok := secret.Data["github_app_installation_id"]; ok { | ||||||
| 		var err error | 		var err error | ||||||
|  | @ -189,7 +181,7 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha | ||||||
| 
 | 
 | ||||||
| 	config := listenerconfig.Config{ | 	config := listenerconfig.Config{ | ||||||
| 		ConfigureUrl:                autoscalingListener.Spec.GitHubConfigUrl, | 		ConfigureUrl:                autoscalingListener.Spec.GitHubConfigUrl, | ||||||
| 		AppID:                       appID, | 		AppID:                       string(secret.Data["github_app_id"]), | ||||||
| 		AppInstallationID:           appInstallationID, | 		AppInstallationID:           appInstallationID, | ||||||
| 		AppPrivateKey:               string(secret.Data["github_app_private_key"]), | 		AppPrivateKey:               string(secret.Data["github_app_private_key"]), | ||||||
| 		Token:                       string(secret.Data["github_token"]), | 		Token:                       string(secret.Data["github_token"]), | ||||||
|  | @ -207,6 +199,10 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha | ||||||
| 		Metrics:                     autoscalingListener.Spec.Metrics, | 		Metrics:                     autoscalingListener.Spec.Metrics, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if err := config.Validate(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("invalid listener config: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	var buf bytes.Buffer | 	var buf bytes.Buffer | ||||||
| 	if err := json.NewEncoder(&buf).Encode(config); err != nil { | 	if err := json.NewEncoder(&buf).Encode(config); err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to encode config: %w", err) | 		return nil, fmt.Errorf("failed to encode config: %w", err) | ||||||
|  | @ -278,9 +274,7 @@ func (b *ResourceBuilder) newScaleSetListenerPod(autoscalingListener *v1alpha1.A | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	labels := make(map[string]string, len(autoscalingListener.Labels)) | 	labels := make(map[string]string, len(autoscalingListener.Labels)) | ||||||
| 	for key, val := range autoscalingListener.Labels { | 	maps.Copy(labels, autoscalingListener.Labels) | ||||||
| 		labels[key] = val |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	newRunnerScaleSetListenerPod := &corev1.Pod{ | 	newRunnerScaleSetListenerPod := &corev1.Pod{ | ||||||
| 		TypeMeta: metav1.TypeMeta{ | 		TypeMeta: metav1.TypeMeta{ | ||||||
|  | @ -429,7 +423,7 @@ func mergeListenerContainer(base, from *corev1.Container) { | ||||||
| func (b *ResourceBuilder) newScaleSetListenerServiceAccount(autoscalingListener *v1alpha1.AutoscalingListener) *corev1.ServiceAccount { | func (b *ResourceBuilder) newScaleSetListenerServiceAccount(autoscalingListener *v1alpha1.AutoscalingListener) *corev1.ServiceAccount { | ||||||
| 	return &corev1.ServiceAccount{ | 	return &corev1.ServiceAccount{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:      scaleSetListenerServiceAccountName(autoscalingListener), | 			Name:      autoscalingListener.Name, | ||||||
| 			Namespace: autoscalingListener.Namespace, | 			Namespace: autoscalingListener.Namespace, | ||||||
| 			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{ | 			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{ | ||||||
| 				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | 				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | ||||||
|  | @ -444,7 +438,7 @@ func (b *ResourceBuilder) newScaleSetListenerRole(autoscalingListener *v1alpha1. | ||||||
| 	rulesHash := hash.ComputeTemplateHash(&rules) | 	rulesHash := hash.ComputeTemplateHash(&rules) | ||||||
| 	newRole := &rbacv1.Role{ | 	newRole := &rbacv1.Role{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:      scaleSetListenerRoleName(autoscalingListener), | 			Name:      autoscalingListener.Name, | ||||||
| 			Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | 			Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | ||||||
| 			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{ | 			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{ | ||||||
| 				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | 				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | ||||||
|  | @ -478,7 +472,7 @@ func (b *ResourceBuilder) newScaleSetListenerRoleBinding(autoscalingListener *v1 | ||||||
| 
 | 
 | ||||||
| 	newRoleBinding := &rbacv1.RoleBinding{ | 	newRoleBinding := &rbacv1.RoleBinding{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:      scaleSetListenerRoleName(autoscalingListener), | 			Name:      autoscalingListener.Name, | ||||||
| 			Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | 			Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | ||||||
| 			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{ | 			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{ | ||||||
| 				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | 				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | ||||||
|  | @ -501,7 +495,7 @@ func (b *ResourceBuilder) newScaleSetListenerSecretMirror(autoscalingListener *v | ||||||
| 
 | 
 | ||||||
| 	newListenerSecret := &corev1.Secret{ | 	newListenerSecret := &corev1.Secret{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:      scaleSetListenerSecretMirrorName(autoscalingListener), | 			Name:      autoscalingListener.Name, | ||||||
| 			Namespace: autoscalingListener.Namespace, | 			Namespace: autoscalingListener.Namespace, | ||||||
| 			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{ | 			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{ | ||||||
| 				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | 				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, | ||||||
|  | @ -543,8 +537,8 @@ func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.A | ||||||
| 	newEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{ | 	newEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{ | ||||||
| 		TypeMeta: metav1.TypeMeta{}, | 		TypeMeta: metav1.TypeMeta{}, | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			GenerateName: autoscalingRunnerSet.ObjectMeta.Name + "-", | 			GenerateName: autoscalingRunnerSet.Name + "-", | ||||||
| 			Namespace:    autoscalingRunnerSet.ObjectMeta.Namespace, | 			Namespace:    autoscalingRunnerSet.Namespace, | ||||||
| 			Labels:       labels, | 			Labels:       labels, | ||||||
| 			Annotations:  newAnnotations, | 			Annotations:  newAnnotations, | ||||||
| 			OwnerReferences: []metav1.OwnerReference{ | 			OwnerReferences: []metav1.OwnerReference{ | ||||||
|  | @ -617,18 +611,18 @@ func (b *ResourceBuilder) newEphemeralRunnerPod(ctx context.Context, runner *v1a | ||||||
| 	labels := map[string]string{} | 	labels := map[string]string{} | ||||||
| 	annotations := map[string]string{} | 	annotations := map[string]string{} | ||||||
| 
 | 
 | ||||||
| 	for k, v := range runner.ObjectMeta.Labels { | 	for k, v := range runner.Labels { | ||||||
| 		labels[k] = v | 		labels[k] = v | ||||||
| 	} | 	} | ||||||
| 	for k, v := range runner.Spec.PodTemplateSpec.Labels { | 	for k, v := range runner.Spec.Labels { | ||||||
| 		labels[k] = v | 		labels[k] = v | ||||||
| 	} | 	} | ||||||
| 	labels["actions-ephemeral-runner"] = string(corev1.ConditionTrue) | 	labels["actions-ephemeral-runner"] = string(corev1.ConditionTrue) | ||||||
| 
 | 
 | ||||||
| 	for k, v := range runner.ObjectMeta.Annotations { | 	for k, v := range runner.Annotations { | ||||||
| 		annotations[k] = v | 		annotations[k] = v | ||||||
| 	} | 	} | ||||||
| 	for k, v := range runner.Spec.PodTemplateSpec.Annotations { | 	for k, v := range runner.Spec.Annotations { | ||||||
| 		annotations[k] = v | 		annotations[k] = v | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -640,8 +634,8 @@ func (b *ResourceBuilder) newEphemeralRunnerPod(ctx context.Context, runner *v1a | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	objectMeta := metav1.ObjectMeta{ | 	objectMeta := metav1.ObjectMeta{ | ||||||
| 		Name:        runner.ObjectMeta.Name, | 		Name:        runner.Name, | ||||||
| 		Namespace:   runner.ObjectMeta.Namespace, | 		Namespace:   runner.Namespace, | ||||||
| 		Labels:      labels, | 		Labels:      labels, | ||||||
| 		Annotations: annotations, | 		Annotations: annotations, | ||||||
| 		OwnerReferences: []metav1.OwnerReference{ | 		OwnerReferences: []metav1.OwnerReference{ | ||||||
|  | @ -657,10 +651,10 @@ func (b *ResourceBuilder) newEphemeralRunnerPod(ctx context.Context, runner *v1a | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	newPod.ObjectMeta = objectMeta | 	newPod.ObjectMeta = objectMeta | ||||||
| 	newPod.Spec = runner.Spec.PodTemplateSpec.Spec | 	newPod.Spec = runner.Spec.Spec | ||||||
| 	newPod.Spec.Containers = make([]corev1.Container, 0, len(runner.Spec.PodTemplateSpec.Spec.Containers)) | 	newPod.Spec.Containers = make([]corev1.Container, 0, len(runner.Spec.Spec.Containers)) | ||||||
| 
 | 
 | ||||||
| 	for _, c := range runner.Spec.PodTemplateSpec.Spec.Containers { | 	for _, c := range runner.Spec.Spec.Containers { | ||||||
| 		if c.Name == v1alpha1.EphemeralRunnerContainerName { | 		if c.Name == v1alpha1.EphemeralRunnerContainerName { | ||||||
| 			c.Env = append( | 			c.Env = append( | ||||||
| 				c.Env, | 				c.Env, | ||||||
|  | @ -713,30 +707,6 @@ func scaleSetListenerName(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) s | ||||||
| 	return fmt.Sprintf("%v-%v-listener", autoscalingRunnerSet.Name, namespaceHash) | 	return fmt.Sprintf("%v-%v-listener", autoscalingRunnerSet.Name, namespaceHash) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func scaleSetListenerServiceAccountName(autoscalingListener *v1alpha1.AutoscalingListener) string { |  | ||||||
| 	namespaceHash := hash.FNVHashString(autoscalingListener.Spec.AutoscalingRunnerSetNamespace) |  | ||||||
| 	if len(namespaceHash) > 8 { |  | ||||||
| 		namespaceHash = namespaceHash[:8] |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("%v-%v-listener", autoscalingListener.Spec.AutoscalingRunnerSetName, namespaceHash) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func scaleSetListenerRoleName(autoscalingListener *v1alpha1.AutoscalingListener) string { |  | ||||||
| 	namespaceHash := hash.FNVHashString(autoscalingListener.Spec.AutoscalingRunnerSetNamespace) |  | ||||||
| 	if len(namespaceHash) > 8 { |  | ||||||
| 		namespaceHash = namespaceHash[:8] |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("%v-%v-listener", autoscalingListener.Spec.AutoscalingRunnerSetName, namespaceHash) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func scaleSetListenerSecretMirrorName(autoscalingListener *v1alpha1.AutoscalingListener) string { |  | ||||||
| 	namespaceHash := hash.FNVHashString(autoscalingListener.Spec.AutoscalingRunnerSetNamespace) |  | ||||||
| 	if len(namespaceHash) > 8 { |  | ||||||
| 		namespaceHash = namespaceHash[:8] |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("%v-%v-listener", autoscalingListener.Spec.AutoscalingRunnerSetName, namespaceHash) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func proxyListenerSecretName(autoscalingListener *v1alpha1.AutoscalingListener) string { | func proxyListenerSecretName(autoscalingListener *v1alpha1.AutoscalingListener) string { | ||||||
| 	namespaceHash := hash.FNVHashString(autoscalingListener.Spec.AutoscalingRunnerSetNamespace) | 	namespaceHash := hash.FNVHashString(autoscalingListener.Spec.AutoscalingRunnerSetNamespace) | ||||||
| 	if len(namespaceHash) > 8 { | 	if len(namespaceHash) > 8 { | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import ( | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/onsi/ginkgo/config" | 	"github.com/onsi/ginkgo/config" | ||||||
| 
 | 
 | ||||||
|  | @ -79,6 +80,15 @@ var _ = BeforeSuite(func() { | ||||||
| 	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) | 	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 	Expect(k8sClient).ToNot(BeNil()) | 	Expect(k8sClient).ToNot(BeNil()) | ||||||
|  | 
 | ||||||
|  | 	failedRunnerBackoff = []time.Duration{ | ||||||
|  | 		20 * time.Millisecond, | ||||||
|  | 		20 * time.Millisecond, | ||||||
|  | 		20 * time.Millisecond, | ||||||
|  | 		20 * time.Millisecond, | ||||||
|  | 		20 * time.Millisecond, | ||||||
|  | 		20 * time.Millisecond, | ||||||
|  | 	} | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| var _ = AfterSuite(func() { | var _ = AfterSuite(func() { | ||||||
|  |  | ||||||
|  | @ -345,7 +345,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByPercentageRunner | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var runnerPodList corev1.PodList | 	var runnerPodList corev1.PodList | ||||||
| 	if err := r.Client.List(ctx, &runnerPodList, client.InNamespace(hra.Namespace), client.MatchingLabels(map[string]string{ | 	if err := r.List(ctx, &runnerPodList, client.InNamespace(hra.Namespace), client.MatchingLabels(map[string]string{ | ||||||
| 		kindLabel: hra.Spec.ScaleTargetRef.Name, | 		kindLabel: hra.Spec.ScaleTargetRef.Name, | ||||||
| 	})); err != nil { | 	})); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ func newGithubClient(server *httptest.Server) *github.Client { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
| 	client.Client.BaseURL = baseURL | 	client.BaseURL = baseURL | ||||||
| 
 | 
 | ||||||
| 	return client | 	return client | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -82,8 +82,8 @@ func (s *batchScaler) Add(st *ScaleTarget) { | ||||||
| 						break batch | 						break batch | ||||||
| 					case st := <-s.queue: | 					case st := <-s.queue: | ||||||
| 						nsName := types.NamespacedName{ | 						nsName := types.NamespacedName{ | ||||||
| 							Namespace: st.HorizontalRunnerAutoscaler.Namespace, | 							Namespace: st.Namespace, | ||||||
| 							Name:      st.HorizontalRunnerAutoscaler.Name, | 							Name:      st.Name, | ||||||
| 						} | 						} | ||||||
| 						b, ok := batches[nsName] | 						b, ok := batches[nsName] | ||||||
| 						if !ok { | 						if !ok { | ||||||
|  | @ -208,7 +208,7 @@ func (s *batchScaler) planBatchScale(ctx context.Context, batch batchScaleOperat | ||||||
| 			//
 | 			//
 | ||||||
| 			// In other words, updating HRA.spec.scaleTriggers[].duration does not result in delaying capacity reservations expiration any longer
 | 			// In other words, updating HRA.spec.scaleTriggers[].duration does not result in delaying capacity reservations expiration any longer
 | ||||||
| 			// than the "intended" duration, which is the duration of the trigger when the reservation was created.
 | 			// than the "intended" duration, which is the duration of the trigger when the reservation was created.
 | ||||||
| 			duration := copy.Spec.CapacityReservations[i].ExpirationTime.Time.Sub(copy.Spec.CapacityReservations[i].EffectiveTime.Time) | 			duration := copy.Spec.CapacityReservations[i].ExpirationTime.Sub(copy.Spec.CapacityReservations[i].EffectiveTime.Time) | ||||||
| 			copy.Spec.CapacityReservations[i].EffectiveTime = metav1.Time{Time: now} | 			copy.Spec.CapacityReservations[i].EffectiveTime = metav1.Time{Time: now} | ||||||
| 			copy.Spec.CapacityReservations[i].ExpirationTime = metav1.Time{Time: now.Add(duration)} | 			copy.Spec.CapacityReservations[i].ExpirationTime = metav1.Time{Time: now.Add(duration)} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -503,13 +503,13 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getManagedRunnerGroup | ||||||
| 		switch kind { | 		switch kind { | ||||||
| 		case "RunnerSet": | 		case "RunnerSet": | ||||||
| 			var rs v1alpha1.RunnerSet | 			var rs v1alpha1.RunnerSet | ||||||
| 			if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rs); err != nil { | 			if err := autoscaler.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rs); err != nil { | ||||||
| 				return groups, err | 				return groups, err | ||||||
| 			} | 			} | ||||||
| 			o, e, g = rs.Spec.Organization, rs.Spec.Enterprise, rs.Spec.Group | 			o, e, g = rs.Spec.Organization, rs.Spec.Enterprise, rs.Spec.Group | ||||||
| 		case "RunnerDeployment", "": | 		case "RunnerDeployment", "": | ||||||
| 			var rd v1alpha1.RunnerDeployment | 			var rd v1alpha1.RunnerDeployment | ||||||
| 			if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil { | 			if err := autoscaler.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil { | ||||||
| 				return groups, err | 				return groups, err | ||||||
| 			} | 			} | ||||||
| 			o, e, g = rd.Spec.Template.Spec.Organization, rd.Spec.Template.Spec.Enterprise, rd.Spec.Template.Spec.Group | 			o, e, g = rd.Spec.Template.Spec.Organization, rd.Spec.Template.Spec.Enterprise, rd.Spec.Template.Spec.Group | ||||||
|  | @ -562,7 +562,7 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getJobScaleTarget(ctx | ||||||
| 
 | 
 | ||||||
| HRA: | HRA: | ||||||
| 	for _, hra := range hras { | 	for _, hra := range hras { | ||||||
| 		if !hra.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !hra.DeletionTimestamp.IsZero() { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -603,7 +603,7 @@ HRA: | ||||||
| 		case "RunnerSet": | 		case "RunnerSet": | ||||||
| 			var rs v1alpha1.RunnerSet | 			var rs v1alpha1.RunnerSet | ||||||
| 
 | 
 | ||||||
| 			if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rs); err != nil { | 			if err := autoscaler.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rs); err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -634,7 +634,7 @@ HRA: | ||||||
| 		case "RunnerDeployment", "": | 		case "RunnerDeployment", "": | ||||||
| 			var rd v1alpha1.RunnerDeployment | 			var rd v1alpha1.RunnerDeployment | ||||||
| 
 | 
 | ||||||
| 			if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil { | 			if err := autoscaler.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -676,7 +676,7 @@ func getValidCapacityReservations(autoscaler *v1alpha1.HorizontalRunnerAutoscale | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
| 
 | 
 | ||||||
| 	for _, reservation := range autoscaler.Spec.CapacityReservations { | 	for _, reservation := range autoscaler.Spec.CapacityReservations { | ||||||
| 		if reservation.ExpirationTime.Time.After(now) { | 		if reservation.ExpirationTime.After(now) { | ||||||
| 			capacityReservations = append(capacityReservations, reservation) | 			capacityReservations = append(capacityReservations, reservation) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -713,7 +713,7 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) indexer(rawObj client | ||||||
| 	switch hra.Spec.ScaleTargetRef.Kind { | 	switch hra.Spec.ScaleTargetRef.Kind { | ||||||
| 	case "", "RunnerDeployment": | 	case "", "RunnerDeployment": | ||||||
| 		var rd v1alpha1.RunnerDeployment | 		var rd v1alpha1.RunnerDeployment | ||||||
| 		if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil { | 		if err := autoscaler.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil { | ||||||
| 			autoscaler.Log.V(1).Info(fmt.Sprintf("RunnerDeployment not found with scale target ref name %s for hra %s", hra.Spec.ScaleTargetRef.Name, hra.Name)) | 			autoscaler.Log.V(1).Info(fmt.Sprintf("RunnerDeployment not found with scale target ref name %s for hra %s", hra.Spec.ScaleTargetRef.Name, hra.Name)) | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
|  | @ -740,7 +740,7 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) indexer(rawObj client | ||||||
| 		return keys | 		return keys | ||||||
| 	case "RunnerSet": | 	case "RunnerSet": | ||||||
| 		var rs v1alpha1.RunnerSet | 		var rs v1alpha1.RunnerSet | ||||||
| 		if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rs); err != nil { | 		if err := autoscaler.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rs); err != nil { | ||||||
| 			autoscaler.Log.V(1).Info(fmt.Sprintf("RunnerSet not found with scale target ref name %s for hra %s", hra.Spec.ScaleTargetRef.Name, hra.Name)) | 			autoscaler.Log.V(1).Info(fmt.Sprintf("RunnerSet not found with scale target ref name %s for hra %s", hra.Spec.ScaleTargetRef.Name, hra.Name)) | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -71,7 +71,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(ctx context.Context, re | ||||||
| 		return ctrl.Result{}, client.IgnoreNotFound(err) | 		return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !hra.ObjectMeta.DeletionTimestamp.IsZero() { | 	if !hra.DeletionTimestamp.IsZero() { | ||||||
| 		r.GitHubClient.DeinitForHRA(&hra) | 		r.GitHubClient.DeinitForHRA(&hra) | ||||||
| 
 | 
 | ||||||
| 		return ctrl.Result{}, nil | 		return ctrl.Result{}, nil | ||||||
|  | @ -91,7 +91,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(ctx context.Context, re | ||||||
| 			return ctrl.Result{}, client.IgnoreNotFound(err) | 			return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if !rd.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !rd.DeletionTimestamp.IsZero() { | ||||||
| 			return ctrl.Result{}, nil | 			return ctrl.Result{}, nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -120,14 +120,14 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(ctx context.Context, re | ||||||
| 					copy.Spec.EffectiveTime = &metav1.Time{Time: *effectiveTime} | 					copy.Spec.EffectiveTime = &metav1.Time{Time: *effectiveTime} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if err := r.Client.Patch(ctx, copy, client.MergeFrom(&rd)); err != nil { | 				if err := r.Patch(ctx, copy, client.MergeFrom(&rd)); err != nil { | ||||||
| 					return fmt.Errorf("patching runnerdeployment to have %d replicas: %w", newDesiredReplicas, err) | 					return fmt.Errorf("patching runnerdeployment to have %d replicas: %w", newDesiredReplicas, err) | ||||||
| 				} | 				} | ||||||
| 			} else if ephemeral && effectiveTime != nil { | 			} else if ephemeral && effectiveTime != nil { | ||||||
| 				copy := rd.DeepCopy() | 				copy := rd.DeepCopy() | ||||||
| 				copy.Spec.EffectiveTime = &metav1.Time{Time: *effectiveTime} | 				copy.Spec.EffectiveTime = &metav1.Time{Time: *effectiveTime} | ||||||
| 
 | 
 | ||||||
| 				if err := r.Client.Patch(ctx, copy, client.MergeFrom(&rd)); err != nil { | 				if err := r.Patch(ctx, copy, client.MergeFrom(&rd)); err != nil { | ||||||
| 					return fmt.Errorf("patching runnerdeployment to have %d replicas: %w", newDesiredReplicas, err) | 					return fmt.Errorf("patching runnerdeployment to have %d replicas: %w", newDesiredReplicas, err) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | @ -142,7 +142,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(ctx context.Context, re | ||||||
| 			return ctrl.Result{}, client.IgnoreNotFound(err) | 			return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if !rs.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !rs.DeletionTimestamp.IsZero() { | ||||||
| 			return ctrl.Result{}, nil | 			return ctrl.Result{}, nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -160,7 +160,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(ctx context.Context, re | ||||||
| 			org:        rs.Spec.Organization, | 			org:        rs.Spec.Organization, | ||||||
| 			repo:       rs.Spec.Repository, | 			repo:       rs.Spec.Repository, | ||||||
| 			replicas:   replicas, | 			replicas:   replicas, | ||||||
| 			labels:     rs.Spec.RunnerConfig.Labels, | 			labels:     rs.Spec.Labels, | ||||||
| 			getRunnerMap: func() (map[string]struct{}, error) { | 			getRunnerMap: func() (map[string]struct{}, error) { | ||||||
| 				// return the list of runners in namespace. Horizontal Runner Autoscaler should only be responsible for scaling resources in its own ns.
 | 				// return the list of runners in namespace. Horizontal Runner Autoscaler should only be responsible for scaling resources in its own ns.
 | ||||||
| 				var runnerPodList corev1.PodList | 				var runnerPodList corev1.PodList | ||||||
|  | @ -224,14 +224,14 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(ctx context.Context, re | ||||||
| 					copy.Spec.EffectiveTime = &metav1.Time{Time: *effectiveTime} | 					copy.Spec.EffectiveTime = &metav1.Time{Time: *effectiveTime} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if err := r.Client.Patch(ctx, copy, client.MergeFrom(&rs)); err != nil { | 				if err := r.Patch(ctx, copy, client.MergeFrom(&rs)); err != nil { | ||||||
| 					return fmt.Errorf("patching runnerset to have %d replicas: %w", newDesiredReplicas, err) | 					return fmt.Errorf("patching runnerset to have %d replicas: %w", newDesiredReplicas, err) | ||||||
| 				} | 				} | ||||||
| 			} else if ephemeral && effectiveTime != nil { | 			} else if ephemeral && effectiveTime != nil { | ||||||
| 				copy := rs.DeepCopy() | 				copy := rs.DeepCopy() | ||||||
| 				copy.Spec.EffectiveTime = &metav1.Time{Time: *effectiveTime} | 				copy.Spec.EffectiveTime = &metav1.Time{Time: *effectiveTime} | ||||||
| 
 | 
 | ||||||
| 				if err := r.Client.Patch(ctx, copy, client.MergeFrom(&rs)); err != nil { | 				if err := r.Patch(ctx, copy, client.MergeFrom(&rs)); err != nil { | ||||||
| 					return fmt.Errorf("patching runnerset to have %d replicas: %w", newDesiredReplicas, err) | 					return fmt.Errorf("patching runnerset to have %d replicas: %w", newDesiredReplicas, err) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | @ -253,7 +253,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) scaleTargetFromRD(ctx context.Con | ||||||
| 		org:        rd.Spec.Template.Spec.Organization, | 		org:        rd.Spec.Template.Spec.Organization, | ||||||
| 		repo:       rd.Spec.Template.Spec.Repository, | 		repo:       rd.Spec.Template.Spec.Repository, | ||||||
| 		replicas:   rd.Spec.Replicas, | 		replicas:   rd.Spec.Replicas, | ||||||
| 		labels:     rd.Spec.Template.Spec.RunnerConfig.Labels, | 		labels:     rd.Spec.Template.Spec.Labels, | ||||||
| 		getRunnerMap: func() (map[string]struct{}, error) { | 		getRunnerMap: func() (map[string]struct{}, error) { | ||||||
| 			// return the list of runners in namespace. Horizontal Runner Autoscaler should only be responsible for scaling resources in its own ns.
 | 			// return the list of runners in namespace. Horizontal Runner Autoscaler should only be responsible for scaling resources in its own ns.
 | ||||||
| 			var runnerList v1alpha1.RunnerList | 			var runnerList v1alpha1.RunnerList | ||||||
|  | @ -484,7 +484,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) computeReplicasWithCache(ghc *arc | ||||||
| 	var reserved int | 	var reserved int | ||||||
| 
 | 
 | ||||||
| 	for _, reservation := range hra.Spec.CapacityReservations { | 	for _, reservation := range hra.Spec.CapacityReservations { | ||||||
| 		if reservation.ExpirationTime.Time.After(now) { | 		if reservation.ExpirationTime.After(now) { | ||||||
| 			reserved += reservation.Replicas | 			reserved += reservation.Replicas | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -20,12 +20,13 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"k8s.io/apimachinery/pkg/api/resource" |  | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"k8s.io/apimachinery/pkg/api/resource" | ||||||
|  | 
 | ||||||
| 	"github.com/actions/actions-runner-controller/build" | 	"github.com/actions/actions-runner-controller/build" | ||||||
| 	"github.com/actions/actions-runner-controller/hash" | 	"github.com/actions/actions-runner-controller/hash" | ||||||
| 	"github.com/go-logr/logr" | 	"github.com/go-logr/logr" | ||||||
|  | @ -107,12 +108,12 @@ func (r *RunnerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr | ||||||
| 		return ctrl.Result{}, client.IgnoreNotFound(err) | 		return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if runner.ObjectMeta.DeletionTimestamp.IsZero() { | 	if runner.DeletionTimestamp.IsZero() { | ||||||
| 		finalizers, added := addFinalizer(runner.ObjectMeta.Finalizers, finalizerName) | 		finalizers, added := addFinalizer(runner.Finalizers, finalizerName) | ||||||
| 
 | 
 | ||||||
| 		if added { | 		if added { | ||||||
| 			newRunner := runner.DeepCopy() | 			newRunner := runner.DeepCopy() | ||||||
| 			newRunner.ObjectMeta.Finalizers = finalizers | 			newRunner.Finalizers = finalizers | ||||||
| 
 | 
 | ||||||
| 			if err := r.Update(ctx, newRunner); err != nil { | 			if err := r.Update(ctx, newRunner); err != nil { | ||||||
| 				log.Error(err, "Failed to update runner") | 				log.Error(err, "Failed to update runner") | ||||||
|  | @ -271,11 +272,11 @@ func ephemeralRunnerContainerStatus(pod *corev1.Pod) *corev1.ContainerStatus { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *RunnerReconciler) processRunnerDeletion(runner v1alpha1.Runner, ctx context.Context, log logr.Logger, pod *corev1.Pod) (reconcile.Result, error) { | func (r *RunnerReconciler) processRunnerDeletion(runner v1alpha1.Runner, ctx context.Context, log logr.Logger, pod *corev1.Pod) (reconcile.Result, error) { | ||||||
| 	finalizers, removed := removeFinalizer(runner.ObjectMeta.Finalizers, finalizerName) | 	finalizers, removed := removeFinalizer(runner.Finalizers, finalizerName) | ||||||
| 
 | 
 | ||||||
| 	if removed { | 	if removed { | ||||||
| 		newRunner := runner.DeepCopy() | 		newRunner := runner.DeepCopy() | ||||||
| 		newRunner.ObjectMeta.Finalizers = finalizers | 		newRunner.Finalizers = finalizers | ||||||
| 
 | 
 | ||||||
| 		if err := r.Patch(ctx, newRunner, client.MergeFrom(&runner)); err != nil { | 		if err := r.Patch(ctx, newRunner, client.MergeFrom(&runner)); err != nil { | ||||||
| 			log.Error(err, "Unable to remove finalizer") | 			log.Error(err, "Unable to remove finalizer") | ||||||
|  | @ -305,8 +306,8 @@ func (r *RunnerReconciler) processRunnerCreation(ctx context.Context, runner v1a | ||||||
| 	if needsServiceAccount { | 	if needsServiceAccount { | ||||||
| 		serviceAccount := &corev1.ServiceAccount{ | 		serviceAccount := &corev1.ServiceAccount{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{ | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
| 				Name:      runner.ObjectMeta.Name, | 				Name:      runner.Name, | ||||||
| 				Namespace: runner.ObjectMeta.Namespace, | 				Namespace: runner.Namespace, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 		if res := r.createObject(ctx, serviceAccount, serviceAccount.ObjectMeta, &runner, log); res != nil { | 		if res := r.createObject(ctx, serviceAccount, serviceAccount.ObjectMeta, &runner, log); res != nil { | ||||||
|  | @ -321,7 +322,7 @@ func (r *RunnerReconciler) processRunnerCreation(ctx context.Context, runner v1a | ||||||
| 					APIGroups:     []string{"actions.summerwind.dev"}, | 					APIGroups:     []string{"actions.summerwind.dev"}, | ||||||
| 					Resources:     []string{"runners/status"}, | 					Resources:     []string{"runners/status"}, | ||||||
| 					Verbs:         []string{"get", "update", "patch"}, | 					Verbs:         []string{"get", "update", "patch"}, | ||||||
| 					ResourceNames: []string{runner.ObjectMeta.Name}, | 					ResourceNames: []string{runner.Name}, | ||||||
| 				}, | 				}, | ||||||
| 			}...) | 			}...) | ||||||
| 		} | 		} | ||||||
|  | @ -359,8 +360,8 @@ func (r *RunnerReconciler) processRunnerCreation(ctx context.Context, runner v1a | ||||||
| 
 | 
 | ||||||
| 		role := &rbacv1.Role{ | 		role := &rbacv1.Role{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{ | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
| 				Name:      runner.ObjectMeta.Name, | 				Name:      runner.Name, | ||||||
| 				Namespace: runner.ObjectMeta.Namespace, | 				Namespace: runner.Namespace, | ||||||
| 			}, | 			}, | ||||||
| 			Rules: rules, | 			Rules: rules, | ||||||
| 		} | 		} | ||||||
|  | @ -370,19 +371,19 @@ func (r *RunnerReconciler) processRunnerCreation(ctx context.Context, runner v1a | ||||||
| 
 | 
 | ||||||
| 		roleBinding := &rbacv1.RoleBinding{ | 		roleBinding := &rbacv1.RoleBinding{ | ||||||
| 			ObjectMeta: metav1.ObjectMeta{ | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
| 				Name:      runner.ObjectMeta.Name, | 				Name:      runner.Name, | ||||||
| 				Namespace: runner.ObjectMeta.Namespace, | 				Namespace: runner.Namespace, | ||||||
| 			}, | 			}, | ||||||
| 			RoleRef: rbacv1.RoleRef{ | 			RoleRef: rbacv1.RoleRef{ | ||||||
| 				APIGroup: "rbac.authorization.k8s.io", | 				APIGroup: "rbac.authorization.k8s.io", | ||||||
| 				Kind:     "Role", | 				Kind:     "Role", | ||||||
| 				Name:     runner.ObjectMeta.Name, | 				Name:     runner.Name, | ||||||
| 			}, | 			}, | ||||||
| 			Subjects: []rbacv1.Subject{ | 			Subjects: []rbacv1.Subject{ | ||||||
| 				{ | 				{ | ||||||
| 					Kind:      "ServiceAccount", | 					Kind:      "ServiceAccount", | ||||||
| 					Name:      runner.ObjectMeta.Name, | 					Name:      runner.Name, | ||||||
| 					Namespace: runner.ObjectMeta.Namespace, | 					Namespace: runner.Namespace, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
|  | @ -482,7 +483,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) { | ||||||
| 
 | 
 | ||||||
| 	labels := map[string]string{} | 	labels := map[string]string{} | ||||||
| 
 | 
 | ||||||
| 	for k, v := range runner.ObjectMeta.Labels { | 	for k, v := range runner.Labels { | ||||||
| 		labels[k] = v | 		labels[k] = v | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -511,8 +512,8 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) { | ||||||
| 	//
 | 	//
 | ||||||
| 	//     See https://github.com/actions/actions-runner-controller/issues/143 for more context.
 | 	//     See https://github.com/actions/actions-runner-controller/issues/143 for more context.
 | ||||||
| 	labels[LabelKeyPodTemplateHash] = hash.FNVHashStringObjects( | 	labels[LabelKeyPodTemplateHash] = hash.FNVHashStringObjects( | ||||||
| 		filterLabels(runner.ObjectMeta.Labels, LabelKeyRunnerTemplateHash), | 		filterLabels(runner.Labels, LabelKeyRunnerTemplateHash), | ||||||
| 		runner.ObjectMeta.Annotations, | 		runner.Annotations, | ||||||
| 		runner.Spec, | 		runner.Spec, | ||||||
| 		ghc.GithubBaseURL, | 		ghc.GithubBaseURL, | ||||||
| 		// Token change should trigger replacement.
 | 		// Token change should trigger replacement.
 | ||||||
|  | @ -523,10 +524,10 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) { | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	objectMeta := metav1.ObjectMeta{ | 	objectMeta := metav1.ObjectMeta{ | ||||||
| 		Name:        runner.ObjectMeta.Name, | 		Name:        runner.Name, | ||||||
| 		Namespace:   runner.ObjectMeta.Namespace, | 		Namespace:   runner.Namespace, | ||||||
| 		Labels:      labels, | 		Labels:      labels, | ||||||
| 		Annotations: runner.ObjectMeta.Annotations, | 		Annotations: runner.Annotations, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template.ObjectMeta = objectMeta | 	template.ObjectMeta = objectMeta | ||||||
|  | @ -649,7 +650,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) { | ||||||
| 	if runnerSpec.ServiceAccountName != "" { | 	if runnerSpec.ServiceAccountName != "" { | ||||||
| 		pod.Spec.ServiceAccountName = runnerSpec.ServiceAccountName | 		pod.Spec.ServiceAccountName = runnerSpec.ServiceAccountName | ||||||
| 	} else if r.RunnerPodDefaults.UseRunnerStatusUpdateHook || runner.Spec.ContainerMode == "kubernetes" { | 	} else if r.RunnerPodDefaults.UseRunnerStatusUpdateHook || runner.Spec.ContainerMode == "kubernetes" { | ||||||
| 		pod.Spec.ServiceAccountName = runner.ObjectMeta.Name | 		pod.Spec.ServiceAccountName = runner.Name | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if runnerSpec.AutomountServiceAccountToken != nil { | 	if runnerSpec.AutomountServiceAccountToken != nil { | ||||||
|  | @ -704,7 +705,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) { | ||||||
| 		pod.Spec.RuntimeClassName = runnerSpec.RuntimeClassName | 		pod.Spec.RuntimeClassName = runnerSpec.RuntimeClassName | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pod.ObjectMeta.Name = runner.ObjectMeta.Name | 	pod.Name = runner.Name | ||||||
| 
 | 
 | ||||||
| 	// Inject the registration token and the runner name
 | 	// Inject the registration token and the runner name
 | ||||||
| 	updated := mutatePod(&pod, runner.Status.Registration.Token) | 	updated := mutatePod(&pod, runner.Status.Registration.Token) | ||||||
|  | @ -720,7 +721,7 @@ func mutatePod(pod *corev1.Pod, token string) *corev1.Pod { | ||||||
| 	updated := pod.DeepCopy() | 	updated := pod.DeepCopy() | ||||||
| 
 | 
 | ||||||
| 	if getRunnerEnv(pod, EnvVarRunnerName) == "" { | 	if getRunnerEnv(pod, EnvVarRunnerName) == "" { | ||||||
| 		setRunnerEnv(updated, EnvVarRunnerName, pod.ObjectMeta.Name) | 		setRunnerEnv(updated, EnvVarRunnerName, pod.Name) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if getRunnerEnv(pod, EnvVarRunnerToken) == "" { | 	if getRunnerEnv(pod, EnvVarRunnerToken) == "" { | ||||||
|  | @ -770,11 +771,11 @@ func runnerHookEnvs(pod *corev1.Pod) ([]corev1.EnvVar, error) { | ||||||
| 
 | 
 | ||||||
| func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, githubBaseURL string, d RunnerPodDefaults) (corev1.Pod, error) { | func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, githubBaseURL string, d RunnerPodDefaults) (corev1.Pod, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		privileged                bool = true | 		privileged                = true | ||||||
| 		dockerdInRunner           bool = runnerSpec.DockerdWithinRunnerContainer != nil && *runnerSpec.DockerdWithinRunnerContainer | 		dockerdInRunner           = runnerSpec.DockerdWithinRunnerContainer != nil && *runnerSpec.DockerdWithinRunnerContainer | ||||||
| 		dockerEnabled             bool = runnerSpec.DockerEnabled == nil || *runnerSpec.DockerEnabled | 		dockerEnabled             = runnerSpec.DockerEnabled == nil || *runnerSpec.DockerEnabled | ||||||
| 		ephemeral                 bool = runnerSpec.Ephemeral == nil || *runnerSpec.Ephemeral | 		ephemeral                 = runnerSpec.Ephemeral == nil || *runnerSpec.Ephemeral | ||||||
| 		dockerdInRunnerPrivileged bool = dockerdInRunner | 		dockerdInRunnerPrivileged = dockerdInRunner | ||||||
| 
 | 
 | ||||||
| 		defaultRunnerImage            = d.RunnerImage | 		defaultRunnerImage            = d.RunnerImage | ||||||
| 		defaultRunnerImagePullSecrets = d.RunnerImagePullSecrets | 		defaultRunnerImagePullSecrets = d.RunnerImagePullSecrets | ||||||
|  | @ -797,10 +798,10 @@ func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, ru | ||||||
| 	template = *template.DeepCopy() | 	template = *template.DeepCopy() | ||||||
| 
 | 
 | ||||||
| 	// This label selector is used by default when rd.Spec.Selector is empty.
 | 	// This label selector is used by default when rd.Spec.Selector is empty.
 | ||||||
| 	template.ObjectMeta.Labels = CloneAndAddLabel(template.ObjectMeta.Labels, LabelKeyRunner, "") | 	template.Labels = CloneAndAddLabel(template.Labels, LabelKeyRunner, "") | ||||||
| 	template.ObjectMeta.Labels = CloneAndAddLabel(template.ObjectMeta.Labels, LabelKeyPodMutation, LabelValuePodMutation) | 	template.Labels = CloneAndAddLabel(template.Labels, LabelKeyPodMutation, LabelValuePodMutation) | ||||||
| 	if runnerSpec.GitHubAPICredentialsFrom != nil { | 	if runnerSpec.GitHubAPICredentialsFrom != nil { | ||||||
| 		template.ObjectMeta.Annotations = CloneAndAddLabel(template.ObjectMeta.Annotations, annotationKeyGitHubAPICredsSecret, runnerSpec.GitHubAPICredentialsFrom.SecretRef.Name) | 		template.Annotations = CloneAndAddLabel(template.Annotations, annotationKeyGitHubAPICredsSecret, runnerSpec.GitHubAPICredentialsFrom.SecretRef.Name) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	workDir := runnerSpec.WorkDir | 	workDir := runnerSpec.WorkDir | ||||||
|  | @ -887,10 +888,11 @@ func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, ru | ||||||
| 
 | 
 | ||||||
| 	for i := range template.Spec.Containers { | 	for i := range template.Spec.Containers { | ||||||
| 		c := template.Spec.Containers[i] | 		c := template.Spec.Containers[i] | ||||||
| 		if c.Name == containerName { | 		switch c.Name { | ||||||
|  | 		case containerName: | ||||||
| 			runnerContainerIndex = i | 			runnerContainerIndex = i | ||||||
| 			runnerContainer = &c | 			runnerContainer = &c | ||||||
| 		} else if c.Name == "docker" { | 		case "docker": | ||||||
| 			dockerdContainerIndex = i | 			dockerdContainerIndex = i | ||||||
| 			dockerdContainer = &c | 			dockerdContainer = &c | ||||||
| 		} | 		} | ||||||
|  | @ -1364,7 +1366,7 @@ func applyWorkVolumeClaimTemplateToPod(pod *corev1.Pod, workVolumeClaimTemplate | ||||||
| 	} | 	} | ||||||
| 	for i := range pod.Spec.Volumes { | 	for i := range pod.Spec.Volumes { | ||||||
| 		if pod.Spec.Volumes[i].Name == "work" { | 		if pod.Spec.Volumes[i].Name == "work" { | ||||||
| 			return fmt.Errorf("Work volume should not be specified in container mode kubernetes. workVolumeClaimTemplate field should be used instead.") | 			return fmt.Errorf("work volume should not be specified in container mode kubernetes. workVolumeClaimTemplate field should be used instead") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	pod.Spec.Volumes = append(pod.Spec.Volumes, workVolumeClaimTemplate.V1Volume()) | 	pod.Spec.Volumes = append(pod.Spec.Volumes, workVolumeClaimTemplate.V1Volume()) | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(envvars) == 0 { | 	if len(envvars) == 0 { | ||||||
| 		return ctrl.Result{}, errors.New("Could not determine env vars for runner Pod") | 		return ctrl.Result{}, errors.New("could not determine env vars for runner Pod") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var enterprise, org, repo string | 	var enterprise, org, repo string | ||||||
|  | @ -103,8 +103,8 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( | ||||||
| 		return ctrl.Result{}, err | 		return ctrl.Result{}, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if runnerPod.ObjectMeta.DeletionTimestamp.IsZero() { | 	if runnerPod.DeletionTimestamp.IsZero() { | ||||||
| 		finalizers, added := addFinalizer(runnerPod.ObjectMeta.Finalizers, runnerPodFinalizerName) | 		finalizers, added := addFinalizer(runnerPod.Finalizers, runnerPodFinalizerName) | ||||||
| 
 | 
 | ||||||
| 		var cleanupFinalizersAdded bool | 		var cleanupFinalizersAdded bool | ||||||
| 		if isContainerMode { | 		if isContainerMode { | ||||||
|  | @ -113,7 +113,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( | ||||||
| 
 | 
 | ||||||
| 		if added || cleanupFinalizersAdded { | 		if added || cleanupFinalizersAdded { | ||||||
| 			newRunner := runnerPod.DeepCopy() | 			newRunner := runnerPod.DeepCopy() | ||||||
| 			newRunner.ObjectMeta.Finalizers = finalizers | 			newRunner.Finalizers = finalizers | ||||||
| 
 | 
 | ||||||
| 			if err := r.Patch(ctx, newRunner, client.MergeFrom(&runnerPod)); err != nil { | 			if err := r.Patch(ctx, newRunner, client.MergeFrom(&runnerPod)); err != nil { | ||||||
| 				log.Error(err, "Failed to update runner") | 				log.Error(err, "Failed to update runner") | ||||||
|  | @ -142,7 +142,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if finalizers, removed := removeFinalizer(runnerPod.ObjectMeta.Finalizers, runnerLinkedResourcesFinalizerName); removed { | 		if finalizers, removed := removeFinalizer(runnerPod.Finalizers, runnerLinkedResourcesFinalizerName); removed { | ||||||
| 			if err := r.cleanupRunnerLinkedPods(ctx, &runnerPod, log); err != nil { | 			if err := r.cleanupRunnerLinkedPods(ctx, &runnerPod, log); err != nil { | ||||||
| 				log.Info("Runner-linked pods clean up that has failed due to an error. If this persists, please manually remove the runner-linked pods to unblock ARC", "err", err.Error()) | 				log.Info("Runner-linked pods clean up that has failed due to an error. If this persists, please manually remove the runner-linked pods to unblock ARC", "err", err.Error()) | ||||||
| 				return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil | 				return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil | ||||||
|  | @ -152,7 +152,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( | ||||||
| 				return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil | 				return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil | ||||||
| 			} | 			} | ||||||
| 			patchedPod := runnerPod.DeepCopy() | 			patchedPod := runnerPod.DeepCopy() | ||||||
| 			patchedPod.ObjectMeta.Finalizers = finalizers | 			patchedPod.Finalizers = finalizers | ||||||
| 
 | 
 | ||||||
| 			if err := r.Patch(ctx, patchedPod, client.MergeFrom(&runnerPod)); err != nil { | 			if err := r.Patch(ctx, patchedPod, client.MergeFrom(&runnerPod)); err != nil { | ||||||
| 				log.Error(err, "Failed to update runner for finalizer linked resources removal") | 				log.Error(err, "Failed to update runner for finalizer linked resources removal") | ||||||
|  | @ -163,7 +163,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( | ||||||
| 			runnerPod = *patchedPod | 			runnerPod = *patchedPod | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		finalizers, removed := removeFinalizer(runnerPod.ObjectMeta.Finalizers, runnerPodFinalizerName) | 		finalizers, removed := removeFinalizer(runnerPod.Finalizers, runnerPodFinalizerName) | ||||||
| 
 | 
 | ||||||
| 		if removed { | 		if removed { | ||||||
| 			// In a standard scenario, the upstream controller, like runnerset-controller, ensures this runner to be gracefully stopped before the deletion timestamp is set.
 | 			// In a standard scenario, the upstream controller, like runnerset-controller, ensures this runner to be gracefully stopped before the deletion timestamp is set.
 | ||||||
|  | @ -175,7 +175,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			patchedPod := updatedPod.DeepCopy() | 			patchedPod := updatedPod.DeepCopy() | ||||||
| 			patchedPod.ObjectMeta.Finalizers = finalizers | 			patchedPod.Finalizers = finalizers | ||||||
| 
 | 
 | ||||||
| 			// We commit the removal of the finalizer so that Kuberenetes notices it and delete the pod resource from the cluster.
 | 			// We commit the removal of the finalizer so that Kuberenetes notices it and delete the pod resource from the cluster.
 | ||||||
| 			if err := r.Patch(ctx, patchedPod, client.MergeFrom(&runnerPod)); err != nil { | 			if err := r.Patch(ctx, patchedPod, client.MergeFrom(&runnerPod)); err != nil { | ||||||
|  | @ -284,7 +284,7 @@ func (r *RunnerPodReconciler) cleanupRunnerLinkedPods(ctx context.Context, pod * | ||||||
| 	var runnerLinkedPodList corev1.PodList | 	var runnerLinkedPodList corev1.PodList | ||||||
| 	if err := r.List(ctx, &runnerLinkedPodList, client.InNamespace(pod.Namespace), client.MatchingLabels( | 	if err := r.List(ctx, &runnerLinkedPodList, client.InNamespace(pod.Namespace), client.MatchingLabels( | ||||||
| 		map[string]string{ | 		map[string]string{ | ||||||
| 			"runner-pod": pod.ObjectMeta.Name, | 			"runner-pod": pod.Name, | ||||||
| 		}, | 		}, | ||||||
| 	)); err != nil { | 	)); err != nil { | ||||||
| 		return fmt.Errorf("failed to list runner-linked pods: %w", err) | 		return fmt.Errorf("failed to list runner-linked pods: %w", err) | ||||||
|  | @ -295,7 +295,7 @@ func (r *RunnerPodReconciler) cleanupRunnerLinkedPods(ctx context.Context, pod * | ||||||
| 		errs []error | 		errs []error | ||||||
| 	) | 	) | ||||||
| 	for _, p := range runnerLinkedPodList.Items { | 	for _, p := range runnerLinkedPodList.Items { | ||||||
| 		if !p.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !p.DeletionTimestamp.IsZero() { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -307,7 +307,7 @@ func (r *RunnerPodReconciler) cleanupRunnerLinkedPods(ctx context.Context, pod * | ||||||
| 				if kerrors.IsNotFound(err) || kerrors.IsGone(err) { | 				if kerrors.IsNotFound(err) || kerrors.IsGone(err) { | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
| 				errs = append(errs, fmt.Errorf("delete pod %q error: %v", p.ObjectMeta.Name, err)) | 				errs = append(errs, fmt.Errorf("delete pod %q error: %v", p.Name, err)) | ||||||
| 			} | 			} | ||||||
| 		}() | 		}() | ||||||
| 	} | 	} | ||||||
|  | @ -330,7 +330,7 @@ func (r *RunnerPodReconciler) cleanupRunnerLinkedSecrets(ctx context.Context, po | ||||||
| 	var runnerLinkedSecretList corev1.SecretList | 	var runnerLinkedSecretList corev1.SecretList | ||||||
| 	if err := r.List(ctx, &runnerLinkedSecretList, client.InNamespace(pod.Namespace), client.MatchingLabels( | 	if err := r.List(ctx, &runnerLinkedSecretList, client.InNamespace(pod.Namespace), client.MatchingLabels( | ||||||
| 		map[string]string{ | 		map[string]string{ | ||||||
| 			"runner-pod": pod.ObjectMeta.Name, | 			"runner-pod": pod.Name, | ||||||
| 		}, | 		}, | ||||||
| 	)); err != nil { | 	)); err != nil { | ||||||
| 		return fmt.Errorf("failed to list runner-linked secrets: %w", err) | 		return fmt.Errorf("failed to list runner-linked secrets: %w", err) | ||||||
|  | @ -341,7 +341,7 @@ func (r *RunnerPodReconciler) cleanupRunnerLinkedSecrets(ctx context.Context, po | ||||||
| 		errs []error | 		errs []error | ||||||
| 	) | 	) | ||||||
| 	for _, s := range runnerLinkedSecretList.Items { | 	for _, s := range runnerLinkedSecretList.Items { | ||||||
| 		if !s.ObjectMeta.DeletionTimestamp.IsZero() { | 		if !s.DeletionTimestamp.IsZero() { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -353,7 +353,7 @@ func (r *RunnerPodReconciler) cleanupRunnerLinkedSecrets(ctx context.Context, po | ||||||
| 				if kerrors.IsNotFound(err) || kerrors.IsGone(err) { | 				if kerrors.IsNotFound(err) || kerrors.IsGone(err) { | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
| 				errs = append(errs, fmt.Errorf("delete secret %q error: %v", s.ObjectMeta.Name, err)) | 				errs = append(errs, fmt.Errorf("delete secret %q error: %v", s.Name, err)) | ||||||
| 			} | 			} | ||||||
| 		}() | 		}() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -90,7 +90,7 @@ var _ owner = (*ownerStatefulSet)(nil) | ||||||
| func (s *ownerStatefulSet) pods(ctx context.Context, c client.Client) ([]corev1.Pod, error) { | func (s *ownerStatefulSet) pods(ctx context.Context, c client.Client) ([]corev1.Pod, error) { | ||||||
| 	var podList corev1.PodList | 	var podList corev1.PodList | ||||||
| 
 | 
 | ||||||
| 	if err := c.List(ctx, &podList, client.MatchingLabels(s.StatefulSet.Spec.Template.ObjectMeta.Labels)); err != nil { | 	if err := c.List(ctx, &podList, client.MatchingLabels(s.StatefulSet.Spec.Template.Labels)); err != nil { | ||||||
| 		s.Log.Error(err, "Failed to list pods managed by statefulset") | 		s.Log.Error(err, "Failed to list pods managed by statefulset") | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -73,7 +73,7 @@ func (r *RunnerDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 		return ctrl.Result{}, client.IgnoreNotFound(err) | 		return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !rd.ObjectMeta.DeletionTimestamp.IsZero() { | 	if !rd.DeletionTimestamp.IsZero() { | ||||||
| 		return ctrl.Result{}, nil | 		return ctrl.Result{}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -112,7 +112,7 @@ func (r *RunnerDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if newestSet == nil { | 	if newestSet == nil { | ||||||
| 		if err := r.Client.Create(ctx, desiredRS); err != nil { | 		if err := r.Create(ctx, desiredRS); err != nil { | ||||||
| 			log.Error(err, "Failed to create runnerreplicaset resource") | 			log.Error(err, "Failed to create runnerreplicaset resource") | ||||||
| 
 | 
 | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
|  | @ -138,7 +138,7 @@ func (r *RunnerDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if newestTemplateHash != desiredTemplateHash { | 	if newestTemplateHash != desiredTemplateHash { | ||||||
| 		if err := r.Client.Create(ctx, desiredRS); err != nil { | 		if err := r.Create(ctx, desiredRS); err != nil { | ||||||
| 			log.Error(err, "Failed to create runnerreplicaset resource") | 			log.Error(err, "Failed to create runnerreplicaset resource") | ||||||
| 
 | 
 | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
|  | @ -159,7 +159,7 @@ func (r *RunnerDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 		// but we still need to update the existing replicaset with it.
 | 		// but we still need to update the existing replicaset with it.
 | ||||||
| 		// Otherwise selector-based runner query will never work on replicasets created before the controller v0.17.0
 | 		// Otherwise selector-based runner query will never work on replicasets created before the controller v0.17.0
 | ||||||
| 		// See https://github.com/actions/actions-runner-controller/pull/355#discussion_r585379259
 | 		// See https://github.com/actions/actions-runner-controller/pull/355#discussion_r585379259
 | ||||||
| 		if err := r.Client.Update(ctx, updateSet); err != nil { | 		if err := r.Update(ctx, updateSet); err != nil { | ||||||
| 			log.Error(err, "Failed to update runnerreplicaset resource") | 			log.Error(err, "Failed to update runnerreplicaset resource") | ||||||
| 
 | 
 | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
|  | @ -195,7 +195,7 @@ func (r *RunnerDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 		newestSet.Spec.Replicas = &newDesiredReplicas | 		newestSet.Spec.Replicas = &newDesiredReplicas | ||||||
| 		newestSet.Spec.EffectiveTime = rd.Spec.EffectiveTime | 		newestSet.Spec.EffectiveTime = rd.Spec.EffectiveTime | ||||||
| 
 | 
 | ||||||
| 		if err := r.Client.Update(ctx, newestSet); err != nil { | 		if err := r.Update(ctx, newestSet); err != nil { | ||||||
| 			log.Error(err, "Failed to update runnerreplicaset resource") | 			log.Error(err, "Failed to update runnerreplicaset resource") | ||||||
| 
 | 
 | ||||||
| 			return ctrl.Result{}, err | 			return ctrl.Result{}, err | ||||||
|  | @ -257,7 +257,7 @@ func (r *RunnerDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 				updated := rs.DeepCopy() | 				updated := rs.DeepCopy() | ||||||
| 				zero := 0 | 				zero := 0 | ||||||
| 				updated.Spec.Replicas = &zero | 				updated.Spec.Replicas = &zero | ||||||
| 				if err := r.Client.Update(ctx, updated); err != nil { | 				if err := r.Update(ctx, updated); err != nil { | ||||||
| 					rslog.Error(err, "Failed to scale runnerreplicaset to zero") | 					rslog.Error(err, "Failed to scale runnerreplicaset to zero") | ||||||
| 
 | 
 | ||||||
| 					return ctrl.Result{}, err | 					return ctrl.Result{}, err | ||||||
|  | @ -268,7 +268,7 @@ func (r *RunnerDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if err := r.Client.Delete(ctx, &rs); err != nil { | 			if err := r.Delete(ctx, &rs); err != nil { | ||||||
| 				rslog.Error(err, "Failed to delete runnerreplicaset resource") | 				rslog.Error(err, "Failed to delete runnerreplicaset resource") | ||||||
| 
 | 
 | ||||||
| 				return ctrl.Result{}, err | 				return ctrl.Result{}, err | ||||||
|  | @ -445,10 +445,10 @@ func newRunnerReplicaSet(rd *v1alpha1.RunnerDeployment, commonRunnerLabels []str | ||||||
| 	templateHash := ComputeHash(&newRSTemplate) | 	templateHash := ComputeHash(&newRSTemplate) | ||||||
| 
 | 
 | ||||||
| 	// Add template hash label to selector.
 | 	// Add template hash label to selector.
 | ||||||
| 	newRSTemplate.ObjectMeta.Labels = CloneAndAddLabel(newRSTemplate.ObjectMeta.Labels, LabelKeyRunnerTemplateHash, templateHash) | 	newRSTemplate.Labels = CloneAndAddLabel(newRSTemplate.Labels, LabelKeyRunnerTemplateHash, templateHash) | ||||||
| 
 | 
 | ||||||
| 	// This label selector is used by default when rd.Spec.Selector is empty.
 | 	// This label selector is used by default when rd.Spec.Selector is empty.
 | ||||||
| 	newRSTemplate.ObjectMeta.Labels = CloneAndAddLabel(newRSTemplate.ObjectMeta.Labels, LabelKeyRunnerDeploymentName, rd.Name) | 	newRSTemplate.Labels = CloneAndAddLabel(newRSTemplate.Labels, LabelKeyRunnerDeploymentName, rd.Name) | ||||||
| 
 | 
 | ||||||
| 	selector := getSelector(rd) | 	selector := getSelector(rd) | ||||||
| 
 | 
 | ||||||
|  | @ -457,9 +457,9 @@ func newRunnerReplicaSet(rd *v1alpha1.RunnerDeployment, commonRunnerLabels []str | ||||||
| 	rs := v1alpha1.RunnerReplicaSet{ | 	rs := v1alpha1.RunnerReplicaSet{ | ||||||
| 		TypeMeta: metav1.TypeMeta{}, | 		TypeMeta: metav1.TypeMeta{}, | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			GenerateName: rd.ObjectMeta.Name + "-", | 			GenerateName: rd.Name + "-", | ||||||
| 			Namespace:    rd.ObjectMeta.Namespace, | 			Namespace:    rd.Namespace, | ||||||
| 			Labels:       newRSTemplate.ObjectMeta.Labels, | 			Labels:       newRSTemplate.Labels, | ||||||
| 		}, | 		}, | ||||||
| 		Spec: v1alpha1.RunnerReplicaSetSpec{ | 		Spec: v1alpha1.RunnerReplicaSetSpec{ | ||||||
| 			Replicas:      rd.Spec.Replicas, | 			Replicas:      rd.Spec.Replicas, | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ func (r *RunnerReplicaSetReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 		return ctrl.Result{}, client.IgnoreNotFound(err) | 		return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !rs.ObjectMeta.DeletionTimestamp.IsZero() { | 	if !rs.DeletionTimestamp.IsZero() { | ||||||
| 		// RunnerReplicaSet cannot be gracefuly removed.
 | 		// RunnerReplicaSet cannot be gracefuly removed.
 | ||||||
| 		// That means any runner that is running a job can be prematurely terminated.
 | 		// That means any runner that is running a job can be prematurely terminated.
 | ||||||
| 		// To gracefully remove a RunnerReplicaSet, scale it down to zero first, observe RunnerReplicaSet's status replicas,
 | 		// To gracefully remove a RunnerReplicaSet, scale it down to zero first, observe RunnerReplicaSet's status replicas,
 | ||||||
|  | @ -70,14 +70,14 @@ func (r *RunnerReplicaSetReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 		return ctrl.Result{}, nil | 		return ctrl.Result{}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if rs.ObjectMeta.Labels == nil { | 	if rs.Labels == nil { | ||||||
| 		rs.ObjectMeta.Labels = map[string]string{} | 		rs.Labels = map[string]string{} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Template hash is usually set by the upstream controller(RunnerDeplloyment controller) on authoring
 | 	// Template hash is usually set by the upstream controller(RunnerDeplloyment controller) on authoring
 | ||||||
| 	// RunerReplicaset resource, but it may be missing when the user directly created RunnerReplicaSet.
 | 	// RunerReplicaset resource, but it may be missing when the user directly created RunnerReplicaSet.
 | ||||||
| 	// As a template hash is required by by the runner replica management, we dynamically add it here without ever persisting it.
 | 	// As a template hash is required by by the runner replica management, we dynamically add it here without ever persisting it.
 | ||||||
| 	if rs.ObjectMeta.Labels[LabelKeyRunnerTemplateHash] == "" { | 	if rs.Labels[LabelKeyRunnerTemplateHash] == "" { | ||||||
| 		template := rs.Spec.DeepCopy() | 		template := rs.Spec.DeepCopy() | ||||||
| 		template.Replicas = nil | 		template.Replicas = nil | ||||||
| 		template.EffectiveTime = nil | 		template.EffectiveTime = nil | ||||||
|  | @ -85,8 +85,8 @@ func (r *RunnerReplicaSetReconciler) Reconcile(ctx context.Context, req ctrl.Req | ||||||
| 
 | 
 | ||||||
| 		log.Info("Using auto-generated template hash", "value", templateHash) | 		log.Info("Using auto-generated template hash", "value", templateHash) | ||||||
| 
 | 
 | ||||||
| 		rs.ObjectMeta.Labels = CloneAndAddLabel(rs.ObjectMeta.Labels, LabelKeyRunnerTemplateHash, templateHash) | 		rs.Labels = CloneAndAddLabel(rs.Labels, LabelKeyRunnerTemplateHash, templateHash) | ||||||
| 		rs.Spec.Template.ObjectMeta.Labels = CloneAndAddLabel(rs.Spec.Template.ObjectMeta.Labels, LabelKeyRunnerTemplateHash, templateHash) | 		rs.Spec.Template.Labels = CloneAndAddLabel(rs.Spec.Template.Labels, LabelKeyRunnerTemplateHash, templateHash) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector) | 	selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector) | ||||||
|  | @ -169,8 +169,8 @@ func (r *RunnerReplicaSetReconciler) newRunner(rs v1alpha1.RunnerReplicaSet) (v1 | ||||||
| 	// the "runner template hash" label to the template.meta which is necessary to make this controller work correctly
 | 	// the "runner template hash" label to the template.meta which is necessary to make this controller work correctly
 | ||||||
| 	objectMeta := rs.Spec.Template.ObjectMeta.DeepCopy() | 	objectMeta := rs.Spec.Template.ObjectMeta.DeepCopy() | ||||||
| 
 | 
 | ||||||
| 	objectMeta.GenerateName = rs.ObjectMeta.Name + "-" | 	objectMeta.GenerateName = rs.Name + "-" | ||||||
| 	objectMeta.Namespace = rs.ObjectMeta.Namespace | 	objectMeta.Namespace = rs.Namespace | ||||||
| 	if objectMeta.Annotations == nil { | 	if objectMeta.Annotations == nil { | ||||||
| 		objectMeta.Annotations = map[string]string{} | 		objectMeta.Annotations = map[string]string{} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -77,7 +77,7 @@ func (r *RunnerSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( | ||||||
| 		return ctrl.Result{}, err | 		return ctrl.Result{}, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !runnerSet.ObjectMeta.DeletionTimestamp.IsZero() { | 	if !runnerSet.DeletionTimestamp.IsZero() { | ||||||
| 		r.GitHubClient.DeinitForRunnerSet(runnerSet) | 		r.GitHubClient.DeinitForRunnerSet(runnerSet) | ||||||
| 
 | 
 | ||||||
| 		return ctrl.Result{}, nil | 		return ctrl.Result{}, nil | ||||||
|  | @ -191,11 +191,11 @@ func (r *RunnerSetReconciler) newStatefulSet(ctx context.Context, runnerSet *v1a | ||||||
| 	runnerSetWithOverrides.Labels = append(runnerSetWithOverrides.Labels, r.CommonRunnerLabels...) | 	runnerSetWithOverrides.Labels = append(runnerSetWithOverrides.Labels, r.CommonRunnerLabels...) | ||||||
| 
 | 
 | ||||||
| 	template := corev1.Pod{ | 	template := corev1.Pod{ | ||||||
| 		ObjectMeta: runnerSetWithOverrides.StatefulSetSpec.Template.ObjectMeta, | 		ObjectMeta: runnerSetWithOverrides.Template.ObjectMeta, | ||||||
| 		Spec:       runnerSetWithOverrides.StatefulSetSpec.Template.Spec, | 		Spec:       runnerSetWithOverrides.Template.Spec, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if runnerSet.Spec.RunnerConfig.ContainerMode == "kubernetes" { | 	if runnerSet.Spec.ContainerMode == "kubernetes" { | ||||||
| 		found := false | 		found := false | ||||||
| 		for i := range template.Spec.Containers { | 		for i := range template.Spec.Containers { | ||||||
| 			if template.Spec.Containers[i].Name == containerName { | 			if template.Spec.Containers[i].Name == containerName { | ||||||
|  | @ -208,7 +208,7 @@ func (r *RunnerSetReconciler) newStatefulSet(ctx context.Context, runnerSet *v1a | ||||||
| 			}) | 			}) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		workDir := runnerSet.Spec.RunnerConfig.WorkDir | 		workDir := runnerSet.Spec.WorkDir | ||||||
| 		if workDir == "" { | 		if workDir == "" { | ||||||
| 			workDir = "/runner/_work" | 			workDir = "/runner/_work" | ||||||
| 		} | 		} | ||||||
|  | @ -219,7 +219,7 @@ func (r *RunnerSetReconciler) newStatefulSet(ctx context.Context, runnerSet *v1a | ||||||
| 		template.Spec.ServiceAccountName = runnerSet.Spec.ServiceAccountName | 		template.Spec.ServiceAccountName = runnerSet.Spec.ServiceAccountName | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template.ObjectMeta.Labels = CloneAndAddLabel(template.ObjectMeta.Labels, LabelKeyRunnerSetName, runnerSet.Name) | 	template.Labels = CloneAndAddLabel(template.Labels, LabelKeyRunnerSetName, runnerSet.Name) | ||||||
| 
 | 
 | ||||||
| 	ghc, err := r.GitHubClient.InitForRunnerSet(ctx, runnerSet) | 	ghc, err := r.GitHubClient.InitForRunnerSet(ctx, runnerSet) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -228,38 +228,38 @@ func (r *RunnerSetReconciler) newStatefulSet(ctx context.Context, runnerSet *v1a | ||||||
| 
 | 
 | ||||||
| 	githubBaseURL := ghc.GithubBaseURL | 	githubBaseURL := ghc.GithubBaseURL | ||||||
| 
 | 
 | ||||||
| 	pod, err := newRunnerPodWithContainerMode(runnerSet.Spec.RunnerConfig.ContainerMode, template, runnerSet.Spec.RunnerConfig, githubBaseURL, r.RunnerPodDefaults) | 	pod, err := newRunnerPodWithContainerMode(runnerSet.Spec.ContainerMode, template, runnerSet.Spec.RunnerConfig, githubBaseURL, r.RunnerPodDefaults) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	runnerSetWithOverrides.StatefulSetSpec.Template.ObjectMeta = pod.ObjectMeta | 	runnerSetWithOverrides.Template.ObjectMeta = pod.ObjectMeta | ||||||
| 	runnerSetWithOverrides.StatefulSetSpec.Template.Spec = pod.Spec | 	runnerSetWithOverrides.Template.Spec = pod.Spec | ||||||
| 	// NOTE: Seems like the only supported restart policy for statefulset is "Always"?
 | 	// NOTE: Seems like the only supported restart policy for statefulset is "Always"?
 | ||||||
| 	// I got errosr like the below when tried to use "OnFailure":
 | 	// I got errosr like the below when tried to use "OnFailure":
 | ||||||
| 	//   StatefulSet.apps \"example-runnersetpg9rx\" is invalid: [spec.template.metadata.labels: Invalid value: map[string]string{\"runner-template-hash\"
 | 	//   StatefulSet.apps \"example-runnersetpg9rx\" is invalid: [spec.template.metadata.labels: Invalid value: map[string]string{\"runner-template-hash\"
 | ||||||
| 	//   :\"85d7578bd6\", \"runnerset-name\":\"example-runnerset\"}: `selector` does not match template `labels`, spec.
 | 	//   :\"85d7578bd6\", \"runnerset-name\":\"example-runnerset\"}: `selector` does not match template `labels`, spec.
 | ||||||
| 	//   template.spec.restartPolicy: Unsupported value: \"OnFailure\": supported values: \"Always\"]
 | 	//   template.spec.restartPolicy: Unsupported value: \"OnFailure\": supported values: \"Always\"]
 | ||||||
| 	runnerSetWithOverrides.StatefulSetSpec.Template.Spec.RestartPolicy = corev1.RestartPolicyAlways | 	runnerSetWithOverrides.Template.Spec.RestartPolicy = corev1.RestartPolicyAlways | ||||||
| 
 | 
 | ||||||
| 	templateHash := ComputeHash(pod.Spec) | 	templateHash := ComputeHash(pod.Spec) | ||||||
| 
 | 
 | ||||||
| 	// Add template hash label to selector.
 | 	// Add template hash label to selector.
 | ||||||
| 	runnerSetWithOverrides.Template.ObjectMeta.Labels = CloneAndAddLabel(runnerSetWithOverrides.Template.ObjectMeta.Labels, LabelKeyRunnerTemplateHash, templateHash) | 	runnerSetWithOverrides.Template.Labels = CloneAndAddLabel(runnerSetWithOverrides.Template.Labels, LabelKeyRunnerTemplateHash, templateHash) | ||||||
| 
 | 
 | ||||||
| 	selector := getRunnerSetSelector(runnerSet) | 	selector := getRunnerSetSelector(runnerSet) | ||||||
| 	selector = CloneSelectorAndAddLabel(selector, LabelKeyRunnerTemplateHash, templateHash) | 	selector = CloneSelectorAndAddLabel(selector, LabelKeyRunnerTemplateHash, templateHash) | ||||||
| 	selector = CloneSelectorAndAddLabel(selector, LabelKeyRunnerSetName, runnerSet.Name) | 	selector = CloneSelectorAndAddLabel(selector, LabelKeyRunnerSetName, runnerSet.Name) | ||||||
| 	selector = CloneSelectorAndAddLabel(selector, LabelKeyPodMutation, LabelValuePodMutation) | 	selector = CloneSelectorAndAddLabel(selector, LabelKeyPodMutation, LabelValuePodMutation) | ||||||
| 
 | 
 | ||||||
| 	runnerSetWithOverrides.StatefulSetSpec.Selector = selector | 	runnerSetWithOverrides.Selector = selector | ||||||
| 
 | 
 | ||||||
| 	rs := appsv1.StatefulSet{ | 	rs := appsv1.StatefulSet{ | ||||||
| 		TypeMeta: metav1.TypeMeta{}, | 		TypeMeta: metav1.TypeMeta{}, | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			GenerateName: runnerSet.ObjectMeta.Name + "-", | 			GenerateName: runnerSet.Name + "-", | ||||||
| 			Namespace:    runnerSet.ObjectMeta.Namespace, | 			Namespace:    runnerSet.Namespace, | ||||||
| 			Labels:       CloneAndAddLabel(runnerSet.ObjectMeta.Labels, LabelKeyRunnerTemplateHash, templateHash), | 			Labels:       CloneAndAddLabel(runnerSet.Labels, LabelKeyRunnerTemplateHash, templateHash), | ||||||
| 			Annotations: map[string]string{ | 			Annotations: map[string]string{ | ||||||
| 				SyncTimeAnnotationKey: time.Now().Format(time.RFC3339), | 				SyncTimeAnnotationKey: time.Now().Format(time.RFC3339), | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ const ( | ||||||
| func syncVolumes(ctx context.Context, c client.Client, log logr.Logger, ns string, runnerSet *v1alpha1.RunnerSet, statefulsets []appsv1.StatefulSet) (*ctrl.Result, error) { | func syncVolumes(ctx context.Context, c client.Client, log logr.Logger, ns string, runnerSet *v1alpha1.RunnerSet, statefulsets []appsv1.StatefulSet) (*ctrl.Result, error) { | ||||||
| 	log = log.WithValues("ns", ns) | 	log = log.WithValues("ns", ns) | ||||||
| 
 | 
 | ||||||
| 	for _, t := range runnerSet.Spec.StatefulSetSpec.VolumeClaimTemplates { | 	for _, t := range runnerSet.Spec.VolumeClaimTemplates { | ||||||
| 		for _, sts := range statefulsets { | 		for _, sts := range statefulsets { | ||||||
| 			pvcName := fmt.Sprintf("%s-%s-0", t.Name, sts.Name) | 			pvcName := fmt.Sprintf("%s-%s-0", t.Name, sts.Name) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ type testResourceReader struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *testResourceReader) Get(_ context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error { | func (r *testResourceReader) Get(_ context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error { | ||||||
| 	nsName := types.NamespacedName{Namespace: key.Namespace, Name: key.Name} | 	nsName := types.NamespacedName(key) | ||||||
| 	ret, ok := r.objects[nsName] | 	ret, ok := r.objects[nsName] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return &kerrors.StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonNotFound}} | 		return &kerrors.StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonNotFound}} | ||||||
|  |  | ||||||
|  | @ -64,22 +64,22 @@ func Test_workVolumeClaimTemplateVolumeV1VolumeTransformation(t *testing.T) { | ||||||
| 		t.Errorf("want name %q, got %q\n", want.Name, got.Name) | 		t.Errorf("want name %q, got %q\n", want.Name, got.Name) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if got.VolumeSource.Ephemeral == nil { | 	if got.Ephemeral == nil { | ||||||
| 		t.Fatal("work volume claim template should transform itself into Ephemeral volume source\n") | 		t.Fatal("work volume claim template should transform itself into Ephemeral volume source\n") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if got.VolumeSource.Ephemeral.VolumeClaimTemplate == nil { | 	if got.Ephemeral.VolumeClaimTemplate == nil { | ||||||
| 		t.Fatal("work volume claim template should have ephemeral volume claim template set\n") | 		t.Fatal("work volume claim template should have ephemeral volume claim template set\n") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	gotClassName := *got.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.StorageClassName | 	gotClassName := *got.Ephemeral.VolumeClaimTemplate.Spec.StorageClassName | ||||||
| 	wantClassName := *want.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.StorageClassName | 	wantClassName := *want.Ephemeral.VolumeClaimTemplate.Spec.StorageClassName | ||||||
| 	if gotClassName != wantClassName { | 	if gotClassName != wantClassName { | ||||||
| 		t.Errorf("expected storage class name %q, got %q\n", wantClassName, gotClassName) | 		t.Errorf("expected storage class name %q, got %q\n", wantClassName, gotClassName) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	gotAccessModes := got.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.AccessModes | 	gotAccessModes := got.Ephemeral.VolumeClaimTemplate.Spec.AccessModes | ||||||
| 	wantAccessModes := want.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.AccessModes | 	wantAccessModes := want.Ephemeral.VolumeClaimTemplate.Spec.AccessModes | ||||||
| 	if len(gotAccessModes) != len(wantAccessModes) { | 	if len(gotAccessModes) != len(wantAccessModes) { | ||||||
| 		t.Fatalf("access modes lengths missmatch: got %v, expected %v\n", gotAccessModes, wantAccessModes) | 		t.Fatalf("access modes lengths missmatch: got %v, expected %v\n", gotAccessModes, wantAccessModes) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,6 +1,11 @@ | ||||||
| # Visualizing Autoscaling Runner Scale Set metrics with Grafana | # Visualizing Autoscaling Runner Scale Set metrics with Grafana | ||||||
| 
 | 
 | ||||||
| With metrics introduced in [gha-runner-scale-set-0.5.0](https://github.com/actions/actions-runner-controller/releases/tag/gha-runner-scale-set-0.5.0), you can now visualize the autoscaling behavior of your runner scale set with your tool of choice. This sample shows how to visualize the metrics with [Grafana](https://grafana.com/). | With the metrics support introduced in [gha-runner-scale-set-0.5.0](https://github.com/actions/actions-runner-controller/releases/tag/gha-runner-scale-set-0.5.0), you can visualize the autoscaling behavior of your runner scale set with your tool of choice.  | ||||||
|  | 
 | ||||||
|  | This sample dashboard shows how to visualize the metrics with [Grafana](https://grafana.com/). | ||||||
|  | 
 | ||||||
|  | > [!NOTE] | ||||||
|  | > We do not intend to provide a supported ARC dashboard. This is simply a reference and a demonstration for how you could leverage the metrics emitted by the controller-manager and listeners to visualize the autoscaling behavior of your runner scale set. We offer no promises of future upgrades to this sample. | ||||||
| 
 | 
 | ||||||
| ## Demo | ## Demo | ||||||
| 
 | 
 | ||||||
|  | @ -8,12 +13,43 @@ With metrics introduced in [gha-runner-scale-set-0.5.0](https://github.com/actio | ||||||
| 
 | 
 | ||||||
| ## Setup | ## Setup | ||||||
| 
 | 
 | ||||||
| We do not intend to provide a supported ARC dashboard. This is simply a reference and a demonstration for how you could leverage the metrics emitted by the controller-manager and listeners to visualize the autoscaling behavior of your runner scale set. We offer no promises of future upgrades to this sample. |  | ||||||
| 
 |  | ||||||
| 1. Make sure to have [Grafana](https://grafana.com/docs/grafana/latest/installation/) and [Prometheus](https://prometheus.io/docs/prometheus/latest/installation/) running in your cluster. | 1. Make sure to have [Grafana](https://grafana.com/docs/grafana/latest/installation/) and [Prometheus](https://prometheus.io/docs/prometheus/latest/installation/) running in your cluster. | ||||||
| 2. Make sure that Prometheus is properly scraping the metrics endpoints of the controller-manager and listeners. | 2. Make sure that Prometheus is properly scraping the metrics endpoints of the controller-manager and listeners. | ||||||
| 3. Import the [dashboard](ARC-Autoscaling-Runner-Set-Monitoring_1692627561838.json) into Grafana. | 3. Import the [dashboard](ARC-Autoscaling-Runner-Set-Monitoring_1692627561838.json) into Grafana. | ||||||
| 
 | 
 | ||||||
|  | ## Required metrics | ||||||
|  | 
 | ||||||
|  | This sample relies on the suggestion listener metrics configuration in the scale set [values.yaml](https://github.com/actions/actions-runner-controller/blob/ea27448da51385470b1ce67150aa695cfa45fd3f/charts/gha-runner-scale-set/values.yaml#L129-L270). | ||||||
|  | 
 | ||||||
|  | The following metrics are required to be scraped by Prometheus in order to populate the dashboard: | ||||||
|  | 
 | ||||||
|  | | Metric | Required labels | Source | | ||||||
|  | | ------ | ----------- | -----| | ||||||
|  | | container_fs_writes_bytes_total | namespace | cAdvisor | ||||||
|  | | container_fs_reads_bytes_total | namespace | cAdvisor | ||||||
|  | | container_memory_working_set_bytes | namespace | cAdvisor | ||||||
|  | | controller_runtime_active_workers | controller | ARC Controller | ||||||
|  | | controller_runtime_reconcile_time_seconds_sum | namespace | ARC Controller | ||||||
|  | | controller_runtime_reconcile_errors_total | namespace | ARC Controller | ||||||
|  | | gha_assigned_jobs | actions_github_com_scale_set_name, namespace | ARC Controller | ||||||
|  | | gha_controller_failed_ephemeral_runners | name, namespace | ARC Controller | ||||||
|  | | gha_controller_pending_ephemeral_runners | name, namespace | ARC Controller | ||||||
|  | | gha_controller_running_ephemeral_runners | name, namespace | ARC Controller | ||||||
|  | | gha_controller_running_listeners | namespace | ARC Controller | ||||||
|  | | gha_desired_runners | actions_github_com_scale_set_name, namespace | ARC Listener | ||||||
|  | | gha_idle_runners | actions_github_com_scale_set_name, namespace | ARC Listener | ||||||
|  | | gha_job_execution_duration_seconds_bucket | actions_github_com_scale_set_name, actions_github_com_scale_set_namespace | ARC Listener | ||||||
|  | | gha_job_startup_duration_seconds_bucket | actions_github_com_scale_set_name, actions_github_com_scale_set_namespace | ARC Listener | ||||||
|  | | gha_registered_runners | actions_github_com_scale_set_name, namespace | ARC Listener | ||||||
|  | | gha_running_jobs | actions_github_com_scale_set_name, actions_github_com_scale_set_namespace | ARC Listener | ||||||
|  | | kube_pod_container_status_ready | namespace | kube-state-metrics | ||||||
|  | | kube_pod_container_status_terminated_reason | namespace, reason | kube-state-metrics | ||||||
|  | | kube_pod_container_status_waiting | namespace | kube-state-metrics | ||||||
|  | | rest_client_requests_total | code, method, namespace | ARC Controller | ||||||
|  | | scrape_duration_seconds | | prometheus | ||||||
|  | | workqueue_depth | name, namespace | ARC Controller | ||||||
|  | | workqueue_queue_duration_seconds_sum | namespace | ARC Controller | ||||||
|  | 
 | ||||||
| ## Details | ## Details | ||||||
| 
 | 
 | ||||||
| This dashboard demonstrates some of the metrics provided by ARC and the underlying Kubernetes runtime. It provides a sample visualization of the behavior of the runner scale set, the ARC controllers, and the listeners. This should not be considered a comprehensive dashboard; it is a starting point that can be used with other metrics and logs to understand the health of the cluster. Review the [GitHub documentation detailing the Actions Runner Controller metrics and how to enable them](https://docs.github.com/en/enterprise-server@3.10/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/deploying-runner-scale-sets-with-actions-runner-controller#enabling-metrics). | This dashboard demonstrates some of the metrics provided by ARC and the underlying Kubernetes runtime. It provides a sample visualization of the behavior of the runner scale set, the ARC controllers, and the listeners. This should not be considered a comprehensive dashboard; it is a starting point that can be used with other metrics and logs to understand the health of the cluster. Review the [GitHub documentation detailing the Actions Runner Controller metrics and how to enable them](https://docs.github.com/en/enterprise-server@3.10/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/deploying-runner-scale-sets-with-actions-runner-controller#enabling-metrics). | ||||||
|  | @ -22,16 +58,25 @@ The dashboard includes the following metrics: | ||||||
| 
 | 
 | ||||||
| | Label                            | Description                                         | | | Label                            | Description                                         | | ||||||
| | -------------------------------- | ----------------------------------------------------| | | -------------------------------- | ----------------------------------------------------| | ||||||
| | Active listeners                 | The number of listeners currently running and attempting to manage jobs for the scale set. This should match the number of scale sets deployed. | | | Startup Duration                 | Heat map of the wait time before a job starts, with the colors indicating the increase in the number of jobs in that time bucket. An increasing time can indicate that the cluster is resource constrained and may need additional nodes or resources to handle the load. | | ||||||
| | Runner States                    | Displays the number of runners in a given state. The finished and deleted states are not included in this panel. | | | Execution Duration                 | Heat map of the execution time for a job, with the colors indicating the increase in the number of jobs in that time bucket. Time can be affected by the number of steps in the job, the allocated CPU, and whether there is resource contention on the node that is impacting performance | | ||||||
| | Failed (total)                   | The total number of ephemeral runners that have failed to properly start. This may require reviewing the custom resource and logs to identify and resolve the root causes. Common causes include resource issues and failure to pull the required image. | | | Assigned Jobs                    | The number of jobs that have been assigned to the listener. This is the number of jobs that the listener is responsible for providing a runner to process. | | ||||||
| | Pending (total)                  | The total number of ephemeral runners that ARC has requested and is waiting for Kubernetes to provide in a running state. If the Kubernetes API server is responsive, this will typically match the number of runner pods that are in a pending state. This number includes requests for runner pods that have not yet been scheduled. When this number is higher than the number of runner pods in a pending state, it can indicate performance issues with the API server and resource contention. | | | Desired Runners                  | The number of runners that the listener is requesting from the controller. This is the number of runners required to process the assigned jobs and provide idle runners. It is limited by the configured maximum runner count for the scale set. | | ||||||
| | Idle (total)                     | The total number of ephemeral runners that are available to accept jobs across all scale sets. Keeping a pool of idle runners can enable a faster start time under load, but excessive idle runners will consume resources and can prevent nodes from scaling down. | | | Idle Runners                     | The total number of ephemeral runners that are available to accept jobs across all selected scale sets. Keeping a pool of idle runners can enable a faster start time under load, but excessive idle runners will consume resources and can prevent nodes from scaling down. | | ||||||
| | Total assigned jobs per listener | The number of workflow jobs acquired and assigned to the listener. The listener must provide supporting runners to complete these jobs. Once jobs are assigned, they cannot be delegated to other listeners and must be processed by the scale set or cancelled. | | | Running Jobs | The number of runners that are currently processing jobs. | | ||||||
| | Assigned vs running jobs         | Compares the number of jobs assigned against the number of runners that are currently processing jobs. When running jobs is less than assigned jobs, it can indicate that ARC is waiting on Kubernetes to provide and start additional runners. | | | Failed Runners                   | The total number of ephemeral runners that have failed to properly start. This may require reviewing the custom resource and logs to identify and resolve the root causes. Common causes include resource issues and failure to pull the required image. | | ||||||
| | Average startup duration         | The average time in seconds between when jobs are assigned and when a runner accepts the job and begins processing. An increasing duration can indicate that the cluster has resource contention or a lack of available nodes for scheduling jobs | | | Listeners                 | The number of listeners currently running and attempting to manage jobs for the scale set. This should match the number of scale sets deployed. | | ||||||
| | Average execution duration       | The average time in seconds that runners are taking to complete a job. Changes in this value reflect the efficiency of workflow jobs and the pod configuration. If the value is decreasing without changes to the job, it can indicate resource contention or CPU throttling. | | | Pending Runners                  | The total number of ephemeral runners that ARC has requested and is waiting for Kubernetes to provide in a running state. If the Kubernetes API server is responsive, this will typically match the number of runner pods that are in a pending state. This number includes requests for runner pods that have not yet been scheduled. When this number is higher than the number of runner pods in a pending state, it can indicate performance issues. | | ||||||
|  | | Registered Runners               | The total number of ephemeral runners that have been successfully registered. | | ||||||
|  | | Active Runners | The total number of runners that are active and either available or processing jobs. | | ||||||
|  | | Out of Memory | The number of containers that have been terminated by the OOMKiller. This can indicate that the requests/ limits for one or more pods on the node were configured improperly, allowing pods to request more memory than the node had available. | | ||||||
|  | | Peak Container Memory | The maximum amount of memory used by any container in a given namespace during the selected time. This can be used for tuning the memory limits for the pods and for alerts as containers get close to their limits. | ||||||
|  | | Container I/O | Shows the number of bytes read and written to the container filesystem. This can be used to identify if the container is reading or writing a large amount of data to the filesystem, which can impact performance. | | ||||||
|  | | Container Pod Status | Shows the number of containers in each status (waiting, running, terminated, ready). This can be used to identify if there are a large number of containers that are failing to start or are in a waiting state. | | ||||||
|  | | Reconcile time              | The time to perform a single reconciliation task from a controller's work queue. This metric reflects the time it takes for ARC to complete each step in the processing of creating, managing, and cleaning up runners. As this increases, it can indicate resource contention, processing delays, or delays from the API server. | | ||||||
|  | | Workqueue Queue Duration | The time items spent in the work queue for a controller before being processed. This is often related to the work queue depth; as the number of items increases, it can take an increasing amount of time for an item to be processed. | | ||||||
| | Reconciliation errors            | Reconciliation is the process of a controller ensuring the desired state and actual state of the resources match. Each time an event occurs on a resource watched by the controller, the controller is required to indicate if the new state matches the desired state. Kubernetes adds a task to the work queue for the controller to perform this reconciliation. Errors indicate that controller has not achieved a desired state and is requesting Kubernetes to queue another request for reconciliation. Ideally, this number remains close to zero. An increasing number can indicate resource contention or delays processing API server requests. This reflects Kubernetes resources that ARC is waiting to be provided or in the necessary state. As a concrete example, ARC will request the creation of a secret prior to creating the pod. If the response indicates the secret is not immediately ready, ARC will requeue the reconciliation task with the error details, incrementing this count. | | | Reconciliation errors            | Reconciliation is the process of a controller ensuring the desired state and actual state of the resources match. Each time an event occurs on a resource watched by the controller, the controller is required to indicate if the new state matches the desired state. Kubernetes adds a task to the work queue for the controller to perform this reconciliation. Errors indicate that controller has not achieved a desired state and is requesting Kubernetes to queue another request for reconciliation. Ideally, this number remains close to zero. An increasing number can indicate resource contention or delays processing API server requests. This reflects Kubernetes resources that ARC is waiting to be provided or in the necessary state. As a concrete example, ARC will request the creation of a secret prior to creating the pod. If the response indicates the secret is not immediately ready, ARC will requeue the reconciliation task with the error details, incrementing this count. | | ||||||
| | Reconciliation time              | A histogram reflecting the time in seconds to perform a single reconciliation task from the controller's work queue. A histogram counts the number of requests that are processed within a given bucket of time. This metric reflects the time it takes for ARC to complete each step in the processing of creating, managing, and cleaning up runners. As this increases, it can indicate resource contention or processing delays within Kubernetes or the API server. This displays shows an average, which may hide larger or smaller times that are occurring in the processing. | | | Workqueue depth                  | The number of tasks that Kubernetes has queued for the ARC controllers to process. This includes reconciliation requests and tasks initiated by the controller. Managing a runner requires multiple steps to prepare, create, update, and delete the runner, its resources, and the ARC custom resources. As each step is completed (or trigger reconciliation), new tasks are queued for processing. The controller will then use one or more workers to process these tasks in the order they were queued. As the depth increases, it indicates more tasks awaiting time from the controller. Growth indicates increasing work and may reflect Kubernetes resource contention or processing latencies. Each request for a new runner will result in multiple tasks being added to the work queue to prepare and create the runner and the related ARC custom resources. | | ||||||
| | Workqueue depth                  | The number of tasks that Kubernetes queued for the ARC controllers to process. This includes reconciliation requests and tasks from ARC. ARC sequentially processes a work queue of single, small task to avoid concurrency issues. Managing a runner requires multiple steps to prepare, create, update, and delete the runner, its resources, and the ARC custom resources. As each step is completed (or trigger reconciliation), new tasks are queued for processing. As the depth increases, it indicates more tasks awaiting time from the controller. Growth indicates increasing work and may indicate Kubernetes resource contention or processing latencies. Each request for a new runner will result in multiple tasks being added to the work queue to prepare and create the runner and the related ARC custom resources. | | | Active Workers | The number of workers that are actively processing tasks in the work queue. If the queue is empty, then there may be no workers required to process the tasks. The number of workers for the ephemeral runner is configurable in the scale set values file. | | ||||||
|  | | API Calls | Shows the number of calls to the API server by status code and HTTP method. The method indicates the type of activity being performed, while the status code indicates the result of the activity. Error codes of 500 and above often indicate a Kubernetes issue. |  | ||||||
| | Scrape Duration (seconds)        | The amount of time required for Prometheus to read the configured metrics from components in the cluster. An increasing number may indicate a lack of resources for Prometheus and a risk of the process exceeding the configured timeout, leading to lost metrics data.  |  | | Scrape Duration (seconds)        | The amount of time required for Prometheus to read the configured metrics from components in the cluster. An increasing number may indicate a lack of resources for Prometheus and a risk of the process exceeding the configured timeout, leading to lost metrics data.  |  | ||||||
|  |  | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| version https://git-lfs.github.com/spec/v1 | version https://git-lfs.github.com/spec/v1 | ||||||
| oid sha256:b871862ef58b3480017edfa168d54f8269c8f5c542eb27e9da3e6fcb72294ecb | oid sha256:9bf448c6e9dad0e9e615f82e17883cf34b09b14f5461189167b798df40106c27 | ||||||
| size 606907 | size 351602 | ||||||
|  |  | ||||||
|  | @ -1212,7 +1212,7 @@ func createJWTForGitHubApp(appAuth *GitHubAppAuth) (string, error) { | ||||||
| 	claims := &jwt.RegisteredClaims{ | 	claims := &jwt.RegisteredClaims{ | ||||||
| 		IssuedAt:  jwt.NewNumericDate(issuedAt), | 		IssuedAt:  jwt.NewNumericDate(issuedAt), | ||||||
| 		ExpiresAt: jwt.NewNumericDate(expiresAt), | 		ExpiresAt: jwt.NewNumericDate(expiresAt), | ||||||
| 		Issuer:    strconv.FormatInt(appAuth.AppID, 10), | 		Issuer:    appAuth.AppID, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) | 	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ func TestAcquireJobs(t *testing.T) { | ||||||
| 			RunnerScaleSet:          &actions.RunnerScaleSet{Id: 1}, | 			RunnerScaleSet:          &actions.RunnerScaleSet{Id: 1}, | ||||||
| 			MessageQueueAccessToken: "abc", | 			MessageQueueAccessToken: "abc", | ||||||
| 		} | 		} | ||||||
| 		var requestIDs []int64 = []int64{1} | 		var requestIDs = []int64{1} | ||||||
| 
 | 
 | ||||||
| 		retryMax := 1 | 		retryMax := 1 | ||||||
| 		actualRetry := 0 | 		actualRetry := 0 | ||||||
|  |  | ||||||
|  | @ -67,7 +67,7 @@ func TestGetRunnerByName(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	t.Run("Get Runner by Name", func(t *testing.T) { | 	t.Run("Get Runner by Name", func(t *testing.T) { | ||||||
| 		var runnerID int64 = 1 | 		var runnerID int64 = 1 | ||||||
| 		var runnerName string = "self-hosted-ubuntu" | 		var runnerName = "self-hosted-ubuntu" | ||||||
| 		want := &actions.RunnerReference{ | 		want := &actions.RunnerReference{ | ||||||
| 			Id:   int(runnerID), | 			Id:   int(runnerID), | ||||||
| 			Name: runnerName, | 			Name: runnerName, | ||||||
|  | @ -87,7 +87,7 @@ func TestGetRunnerByName(t *testing.T) { | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	t.Run("Get Runner by name with not exist runner", func(t *testing.T) { | 	t.Run("Get Runner by name with not exist runner", func(t *testing.T) { | ||||||
| 		var runnerName string = "self-hosted-ubuntu" | 		var runnerName = "self-hosted-ubuntu" | ||||||
| 		response := []byte(`{"count": 0, "value": []}`) | 		response := []byte(`{"count": 0, "value": []}`) | ||||||
| 
 | 
 | ||||||
| 		server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 		server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | @ -103,7 +103,7 @@ func TestGetRunnerByName(t *testing.T) { | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	t.Run("Default retries on server error", func(t *testing.T) { | 	t.Run("Default retries on server error", func(t *testing.T) { | ||||||
| 		var runnerName string = "self-hosted-ubuntu" | 		var runnerName = "self-hosted-ubuntu" | ||||||
| 
 | 
 | ||||||
| 		retryWaitMax := 1 * time.Millisecond | 		retryWaitMax := 1 * time.Millisecond | ||||||
| 		retryMax := 1 | 		retryMax := 1 | ||||||
|  | @ -181,7 +181,7 @@ func TestGetRunnerGroupByName(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	t.Run("Get RunnerGroup by Name", func(t *testing.T) { | 	t.Run("Get RunnerGroup by Name", func(t *testing.T) { | ||||||
| 		var runnerGroupID int64 = 1 | 		var runnerGroupID int64 = 1 | ||||||
| 		var runnerGroupName string = "test-runner-group" | 		var runnerGroupName = "test-runner-group" | ||||||
| 		want := &actions.RunnerGroup{ | 		want := &actions.RunnerGroup{ | ||||||
| 			ID:   runnerGroupID, | 			ID:   runnerGroupID, | ||||||
| 			Name: runnerGroupName, | 			Name: runnerGroupName, | ||||||
|  | @ -201,7 +201,7 @@ func TestGetRunnerGroupByName(t *testing.T) { | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	t.Run("Get RunnerGroup by name with not exist runner group", func(t *testing.T) { | 	t.Run("Get RunnerGroup by name with not exist runner group", func(t *testing.T) { | ||||||
| 		var runnerGroupName string = "test-runner-group" | 		var runnerGroupName = "test-runner-group" | ||||||
| 		response := []byte(`{"count": 0, "value": []}`) | 		response := []byte(`{"count": 0, "value": []}`) | ||||||
| 
 | 
 | ||||||
| 		server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 		server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  |  | ||||||
|  | @ -57,7 +57,7 @@ func TestClient_Identifier(t *testing.T) { | ||||||
| 		} | 		} | ||||||
| 		defaultAppCreds := &actions.ActionsAuth{ | 		defaultAppCreds := &actions.ActionsAuth{ | ||||||
| 			AppCreds: &actions.GitHubAppAuth{ | 			AppCreds: &actions.GitHubAppAuth{ | ||||||
| 				AppID:             123, | 				AppID:             "123", | ||||||
| 				AppInstallationID: 123, | 				AppInstallationID: 123, | ||||||
| 				AppPrivateKey:     "private key", | 				AppPrivateKey:     "private key", | ||||||
| 			}, | 			}, | ||||||
|  | @ -90,7 +90,7 @@ func TestClient_Identifier(t *testing.T) { | ||||||
| 				old:  defaultAppCreds, | 				old:  defaultAppCreds, | ||||||
| 				new: &actions.ActionsAuth{ | 				new: &actions.ActionsAuth{ | ||||||
| 					AppCreds: &actions.GitHubAppAuth{ | 					AppCreds: &actions.GitHubAppAuth{ | ||||||
| 						AppID:             456, | 						AppID:             "456", | ||||||
| 						AppInstallationID: 456, | 						AppInstallationID: 456, | ||||||
| 						AppPrivateKey:     "new private key", | 						AppPrivateKey:     "new private key", | ||||||
| 					}, | 					}, | ||||||
|  |  | ||||||
|  | @ -23,7 +23,8 @@ type multiClient struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type GitHubAppAuth struct { | type GitHubAppAuth struct { | ||||||
| 	AppID             int64 | 	// AppID is the ID or the Client ID of the application
 | ||||||
|  | 	AppID             string | ||||||
| 	AppInstallationID int64 | 	AppInstallationID int64 | ||||||
| 	AppPrivateKey     string | 	AppPrivateKey     string | ||||||
| } | } | ||||||
|  | @ -124,16 +125,11 @@ func (m *multiClient) GetClientFromSecret(ctx context.Context, githubConfigURL, | ||||||
| 		return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...) | 		return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	parsedAppID, err := strconv.ParseInt(appID, 10, 64) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	parsedAppInstallationID, err := strconv.ParseInt(appInstallationID, 10, 64) | 	parsedAppInstallationID, err := strconv.ParseInt(appInstallationID, 10, 64) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	auth.AppCreds = &GitHubAppAuth{AppID: parsedAppID, AppInstallationID: parsedAppInstallationID, AppPrivateKey: appPrivateKey} | 	auth.AppCreds = &GitHubAppAuth{AppID: appID, AppInstallationID: parsedAppInstallationID, AppPrivateKey: appPrivateKey} | ||||||
| 	return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...) | 	return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -137,7 +137,7 @@ etFcaQuTHEZyRhhJ4BU= | ||||||
| -----END PRIVATE KEY-----` | -----END PRIVATE KEY-----` | ||||||
| 
 | 
 | ||||||
| 	auth := &GitHubAppAuth{ | 	auth := &GitHubAppAuth{ | ||||||
| 		AppID:         123, | 		AppID:         "123", | ||||||
| 		AppPrivateKey: key, | 		AppPrivateKey: key, | ||||||
| 	} | 	} | ||||||
| 	jwt, err := createJWTForGitHubApp(auth) | 	jwt, err := createJWTForGitHubApp(auth) | ||||||
|  |  | ||||||
|  | @ -127,7 +127,7 @@ func NewServer(opts ...Option) *httptest.Server { | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		// For ListRunners
 | 		// For ListRunners
 | ||||||
| 		"/repos/test/valid/actions/runners": config.FixedResponses.ListRunners, | 		"/repos/test/valid/actions/runners": config.ListRunners, | ||||||
| 		"/repos/test/invalid/actions/runners": &Handler{ | 		"/repos/test/invalid/actions/runners": &Handler{ | ||||||
| 			Status: http.StatusNoContent, | 			Status: http.StatusNoContent, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
|  | @ -204,10 +204,10 @@ func NewServer(opts ...Option) *httptest.Server { | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		// For auto-scaling based on the number of queued(pending) workflow runs
 | 		// For auto-scaling based on the number of queued(pending) workflow runs
 | ||||||
| 		"/repos/test/valid/actions/runs": config.FixedResponses.ListRepositoryWorkflowRuns, | 		"/repos/test/valid/actions/runs": config.ListRepositoryWorkflowRuns, | ||||||
| 
 | 
 | ||||||
| 		// For auto-scaling based on the number of queued(pending) workflow jobs
 | 		// For auto-scaling based on the number of queued(pending) workflow jobs
 | ||||||
| 		"/repos/test/valid/actions/runs/": config.FixedResponses.ListWorkflowJobs, | 		"/repos/test/valid/actions/runs/": config.ListWorkflowJobs, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	mux := http.NewServeMux() | 	mux := http.NewServeMux() | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ type Option func(*ServerConfig) | ||||||
| 
 | 
 | ||||||
| func WithListRepositoryWorkflowRunsResponse(status int, body, queued, in_progress string) Option { | func WithListRepositoryWorkflowRunsResponse(status int, body, queued, in_progress string) Option { | ||||||
| 	return func(c *ServerConfig) { | 	return func(c *ServerConfig) { | ||||||
| 		c.FixedResponses.ListRepositoryWorkflowRuns = &Handler{ | 		c.ListRepositoryWorkflowRuns = &Handler{ | ||||||
| 			Status: status, | 			Status: status, | ||||||
| 			Body:   body, | 			Body:   body, | ||||||
| 			Statuses: map[string]string{ | 			Statuses: map[string]string{ | ||||||
|  | @ -25,7 +25,7 @@ func WithListRepositoryWorkflowRunsResponse(status int, body, queued, in_progres | ||||||
| 
 | 
 | ||||||
| func WithListWorkflowJobsResponse(status int, bodies map[int]string) Option { | func WithListWorkflowJobsResponse(status int, bodies map[int]string) Option { | ||||||
| 	return func(c *ServerConfig) { | 	return func(c *ServerConfig) { | ||||||
| 		c.FixedResponses.ListWorkflowJobs = &MapHandler{ | 		c.ListWorkflowJobs = &MapHandler{ | ||||||
| 			Status: status, | 			Status: status, | ||||||
| 			Bodies: bodies, | 			Bodies: bodies, | ||||||
| 		} | 		} | ||||||
|  | @ -34,7 +34,7 @@ func WithListWorkflowJobsResponse(status int, bodies map[int]string) Option { | ||||||
| 
 | 
 | ||||||
| func WithListRunnersResponse(status int, body string) Option { | func WithListRunnersResponse(status int, body string) Option { | ||||||
| 	return func(c *ServerConfig) { | 	return func(c *ServerConfig) { | ||||||
| 		c.FixedResponses.ListRunners = &ListRunnersHandler{ | 		c.ListRunners = &ListRunnersHandler{ | ||||||
| 			Status: status, | 			Status: status, | ||||||
| 			Body:   body, | 			Body:   body, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -290,7 +290,7 @@ func (c *Client) ListRunnerGroupRepositoryAccesses(ctx context.Context, org stri | ||||||
| 
 | 
 | ||||||
| 	opts := github.ListOptions{PerPage: 100} | 	opts := github.ListOptions{PerPage: 100} | ||||||
| 	for { | 	for { | ||||||
| 		list, res, err := c.Client.Actions.ListRepositoryAccessRunnerGroup(ctx, org, runnerGroupId, &opts) | 		list, res, err := c.Actions.ListRepositoryAccessRunnerGroup(ctx, org, runnerGroupId, &opts) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, fmt.Errorf("failed to list repository access for runner group: %w", err) | 			return nil, fmt.Errorf("failed to list repository access for runner group: %w", err) | ||||||
| 		} | 		} | ||||||
|  | @ -323,32 +323,32 @@ func (c *Client) cleanup() { | ||||||
| 
 | 
 | ||||||
| func (c *Client) createRegistrationToken(ctx context.Context, enterprise, org, repo string) (*github.RegistrationToken, *github.Response, error) { | func (c *Client) createRegistrationToken(ctx context.Context, enterprise, org, repo string) (*github.RegistrationToken, *github.Response, error) { | ||||||
| 	if len(repo) > 0 { | 	if len(repo) > 0 { | ||||||
| 		return c.Client.Actions.CreateRegistrationToken(ctx, org, repo) | 		return c.Actions.CreateRegistrationToken(ctx, org, repo) | ||||||
| 	} | 	} | ||||||
| 	if len(org) > 0 { | 	if len(org) > 0 { | ||||||
| 		return c.Client.Actions.CreateOrganizationRegistrationToken(ctx, org) | 		return c.Actions.CreateOrganizationRegistrationToken(ctx, org) | ||||||
| 	} | 	} | ||||||
| 	return c.Client.Enterprise.CreateRegistrationToken(ctx, enterprise) | 	return c.Enterprise.CreateRegistrationToken(ctx, enterprise) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) removeRunner(ctx context.Context, enterprise, org, repo string, runnerID int64) (*github.Response, error) { | func (c *Client) removeRunner(ctx context.Context, enterprise, org, repo string, runnerID int64) (*github.Response, error) { | ||||||
| 	if len(repo) > 0 { | 	if len(repo) > 0 { | ||||||
| 		return c.Client.Actions.RemoveRunner(ctx, org, repo, runnerID) | 		return c.Actions.RemoveRunner(ctx, org, repo, runnerID) | ||||||
| 	} | 	} | ||||||
| 	if len(org) > 0 { | 	if len(org) > 0 { | ||||||
| 		return c.Client.Actions.RemoveOrganizationRunner(ctx, org, runnerID) | 		return c.Actions.RemoveOrganizationRunner(ctx, org, runnerID) | ||||||
| 	} | 	} | ||||||
| 	return c.Client.Enterprise.RemoveRunner(ctx, enterprise, runnerID) | 	return c.Enterprise.RemoveRunner(ctx, enterprise, runnerID) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) listRunners(ctx context.Context, enterprise, org, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) { | func (c *Client) listRunners(ctx context.Context, enterprise, org, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) { | ||||||
| 	if len(repo) > 0 { | 	if len(repo) > 0 { | ||||||
| 		return c.Client.Actions.ListRunners(ctx, org, repo, opts) | 		return c.Actions.ListRunners(ctx, org, repo, opts) | ||||||
| 	} | 	} | ||||||
| 	if len(org) > 0 { | 	if len(org) > 0 { | ||||||
| 		return c.Client.Actions.ListOrganizationRunners(ctx, org, opts) | 		return c.Actions.ListOrganizationRunners(ctx, org, opts) | ||||||
| 	} | 	} | ||||||
| 	return c.Client.Enterprise.ListRunners(ctx, enterprise, opts) | 	return c.Enterprise.ListRunners(ctx, enterprise, opts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) ListRepositoryWorkflowRuns(ctx context.Context, user string, repoName string) ([]*github.WorkflowRun, error) { | func (c *Client) ListRepositoryWorkflowRuns(ctx context.Context, user string, repoName string) ([]*github.WorkflowRun, error) { | ||||||
|  | @ -381,7 +381,7 @@ func (c *Client) listRepositoryWorkflowRuns(ctx context.Context, user string, re | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for { | 	for { | ||||||
| 		list, res, err := c.Client.Actions.ListRepositoryWorkflowRuns(ctx, user, repoName, &opts) | 		list, res, err := c.Actions.ListRepositoryWorkflowRuns(ctx, user, repoName, &opts) | ||||||
| 
 | 
 | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return workflowRuns, fmt.Errorf("failed to list workflow runs: %v", err) | 			return workflowRuns, fmt.Errorf("failed to list workflow runs: %v", err) | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ func newTestClient() *Client { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
| 	client.Client.BaseURL = baseURL | 	client.BaseURL = baseURL | ||||||
| 
 | 
 | ||||||
| 	return client | 	return client | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										3
									
								
								go.mod
								
								
								
								
							|  | @ -1,6 +1,7 @@ | ||||||
| module github.com/actions/actions-runner-controller | module github.com/actions/actions-runner-controller | ||||||
| 
 | 
 | ||||||
| go 1.24.0 | go 1.24.3 | ||||||
|  | 
 | ||||||
| require ( | require ( | ||||||
| 	github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 | 	github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 | ||||||
| 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc | 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc | ||||||
|  |  | ||||||
|  | @ -6,8 +6,8 @@ DIND_ROOTLESS_RUNNER_NAME ?= ${DOCKER_USER}/actions-runner-dind-rootless | ||||||
| OS_IMAGE ?= ubuntu-22.04 | OS_IMAGE ?= ubuntu-22.04 | ||||||
| TARGETPLATFORM ?= $(shell arch) | TARGETPLATFORM ?= $(shell arch) | ||||||
| 
 | 
 | ||||||
| RUNNER_VERSION ?= 2.323.0 | RUNNER_VERSION ?= 2.325.0 | ||||||
| RUNNER_CONTAINER_HOOKS_VERSION ?= 0.6.2 | RUNNER_CONTAINER_HOOKS_VERSION ?= 0.7.0 | ||||||
| DOCKER_VERSION ?= 24.0.7 | DOCKER_VERSION ?= 24.0.7 | ||||||
| 
 | 
 | ||||||
| # default list of platforms for which multiarch image is built
 | # default list of platforms for which multiarch image is built
 | ||||||
|  |  | ||||||
|  | @ -1,2 +1,2 @@ | ||||||
| RUNNER_VERSION=2.323.0 | RUNNER_VERSION=2.325.0 | ||||||
| RUNNER_CONTAINER_HOOKS_VERSION=0.6.2 | RUNNER_CONTAINER_HOOKS_VERSION=0.7.0 | ||||||
|  | @ -36,8 +36,8 @@ var ( | ||||||
| 
 | 
 | ||||||
| 	testResultCMNamePrefix = "test-result-" | 	testResultCMNamePrefix = "test-result-" | ||||||
| 
 | 
 | ||||||
| 	RunnerVersion               = "2.323.0" | 	RunnerVersion               = "2.325.0" | ||||||
| 	RunnerContainerHooksVersion = "0.6.2" | 	RunnerContainerHooksVersion = "0.7.0" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // If you're willing to run this test via VS Code "run test" or "debug test",
 | // If you're willing to run this test via VS Code "run test" or "debug test",
 | ||||||
|  | @ -598,9 +598,9 @@ func initTestEnv(t *testing.T, k8sMinorVer string, vars vars) *env { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		e.Kind = testing.StartKind(t, k8sMinorVer, testing.Preload(images...)) | 		e.Kind = testing.StartKind(t, k8sMinorVer, testing.Preload(images...)) | ||||||
| 		e.Env.Kubeconfig = e.Kind.Kubeconfig() | 		e.Kubeconfig = e.Kind.Kubeconfig() | ||||||
| 	} else { | 	} else { | ||||||
| 		e.Env.Kubeconfig = e.remoteKubeconfig | 		e.Kubeconfig = e.remoteKubeconfig | ||||||
| 
 | 
 | ||||||
| 		// Kind automatically installs https://github.com/rancher/local-path-provisioner for PVs.
 | 		// Kind automatically installs https://github.com/rancher/local-path-provisioner for PVs.
 | ||||||
| 		// But assuming the remote cluster isn't a kind Kubernetes cluster,
 | 		// But assuming the remote cluster isn't a kind Kubernetes cluster,
 | ||||||
|  | @ -1106,7 +1106,7 @@ func installActionsWorkflow(t *testing.T, testName, runnerLabel, testResultCMNam | ||||||
| 				testing.Step{ | 				testing.Step{ | ||||||
| 					Uses: "actions/setup-go@v3", | 					Uses: "actions/setup-go@v3", | ||||||
| 					With: &testing.With{ | 					With: &testing.With{ | ||||||
| 						GoVersion: "1.24.0", | 						GoVersion: "1.24.3", | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			) | 			) | ||||||
|  | @ -1181,7 +1181,7 @@ func installActionsWorkflow(t *testing.T, testName, runnerLabel, testResultCMNam | ||||||
| 				steps = append(steps, | 				steps = append(steps, | ||||||
| 					testing.Step{ | 					testing.Step{ | ||||||
| 						Name: "Set up Docker Buildx", | 						Name: "Set up Docker Buildx", | ||||||
| 						Uses: "docker/setup-buildx-action@v1", | 						Uses: "docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2", | ||||||
| 						With: setupBuildXActionWith, | 						With: setupBuildXActionWith, | ||||||
| 					}, | 					}, | ||||||
| 					testing.Step{ | 					testing.Step{ | ||||||
|  | @ -1193,7 +1193,7 @@ func installActionsWorkflow(t *testing.T, testName, runnerLabel, testResultCMNam | ||||||
| 						Run: "docker run --rm test1", | 						Run: "docker run --rm test1", | ||||||
| 					}, | 					}, | ||||||
| 					testing.Step{ | 					testing.Step{ | ||||||
| 						Uses: "addnab/docker-run-action@v3", | 						Uses: "addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185", | ||||||
| 						With: &testing.With{ | 						With: &testing.With{ | ||||||
| 							Image: "test1", | 							Image: "test1", | ||||||
| 							Run:   "hello", | 							Run:   "hello", | ||||||
|  | @ -1234,7 +1234,7 @@ func installActionsWorkflow(t *testing.T, testName, runnerLabel, testResultCMNam | ||||||
| 
 | 
 | ||||||
| 		steps = append(steps, | 		steps = append(steps, | ||||||
| 			testing.Step{ | 			testing.Step{ | ||||||
| 				Uses: "azure/setup-kubectl@v1", | 				Uses: "azure/setup-kubectl@3e0aec4d80787158d308d7b364cb1b702e7feb7f", | ||||||
| 				With: &testing.With{ | 				With: &testing.With{ | ||||||
| 					Version: "v1.24.0", | 					Version: "v1.24.0", | ||||||
| 				}, | 				}, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue